From: Yan Yang Date: Mon, 17 Sep 2018 08:34:27 +0000 (+0000) Subject: Merge "Use managed guava version" X-Git-Tag: 1.2.0~2 X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=3e42b3c95ade30a5083b993514b6b0d0734e0075;hp=a9d302f2b9786d245a13419f9505d886945dcb95;p=vfc%2Fgvnfm%2Fvnflcm.git Merge "Use managed guava version" --- diff --git a/lcm/lcm/nf/biz/common.py b/lcm/lcm/nf/biz/common.py new file mode 100644 index 00000000..308ba060 --- /dev/null +++ b/lcm/lcm/nf/biz/common.py @@ -0,0 +1,145 @@ +# Copyright 2017 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 uuid + +from lcm.pub.database.models import VmInstModel, NetworkInstModel, \ + SubNetworkInstModel, PortInstModel, StorageInstModel, FlavourInstModel, VNFCInstModel +from lcm.pub.utils.jobutil import JobUtil +from lcm.pub.utils.values import ignore_case_get, get_none, get_boolean, get_integer + + +def volume_save(job_id, nf_inst_id, ret): + JobUtil.add_job_status(job_id, 25, 'Create vloumns!') + StorageInstModel.objects.create( + storageid=str(uuid.uuid4()), + vimid=ignore_case_get(ret, "vimId"), + resourceid=ignore_case_get(ret, "id"), + name=ignore_case_get(ret, "name"), + tenant=ignore_case_get(ret, "tenantId"), + create_time=ignore_case_get(ret, "createTime"), + storagetype=get_none(ignore_case_get(ret, "type")), + size=ignore_case_get(ret, "size"), + insttype=0, + is_predefined=ignore_case_get(ret, "returnCode"), + nodeId=ignore_case_get(ret, "nodeId"), + instid=nf_inst_id) + + +def network_save(job_id, nf_inst_id, ret): + JobUtil.add_job_status(job_id, 35, 'Create networks!') + NetworkInstModel.objects.create( + networkid=str(uuid.uuid4()), + name=ignore_case_get(ret, "name"), + vimid=ignore_case_get(ret, "vimId"), + resourceid=ignore_case_get(ret, "id"), + tenant=ignore_case_get(ret, "tenantId"), + segmentid=str(ignore_case_get(ret, "segmentationId")), + network_type=ignore_case_get(ret, "networkType"), + physicalNetwork=ignore_case_get(ret, "physicalNetwork"), + vlantrans=get_boolean(ignore_case_get(ret, "vlanTransparent")), + is_shared=get_boolean(ignore_case_get(ret, "shared")), + routerExternal=get_boolean(ignore_case_get(ret, "routerExternal")), + insttype=0, + is_predefined=ignore_case_get(ret, "returnCode"), + nodeId=ignore_case_get(ret, "nodeId"), + instid=nf_inst_id) + + +def subnet_save(job_id, nf_inst_id, ret): + JobUtil.add_job_status(job_id, 40, 'Create subnets!') + SubNetworkInstModel.objects.create( + subnetworkid=str(uuid.uuid4()), + name=ignore_case_get(ret, "name"), + vimid=ignore_case_get(ret, "vimId"), + resourceid=ignore_case_get(ret, "id"), + tenant=ignore_case_get(ret, "tenantId"), + networkid=ignore_case_get(ret, "networkId"), + cidr=ignore_case_get(ret, "cidr"), + ipversion=ignore_case_get(ret, "ipversion"), + isdhcpenabled=ignore_case_get(ret, "enableDhcp"), + gatewayip=ignore_case_get(ret, "gatewayIp"), + dnsNameservers=ignore_case_get(ret, "dnsNameservers"), + hostRoutes=ignore_case_get(ret, "hostRoutes"), + allocationPools=ignore_case_get(ret, "allocationPools"), + insttype=0, + is_predefined=ignore_case_get(ret, "returnCode"), + instid=nf_inst_id) + + +def port_save(job_id, nf_inst_id, ret): + JobUtil.add_job_status(job_id, 50, 'Create ports!') + PortInstModel.objects.create( + portid=str(uuid.uuid4()), + networkid=ignore_case_get(ret, "networkId"), + subnetworkid=ignore_case_get(ret, "subnetId"), + name=ignore_case_get(ret, "name"), + vimid=ignore_case_get(ret, "vimId"), + resourceid=ignore_case_get(ret, "id"), + tenant=ignore_case_get(ret, "tenantId"), + macaddress=ignore_case_get(ret, "macAddress"), + ipaddress=ignore_case_get(ret, "ip"), + typevirtualnic=ignore_case_get(ret, "vnicType"), + securityGroups=ignore_case_get(ret, "securityGroups"), + insttype=0, + is_predefined=ignore_case_get(ret, "returnCode"), + nodeId=ignore_case_get(ret, "nodeId"), + instid=nf_inst_id) + + +def flavor_save(job_id, nf_inst_id, ret): + JobUtil.add_job_status(job_id, 60, 'Create flavors!') + FlavourInstModel.objects.create( + flavourid=str(uuid.uuid4()), + name=ignore_case_get(ret, "name"), + vimid=ignore_case_get(ret, "vimId"), + resourceid=ignore_case_get(ret, "id"), + tenant=ignore_case_get(ret, "tenantId"), + vcpu=get_integer(ignore_case_get(ret, "vcpu")), + memory=get_integer(ignore_case_get(ret, "memory")), + disk=get_integer(ignore_case_get(ret, "disk")), + ephemeral=get_integer(ignore_case_get(ret, "ephemeral")), + swap=get_integer(ignore_case_get(ret, "swap")), + isPublic=get_boolean(ignore_case_get(ret, "isPublic")), + extraspecs=ignore_case_get(ret, "extraSpecs"), + is_predefined=ret.get("returnCode", int(0)), + instid=nf_inst_id) + + +def vm_save(job_id, nf_inst_id, ret): + JobUtil.add_job_status(job_id, 70, 'Create vms!') + vm_id = str(uuid.uuid4()) + VmInstModel.objects.create( + vmid=vm_id, + vmname=ignore_case_get(ret, "name"), + vimid=ignore_case_get(ret, "vimId"), + resourceid=ignore_case_get(ret, "id"), + tenant=ignore_case_get(ret, "tenantId"), + nic_array=ignore_case_get(ret, "nicArray"), + metadata=ignore_case_get(ret, "metadata"), + volume_array=ignore_case_get(ret, "volumeArray"), + server_group=ignore_case_get(ret, "serverGroup"), + availability_zone=str(ignore_case_get(ret, "availabilityZone", "undefined")), + flavor_id=ignore_case_get(ret, "flavorId"), + security_groups=ignore_case_get(ret, "securityGroups"), + operationalstate=ignore_case_get(ret, "status"), + insttype=0, + is_predefined=ignore_case_get(ret, "returnCode"), + instid=nf_inst_id) + VNFCInstModel.objects.create( + vnfcinstanceid=str(uuid.uuid4()), + vduid=ignore_case_get(ret, "id"), + is_predefined=ignore_case_get(ret, "returnCode"), + instid=nf_inst_id, + vmid=vm_id) diff --git a/lcm/lcm/nf/biz/create_subscription.py b/lcm/lcm/nf/biz/create_subscription.py new file mode 100644 index 00000000..f42a59c0 --- /dev/null +++ b/lcm/lcm/nf/biz/create_subscription.py @@ -0,0 +1,139 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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 ast +import json +import logging +import requests +import uuid + +from collections import Counter + +from rest_framework import status + +from lcm.nf import const +from lcm.pub.database.models import SubscriptionModel +from lcm.pub.exceptions import NFLCMException +from lcm.pub.utils.values import ignore_case_get + +logger = logging.getLogger(__name__) + + +def is_filter_type_equal(new_filter, existing_filter): + return Counter(new_filter) == Counter(existing_filter) + + +class CreateSubscription: + def __init__(self, data): + self.data = data + self.filter = ignore_case_get(self.data, "filter", {}) + self.callback_uri = ignore_case_get(self.data, "callbackUri") + self.authentication = ignore_case_get(self.data, "authentication", {}) + self.notification_types = ignore_case_get(self.filter, "notificationTypes", []) + self.operation_types = ignore_case_get(self.filter, "operationTypes", []) + self.operation_states = ignore_case_get(self.filter, "notificationStates", []) + self.vnf_filter = \ + ignore_case_get(self.filter, "vnfInstanceSubscriptionFilter", {}) + + def check_callbackuri_connection(self): + logger.debug("SubscribeNotification-post::> Sending GET request " + "to %s" % self.callback_uri) + try: + response = requests.get(self.callback_uri, timeout=2) + if response.status_code != status.HTTP_204_NO_CONTENT: + raise NFLCMException("callbackUri %s returns %s status " + "code." % (self.callback_uri, response.status_code)) + except Exception: + raise NFLCMException("callbackUri %s didn't return 204 status" + "code." % self.callback_uri) + + def do_biz(self): + self.subscription_id = str(uuid.uuid4()) + # self.check_callbackuri_connection() + self.check_valid_auth_info() + self.check_filter_types() + self.check_valid() + self.save_db() + subscription = SubscriptionModel.objects.get(subscription_id=self.subscription_id) + return subscription + + def check_filter_types(self): + logger.debug("SubscribeNotification--post::> Validating " + "operationTypes and operationStates if exists") + if self.operation_types and \ + const.LCCNNOTIFICATION not in self.notification_types: + raise NFLCMException("If you are setting operationTypes," + "then notificationTypes " + "must be " + const.LCCNNOTIFICATION) + if self.operation_states and \ + const.LCCNNOTIFICATION not in self.notification_types: + raise NFLCMException("If you are setting operationStates," + "then notificationTypes " + "must be " + const.LCCNNOTIFICATION) + + def check_valid_auth_info(self): + logger.debug("SubscribeNotification--post::> Validating Auth " + "details if provided") + if self.authentication.get("paramsBasic", {}) and \ + const.BASIC not in self.authentication.get("authType"): + raise NFLCMException('Auth type should be ' + const.BASIC) + if self.authentication.get("paramsOauth2ClientCredentials", {}) and \ + const.OAUTH2_CLIENT_CREDENTIALS not in self.authentication.get("authType"): + raise NFLCMException('Auth type should be ' + const.OAUTH2_CLIENT_CREDENTIALS) + + def check_filter_exists(self, sub): + # Check the notificationTypes, operationTypes, operationStates + for filter_type in ["operation_types", + "notification_types", "operation_states"]: + if not is_filter_type_equal(getattr(self, filter_type), + ast.literal_eval(getattr(sub, filter_type))): + return False + # If all the above types are same then check vnf instance filters + nf_filter = json.loads(sub.vnf_instance_filter) + for vnf_filter_type in ["vnfdIds", "vnfInstanceIds", + "vnfInstanceNames"]: + if not is_filter_type_equal(self.vnf_filter.get(vnf_filter_type, []), + nf_filter.get(vnf_filter_type, [])): + return False + return True + + def check_valid(self): + logger.debug("SubscribeNotification--post::> Checking DB if " + "callbackUri already exists") + subscriptions = SubscriptionModel.objects.filter(callback_uri=self.callback_uri) + if not subscriptions.exists(): + return True + for subscription in subscriptions: + if self.check_filter_exists(subscription): + raise NFLCMException("Already Subscription exists with the " + "same callbackUri and filter") + return False + + def save_db(self): + logger.debug("SubscribeNotification--post::> Saving the subscription " + "%s to the database" % self.subscription_id) + links = { + "self": { + "href": const.ROOT_URI + self.subscription_id + } + } + SubscriptionModel.objects.create(subscription_id=self.subscription_id, + callback_uri=self.callback_uri, + auth_info=self.authentication, + notification_types=json.dumps(self.notification_types), + operation_types=json.dumps(self.operation_types), + operation_states=json.dumps(self.operation_states), + vnf_instance_filter=json.dumps(self.vnf_filter), + links=json.dumps(links)) + logger.debug('Create Subscription[%s] success', self.subscription_id) diff --git a/lcm/lcm/nf/biz/create_vnf.py b/lcm/lcm/nf/biz/create_vnf.py index de857b61..c86ec14c 100644 --- a/lcm/lcm/nf/biz/create_vnf.py +++ b/lcm/lcm/nf/biz/create_vnf.py @@ -57,7 +57,7 @@ class CreateVnf: version=version, vendor=provider, netype=netype, - vnfd_model=self.vnfd_info, + vnfd_model=json.dumps(self.vnfd_info), status='NOT_INSTANTIATED', nf_desc=self.description, vnfdid=self.csar_id, diff --git a/lcm/lcm/nf/biz/grant_vnf.py b/lcm/lcm/nf/biz/grant_vnf.py index 1997e0c0..92f22350 100644 --- a/lcm/lcm/nf/biz/grant_vnf.py +++ b/lcm/lcm/nf/biz/grant_vnf.py @@ -18,53 +18,104 @@ import logging from lcm.pub.database.models import NfInstModel from lcm.pub.msapi.gvnfmdriver import apply_grant_to_nfvo from lcm.pub.utils.values import ignore_case_get +from lcm.nf.const import GRANT_TYPE logger = logging.getLogger(__name__) def grant_resource(data, nf_inst_id, job_id, grant_type, vdus): logger.info("Grant resource begin") - if grant_type == "Terminate": - lifecycleOperration = "Terminate" - elif grant_type == "Instantiate": - lifecycleOperration = "Instantiate" content_args = { 'vnfInstanceId': nf_inst_id, - 'vnfDescriptorId': '', - 'lifecycleOperation': lifecycleOperration, 'vnfLcmOpOccId': job_id, + 'vnfdId': None, # TODO + 'flavourId': None, # TODO + 'operate': grant_type, + 'isAutomaticInvocation': True, # TODO + 'instantiationLevelId': None, # TODO 'addResources': [], + 'tmpResources': [], + 'updateResources': [], 'removeResources': [], 'placementConstraints': [], - 'additionalParams': {} + 'vimConstraints': [], + 'additionalParams': {}, + '_links': None # TODO } - if grant_type == "Terminate": + if grant_type == GRANT_TYPE.TERMINATE: res_index = 1 for vdu in vdus: res_def = { - 'type': 'VDU', - 'resDefId': str(res_index), - 'resDesId': vdu.resouceid} + 'id': str(res_index), + 'type': 'COMPUTE', + 'vduId': None, + 'resourceTemplateId': None, + 'resource': { + 'vimConnectionId': None, + 'resourceProviderId': None, + 'resourceid': vdu.resourceid, + 'vimLevelResourceType': None + } + } content_args['removeResources'].append(res_def) res_index += 1 content_args['additionalParams']['vimid'] = vdus[0].vimid - elif grant_type == "Instantiate": + elif grant_type == GRANT_TYPE.INSTANTIATE: vim_id = ignore_case_get(ignore_case_get(data, "additionalParams"), "vimId") res_index = 1 for vdu in vdus: res_def = { - 'type': 'VDU', - 'resDefId': str(res_index), - 'resDesId': ignore_case_get(vdu, "vdu_id") + 'id': str(res_index), + 'type': 'COMPUTE', + 'vduId': None, + 'resourceTemplateId': None, # TODO: shall be present for the planned creation of new resources. + 'resource': None } content_args['addResources'].append(res_def) res_index += 1 content_args['additionalParams']['vimid'] = vim_id + elif grant_type == GRANT_TYPE.HEAL_RESTART: + res_index = 1 + res_def = { + 'type': 'VDU', + 'resDefId': str(res_index), + 'resDesId': vdus[0].resourceid} + content_args['updateResources'].append(res_def) + content_args['additionalParams']['vimid'] = vdus[0].vimid + elif grant_type == GRANT_TYPE.HEAL_CREATE: + vim_id = vdus[0]["properties"]["location_info"]["vimid"] + res_index = 1 + res_def = { + 'type': 'VDU', + 'resDefId': str(res_index), + 'resDesId': ignore_case_get(vdus[0], "vdu_id") + } + content_args['addResources'].append(res_def) + content_args['additionalParams']['vimid'] = vim_id + elif grant_type == GRANT_TYPE.OPERATE: + res_index = 1 + for vdu in vdus: + res_def = { + 'id': str(res_index), + 'type': 'COMPUTE', + 'vduId': None, + 'resourceTemplateId': None, + 'resource': { + 'vimConnectionId': None, + 'resourceProviderId': None, + 'resourceid': vdu.resourceid, + 'vimLevelResourceType': None + } + } + content_args['updateResources'].append(res_def) + res_index += 1 + content_args['additionalParams']['vimid'] = vdus[0].vimid vnfInsts = NfInstModel.objects.filter(nfinstid=nf_inst_id) content_args['additionalParams']['vnfmid'] = vnfInsts[0].vnfminstid logger.info('Grant request data=%s' % content_args) apply_result = apply_grant_to_nfvo(json.dumps(content_args)) + logger.info("apply_result: %s" % apply_result) return apply_result diff --git a/lcm/lcm/nf/biz/heal_vnf.py b/lcm/lcm/nf/biz/heal_vnf.py new file mode 100644 index 00000000..bf8e34a0 --- /dev/null +++ b/lcm/lcm/nf/biz/heal_vnf.py @@ -0,0 +1,117 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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 json +import logging +import traceback +from threading import Thread + +from lcm.pub.database.models import NfInstModel, VmInstModel +from lcm.pub.exceptions import NFLCMException +from lcm.pub.msapi.gvnfmdriver import notify_lcm_to_nfvo, prepare_notification_data +from lcm.pub.utils.jobutil import JobUtil +from lcm.pub.utils.timeutil import now_time +from lcm.pub.utils.values import ignore_case_get +from lcm.pub.vimapi import adaptor +from lcm.nf.biz.grant_vnf import grant_resource +from lcm.nf.const import VNF_STATUS, GRANT_TYPE, HEAL_ACTION_TYPE, CHANGE_TYPE, OPERATION_TYPE +from lcm.nf.biz import common + +logger = logging.getLogger(__name__) + + +class HealVnf(Thread): + def __init__(self, data, nf_inst_id, job_id): + super(HealVnf, self).__init__() + self.data = data + self.nf_inst_id = nf_inst_id + self.job_id = job_id + self.affectedvm = ignore_case_get(ignore_case_get(self.data, "additionalParams"), "affectedvm") + # TODO: Check if we could move the action param into the list of affectedvm structure + self.action = ignore_case_get(ignore_case_get(self.data, "additionalParams"), "action") + self.grant_type = "" + if self.action == HEAL_ACTION_TYPE.START: + self.grant_type = GRANT_TYPE.HEAL_CREATE + elif self.action == HEAL_ACTION_TYPE.RESTART: + self.grant_type = GRANT_TYPE.HEAL_RESTART + + def run(self): + try: + self.heal_pre() + self.apply_grant() + self.heal_resource() + JobUtil.add_job_status(self.job_id, 100, "Heal Vnf success.") + NfInstModel.objects.filter(nfinstid=self.nf_inst_id).update(status='INSTANTIATED', lastuptime=now_time()) + self.lcm_notify() + except NFLCMException as e: + logger.error(e.message) + self.vnf_heal_failed_handle(e.message) + except Exception as e: + logger.error(e.message) + self.vnf_heal_failed_handle(traceback.format_exc()) + + def heal_pre(self): + if self.action not in (HEAL_ACTION_TYPE.START, HEAL_ACTION_TYPE.RESTART): + raise NFLCMException("Action type in Request in invalid. Should be %s or %s" % (HEAL_ACTION_TYPE.START, HEAL_ACTION_TYPE.RESTART)) + + self.vm_id = ignore_case_get(self.affectedvm, "vmid") + self.vdu_id = ignore_case_get(self.affectedvm, "vduid") + self.vm_name = ignore_case_get(self.affectedvm, "vmname") + if not (self.vm_id and self.vdu_id and self.vm_name): + raise NFLCMException("VM identifiers is not present in request.") + + self.vnf_insts = NfInstModel.objects.filter(nfinstid=self.nf_inst_id) + self.vnfd_info = json.loads(self.vnf_insts[0].vnfd_model) + + def apply_grant(self): + if self.action == HEAL_ACTION_TYPE.RESTART: + self.vdu = VmInstModel.objects.filter(instid=self.nf_inst_id, is_predefined=1, vmid=self.vm_id, vmname=self.vm_name) + if not self.vdu: + raise NFLCMException("VNF Vm does not exist.") + self.vimid = self.vdu[0].vimid + self.tenant = self.vdu[0].tenant + elif self.action == HEAL_ACTION_TYPE.START: + vdus = ignore_case_get(self.vnfd_info, "vdus") + self.vdu = [elem for elem in vdus if ignore_case_get(elem, "vdu_id") == self.vdu_id] + if not self.vdu: + raise NFLCMException("VNF Vm does not exist.") + apply_result = grant_resource(data=self.data, nf_inst_id=self.nf_inst_id, job_id=self.job_id, + grant_type=self.grant_type, vdus=self.vdu) + if self.action == HEAL_ACTION_TYPE.START: + self.vimid = ignore_case_get(apply_result, "vimid"), + self.tenant = ignore_case_get(apply_result, "tenant") + logger.info("Grant resource, response: %s" % apply_result) + JobUtil.add_job_status(self.job_id, 20, 'Nf Healing grant_resource finish') + + def heal_resource(self): + logger.info('Heal resource begin') + data = {'action': self.action, 'vimid': self.vimid, 'tenant': self.tenant} + adaptor.heal_vim_res(self.vdu, self.vnfd_info, self.do_notify, data, json.loads(self.vnf_insts[0].vimInfo), json.loads(self.vnf_insts[0].resInfo)) + logger.info('Heal resource complete') + + def do_notify(self, res_type, ret): + logger.info('Creating [%s] resource' % res_type) + resource_save_method = getattr(common, res_type + '_save') + resource_save_method(self.job_id, self.nf_inst_id, ret) + + def lcm_notify(self): + notification_content = prepare_notification_data(self.nf_inst_id, self.job_id, CHANGE_TYPE.MODIFIED, OPERATION_TYPE.HEAL) + logger.info('Notify request data = %s' % notification_content) + resp = notify_lcm_to_nfvo(json.dumps(notification_content)) + logger.info('Lcm notify end, response %s' % resp) + + def vnf_heal_failed_handle(self, error_msg): + logger.error('VNF Healing failed, detail message: %s' % error_msg) + NfInstModel.objects.filter(nfinstid=self.nf_inst_id).update(status=VNF_STATUS.FAILED, lastuptime=now_time()) + JobUtil.add_job_status(self.job_id, 255, error_msg) diff --git a/lcm/lcm/nf/biz/instantiate_vnf.py b/lcm/lcm/nf/biz/instantiate_vnf.py index 161cd7d1..606dbab0 100644 --- a/lcm/lcm/nf/biz/instantiate_vnf.py +++ b/lcm/lcm/nf/biz/instantiate_vnf.py @@ -15,19 +15,21 @@ import json import logging import traceback -import uuid from threading import Thread -from lcm.pub.database.models import NfInstModel, VmInstModel, NetworkInstModel, \ - SubNetworkInstModel, PortInstModel, StorageInstModel, FlavourInstModel, VNFCInstModel +from lcm.pub.database.models import NfInstModel from lcm.pub.exceptions import NFLCMException -from lcm.pub.msapi.gvnfmdriver import notify_lcm_to_nfvo, prepare_notification_data +from lcm.pub.msapi.gvnfmdriver import prepare_notification_data +# from lcm.pub.msapi.gvnfmdriver import notify_lcm_to_nfvo from lcm.pub.msapi.sdc_run_catalog import query_vnfpackage_by_id from lcm.pub.utils.jobutil import JobUtil from lcm.pub.utils.timeutil import now_time -from lcm.pub.utils.values import ignore_case_get, get_none, get_boolean, get_integer +from lcm.pub.utils.notificationsutil import NotificationsUtil +from lcm.pub.utils.values import ignore_case_get from lcm.pub.vimapi import adaptor from lcm.nf.biz.grant_vnf import grant_resource +from lcm.nf.const import CHANGE_TYPE, GRANT_TYPE, OPERATION_TYPE +from lcm.nf.biz import common logger = logging.getLogger(__name__) @@ -39,7 +41,7 @@ class InstantiateVnf(Thread): self.nf_inst_id = nf_inst_id self.job_id = job_id self.vim_id = ignore_case_get(ignore_case_get(self.data, "additionalParams"), "vimId") - self.grant_type = "Instantiate" + self.grant_type = GRANT_TYPE.INSTANTIATE def run(self): try: @@ -73,8 +75,9 @@ class InstantiateVnf(Thread): inputs = json.loads(inputs) for key, val in inputs.items(): input_parameters.append({"key": key, "value": val}) - vnf_package_info = query_vnfpackage_by_id(self.vnfd_id) - self.vnfd_info = json.loads(ignore_case_get(ignore_case_get(vnf_package_info, "packageInfo"), "vnfdModel")) + vnf_package = query_vnfpackage_by_id(self.vnfd_id) + pkg_info = ignore_case_get(vnf_package, "packageInfo") + self.vnfd_info = json.loads(ignore_case_get(pkg_info, "vnfdModel")) self.update_cps() metadata = ignore_case_get(self.vnfd_info, "metadata") @@ -89,7 +92,7 @@ class InstantiateVnf(Thread): version=version, vendor=vendor, netype=netype, - vnfd_model=self.vnfd_info, + vnfd_model=json.dumps(self.vnfd_info), status='NOT_INSTANTIATED', vnfdid=self.vnfd_id, localizationLanguage=ignore_case_get(self.data, 'localizationLanguage'), @@ -122,15 +125,20 @@ class InstantiateVnf(Thread): def create_res(self): logger.info("Create resource start") - adaptor.create_vim_res(self.vnfd_info, self.do_notify) + vim_cache, res_cache = {}, {} + adaptor.create_vim_res(self.vnfd_info, self.do_notify, vim_cache=vim_cache, res_cache=res_cache) JobUtil.add_job_status(self.job_id, 70, '[NF instantiation] create resource finish') + NfInstModel.objects.filter(nfinstid=self.nf_inst_id).\ + update(vimInfo=json.dumps(vim_cache), + resInfo=json.dumps(res_cache)) logger.info("Create resource finish") def lcm_notify(self): - notification_content = prepare_notification_data(self.nf_inst_id, self.job_id, "ADDED") + notification_content = prepare_notification_data(self.nf_inst_id, self.job_id, CHANGE_TYPE.ADDED, OPERATION_TYPE.INSTANTIATE) logger.info('Notify request data = %s' % notification_content) - resp = notify_lcm_to_nfvo(json.dumps(notification_content)) - logger.info('Lcm notify end, response %s' % resp) + # resp = notify_lcm_to_nfvo(json.dumps(notification_content)) + # logger.info('Lcm notify end, response %s' % resp) + NotificationsUtil().send_notification(notification_content) def vnf_inst_failed_handle(self, error_msg): logger.error('VNF instantiation failed, detail message: %s' % error_msg) @@ -139,7 +147,7 @@ class InstantiateVnf(Thread): def do_notify(self, res_type, ret): logger.info('Creating [%s] resource' % res_type) - resource_save_method = globals().get(res_type + '_save') + resource_save_method = getattr(common, res_type + '_save') resource_save_method(self.job_id, self.nf_inst_id, ret) def update_cps(self): @@ -153,15 +161,38 @@ class InstantiateVnf(Thread): break def set_location(self, apply_result): + vim_connections = ignore_case_get(apply_result, "vimConnections") + vnfid = ignore_case_get(apply_result, "vnfInstanceId") + directive = ignore_case_get(apply_result, "directive") + vim_assets = ignore_case_get(apply_result, "vimAssets") + access_info = ignore_case_get(vim_connections[0], "accessInfo") + tenant = ignore_case_get(access_info, "tenant") + vimid = ignore_case_get(vim_connections[0], "vimId") + cloud_owner, cloud_regionid = vimid.split("_") + vdu_info = [] + + for flavor in ignore_case_get(vim_assets, "vimComputeResourceFlavour"): + vdu_info.append({"vduName": flavor["resourceProviderId"], + "flavorName": flavor["vimFlavourId"], + "directive": directive}) + for resource_type in ['vdus', 'vls']: for resource in ignore_case_get(self.vnfd_info, resource_type): if "location_info" in resource["properties"]: - resource["properties"]["location_info"]["vimid"] = ignore_case_get(apply_result, "vimid") - resource["properties"]["location_info"]["tenant"] = ignore_case_get(apply_result, "tenant") + resource["properties"]["location_info"]["vimid"] = vimid + resource["properties"]["location_info"]["tenant"] = tenant + resource["properties"]["location_info"]["vnfId"] = vnfid + resource["properties"]["location_info"]["cloudOwner"] = cloud_owner + resource["properties"]["location_info"]["cloudRegionId"] = cloud_regionid + resource["properties"]["location_info"]["vduInfo"] = vdu_info else: resource["properties"]["location_info"] = { - "vimid": ignore_case_get(apply_result, "vimid"), - "tenant": ignore_case_get(apply_result, "tenant")} + "vimid": vimid, + "tenant": tenant, + "vnfId": vnfid, + "cloudOwner": cloud_owner, + "cloudRegionId": cloud_regionid, + "vduInfo": vdu_info} ''' def get_subnet_ids(self, ext_cp): @@ -173,128 +204,3 @@ class InstantiateVnf(Thread): subnet_ids.append(ignore_case_get(ip_address, "subnetId")) return subnet_ids ''' - - -def volume_save(job_id, nf_inst_id, ret): - JobUtil.add_job_status(job_id, 25, 'Create vloumns!') - StorageInstModel.objects.create( - storageid=str(uuid.uuid4()), - vimid=ignore_case_get(ret, "vimId"), - resouceid=ignore_case_get(ret, "id"), - name=ignore_case_get(ret, "name"), - tenant=ignore_case_get(ret, "tenantId"), - create_time=ignore_case_get(ret, "createTime"), - storagetype=get_none(ignore_case_get(ret, "type")), - size=ignore_case_get(ret, "size"), - insttype=0, - is_predefined=ignore_case_get(ret, "returnCode"), - nodeId=ignore_case_get(ret, "nodeId"), - instid=nf_inst_id) - - -def network_save(job_id, nf_inst_id, ret): - JobUtil.add_job_status(job_id, 35, 'Create networks!') - NetworkInstModel.objects.create( - networkid=str(uuid.uuid4()), - name=ignore_case_get(ret, "name"), - vimid=ignore_case_get(ret, "vimId"), - resouceid=ignore_case_get(ret, "id"), - tenant=ignore_case_get(ret, "tenantId"), - segmentid=str(ignore_case_get(ret, "segmentationId")), - network_type=ignore_case_get(ret, "networkType"), - physicalNetwork=ignore_case_get(ret, "physicalNetwork"), - vlantrans=get_boolean(ignore_case_get(ret, "vlanTransparent")), - is_shared=get_boolean(ignore_case_get(ret, "shared")), - routerExternal=get_boolean(ignore_case_get(ret, "routerExternal")), - insttype=0, - is_predefined=ignore_case_get(ret, "returnCode"), - nodeId=ignore_case_get(ret, "nodeId"), - instid=nf_inst_id) - - -def subnet_save(job_id, nf_inst_id, ret): - JobUtil.add_job_status(job_id, 40, 'Create subnets!') - SubNetworkInstModel.objects.create( - subnetworkid=str(uuid.uuid4()), - name=ignore_case_get(ret, "name"), - vimid=ignore_case_get(ret, "vimId"), - resouceid=ignore_case_get(ret, "id"), - tenant=ignore_case_get(ret, "tenantId"), - networkid=ignore_case_get(ret, "networkId"), - cidr=ignore_case_get(ret, "cidr"), - ipversion=ignore_case_get(ret, "ipversion"), - isdhcpenabled=ignore_case_get(ret, "enableDhcp"), - gatewayip=ignore_case_get(ret, "gatewayIp"), - dnsNameservers=ignore_case_get(ret, "dnsNameservers"), - hostRoutes=ignore_case_get(ret, "hostRoutes"), - allocationPools=ignore_case_get(ret, "allocationPools"), - insttype=0, - is_predefined=ignore_case_get(ret, "returnCode"), - instid=nf_inst_id) - - -def port_save(job_id, nf_inst_id, ret): - JobUtil.add_job_status(job_id, 50, 'Create ports!') - PortInstModel.objects.create( - portid=str(uuid.uuid4()), - networkid=ignore_case_get(ret, "networkId"), - subnetworkid=ignore_case_get(ret, "subnetId"), - name=ignore_case_get(ret, "name"), - vimid=ignore_case_get(ret, "vimId"), - resouceid=ignore_case_get(ret, "id"), - tenant=ignore_case_get(ret, "tenantId"), - macaddress=ignore_case_get(ret, "macAddress"), - ipaddress=ignore_case_get(ret, "ip"), - typevirtualnic=ignore_case_get(ret, "vnicType"), - securityGroups=ignore_case_get(ret, "securityGroups"), - insttype=0, - is_predefined=ignore_case_get(ret, "returnCode"), - nodeId=ignore_case_get(ret, "nodeId"), - instid=nf_inst_id) - - -def flavor_save(job_id, nf_inst_id, ret): - JobUtil.add_job_status(job_id, 60, 'Create flavors!') - FlavourInstModel.objects.create( - flavourid=str(uuid.uuid4()), - name=ignore_case_get(ret, "name"), - vimid=ignore_case_get(ret, "vimId"), - resouceid=ignore_case_get(ret, "id"), - tenant=ignore_case_get(ret, "tenantId"), - vcpu=get_integer(ignore_case_get(ret, "vcpu")), - memory=get_integer(ignore_case_get(ret, "memory")), - disk=get_integer(ignore_case_get(ret, "disk")), - ephemeral=get_integer(ignore_case_get(ret, "ephemeral")), - swap=get_integer(ignore_case_get(ret, "swap")), - isPublic=get_boolean(ignore_case_get(ret, "isPublic")), - extraspecs=ignore_case_get(ret, "extraSpecs"), - is_predefined=ret.get("returnCode", int(0)), - instid=nf_inst_id) - - -def vm_save(job_id, nf_inst_id, ret): - JobUtil.add_job_status(job_id, 70, 'Create vms!') - vm_id = str(uuid.uuid4()) - VmInstModel.objects.create( - vmid=vm_id, - vmname=ignore_case_get(ret, "name"), - vimid=ignore_case_get(ret, "vimId"), - resouceid=ignore_case_get(ret, "id"), - tenant=ignore_case_get(ret, "tenantId"), - nic_array=ignore_case_get(ret, "nicArray"), - metadata=ignore_case_get(ret, "metadata"), - volume_array=ignore_case_get(ret, "volumeArray"), - server_group=ignore_case_get(ret, "serverGroup"), - availability_zone=str(ignore_case_get(ret, "availabilityZone", "undefined")), - flavor_id=ignore_case_get(ret, "flavorId"), - security_groups=ignore_case_get(ret, "securityGroups"), - operationalstate=ignore_case_get(ret, "status"), - insttype=0, - is_predefined=ignore_case_get(ret, "returnCode"), - instid=nf_inst_id) - VNFCInstModel.objects.create( - vnfcinstanceid=str(uuid.uuid4()), - vduid=ignore_case_get(ret, "id"), - is_predefined=ignore_case_get(ret, "returnCode"), - instid=nf_inst_id, - vmid=vm_id) diff --git a/lcm/lcm/nf/biz/operate_vnf.py b/lcm/lcm/nf/biz/operate_vnf.py new file mode 100644 index 00000000..1c01adaf --- /dev/null +++ b/lcm/lcm/nf/biz/operate_vnf.py @@ -0,0 +1,106 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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 json +import logging +import traceback +from threading import Thread + +from lcm.pub.database.models import NfInstModel, VmInstModel +from lcm.pub.exceptions import NFLCMException +from lcm.pub.msapi.gvnfmdriver import prepare_notification_data +# from lcm.pub.msapi.gvnfmdriver import notify_lcm_to_nfvo +from lcm.pub.utils.jobutil import JobUtil +from lcm.pub.utils.timeutil import now_time +from lcm.pub.utils.notificationsutil import NotificationsUtil +from lcm.pub.utils.values import ignore_case_get +from lcm.pub.vimapi import adaptor +from lcm.nf.biz.grant_vnf import grant_resource +from lcm.nf.const import VNF_STATUS, RESOURCE_MAP, CHANGE_TYPE, GRANT_TYPE, OPERATION_TYPE + +logger = logging.getLogger(__name__) + + +class OperateVnf(Thread): + def __init__(self, data, nf_inst_id, job_id): + super(OperateVnf, self).__init__() + self.data = data + self.nf_inst_id = nf_inst_id + self.job_id = job_id + self.grant_type = GRANT_TYPE.OPERATE + self.changeStateTo = ignore_case_get(self.data, "changeStateTo") + self.stopType = ignore_case_get(self.data, "stopType") + self.gracefulStopTimeout = ignore_case_get(self.data, "gracefulStopTimeout") + self.inst_resource = {'vm': []} + + def run(self): + try: + self.apply_grant() + self.query_inst_resource() + self.operate_resource() + JobUtil.add_job_status(self.job_id, 100, "Operate Vnf success.") + NfInstModel.objects.filter(nfinstid=self.nf_inst_id).update(status='INSTANTIATED', lastuptime=now_time(), operationState=self.changeStateTo) + self.lcm_notify() + except NFLCMException as e: + self.vnf_operate_failed_handle(e.message) + except Exception as e: + logger.error(e.message) + self.vnf_operate_failed_handle(traceback.format_exc()) + + def apply_grant(self): + vdus = VmInstModel.objects.filter(instid=self.nf_inst_id, is_predefined=1) + apply_result = grant_resource(data=self.data, nf_inst_id=self.nf_inst_id, job_id=self.job_id, + grant_type=self.grant_type, vdus=vdus) + logger.info("Grant resource, response: %s" % apply_result) + JobUtil.add_job_status(self.job_id, 20, 'Nf Operate grant_resource finish') + + def query_inst_resource(self): + logger.info('Query resource begin') + # Querying only vm resources now + resource_type = "Vm" + resource_table = globals().get(resource_type + 'InstModel') + resource_insts = resource_table.objects.filter(instid=self.nf_inst_id) + for resource_inst in resource_insts: + if not resource_inst.resourceid: + continue + self.inst_resource[RESOURCE_MAP.get(resource_type)].append(self.get_resource(resource_inst)) + logger.info('Query resource end, resource=%s' % self.inst_resource) + + def get_resource(self, resource): + return { + "vim_id": resource.vimid, + "tenant_id": resource.tenant, + "id": resource.resourceid + } + + def operate_resource(self): + logger.info('Operate resource begin') + adaptor.operate_vim_res(self.inst_resource, self.changeStateTo, self.stopType, self.gracefulStopTimeout, self.do_notify_op) + logger.info('Operate resource complete') + + def lcm_notify(self): + notification_content = prepare_notification_data(self.nf_inst_id, self.job_id, CHANGE_TYPE.MODIFIED, OPERATION_TYPE.OPERATE) + logger.info('Notify request data = %s' % notification_content) + # resp = notify_lcm_to_nfvo(json.dumps(notification_content)) + # logger.info('Lcm notify end, response %s' % resp) + NotificationsUtil().send_notification(notification_content) + + def vnf_operate_failed_handle(self, error_msg): + logger.error('VNF Operation failed, detail message: %s' % error_msg) + NfInstModel.objects.filter(nfinstid=self.nf_inst_id).update(status=VNF_STATUS.FAILED, lastuptime=now_time()) + JobUtil.add_job_status(self.job_id, 255, error_msg) + + def do_notify_op(self, status, resid): + logger.error('VNF resource %s updated to: %s' % (resid, status)) + VmInstModel.objects.filter(instid=self.nf_inst_id, resourceid=resid).update(operationalstate=status) diff --git a/lcm/lcm/nf/biz/query_subscription.py b/lcm/lcm/nf/biz/query_subscription.py new file mode 100644 index 00000000..aedb467d --- /dev/null +++ b/lcm/lcm/nf/biz/query_subscription.py @@ -0,0 +1,69 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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 ast +import json +import logging + +from lcm.pub.database.models import SubscriptionModel +from lcm.pub.exceptions import NFLCMException + +logger = logging.getLogger(__name__) +ROOT_FILTERS = { + 'operationTypes': 'operation_types', + 'operationStates': 'operation_states', + 'notificationTypes': 'notification_types' +} +VNF_INSTANCE_FILTERS = { + "vnfInstanceId": "vnf_instance_filter" +} + + +class QuerySubscription: + def __init__(self, data, subscription_id=''): + self.subscription_id = subscription_id + self.params = data + + def query_multi_subscriptions(self): + query_data = {} + logger.debug("QueryMultiSubscriptions--get--biz::> Check for filters in query params" % self.params) + for query, value in self.params.iteritems(): + if query in ROOT_FILTERS: + query_data[ROOT_FILTERS[query] + '__icontains'] = value + for query, value in self.params.iteritems(): + if query in VNF_INSTANCE_FILTERS: + query_data[VNF_INSTANCE_FILTERS[query] + '__icontains'] = value + # Query the database with filters if the request has fields in request params, else fetch all records + if query_data: + subscriptions = SubscriptionModel.objects.filter(**query_data) + else: + subscriptions = SubscriptionModel.objects.all() + if not subscriptions.exists(): + raise NFLCMException('Subscriptions do not exist') + return [self.fill_resp_data(subscription) for subscription in subscriptions] + + def fill_resp_data(self, subscription): + subscription_filter = { + "notificationTypes": ast.literal_eval(subscription.notification_types), + "operationTypes": ast.literal_eval(subscription.operation_types), + "operationStates": ast.literal_eval(subscription.operation_states), + "vnfInstanceSubscriptionFilter": json.loads(subscription.vnf_instance_filter) + } + resp_data = { + 'id': subscription.subscription_id, + 'callbackUri': subscription.callback_uri, + 'filter': subscription_filter, + '_links': json.loads(subscription.links) + } + return resp_data diff --git a/lcm/lcm/nf/biz/query_vnf.py b/lcm/lcm/nf/biz/query_vnf.py index 09fab6ca..380d455a 100644 --- a/lcm/lcm/nf/biz/query_vnf.py +++ b/lcm/lcm/nf/biz/query_vnf.py @@ -51,7 +51,7 @@ class QueryVnf: "id": s.storageid, "storageResource": { "vimConnectionId": s.vimid, - "resourceId": s.resouceid + "resourceId": s.resourceid } } arr.append(storage) @@ -67,7 +67,7 @@ class QueryVnf: "virtualLinkDescId": v.vldid, "networkResource": { "vimConnectionId": net[0].vimid, - "resourceId": net[0].resouceid + "resourceId": net[0].resourceid } } vl_arr.append(v_dic) @@ -87,7 +87,7 @@ class QueryVnf: "vduId": vnfc.vduid, "computeResource": { "vimConnectionId": vm[0].vimid, - "resourceId": vm[0].resouceid + "resourceId": vm[0].resourceid }, "storageResourceIds": [s.storageid for s in storage] } diff --git a/lcm/lcm/nf/biz/query_vnf_lcm_op_occ.py b/lcm/lcm/nf/biz/query_vnf_lcm_op_occ.py new file mode 100644 index 00000000..01d8b8db --- /dev/null +++ b/lcm/lcm/nf/biz/query_vnf_lcm_op_occ.py @@ -0,0 +1,79 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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 json +import logging + +from lcm.pub.database.models import VNFLcmOpOccModel +from lcm.pub.exceptions import NFLCMException + +logger = logging.getLogger(__name__) +FILTERS = { + 'id': 'id', + 'operationState': 'operation_state', + 'stateEnteredTime': 'state_entered_time', + 'startTime': 'start_time', + 'vnfInstanceId': 'vnf_instance_id', + 'grantId': 'grant_id', + 'operation': 'operation' +} + + +class QueryVnfLcmOpOcc: + def __init__(self, data, lcm_op_occ_id=''): + self.vnf_lcm_op_occ_id = lcm_op_occ_id + self.params = data + + def query_multi_vnf_lcm_op_occ(self): + query_data = {} + logger.debug("QueryMultiVnfLcmOpOccs--get--biz::> Check for filters in query params" % self.params) + for query, value in self.params.iteritems(): + if query in FILTERS: + query_data[FILTERS[query]] = value + # Query the database with filters if the request has fields in request params, else fetch all records + if query_data: + lcm_ops = VNFLcmOpOccModel.objects.filter(**query_data) + else: + lcm_ops = VNFLcmOpOccModel.objects.all() + if not lcm_ops.exists(): + raise NFLCMException('LCM Operation Occurances do not exist') + return [self.fill_resp_data(lcm_op) for lcm_op in lcm_ops] + + def fill_resp_data(self, lcm_op): + resp_data = { + 'id': lcm_op.id, + 'operationState': lcm_op.operation_state, + 'stateEnteredTime': lcm_op.state_entered_time, + 'startTime': lcm_op.start_time, + 'vnfInstanceId': lcm_op.vnf_instance_id, + 'grantId': None, + 'operation': lcm_op.operation, + 'isAutomaticInvocation': lcm_op.is_automatic_invocation, + 'operationParams': json.loads(lcm_op.operation_params), + 'isCancelPending': lcm_op.is_cancel_pending, + 'cancelMode': lcm_op.cancel_mode, + 'error': None if not lcm_op.error else json.loads(lcm_op.error), + 'resourceChanges': None if not lcm_op.resource_changes else json.loads(lcm_op.resource_changes), + 'changedInfo': None if not lcm_op.changed_info else json.loads(lcm_op.changed_info), + 'changedExtConnectivity': None if not lcm_op.changed_ext_connectivity else json.loads(lcm_op.changed_ext_connectivity), + '_links': json.loads(lcm_op.links) + } + return resp_data + + def query_single_vnf_lcm_op_occ(self): + lcm_op = VNFLcmOpOccModel.objects.filter(id=self.vnf_lcm_op_occ_id) + if not lcm_op.exists(): + raise NFLCMException('LCM Operation Occurance does not exist') + resp_data = self.fill_resp_data(lcm_op[0]) + return resp_data diff --git a/lcm/lcm/nf/biz/terminate_vnf.py b/lcm/lcm/nf/biz/terminate_vnf.py index d63cdfa9..4f74f62d 100644 --- a/lcm/lcm/nf/biz/terminate_vnf.py +++ b/lcm/lcm/nf/biz/terminate_vnf.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json +# import json import logging import traceback from threading import Thread @@ -24,12 +24,15 @@ from lcm.pub.database.models import ( FlavourInstModel, SubNetworkInstModel ) from lcm.pub.exceptions import NFLCMException -from lcm.pub.msapi.gvnfmdriver import prepare_notification_data, notify_lcm_to_nfvo +from lcm.pub.msapi.gvnfmdriver import prepare_notification_data +# from lcm.pub.msapi.gvnfmdriver import notify_lcm_to_nfvo from lcm.pub.utils.jobutil import JobUtil from lcm.pub.utils.timeutil import now_time +from lcm.pub.utils.notificationsutil import NotificationsUtil from lcm.pub.utils.values import ignore_case_get from lcm.pub.vimapi import adaptor from lcm.nf.biz.grant_vnf import grant_resource +from lcm.nf.const import CHANGE_TYPE, GRANT_TYPE, OPERATION_TYPE logger = logging.getLogger(__name__) @@ -43,7 +46,7 @@ class TerminateVnf(Thread): self.terminationType = ignore_case_get(self.data, "terminationType") self.gracefulTerminationTimeout = ignore_case_get(self.data, "gracefulTerminationTimeout") self.inst_resource = {'volumn': [], 'network': [], 'subnet': [], 'port': [], 'flavor': [], 'vm': []} - self.grant_type = "Terminate" + self.grant_type = GRANT_TYPE.TERMINATE def run(self): try: @@ -83,7 +86,7 @@ class TerminateVnf(Thread): resource_table = globals().get(resource_type + 'InstModel') resource_insts = resource_table.objects.filter(instid=self.nf_inst_id) for resource_inst in resource_insts: - if not resource_inst.resouceid: + if not resource_inst.resourceid: continue self.inst_resource[RESOURCE_MAP.get(resource_type)].append(self.get_resource(resource_inst)) logger.info('Query resource end, resource=%s' % self.inst_resource) @@ -92,12 +95,12 @@ class TerminateVnf(Thread): return { "vim_id": resource.vimid, "tenant_id": resource.tenant, - "res_id": resource.resouceid, + "res_id": resource.resourceid, "is_predefined": resource.is_predefined } def query_notify_data(self): - self.notify_data = prepare_notification_data(self.nf_inst_id, self.job_id, "RMOVED") + self.notify_data = prepare_notification_data(self.nf_inst_id, self.job_id, CHANGE_TYPE.REMOVED, OPERATION_TYPE.TERMINATE) NetworkInstModel.objects.filter(instid=self.nf_inst_id) StorageInstModel.objects.filter(instid=self.nf_inst_id) PortInstModel.objects.filter(instid=self.nf_inst_id) @@ -114,13 +117,14 @@ class TerminateVnf(Thread): logger.error('Deleting [%s] resource, resourceid [%s]' % (res_type, res_id)) resource_type = RESOURCE_MAP.keys()[RESOURCE_MAP.values().index(res_type)] resource_table = globals().get(resource_type + 'InstModel') - resource_table.objects.filter(instid=self.nf_inst_id, resouceid=res_id).delete() + resource_table.objects.filter(instid=self.nf_inst_id, resourceid=res_id).delete() def lcm_notify(self): NfInstModel.objects.filter(nfinstid=self.nf_inst_id).update(status='NOT_INSTANTIATED', lastuptime=now_time()) logger.info('Send notify request to nfvo') - resp = notify_lcm_to_nfvo(json.dumps(self.notify_data)) - logger.info('Lcm notify end, response: %s' % resp) + # resp = notify_lcm_to_nfvo(json.dumps(self.notify_data)) + # logger.info('Lcm notify end, response: %s' % resp) + NotificationsUtil().send_notification(self.notify_data) def vnf_term_failed_handle(self, error_msg): logger.error('VNF termination failed, detail message: %s' % error_msg) diff --git a/lcm/lcm/nf/const.py b/lcm/lcm/nf/const.py index c5ebf5d0..5e6cb675 100644 --- a/lcm/lcm/nf/const.py +++ b/lcm/lcm/nf/const.py @@ -15,13 +15,86 @@ import json from lcm.pub.utils.jobutil import enum +HEAL_ACTION_TYPE = enum(START="vmCreate", RESTART="vmReset") +ACTION_TYPE = enum(START=1, STOP=2, REBOOT=3) +GRANT_TYPE = enum(INSTANTIATE="INSTANTIATE", TERMINATE="TERMINATE", HEAL_CREATE="Heal Create", HEAL_RESTART="Heal Restart", OPERATE="OPERATE") VNF_STATUS = enum(NULL='null', INSTANTIATING="instantiating", INACTIVE='inactive', ACTIVE="active", FAILED="failed", TERMINATING="terminating", SCALING="scaling", OPERATING="operating", UPDATING="updating", HEALING="healing") +OPERATION_TYPE = enum( + INSTANTIATE="INSTANTIATE", + SCALE="SCALE", + SCALE_TO_LEVEL="SCALE_TO_LEVEL", + CHANGE_FLAVOUR="CHANGE_FLAVOUR", + TERMINATE="TERMINATE", + HEAL="HEAL", + OPERATE="OPERATE", + CHANGE_EXT_CONN="CHANGE_EXT_CONN", + MODIFY_INFO="MODIFY_INFO" +) + +OPERATION_STATE_TYPE = enum( + STARTING="STARTING", + PROCESSING="PROCESSING", + COMPLETED="COMPLETED", + FAILED_TEMP="FAILED_TEMP", + FAILED="FAILED", + ROLLING_BACK="ROLLING_BACK", + ROLLED_BACK="ROLLED_BACK" +) + +CHANGE_TYPE = enum( + ADDED='ADDED', + REMOVED='REMOVED', + MODIFIED='MODIFIED', + TEMPORARY='TEMPORARY', + LINK_PORT_ADDED='LINK_PORT_ADDED', + LINK_PORT_REMOVED='LINK_PORT_REMOVED' +) + RESOURCE_MAP = {'Storage': 'volumn', 'Network': 'network', 'SubNetwork': 'subnet', 'Port': 'port', 'Flavour': 'flavor', 'Vm': 'vm'} +ROOT_URI = "api/vnflcm/v1/subscriptions/" + +AUTH_TYPES = ["BASIC", "OAUTH2_CLIENT_CREDENTIALS", "TLS_CERT"] + +BASIC = "BASIC" + +OAUTH2_CLIENT_CREDENTIALS = "OAUTH2_CLIENT_CREDENTIALS" + +LCCNNOTIFICATION = "VnfLcmOperationOccurrenceNotification" + +NOTIFICATION_TYPES = [ + "VnfLcmOperationOccurrenceNotification", + "VnfIdentifierCreationNotification", + "VnfIdentifierDeletionNotification" +] + +LCM_OPERATION_TYPES = [ + OPERATION_TYPE.INSTANTIATE, + OPERATION_TYPE.SCALE, + OPERATION_TYPE.SCALE_TO_LEVEL, + OPERATION_TYPE.CHANGE_FLAVOUR, + OPERATION_TYPE.TERMINATE, + OPERATION_TYPE.HEAL, + OPERATION_TYPE.OPERATE, + OPERATION_TYPE.CHANGE_EXT_CONN, + OPERATION_TYPE.MODIFY_INFO +] + +LCM_OPERATION_STATE_TYPES = [ + OPERATION_STATE_TYPE.STARTING, + OPERATION_STATE_TYPE.PROCESSING, + OPERATION_STATE_TYPE.COMPLETED, + OPERATION_STATE_TYPE.FAILED_TEMP, + OPERATION_STATE_TYPE.FAILED, + OPERATION_STATE_TYPE.ROLLING_BACK, + OPERATION_STATE_TYPE.ROLLED_BACK +] + + inst_req_data = { "flavourId": "flavour_1", "instantiationLevelId": "instantiationLevel_1", @@ -257,13 +330,6 @@ vnfd_model_dict = { } }, "artifacts": [ - { - "artifact_name": "software_version_file", - "type": "tosca.artifacts.Deployment", - "file": "AppSoftwares/zte-cn-xgw-V5.16.11_NFV-version.zip", - "repository": "", - "deploy_path": "" - } ] } ], @@ -426,6 +492,15 @@ c1_data_get_tenant_id = { } ] } +c1_data_get_tenant_id_1 = { + "tenants": [ + { + "id": "1", + "name": "tenantname" + } + ] +} + # create_volume c2_data_create_volume = { @@ -476,7 +551,7 @@ c4_data_create_network = { "vlanTransparent": True, "networkType": "vlan", "segmentationId": 202, - "physicalNetwork": "ctrl", + "physicalNetwork": "physnet1", "routerExternal": False } @@ -520,7 +595,7 @@ c6_data_create_port = { "subnetName": "subnet1", "macAddress": "212.12.61.23", "ip": "10.43.38.11", - "vnicType": "normal", + "vnicType": "direct", "securityGroups": "" } @@ -650,7 +725,18 @@ vnfdModel = { }, }, ], - "vnic_type": "test", + "vnic_type": "direct", + "role": "root", + "virtual_network_interface_requirements": [ + { + "network_interface_requirements": { + "interfaceType": '{"schema-version": "0", "schema-location":"", "platform-id": "generic", "mandatory": false, "configuration-value": "SR-IOV"}' + }, + "support_mandatory": False, + "name": "SRIOV_Port", + "description": "sriov" + } + ] } } ], @@ -662,6 +748,17 @@ vnfdModel = { "vimid": "test", "tenant": "chinamobile", "availability_zone": "test", + "vnfId": "", + "vnfName": "", + "cloudOwner": "", + "cloudRegionId": "", + "vduInfo": [ + { + "vduName": "VDU_vbng_0", + "flavorName": "flavor_1", + "directive": "" + } + ] }, "name": "test", "inject_files": [], diff --git a/lcm/lcm/nf/serializers/affected_storages.py b/lcm/lcm/nf/serializers/affected_storages.py new file mode 100644 index 00000000..1c01fefb --- /dev/null +++ b/lcm/lcm/nf/serializers/affected_storages.py @@ -0,0 +1,52 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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. + + +from rest_framework import serializers + +from resource_handle import ResourceHandleSerializer +from lcm.nf.const import CHANGE_TYPE + +CHANGE_TYPES = [ + CHANGE_TYPE.ADDED, + CHANGE_TYPE.REMOVED, + CHANGE_TYPE.MODIFIED, + CHANGE_TYPE.TEMPORARY +] + + +class AffectedStoragesSerializer(serializers.Serializer): + id = serializers.UUIDField( + help_text="Identifier of the Storage instance, identifying the " + + "applicable 'virtualStorageResourceInfo' entry in the 'VnfInstance' data type", + required=True + ) + virtualStorageDescId = serializers.UUIDField( + help_text="Identifier of the related VirtualStorage descriptor " + + "in the VNFD. ", + required=True + ) + changeType = serializers.ChoiceField( + help_text="Signals the type of change", + required=True, + choices=CHANGE_TYPES + ) + metadata = serializers.DictField( + help_text="Metadata about this resource. ", + required=False, + allow_null=True) + storageResource = ResourceHandleSerializer( + help_text="Reference to the VirtualStorage resource.", + required=True, + allow_null=False) diff --git a/lcm/lcm/nf/serializers/affected_vls.py b/lcm/lcm/nf/serializers/affected_vls.py new file mode 100644 index 00000000..0b47b27f --- /dev/null +++ b/lcm/lcm/nf/serializers/affected_vls.py @@ -0,0 +1,53 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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. + + +from rest_framework import serializers + +from resource_handle import ResourceHandleSerializer +from lcm.nf.const import CHANGE_TYPE + +CHANGE_TYPES = [ + CHANGE_TYPE.ADDED, + CHANGE_TYPE.REMOVED, + CHANGE_TYPE.MODIFIED, + CHANGE_TYPE.TEMPORARY, + CHANGE_TYPE.LINK_PORT_ADDED, + CHANGE_TYPE.LINK_PORT_REMOVED +] + + +class AffectedVLsSerializer(serializers.Serializer): + id = serializers.UUIDField( + help_text="Identifier of the virtual link instance, identifying " + + "the applicable 'vnfVirtualLinkResourceInfo' ", + required=True + ) + virtualLinkDescId = serializers.UUIDField( + help_text="Identifier of the related VLD in the VNFD.", + required=True + ) + changeType = serializers.ChoiceField( + help_text="Signals the type of change", + required=True, + choices=CHANGE_TYPES + ) + metadata = serializers.DictField( + help_text="Metadata about this resource. ", + required=False, + allow_null=True) + networkResource = ResourceHandleSerializer( + help_text="Reference to the VirtualNetwork resource.", + required=True, + allow_null=False) diff --git a/lcm/lcm/nf/serializers/affected_vnfcs.py b/lcm/lcm/nf/serializers/affected_vnfcs.py new file mode 100644 index 00000000..10d93473 --- /dev/null +++ b/lcm/lcm/nf/serializers/affected_vnfcs.py @@ -0,0 +1,69 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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. + + +from rest_framework import serializers + +from resource_handle import ResourceHandleSerializer +from lcm.nf.const import CHANGE_TYPE + +CHANGE_TYPES = [ + CHANGE_TYPE.ADDED, + CHANGE_TYPE.REMOVED, + CHANGE_TYPE.MODIFIED, + CHANGE_TYPE.TEMPORARY +] + + +class AffectedVnfcsSerializer(serializers.Serializer): + id = serializers.UUIDField( + help_text="Identifier of the Vnfc instance, identifying the " + + "applicable 'vnfcResourceInfo' entry in the 'VnfInstance' data type", + required=True + ) + vduId = serializers.UUIDField( + help_text="Identifier of the related VDU in the VNFD.", + required=True + ) + changeType = serializers.ChoiceField( + help_text="Signals the type of change", + required=True, + choices=CHANGE_TYPES + ) + affectedVnfcCpIds = serializers.ListField( + help_text="Identifiers of CP(s) of the VNFC instance that " + + "were affected by the change", + required=False, + child=serializers.UUIDField(required=True) + ) + addedStorageResourceIds = serializers.ListField( + help_text="References to VirtualStorage resources that " + + "have been added", + required=False, + child=serializers.UUIDField() + ) + removedStorageResourceIds = serializers.ListField( + help_text="References to VirtualStorage resources that " + + "have been removed.", + required=False, + child=serializers.UUIDField() + ) + metadata = serializers.DictField( + help_text="Metadata about this resource. ", + required=False, + allow_null=True) + computeResource = ResourceHandleSerializer( + help_text="Reference to the VirtualCompute resource.", + required=True, + allow_null=False) diff --git a/lcm/lcm/nf/serializers/heal_vnf_req.py b/lcm/lcm/nf/serializers/heal_vnf_req.py new file mode 100644 index 00000000..e020182a --- /dev/null +++ b/lcm/lcm/nf/serializers/heal_vnf_req.py @@ -0,0 +1,25 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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. + +from rest_framework import serializers + + +class HealVnfRequestSerializer(serializers.Serializer): + cause = serializers.CharField(help_text="Cause of NS heal", required=False, allow_null=True) + additionalParams = serializers.DictField( + help_text="Additional input parameters for the healing process, \ + specific to the VNF being healed, \ + as declared in the VNFD as part of HealVnfOpConfig.", + required=False, + allow_null=True) diff --git a/lcm/lcm/nf/serializers/lccn_filter_data.py b/lcm/lcm/nf/serializers/lccn_filter_data.py new file mode 100644 index 00000000..b016a4f0 --- /dev/null +++ b/lcm/lcm/nf/serializers/lccn_filter_data.py @@ -0,0 +1,42 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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. + +from rest_framework import serializers + +from vnf_instance_subscription_filter import VnfInstanceSubscriptionFilter +from lcm.nf.const import NOTIFICATION_TYPES, LCM_OPERATION_TYPES, LCM_OPERATION_STATE_TYPES + + +class LifeCycleChangeNotificationsFilter(serializers.Serializer): + notificationTypes = serializers.ListField( + child=serializers.ChoiceField(required=True, choices=NOTIFICATION_TYPES), + help_text="Match particular notification types", + allow_null=False, + required=False) + operationTypes = serializers.ListField( + child=serializers.ChoiceField(required=True, choices=LCM_OPERATION_TYPES), + help_text="Match particular VNF lifecycle operation types for the " + + "notification of type VnfLcmOperationOccurrenceNotification.", + allow_null=False, + required=False) + operationStates = serializers.ListField( + child=serializers.ChoiceField(required=True, choices=LCM_OPERATION_STATE_TYPES), + help_text="Match particular LCM operation state values as reported " + + "in notifications of type VnfLcmOperationOccurrenceNotification.", + allow_null=False, + required=False) + vnfInstanceSubscriptionFilter = VnfInstanceSubscriptionFilter( + help_text="Filter criteria to select VNF instances about which to notify.", + required=False, + allow_null=False) diff --git a/lcm/lcm/nf/serializers/lccn_subscription.py b/lcm/lcm/nf/serializers/lccn_subscription.py new file mode 100644 index 00000000..a4430ebe --- /dev/null +++ b/lcm/lcm/nf/serializers/lccn_subscription.py @@ -0,0 +1,45 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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. + +from rest_framework import serializers + +from link import LinkSerializer +from lccn_filter_data import LifeCycleChangeNotificationsFilter + + +class LinkSerializer(serializers.Serializer): + self = LinkSerializer( + help_text="URI of this resource.", + required=True, + allow_null=False) + + +class LccnSubscriptionSerializer(serializers.Serializer): + id = serializers.CharField( + help_text="Identifier of this subscription resource.", + max_length=255, + required=True, + allow_null=False) + callbackUri = serializers.CharField( + help_text="The URI of the endpoint to send the notification to.", + max_length=255, + required=True, + allow_null=False) + filter = LifeCycleChangeNotificationsFilter( + help_text="Filter settings for this subscription, to define the " + + "of all notifications this subscription relates to.", + required=False) + _links = LinkSerializer( + help_text="Links to resources related to this resource.", + required=True) diff --git a/lcm/lcm/nf/serializers/lccn_subscription_request.py b/lcm/lcm/nf/serializers/lccn_subscription_request.py new file mode 100644 index 00000000..a11de885 --- /dev/null +++ b/lcm/lcm/nf/serializers/lccn_subscription_request.py @@ -0,0 +1,35 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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. + +from rest_framework import serializers + +from lccn_filter_data import LifeCycleChangeNotificationsFilter +from subscription_auth_data import SubscriptionAuthenticationSerializer + + +class LccnSubscriptionRequestSerializer(serializers.Serializer): + callbackUri = serializers.URLField( + help_text="The URI of the endpoint to send the notification to.", + required=True, + allow_null=False) + filter = LifeCycleChangeNotificationsFilter( + help_text="Filter settings for the subscription, to define the subset of all " + + "notifications this subscription relates to.", + required=False, + allow_null=True) + authentication = SubscriptionAuthenticationSerializer( + help_text="Authentication parameters to configure the use of Authorization when sending " + + "notifications corresponding to this subscription.", + required=False, + allow_null=True) diff --git a/lcm/lcm/nf/serializers/lccn_subscriptions.py b/lcm/lcm/nf/serializers/lccn_subscriptions.py new file mode 100644 index 00000000..c4f70f66 --- /dev/null +++ b/lcm/lcm/nf/serializers/lccn_subscriptions.py @@ -0,0 +1,21 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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. + +from rest_framework import serializers + +from lccn_subscription import LccnSubscriptionSerializer + + +class LccnSubscriptionsSerializer(serializers.ListSerializer): + child = LccnSubscriptionSerializer() diff --git a/lcm/lcm/nf/serializers/notification_types.py b/lcm/lcm/nf/serializers/notification_types.py new file mode 100644 index 00000000..1472e292 --- /dev/null +++ b/lcm/lcm/nf/serializers/notification_types.py @@ -0,0 +1,128 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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. + +from rest_framework import serializers + +from affected_vnfcs import AffectedVnfcsSerializer +from affected_vls import AffectedVLsSerializer +from affected_storages import AffectedStoragesSerializer +from lcm.nf.const import LCM_OPERATION_TYPES, LCM_OPERATION_STATE_TYPES +from link import LinkSerializer +from response import ProblemDetailsSerializer +from ext_virtual_link_info import ExtVirtualLinkInfoSerializer +from vnf_info_modifications import VnfInfoModificationsSerializer + + +class LccnLinksSerializer(serializers.Serializer): + vnfInstance = LinkSerializer( + help_text="Link to the resource representing the VNF instance to " + "which the notified change applies.", + required=True, + allow_null=False) + subscription = LinkSerializer( + help_text="Link to the related subscription.", + required=True, + allow_null=False) + vnfLcmOpOcc = LinkSerializer( + help_text="Link to the VNF lifecycle management operation" + "occurrence that this notification is related to. Shall be" + "present if there is a related lifecycle operation occurance.", + required=False, + allow_null=False) + + +class VnfLcmOperationOccurrenceNotification(serializers.Serializer): + id = serializers.CharField( + help_text="Identifier of this notification", + max_length=255, + required=True, + allow_null=False) + notificationType = serializers.CharField( + help_text="Type of the notification", + max_length=50, + required=True, + allow_null=False) + subscriptionId = serializers.CharField( + help_text="Identifier for the subscription", + required=False) + timeStamp = serializers.CharField( + help_text="Date-time of the generation of the notification.", + required=True) + notificationStatus = serializers.ChoiceField( + help_text="Indicates whether this notification reports about the start" + "of a lifecycle operation or the result of a lifecycle" + "operation", + choices=["START", "RESULT"], + required=True) + operationState = serializers.ChoiceField( + choices=LCM_OPERATION_STATE_TYPES, + help_text="The state of the VNF LCM operation occurrence. ", + required=True) + vnfInstanceId = serializers.CharField( + help_text="The identifier of the VNF instance affected. ", + required=True) + operation = serializers.ChoiceField( + help_text="The lifecycle management operation.", + required=True, + choices=LCM_OPERATION_TYPES) + isAutomaticInvocation = serializers.BooleanField( + help_text="Set to true if this VNF LCM operation occurrence has" + "been triggered by an automated procedure inside the" + "VNFM. Otherwise False", + required=True) + vnfLcmOpOccId = serializers.CharField( + help_text="The identifier of the VNF lifecycle management" + "operation occurrence associated to the notification.", + required=True) + affectedVnfcs = AffectedVnfcsSerializer( + help_text="Information about VNFC instances that were affected " + + "during the lifecycle operation.", + required=False, + many=True + ) + affectedVirtualLinks = AffectedVLsSerializer( + help_text="Information about VL instances that were affected " + + "during the lifecycle operation. ", + required=False, + many=True + ) + affectedVirtualStorages = AffectedStoragesSerializer( + help_text="Information about virtualised storage instances that " + + "were affected during the lifecycle operation", + required=False, + many=True + ) + changedInfo = VnfInfoModificationsSerializer( + help_text="Information about the changed VNF instance information, " + + "including VNF configurable properties", + required=False, + allow_null=True) + changedExtConnectivity = ExtVirtualLinkInfoSerializer( + help_text="Information about changed external connectivity, if this " + + "notification represents the result of a lifecycle operation occurrence. " + + "Shall be present if the 'notificationStatus' is set to 'RESULT' and the " + + "'operation' is set to 'CHANGE_EXT_CONN'. Shall be absent otherwise.", + many=True, + required=False, + allow_null=True) + error = ProblemDetailsSerializer( + help_text="If 'operationState' is 'FAILED_TEMP' or 'FAILED' or " + + "'PROCESSING' or 'ROLLING_BACK' and previous value of 'operationState' " + + "was 'FAILED_TEMP' this attribute shall be present ", + allow_null=True, + required=False + ) + _links = LccnLinksSerializer( + help_text="Links to resources related to this resource.", + required=True) diff --git a/lcm/lcm/nf/serializers/operate_vnf_req.py b/lcm/lcm/nf/serializers/operate_vnf_req.py new file mode 100644 index 00000000..b40e7000 --- /dev/null +++ b/lcm/lcm/nf/serializers/operate_vnf_req.py @@ -0,0 +1,36 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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. + +from rest_framework import serializers + + +class OperateVnfRequestSerializer(serializers.Serializer): + changeStateTo = serializers.ChoiceField( + help_text="The desired operational state (i.e. started or stopped) to change the VNF to.", + choices=["STARTED", "STOPPED"], + required=True) + stopType = serializers.ChoiceField( + help_text="It signals whether forceful or graceful stop is requested.", + choices=["FORCEFUL", "GRACEFUL"], + required=False) + gracefulStopTimeout = serializers.IntegerField( + help_text="The time interval to wait for the VNF to be taken out of service during graceful stop.", + required=False) + additionalParams = serializers.DictField( + help_text="Additional input parameters for the operate process, \ + specific to the VNF being operated, \ + as declared in the VNFD as part of OperateVnfOpConfig.", + child=serializers.CharField(help_text="KeyValue Pairs", allow_blank=True), + required=False, + allow_null=True) diff --git a/lcm/lcm/nf/serializers/response.py b/lcm/lcm/nf/serializers/response.py new file mode 100644 index 00000000..132788ac --- /dev/null +++ b/lcm/lcm/nf/serializers/response.py @@ -0,0 +1,28 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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. + +from rest_framework import serializers + + +class ProblemDetailsSerializer(serializers.Serializer): + type = serializers.CharField(help_text="Type", required=False, allow_null=True) + title = serializers.CharField(help_text="Title", required=False, allow_null=True) + status = serializers.IntegerField(help_text="Status", required=True) + detail = serializers.CharField(help_text="Detail", required=True, allow_null=True) + instance = serializers.CharField(help_text="Instance", required=False, allow_null=True) + additional_details = serializers.ListField( + help_text="Any number of additional attributes, as defined in a " + + "specification or by an implementation.", + required=False, + allow_null=True) diff --git a/lcm/lcm/nf/serializers/subscription_auth_data.py b/lcm/lcm/nf/serializers/subscription_auth_data.py new file mode 100644 index 00000000..f6cf8a77 --- /dev/null +++ b/lcm/lcm/nf/serializers/subscription_auth_data.py @@ -0,0 +1,69 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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. + +from rest_framework import serializers + +from lcm.nf import const + + +class OAuthCredentialsSerializer(serializers.Serializer): + clientId = serializers.CharField( + help_text="Client identifier to be used in the access token " + + "request of the OAuth 2.0 client credentials grant type.", + required=False, + max_length=255, + allow_null=False) + clientPassword = serializers.CharField( + help_text="Client password to be used in the access token " + + "request of the OAuth 2.0 client credentials grant type.", + required=False, + max_length=255, + allow_null=False) + tokenEndpoint = serializers.CharField( + help_text="The token endpoint from which the access token can " + + "be obtained.", + required=False, + max_length=255, + allow_null=False) + + +class BasicAuthSerializer(serializers.Serializer): + userName = serializers.CharField( + help_text="Username to be used in HTTP Basic authentication.", + max_length=255, + required=False, + allow_null=False) + password = serializers.CharField( + help_text="Password to be used in HTTP Basic authentication.", + max_length=255, + required=False, + allow_null=False) + + +class SubscriptionAuthenticationSerializer(serializers.Serializer): + authType = serializers.ListField( + child=serializers.ChoiceField(required=True, choices=const.AUTH_TYPES), + help_text="Defines the types of Authentication / Authorization " + + "which the API consumer is willing to accept when " + + "receiving a notification.", + required=True) + paramsBasic = BasicAuthSerializer( + help_text="Parameters for authentication/authorization using BASIC.", + required=False, + allow_null=False) + paramsOauth2ClientCredentials = OAuthCredentialsSerializer( + help_text="Parameters for authentication/authorization using " + + "OAUTH2_CLIENT_CREDENTIALS.", + required=False, + allow_null=False) diff --git a/lcm/lcm/nf/serializers/vnf_info_modifications.py b/lcm/lcm/nf/serializers/vnf_info_modifications.py new file mode 100644 index 00000000..8098a6ca --- /dev/null +++ b/lcm/lcm/nf/serializers/vnf_info_modifications.py @@ -0,0 +1,99 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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. + + +from rest_framework import serializers + +from vim_connection_info import VimConnectionInfoSerializer + + +class VnfInfoModificationsSerializer(serializers.Serializer): + vnfInstanceName = serializers.CharField( + help_text="If present, this attribute signals modifications of the " + + "'vnfInstanceName' attribute in 'VnfInstance'", + max_length=255, + required=False, + allow_null=True, + allow_blank=True) + vnfInstanceDescription = serializers.CharField( + help_text="If present, this attribute signals modifications of the " + + "'vnfInstanceDescription' attribute in 'VnfInstance'", + required=False, + allow_null=True, + allow_blank=True) + vnfdId = serializers.CharField( + help_text="If present, this attribute signals modifications of the " + + "'vnfdId' attribute in 'VnfInstance'", + max_length=255, + required=False, + allow_null=True, + allow_blank=True) + vnfProvider = serializers.CharField( + help_text="If present, this attribute signals modifications of the " + + "'vnfProvider' attribute in 'VnfInstance'", + max_length=255, + required=False, + allow_null=True) + vnfProductName = serializers.CharField( + help_text="If present, this attribute signals modifications of the " + + "'vnfProductName' attribute in 'vnfInstance'", + max_length=255, + required=False, + allow_null=True, + allow_blank=True) + vnfSoftwareVersion = serializers.CharField( + help_text="If present, this attribute signals modifications of the " + + "'vnfSoftwareVersion' attribute in 'VnfInstance'.", + max_length=255, + required=False, + allow_null=True, + allow_blank=True) + vnfdVersion = serializers.CharField( + help_text="If present, this attribute signals modifications of the " + + "'vnfdVersion' attribute in 'VnfInstance'. ", + max_length=255, + required=False, + allow_null=True, + allow_blank=False) + vnfPkgId = serializers.CharField( + help_text="If present, this attribute signals modifications of the " + + "'vnfPkgId' attribute in 'VnfInstance'.", + max_length=255, + required=False, + allow_null=True, + allow_blank=False) + vnfConfigurableProperties = serializers.DictField( + help_text="If present, this attribute signals modifications of the " + + "'vnfConfigurableProperties' attribute in 'VnfInstance'. ", + child=serializers.CharField(help_text="KeyValue Pairs", allow_blank=True), + required=False, + allow_null=True,) + vimConnectionInfo = VimConnectionInfoSerializer( + help_text="If present, this attribute signals modifications of certain" + + "entries in the 'vimConnectionInfo'", + required=False, + many=True, + allow_null=True) + metadata = serializers.DictField( + help_text="If present, this attribute signals modifications of certain" + + "'metadata' attribute in 'vnfInstance'.", + child=serializers.CharField(help_text="KeyValue Pairs", allow_blank=True), + required=False, + allow_null=True) + extensions = serializers.DictField( + help_text="If present, this attribute signals modifications of certain" + + "'extensions' attribute in 'vnfInstance'.", + child=serializers.CharField(help_text="KeyValue Pairs", allow_blank=True), + required=False, + allow_null=True) diff --git a/lcm/lcm/nf/serializers/vnf_instance_subscription_filter.py b/lcm/lcm/nf/serializers/vnf_instance_subscription_filter.py new file mode 100644 index 00000000..66bf6ff6 --- /dev/null +++ b/lcm/lcm/nf/serializers/vnf_instance_subscription_filter.py @@ -0,0 +1,76 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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. + +from rest_framework import serializers + + +class VersionsSerializer(serializers.Serializer): + vnfSoftwareVersion = serializers.CharField( + max_length=255, + help_text="Software version to match.", + required=True) + vnfdVersions = serializers.ListField( + child=serializers.CharField(max_length=255, required=True), + required=False, + help_text="match VNF instances that belong to VNF products " + + "with certain VNFD versions") + + +class VnfProductsSerializer(serializers.Serializer): + vnfProductName = serializers.CharField( + max_length=255, + help_text="Name of the VNF product to match.", + required=True) + versions = VersionsSerializer( + help_text="match VNF instances that belong to VNF products " + + "with certain versions and a certain product name, from one " + + "particular provider", + required=False, + allow_null=False) + + +class VnfProductsProvidersSerializer(serializers.Serializer): + vnfProvider = serializers.CharField( + max_length=255, + help_text="Name of the VNF provider to match.", + required=True) + vnfProducts = VnfProductsSerializer( + help_text="match VNF instances that belong to VNF products " + + "with certain product names, from one particular provider", + required=False, + allow_null=False) + + +class VnfInstanceSubscriptionFilter(serializers.Serializer): + vnfdIds = serializers.ListField( + child=serializers.UUIDField(), + help_text="VNF instances that were created based on a " + + "VNFD identified by one of the vnfdId values", + required=False, + allow_null=False) + vnfInstanceIds = serializers.ListField( + child=serializers.UUIDField(), + help_text="VNF instance IDs that has to be matched", + required=False, + allow_null=False) + vnfInstanceNames = serializers.ListField( + child=serializers.CharField(max_length=255, required=True), + help_text="VNF Instance names that has to be matched", + required=False, + allow_null=False) + vnfProductsFromProviders = VnfProductsProvidersSerializer( + help_text="match VNF instances that belong to VNF products " + + "from certain providers.", + required=False, + allow_null=False) diff --git a/lcm/lcm/nf/serializers/vnf_lcm_op_occ.py b/lcm/lcm/nf/serializers/vnf_lcm_op_occ.py new file mode 100644 index 00000000..a2b50196 --- /dev/null +++ b/lcm/lcm/nf/serializers/vnf_lcm_op_occ.py @@ -0,0 +1,200 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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. + + +from rest_framework import serializers + +from affected_vnfcs import AffectedVnfcsSerializer +from affected_vls import AffectedVLsSerializer +from affected_storages import AffectedStoragesSerializer +from link import LinkSerializer +from response import ProblemDetailsSerializer +from ext_virtual_link_info import ExtVirtualLinkInfoSerializer +from vnf_info_modifications import VnfInfoModificationsSerializer + + +LCM_OPERATION_TYPES = [ + "INSTANTIATE", + "SCALE", + "SCALE_TO_LEVEL", + "CHANGE_FLAVOUR", + "TERMINATE", + "HEAL", + "OPERATE", + "CHANGE_EXT_CONN", + "MODIFY_INFO" +] + +LCM_OPERATION_STATE_TYPES = [ + "STARTING", + "PROCESSING", + "COMPLETED", + "FAILED_TEMP", + "FAILED", + "ROLLING_BACK", + "ROLLED_BACK" +] + + +class ResourceChangesSerializer(serializers.Serializer): + affectedVnfcs = AffectedVnfcsSerializer( + help_text="Information about VNFC instances that were affected " + + "during the lifecycle operation.", + required=False, + many=True + ) + affectedVirtualLinks = AffectedVLsSerializer( + help_text="Information about VL instances that were affected " + + "during the lifecycle operation. ", + required=False, + many=True + ) + affectedVirtualStorages = AffectedStoragesSerializer( + help_text="Information about virtualised storage instances that " + + "were affected during the lifecycle operation", + required=False, + many=True + ) + + +class LcmOpLinkSerializer(serializers.Serializer): + self = LinkSerializer( + help_text="URI of this resource.", + required=True, + allow_null=False) + vnfInstance = serializers.CharField( + help_text="Link to the VNF instance that the operation applies to.", + required=True) + grant = serializers.CharField( + help_text="Link to the grant for this operation, if one exists.", + required=False) + cancel = serializers.CharField( + help_text="Link to the task resource that represents the 'cancel' " + + "operation for this VNF LCM operation occurrence.", + required=False) + retry = serializers.CharField( + help_text="Link to the task resource that represents the 'retry' " + + "operation for this VNF LCM operation occurrence, if" + + " retrying is currently allowed", + required=False) + rollback = serializers.CharField( + help_text="Link to the task resource that represents the 'cancel' " + + "operation for this VNF LCM operation occurrence.", + required=False) + fail = serializers.CharField( + help_text="Link to the task resource that represents the 'fail' " + + "operation for this VNF LCM operation occurrence.", + required=False) + + +class VNFLCMOpOccSerializer(serializers.Serializer): + id = serializers.CharField( + help_text="Identifier of this VNF lifecycle management operation" + + "occurrence,", + max_length=255, + required=True, + allow_null=False + ) + operationState = serializers.ChoiceField( + help_text="The state of the VNF LCM operation occurrence. ", + required=True, + choices=LCM_OPERATION_STATE_TYPES + ) + stateEnteredTime = serializers.CharField( + help_text="Date-time when the current state was entered.", + max_length=50 + ) + startTime = serializers.CharField( + help_text="Date-time of the start of the operation.", + max_length=50 + ) + vnfInstanceId = serializers.UUIDField( + help_text="Identifier of the VNF instance to which the operation" + + "applies" + ) + grantId = serializers.UUIDField( + help_text="Identifier of the grant related to this VNF LCM operation " + + "occurrence, if such grant exists.", + allow_null=True + ) + operation = serializers.ChoiceField( + help_text="The lifecycle management operation", + required=True, + choices=LCM_OPERATION_TYPES + ) + isAutomaticInvocation = serializers.BooleanField( + help_text="Set to true if this VNF LCM operation occurrence has " + + "been triggered by an automated procedure inside the VNFM. " + + "Set to False otherwise.", + default=False + ) + operationParams = serializers.DictField( + help_text="Input parameters of the LCM operation. This attribute " + + "shall be formatted according to the request data type of the " + + "related LCM operation. The following mapping between operationType and the " + + "data type of this attribute shall apply: " + + "1. INSTANTIATE: InstantiateVnfRequest" + + "2. SCALE: ScaleVnfRequest " + + "3. SCALE_TO_LEVEL: ScaleVnfToLevelRequest " + + "4. CHANGE_FLAVOUR: ChangeVnfFlavourRequest " + + "5. OPERATE: OperateVnfRequest " + + "6. HEAL: HealVnfRequest " + + "7. CHANGE_EXT_CONN: ChangeExtVnfConnectivityRequest " + + "8. TERMINATE: TerminateVnfRequest " + + "9. MODIFY_INFO: VnfInfoModifications", + required=True, + allow_null=False + ) + isCancelPending = serializers.BooleanField( + help_text="If the VNF LCM operation occurrence is in 'STARTING'" + + "'PROCESSING' or 'ROLLING_BACK' state and the operation is being" + + " cancelled, this attribute shall be set to True. Otherwise, " + + " it shall be set to False.", + required=True + ) + cancelMode = serializers.CharField( + help_text="The mode of an ongoing cancellation. Shall be present " + + "when isCancelPending=true, and shall be None otherwise.", + allow_null=True, + required=False + ) + error = ProblemDetailsSerializer( + help_text="If 'operationState' is 'FAILED_TEMP' or 'FAILED' or " + + "'PROCESSING' or 'ROLLING_BACK' and previous value of 'operationState' " + + "was 'FAILED_TEMP' this attribute shall be present ", + allow_null=True, + required=False + ) + resourceChanges = ResourceChangesSerializer( + help_text="It contains information about the cumulative changes " + + "to virtualised resources that were performed so far by the LCM " + + "operation since its start, if applicable.", + required=False, + allow_null=True) + changedInfo = VnfInfoModificationsSerializer( + help_text="Information about the changed VNF instance information, " + + "including VNF configurable properties", + required=False, + allow_null=True) + changedExtConnectivity = ExtVirtualLinkInfoSerializer( + help_text="Information about changed external connectivity, if this " + + "notification represents the result of a lifecycle operation occurrence. " + + "Shall be present if the 'notificationStatus' is set to 'RESULT' and the " + + "'operation' is set to 'CHANGE_EXT_CONN'. Shall be absent otherwise.", + many=True, + required=False, + allow_null=True) + _links = LcmOpLinkSerializer( + help_text="Links to resources related to this resource.", + required=True) diff --git a/lcm/lcm/nf/serializers/vnf_lcm_op_occs.py b/lcm/lcm/nf/serializers/vnf_lcm_op_occs.py new file mode 100644 index 00000000..6cb70906 --- /dev/null +++ b/lcm/lcm/nf/serializers/vnf_lcm_op_occs.py @@ -0,0 +1,20 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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. + +from rest_framework import serializers +from vnf_lcm_op_occ import VNFLCMOpOccSerializer + + +class VNFLCMOpOccsSerializer(serializers.ListSerializer): + child = VNFLCMOpOccSerializer() diff --git a/lcm/lcm/nf/tests/test_delete_vnf.py b/lcm/lcm/nf/tests/test_delete_vnf.py index 26679231..62ed0dfd 100644 --- a/lcm/lcm/nf/tests/test_delete_vnf.py +++ b/lcm/lcm/nf/tests/test_delete_vnf.py @@ -26,13 +26,13 @@ class TestNFTerminate(TestCase): self.client = Client() StorageInstModel.objects.create(storageid="1", vimid="1", - resouceid="11", + resourceid="11", insttype=0, instid="1111", is_predefined=1) NetworkInstModel.objects.create(networkid='1', vimid='1', - resouceid='1', + resourceid='1', name='pnet_network', is_predefined=1, tenant='admin', @@ -40,7 +40,7 @@ class TestNFTerminate(TestCase): instid='1111') SubNetworkInstModel.objects.create(subnetworkid='1', vimid='1', - resouceid='1', + resourceid='1', networkid='1', is_predefined=1, name='sub_pnet', @@ -51,7 +51,7 @@ class TestNFTerminate(TestCase): networkid='1', subnetworkid='1', vimid='1', - resouceid='1', + resourceid='1', is_predefined=1, name='aaa_pnet_cp', tenant='admin', @@ -59,12 +59,12 @@ class TestNFTerminate(TestCase): instid='1111') FlavourInstModel.objects.create(flavourid="1", vimid="1", - resouceid="11", + resourceid="11", instid="1111", is_predefined=1) VmInstModel.objects.create(vmid="1", vimid="1", - resouceid="11", + resourceid="11", insttype=0, instid="1111", vmname="test_01", diff --git a/lcm/lcm/nf/tests/test_heal_vnf.py b/lcm/lcm/nf/tests/test_heal_vnf.py new file mode 100644 index 00000000..27cce093 --- /dev/null +++ b/lcm/lcm/nf/tests/test_heal_vnf.py @@ -0,0 +1,176 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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 json + +import mock +from django.test import TestCase +from rest_framework import status +from rest_framework.test import APIClient + +from lcm.nf.biz.heal_vnf import HealVnf +from lcm.nf.const import c1_data_get_tenant_id_1, c9_data_create_vm, c10_data_get_vm, vnfd_model_dict +from lcm.pub.database.models import NfInstModel, JobStatusModel, VmInstModel +from lcm.pub.utils import restcall +from lcm.pub.utils.jobutil import JobUtil +from lcm.pub.utils.timeutil import now_time +from lcm.pub.vimapi import api + + +class TestNFInstantiate(TestCase): + def setUp(self): + self.client = APIClient() + self.grant_result = { + "vimid": 'vimid_1', + "tenant": 'tenantname_1', + } + self.getvmResult = { + "status": "ACTIVE", + "id": "11", + } + + def tearDown(self): + pass + + def assert_job_result(self, job_id, job_progress, job_detail): + jobs = JobStatusModel.objects.filter(jobid=job_id, + progress=job_progress, + descp=job_detail) + self.assertEqual(1, len(jobs)) + + def test_heal_vnf_not_found(self): + req_data = {} + response = self.client.post("/api/vnflcm/v1/vnf_instances/12/heal", data=req_data, format='json') + self.failUnlessEqual(status.HTTP_404_NOT_FOUND, response.status_code) + + def test_heal_vnf_conflict(self): + req_data = {} + NfInstModel(nfinstid='12', nf_name='VNF1', status='NOT_INSTANTIATED').save() + response = self.client.post("/api/vnflcm/v1/vnf_instances/12/heal", data=req_data, format='json') + self.failUnlessEqual(status.HTTP_409_CONFLICT, response.status_code) + NfInstModel(nfinstid='12', nf_name='VNF1', status='NOT_INSTANTIATED').delete() + + @mock.patch.object(HealVnf, 'run') + def test_heal_vnf_success(self, mock_run): + req_data = {} + NfInstModel(nfinstid='12', nf_name='VNF1', status='INSTANTIATED').save() + response = self.client.post("/api/vnflcm/v1/vnf_instances/12/heal", data=req_data, format='json') + mock_run.re.return_value = None + self.failUnlessEqual(status.HTTP_202_ACCEPTED, response.status_code) + NfInstModel(nfinstid='12', nf_name='VNF1', status='INSTANTIATED').delete() + + @mock.patch.object(restcall, 'call_req') + @mock.patch.object(api, 'call') + def test_heal_vnf_success_reboot(self, mock_call, mock_call_req): + vim_cache = {} + res_cache = {} + + NfInstModel.objects.create(nfinstid='1111', + nf_name='2222', + vnfminstid='1', + package_id='todo', + version='', + vendor='', + netype='', + vnfd_model=json.dumps(vnfd_model_dict), + status='INSTANTIATED', + nf_desc='', + vnfdid='', + vnfSoftwareVersion='', + vnfConfigurableProperties='todo', + localizationLanguage='EN_US', + create_time=now_time(), + resInfo=json.dumps(res_cache), + vimInfo=json.dumps(vim_cache)) + + VmInstModel.objects.create(vmid="1", + vimid="vimid_1", + resourceid="11", + insttype=0, + instid="1111", + vmname="vduinstname", + is_predefined=1, + tenant="tenantname_1", + operationalstate=1) + t1_apply_grant_result = [0, json.JSONEncoder().encode(self.grant_result), '200'] + t2_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] + t3_action_get_vm = [0, json.JSONEncoder().encode(self.getvmResult), '202'] + t4_action_vm_start_reboot = [0, json.JSONEncoder().encode(''), '202'] + mock_call_req.side_effect = [t1_apply_grant_result, t2_lcm_notify_result, t3_action_get_vm, t4_action_vm_start_reboot] + mock_call.side_effect = [self.getvmResult, None] + req_data = { + "cause": "Error", + "additionalParams": { + "action": "vmReset", + "affectedvm": { + "vmid": "1", + "vduid": "vdu1Id", + "vmname": "vduinstname" + } + } + } + self.nf_inst_id = '1111' + self.job_id = JobUtil.create_job('NF', 'HEAL', self.nf_inst_id) + JobUtil.add_job_status(self.job_id, 0, "HEAL_VNF_READY") + HealVnf(req_data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() + self.assert_job_result(self.job_id, 100, "Heal Vnf success.") + + @mock.patch.object(restcall, 'call_req') + @mock.patch.object(api, 'call') + def test_heal_vnf_success_start(self, mock_call, mock_call_req): + vim_cache = {} + res_cache = {"volume": {}, "flavor": {}, "port": {}} + res_cache["volume"]["volume_storage1"] = "vol1" + res_cache["flavor"]["vdu1Id"] = "flavor1" + res_cache["port"]["cpId1"] = "port1" + + NfInstModel.objects.create(nfinstid='1111', + nf_name='2222', + vnfminstid='1', + package_id='todo', + version='', + vendor='', + netype='', + vnfd_model=json.dumps(vnfd_model_dict), + status='INSTANTIATED', + nf_desc='', + vnfdid='', + vnfSoftwareVersion='', + vnfConfigurableProperties='todo', + localizationLanguage='EN_US', + resInfo=json.dumps(res_cache), + vimInfo=json.dumps(vim_cache), + create_time=now_time()) + + t1_apply_grant_result = [0, json.JSONEncoder().encode(self.grant_result), '200'] + t2_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] + t3_action_vm_start_create = [0, json.JSONEncoder().encode(''), '202'] + mock_call_req.side_effect = [t1_apply_grant_result, t2_lcm_notify_result, t3_action_vm_start_create] + mock_call.side_effect = [c1_data_get_tenant_id_1, c9_data_create_vm, c10_data_get_vm] + req_data = { + "cause": "Error", + "additionalParams": { + "action": "vmCreate", + "affectedvm": { + "vmid": "1", + "vduid": "vdu1Id", + "vmname": "vduinstname" + } + } + } + self.nf_inst_id = '1111' + self.job_id = JobUtil.create_job('NF', 'HEAL', self.nf_inst_id) + JobUtil.add_job_status(self.job_id, 0, "HEAL_VNF_READY") + HealVnf(req_data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() + self.assert_job_result(self.job_id, 100, "Heal Vnf success.") diff --git a/lcm/lcm/nf/tests/test_instantiate_vnf.py b/lcm/lcm/nf/tests/test_instantiate_vnf.py index 5801cb90..4784fa29 100644 --- a/lcm/lcm/nf/tests/test_instantiate_vnf.py +++ b/lcm/lcm/nf/tests/test_instantiate_vnf.py @@ -24,10 +24,11 @@ from lcm.nf.biz.instantiate_vnf import InstantiateVnf from lcm.nf.const import c1_data_get_tenant_id, c4_data_create_network, c2_data_create_volume, \ c5_data_create_subnet, c3_data_get_volume, c6_data_create_port, c7_data_create_flavor, c8_data_list_image, \ c9_data_create_vm, c10_data_get_vm, inst_req_data, vnfpackage_info -from lcm.pub.database.models import NfInstModel, JobStatusModel +from lcm.pub.database.models import NfInstModel, JobStatusModel, SubscriptionModel from lcm.pub.utils import restcall from lcm.pub.utils.jobutil import JobUtil from lcm.pub.utils.timeutil import now_time +from lcm.pub.utils.notificationsutil import NotificationsUtil from lcm.pub.vimapi import api @@ -35,8 +36,26 @@ class TestNFInstantiate(TestCase): def setUp(self): self.client = APIClient() self.grant_result = { - "vimid": 'vim_1', - "tenant": 'chinamobile', + "vimConnections": [ + { + "vimid": 'vim_1', + "accessInfo": + { + "tenant": 'chinamobile' + } + }, + ], + "vnfId": "413aa1fe-b4d1-11e8-8268-dff5aab95c63", + "vimAssets": + { + "vimComputeResourceFlavour": [ + { + "resourceProviderId": "vgw", + "vimFlavourId": "yui", + "directive": "" + }, + ] + } } def tearDown(self): @@ -50,22 +69,22 @@ class TestNFInstantiate(TestCase): @mock.patch.object(InstantiateVnf, 'run') def test_instantiate_vnf(self, mock_run): - NfInstModel(nfinstid='12', nf_name='VNF1', status="UN_INSTANTIATED").save() + NfInstModel(nfinstid='12', nf_name='VNF1', status='UN_INSTANTIATED').save() mock_run.re.return_value = None - response = self.client.post("/api/vnflcm/v1/vnf_instances/12/instantiate", data=inst_req_data, format='json') + response = self.client.post('/api/vnflcm/v1/vnf_instances/12/instantiate', data=inst_req_data, format='json') self.failUnlessEqual(status.HTTP_202_ACCEPTED, response.status_code) def test_instantiate_vnf_when_inst_id_not_exist(self): self.nf_inst_id = str(uuid.uuid4()) self.job_id = JobUtil.create_job('NF', 'CREATE', self.nf_inst_id) - JobUtil.add_job_status(self.job_id, 0, "INST_VNF_READY") + JobUtil.add_job_status(self.job_id, 0, 'INST_VNF_READY') jobs = JobStatusModel.objects.filter(jobid=self.job_id, progress=0, - descp="INST_VNF_READY") + descp='INST_VNF_READY') self.assertEqual(1, len(jobs)) data = inst_req_data InstantiateVnf(data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() - self.assert_job_result(self.job_id, 255, "VNF nf_inst_id is not exist.") + self.assert_job_result(self.job_id, 255, 'VNF nf_inst_id is not exist.') def test_instantiate_vnf_when_already_instantiated(self): NfInstModel.objects.create(nfinstid='1111', @@ -81,14 +100,14 @@ class TestNFInstantiate(TestCase): create_time=now_time()) self.nf_inst_id = '1111' self.job_id = JobUtil.create_job('NF', 'CREATE', self.nf_inst_id) - JobUtil.add_job_status(self.job_id, 0, "INST_VNF_READY") + JobUtil.add_job_status(self.job_id, 0, 'INST_VNF_READY') jobs = JobStatusModel.objects.filter(jobid=self.job_id, progress=0, - descp="INST_VNF_READY") + descp='INST_VNF_READY') self.assertEqual(1, len(jobs)) data = inst_req_data InstantiateVnf(data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() - self.assert_job_result(self.job_id, 255, "VNF instantiationState is not NOT_INSTANTIATED.") + self.assert_job_result(self.job_id, 255, 'VNF instantiationState is not NOT_INSTANTIATED.') @mock.patch.object(restcall, 'call_req') def test_instantiate_vnf_when_get_packageinfo_by_csarid_failed(self, mock_call_req): @@ -107,10 +126,10 @@ class TestNFInstantiate(TestCase): mock_call_req.side_effect = [r1_get_vnfpackage_by_vnfdid] self.nf_inst_id = '1111' self.job_id = JobUtil.create_job('NF', 'CREATE', self.nf_inst_id) - JobUtil.add_job_status(self.job_id, 0, "INST_VNF_READY") + JobUtil.add_job_status(self.job_id, 0, 'INST_VNF_READY') data = inst_req_data InstantiateVnf(data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() - self.assert_job_result(self.job_id, 255, "Failed to query vnf CSAR(111) from catalog.") + self.assert_job_result(self.job_id, 255, 'Failed to query vnf CSAR(111) from catalog.') @mock.patch.object(restcall, 'call_req') def test_instantiate_vnf_when_applay_grant_failed(self, mock_call_req): @@ -130,10 +149,10 @@ class TestNFInstantiate(TestCase): mock_call_req.side_effect = [r1_get_vnfpackage_by_vnfdid, r2_apply_grant_result] self.nf_inst_id = '1111' self.job_id = JobUtil.create_job('NF', 'CREATE', self.nf_inst_id) - JobUtil.add_job_status(self.job_id, 0, "INST_VNF_READY") + JobUtil.add_job_status(self.job_id, 0, 'INST_VNF_READY') data = inst_req_data InstantiateVnf(data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() - self.assert_job_result(self.job_id, 255, "Nf instancing apply grant exception") + self.assert_job_result(self.job_id, 255, 'Nf instancing apply grant exception') @mock.patch.object(restcall, 'call_req') @mock.patch.object(api, 'call') @@ -155,14 +174,15 @@ class TestNFInstantiate(TestCase): mock_call.side_effect = [c1_data_get_tenant_id, c2_data_create_volume, c3_data_get_volume] self.nf_inst_id = '1111' self.job_id = JobUtil.create_job('NF', 'CREATE', self.nf_inst_id) - JobUtil.add_job_status(self.job_id, 0, "INST_VNF_READY") + JobUtil.add_job_status(self.job_id, 0, 'INST_VNF_READY') data = inst_req_data InstantiateVnf(data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() - self.assert_job_result(self.job_id, 255, "unexpected exception") + self.assert_job_result(self.job_id, 255, 'unexpected exception') @mock.patch.object(restcall, 'call_req') @mock.patch.object(api, 'call') - def test_instantiate_vnf_success(self, mock_call, mock_call_req): + @mock.patch.object(NotificationsUtil, 'post_notification') + def test_instantiate_vnf_success(self, mock_post_notification, mock_call, mock_call_req): NfInstModel.objects.create(nfinstid='1111', nf_name='vFW_01', package_id='222', @@ -174,11 +194,36 @@ class TestNFInstantiate(TestCase): nf_desc='vFW in Nanjing TIC Edge', vnfdid='111', create_time=now_time()) + SubscriptionModel.objects.create( + subscription_id=str(uuid.uuid4()), + callback_uri='api/gvnfmdriver/v1/vnfs/lifecyclechangesnotification', + auth_info=json.JSONEncoder().encode({ + 'authType': ['BASIC'], + 'paramsBasic': { + 'userName': 'username', + 'password': 'password' + } + }), + notification_types=str([ + 'VnfLcmOperationOccurrenceNotification', + 'VnfIdentifierCreationNotification', + 'VnfIdentifierDeletionNotification' + ]), + operation_types=str(['INSTANTIATE']), + operation_states=str(['COMPLETED']), + vnf_instance_filter=json.JSONEncoder().encode({ + 'vnfdIds': ['111'], + 'vnfProductsFromProviders': [], + 'vnfInstanceIds': ['1111'], + 'vnfInstanceNames': [], + }) + ) r1_get_vnfpackage_by_vnfdid = [0, json.JSONEncoder().encode(vnfpackage_info), '200'] r2_apply_grant_result = [0, json.JSONEncoder().encode(self.grant_result), '200'] r3_all_aai_result = [1, json.JSONEncoder().encode(''), '404'] - r4_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] - mock_call_req.side_effect = [r1_get_vnfpackage_by_vnfdid, r2_apply_grant_result, r3_all_aai_result, r4_lcm_notify_result] + # r4_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] + # mock_call_req.side_effect = [r1_get_vnfpackage_by_vnfdid, r2_apply_grant_result, r3_all_aai_result, r4_lcm_notify_result] + mock_call_req.side_effect = [r1_get_vnfpackage_by_vnfdid, r2_apply_grant_result, r3_all_aai_result] mock_call.side_effect = [c1_data_get_tenant_id, c2_data_create_volume, c3_data_get_volume, c4_data_create_network, @@ -186,9 +231,10 @@ class TestNFInstantiate(TestCase): c6_data_create_port, c7_data_create_flavor, c8_data_list_image, c9_data_create_vm, c10_data_get_vm] + mock_post_notification.side_effect = None self.nf_inst_id = '1111' self.job_id = JobUtil.create_job('NF', 'CREATE', self.nf_inst_id) - JobUtil.add_job_status(self.job_id, 0, "INST_VNF_READY") + JobUtil.add_job_status(self.job_id, 0, 'INST_VNF_READY') data = inst_req_data InstantiateVnf(data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() - self.assert_job_result(self.job_id, 100, "Instantiate Vnf success.") + self.assert_job_result(self.job_id, 100, 'Instantiate Vnf success.') diff --git a/lcm/lcm/nf/tests/test_operate_vnf.py b/lcm/lcm/nf/tests/test_operate_vnf.py new file mode 100644 index 00000000..3ebc90a2 --- /dev/null +++ b/lcm/lcm/nf/tests/test_operate_vnf.py @@ -0,0 +1,302 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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 json +import uuid + +import mock +from django.test import TestCase, Client +from rest_framework import status + +from lcm.nf.biz.operate_vnf import OperateVnf +from lcm.pub.database.models import NfInstModel, JobStatusModel, VmInstModel, SubscriptionModel +from lcm.pub.utils import restcall +from lcm.pub.utils.jobutil import JobUtil +from lcm.pub.utils.notificationsutil import NotificationsUtil +from lcm.pub.utils.timeutil import now_time +from lcm.pub.vimapi import api + + +class TestNFOperate(TestCase): + def setUp(self): + self.client = Client() + + def tearDown(self): + VmInstModel.objects.all().delete() + + def assert_job_result(self, job_id, job_progress, job_detail): + jobs = JobStatusModel.objects.filter(jobid=job_id, + progress=job_progress, + descp=job_detail) + self.assertEqual(1, len(jobs)) + + def test_operate_vnf_not_found(self): + req_data = { + "changeStateTo": "STARTED" + } + response = self.client.post("/api/vnflcm/v1/vnf_instances/12/operate", data=req_data, format='json') + self.failUnlessEqual(status.HTTP_404_NOT_FOUND, response.status_code) + + def test_operate_vnf_conflict(self): + req_data = { + "changeStateTo": "STARTED" + } + NfInstModel(nfinstid='12', nf_name='VNF1', status='NOT_INSTANTIATED').save() + response = self.client.post("/api/vnflcm/v1/vnf_instances/12/operate", data=req_data, format='json') + self.failUnlessEqual(status.HTTP_409_CONFLICT, response.status_code) + NfInstModel(nfinstid='12', nf_name='VNF1', status='NOT_INSTANTIATED').delete() + + @mock.patch.object(OperateVnf, 'run') + def test_operate_vnf_success(self, mock_run): + req_data = { + "changeStateTo": "STARTED" + } + NfInstModel(nfinstid='12', nf_name='VNF1', status='INSTANTIATED').save() + response = self.client.post("/api/vnflcm/v1/vnf_instances/12/operate", data=req_data, format='json') + mock_run.re.return_value = None + self.failUnlessEqual(status.HTTP_202_ACCEPTED, response.status_code) + NfInstModel(nfinstid='12', nf_name='VNF1', status='INSTANTIATED').delete() + + @mock.patch.object(restcall, 'call_req') + @mock.patch.object(api, 'call') + @mock.patch.object(NotificationsUtil, 'post_notification') + def test_operate_vnf_success_start(self, mock_post_notification, mock_call, mock_call_req): + NfInstModel.objects.create(nfinstid='1111', + nf_name='2222', + vnfminstid='1', + package_id='todo', + version='', + vendor='', + netype='', + vnfd_model='', + status='INSTANTIATED', + nf_desc='', + vnfdid='', + vnfSoftwareVersion='', + vnfConfigurableProperties='todo', + localizationLanguage='EN_US', + create_time=now_time()) + + VmInstModel.objects.create(vmid="1", + vimid="1", + resourceid="11", + insttype=0, + instid="1111", + vmname="test_01", + is_predefined=1, + operationalstate=1) + + SubscriptionModel.objects.create( + subscription_id=str(uuid.uuid4()), + callback_uri='api/gvnfmdriver/v1/vnfs/lifecyclechangesnotification', + auth_info=json.JSONEncoder().encode({ + 'authType': ['BASIC'], + 'paramsBasic': { + 'userName': 'username', + 'password': 'password' + } + }), + notification_types=str([ + 'VnfLcmOperationOccurrenceNotification', + 'VnfIdentifierCreationNotification', + 'VnfIdentifierDeletionNotification' + ]), + operation_types=str(['OPERATE']), + operation_states=str(['COMPLETED']), + vnf_instance_filter=json.JSONEncoder().encode({ + 'vnfdIds': [], + 'vnfProductsFromProviders': [], + 'vnfInstanceIds': ['1111'], + 'vnfInstanceNames': [], + }) + ) + + t1_apply_grant_result = [0, json.JSONEncoder().encode(''), '200'] + # t2_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] + t3_action_vm_start_result = [0, json.JSONEncoder().encode(''), '202'] + # mock_call_req.side_effect = [t1_apply_grant_result, t2_lcm_notify_result, t3_action_vm_start_result] + mock_call_req.side_effect = [t1_apply_grant_result, t3_action_vm_start_result] + mock_call.return_value = None + mock_post_notification.return_value = None + req_data = { + "changeStateTo": "STARTED" + } + self.nf_inst_id = '1111' + self.job_id = JobUtil.create_job('NF', 'OPERATE', self.nf_inst_id) + JobUtil.add_job_status(self.job_id, 0, "OPERATE_VNF_READY") + OperateVnf(req_data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() + vm = VmInstModel.objects.filter(vmid="1", vimid="1", resourceid="11") + self.assertEqual("ACTIVE", vm[0].operationalstate) + self.assert_job_result(self.job_id, 100, "Operate Vnf success.") + + @mock.patch.object(restcall, 'call_req') + @mock.patch.object(api, 'call') + @mock.patch.object(NotificationsUtil, 'post_notification') + def test_operate_vnf_success_stop(self, mock_post_notification, mock_call, mock_call_req): + NfInstModel.objects.create(nfinstid='1111', + nf_name='2222', + vnfminstid='1', + package_id='todo', + version='', + vendor='', + netype='', + vnfd_model='', + status='INSTANTIATED', + nf_desc='', + vnfdid='', + vnfSoftwareVersion='', + vnfConfigurableProperties='todo', + localizationLanguage='EN_US', + create_time=now_time()) + + VmInstModel.objects.create(vmid="1", + vimid="1", + resourceid="11", + insttype=0, + instid="1111", + vmname="test_01", + is_predefined=1, + operationalstate=1) + + SubscriptionModel.objects.create( + subscription_id=str(uuid.uuid4()), + callback_uri='api/gvnfmdriver/v1/vnfs/lifecyclechangesnotification', + auth_info=json.JSONEncoder().encode({ + 'authType': ['BASIC'], + 'paramsBasic': { + 'userName': 'username', + 'password': 'password' + } + }), + notification_types=str([ + 'VnfLcmOperationOccurrenceNotification', + 'VnfIdentifierCreationNotification', + 'VnfIdentifierDeletionNotification' + ]), + operation_types=str(['OPERATE']), + operation_states=str(['COMPLETED']), + vnf_instance_filter=json.JSONEncoder().encode({ + 'vnfdIds': [], + 'vnfProductsFromProviders': [], + 'vnfInstanceIds': ['1111'], + 'vnfInstanceNames': [], + }) + ) + + t1_apply_grant_result = [0, json.JSONEncoder().encode(''), '200'] + # t2_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] + t3_action_vm_stop_result = [0, json.JSONEncoder().encode(''), '202'] + # mock_call_req.side_effect = [t1_apply_grant_result, t2_lcm_notify_result, t3_action_vm_stop_result] + mock_call_req.side_effect = [t1_apply_grant_result, t3_action_vm_stop_result] + mock_call.return_value = None + mock_post_notification.return_value = None + req_data = { + "changeStateTo": "STOPPED" + } + self.nf_inst_id = '1111' + self.job_id = JobUtil.create_job('NF', 'OPERATE', self.nf_inst_id) + JobUtil.add_job_status(self.job_id, 0, "OPERATE_VNF_READY") + OperateVnf(req_data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() + vm = VmInstModel.objects.filter(vmid="1", vimid="1", resourceid="11") + self.assertEqual("INACTIVE", vm[0].operationalstate) + self.assert_job_result(self.job_id, 100, "Operate Vnf success.") + + @mock.patch.object(restcall, 'call_req') + @mock.patch.object(api, 'call') + def test_operate_vnf_success_stop_graceful(self, mock_call, mock_call_req): + NfInstModel.objects.create(nfinstid='1111', + nf_name='2222', + vnfminstid='1', + package_id='todo', + version='', + vendor='', + netype='', + vnfd_model='', + status='INSTANTIATED', + nf_desc='', + vnfdid='', + vnfSoftwareVersion='', + vnfConfigurableProperties='todo', + localizationLanguage='EN_US', + create_time=now_time()) + + VmInstModel.objects.create(vmid="1", + vimid="1", + resourceid="11", + insttype=0, + instid="1111", + vmname="test_01", + is_predefined=1, + operationalstate=1) + t1_apply_grant_result = [0, json.JSONEncoder().encode(''), '200'] + t2_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] + t3_action_vm_stop_result = [0, json.JSONEncoder().encode(''), '202'] + mock_call_req.side_effect = [t1_apply_grant_result, t2_lcm_notify_result, t3_action_vm_stop_result] + mock_call.return_value = None + req_data = { + "changeStateTo": "STOPPED", + "stopType": "GRACEFUL", + "gracefulStopTimeout": 2 + } + self.nf_inst_id = '1111' + self.job_id = JobUtil.create_job('NF', 'OPERATE', self.nf_inst_id) + JobUtil.add_job_status(self.job_id, 0, "OPERATE_VNF_READY") + OperateVnf(req_data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() + vm = VmInstModel.objects.filter(vmid="1", vimid="1", resourceid="11") + self.assertEqual("INACTIVE", vm[0].operationalstate) + self.assert_job_result(self.job_id, 100, "Operate Vnf success.") + + @mock.patch.object(restcall, 'call_req') + @mock.patch.object(api, 'call') + def test_operate_vnf_success_stop_forceful(self, mock_call, mock_call_req): + NfInstModel.objects.create(nfinstid='1111', + nf_name='2222', + vnfminstid='1', + package_id='todo', + version='', + vendor='', + netype='', + vnfd_model='', + status='INSTANTIATED', + nf_desc='', + vnfdid='', + vnfSoftwareVersion='', + vnfConfigurableProperties='todo', + localizationLanguage='EN_US', + create_time=now_time()) + + VmInstModel.objects.create(vmid="1", + vimid="1", + resourceid="11", + insttype=0, + instid="1111", + vmname="test_01", + is_predefined=1, + operationalstate=1) + t1_apply_grant_result = [0, json.JSONEncoder().encode(''), '200'] + t2_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] + t3_action_vm_stop_result = [0, json.JSONEncoder().encode(''), '202'] + mock_call_req.side_effect = [t1_apply_grant_result, t2_lcm_notify_result, t3_action_vm_stop_result] + mock_call.return_value = None + req_data = { + "changeStateTo": "STOPPED", + "stopType": "FORCEFUL", + } + self.nf_inst_id = '1111' + self.job_id = JobUtil.create_job('NF', 'OPERATE', self.nf_inst_id) + JobUtil.add_job_status(self.job_id, 0, "OPERATE_VNF_READY") + OperateVnf(req_data, nf_inst_id=self.nf_inst_id, job_id=self.job_id).run() + vm = VmInstModel.objects.filter(vmid="1", vimid="1", resourceid="11") + self.assertEqual("INACTIVE", vm[0].operationalstate) + self.assert_job_result(self.job_id, 100, "Operate Vnf success.") diff --git a/lcm/lcm/nf/tests/test_query_subscriptions.py b/lcm/lcm/nf/tests/test_query_subscriptions.py new file mode 100644 index 00000000..f67ac272 --- /dev/null +++ b/lcm/lcm/nf/tests/test_query_subscriptions.py @@ -0,0 +1,201 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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 json + +from django.test import TestCase, Client +from rest_framework import status + +from lcm.pub.database.models import SubscriptionModel + + +class TestQuerySubscriptions(TestCase): + def setUp(self): + self.client = Client() + self.subscription_id = "99442b18-a5c7-11e8-998c-bf1755941f16" + self.vnf_instance_id = "cd552c9c-ab6f-11e8-b354-236c32aa91a1" + SubscriptionModel.objects.all().delete() + self.test_single_subscription = { + "id": self.subscription_id, + "callbackUri": "http://aurl.com", + "_links": { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + }, + "filter": { + "notificationTypes": ["VnfLcmOperationOccurrenceNotification"], + "operationTypes": ["INSTANTIATE"], + "operationStates": ["STARTING"], + "vnfInstanceSubscriptionFilter": { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + + } + } + + def tearDown(self): + pass + + def test_get_subscriptions(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + response = self.client.get("/api/vnflcm/v1/subscriptions", format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([self.test_single_subscription], response.data) + + def test_get_subscriptions_with_vnf_instance_id(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + dummy_vnf_id = "584b35e2-b2a2-11e8-8e11-645106374fd3" + dummy_subscription_id = "947dcd2c-b2a2-11e8-b365-645106374fd4" + vnf_instance_filter["vnfInstanceIds"].append(dummy_vnf_id) + SubscriptionModel(subscription_id=dummy_subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + + response = self.client.get("/api/vnflcm/v1/subscriptions?vnfInstanceId=" + dummy_vnf_id, format='json') + expected_response = self.test_single_subscription.copy() + expected_response["id"] = dummy_subscription_id + expected_response["filter"]["vnfInstanceSubscriptionFilter"]["vnfInstanceIds"] = \ + vnf_instance_filter["vnfInstanceIds"] + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([expected_response], response.data) + + def test_get_subscriptions_with_unknown_vnf_instance_id(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + response = self.client.get("/api/vnflcm/v1/subscriptions?vnfInstanceId=dummy", format='json') + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + def test_get_subscriptions_with_invalid_filter(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + response = self.client.get("/api/vnflcm/v1/subscriptions?dummy=dummy", format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_get_subscriptions_with_operation_type_filter(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + dummy_vnf_id = "584b35e2-b2a2-11e8-8e11-645106374fd3" + dummy_subscription_id = "947dcd2c-b2a2-11e8-b365-645106374fd4" + vnf_instance_filter["vnfInstanceIds"].append(dummy_vnf_id) + SubscriptionModel(subscription_id=dummy_subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['SCALE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + + response = self.client.get("/api/vnflcm/v1/subscriptions?operationTypes=SCALE", format='json') + expected_response = self.test_single_subscription.copy() + expected_response["id"] = dummy_subscription_id + expected_response["filter"]["vnfInstanceSubscriptionFilter"]["vnfInstanceIds"] = \ + vnf_instance_filter["vnfInstanceIds"] + expected_response["filter"]["operationTypes"] = ["SCALE"] + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([expected_response], response.data) diff --git a/lcm/lcm/nf/tests/test_query_vnf.py b/lcm/lcm/nf/tests/test_query_vnf.py index 95f387b9..e0ecddb5 100644 --- a/lcm/lcm/nf/tests/test_query_vnf.py +++ b/lcm/lcm/nf/tests/test_query_vnf.py @@ -116,7 +116,7 @@ class ResourceTest(TestCase): NfInstModel(nfinstid=vnf_inst_id, nf_name='VNF1', status='INSTANTIATED').save() StorageInstModel(storageid='s02', vimid='vim01', - resouceid='resource01', + resourceid='resource01', insttype=1, instid=vnf_inst_id).save() response = self.client.get("/api/vnflcm/v1/vnf_instances/%s" % vnf_inst_id, format='json') @@ -161,7 +161,7 @@ class ResourceTest(TestCase): status='INSTANTIATED').save() StorageInstModel(storageid='s0%s' % i, vimid='vim0%s' % i, - resouceid='resource0%s' % i, + resourceid='resource0%s' % i, insttype=1, instid='%s' % i).save() response = self.client.get("/api/vnflcm/v1/vnf_instances", format='json') diff --git a/lcm/lcm/nf/tests/test_query_vnf_lcm_op.py b/lcm/lcm/nf/tests/test_query_vnf_lcm_op.py new file mode 100644 index 00000000..b08d4b05 --- /dev/null +++ b/lcm/lcm/nf/tests/test_query_vnf_lcm_op.py @@ -0,0 +1,189 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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 json + +from django.test import TestCase, Client +from rest_framework import status + +from lcm.pub.database.models import VNFLcmOpOccModel + + +class TestVNFLcmOpOccs(TestCase): + def setUp(self): + self.client = Client() + self.vnf_lcm_op_occ_id = "99442b18-a5c7-11e8-998c-bf1755941f16" + VNFLcmOpOccModel.objects.all().delete() + self.test_single_vnf_lcm_op = { + "id": "99442b18-a5c7-11e8-998c-bf1755941f16", + "operationState": "STARTING", + "stateEnteredTime": "2018-07-09", + "startTime": "2018-07-09", + "vnfInstanceId": "cd552c9c-ab6f-11e8-b354-236c32aa91a1", + "grantId": None, + "operation": "SCALE", + "isAutomaticInvocation": False, + "operationParams": {}, + "isCancelPending": False, + "cancelMode": None, + "error": None, + "resourceChanges": None, + "changedInfo": None, + "changedExtConnectivity": None, + "_links": { + "self": { + "href": "demo" + }, + "vnfInstance": "demo" + } + } + self.test_vnflcmop_with_exclude_default = [{ + "id": "99442b18-a5c7-11e8-998c-bf1755941f16", + "operationState": "STARTING", + "stateEnteredTime": "2018-07-09", + "startTime": "2018-07-09", + "vnfInstanceId": "cd552c9c-ab6f-11e8-b354-236c32aa91a1", + "grantId": None, + "operation": "SCALE", + "isAutomaticInvocation": False, + "isCancelPending": False, + "cancelMode": None, + "_links": { + "self": { + "href": "demo" + }, + "vnfInstance": "demo" + } + }] + + self.test_multiple_vnf_lcm_op = [{ + "id": "a6b9415c-ab99-11e8-9d37-dbb5e0378955", + "operationState": "STARTING", + "stateEnteredTime": "2018-07-09", + "startTime": "2018-07-09", + "vnfInstanceId": "cd552c9c-ab6f-11e8-b354-236c32aa91a1", + "grantId": None, + "operation": "INSTANTIATE", + "isAutomaticInvocation": False, + "operationParams": {}, + "isCancelPending": False, + "cancelMode": None, + "error": None, + "resourceChanges": None, + "changedInfo": None, + "changedExtConnectivity": None, + "_links": { + "self": { + "href": "demo" + }, + "vnfInstance": "demo" + } + }] + self.test_multiple_vnf_lcm_op.append( + self.test_single_vnf_lcm_op) + + def tearDown(self): + pass + + def test_get_vnflcmopoccs(self): + lcm_op_id = "99442b18-a5c7-11e8-998c-bf1755941f16" + vnf_instance_id = "cd552c9c-ab6f-11e8-b354-236c32aa91a1" + VNFLcmOpOccModel(id=lcm_op_id, operation_state="STARTING", + state_entered_time="2018-07-09", start_time="2018-07-09", + vnf_instance_id=vnf_instance_id, + grant_id=None, operation="SCALE", is_automatic_invocation=False, + operation_params='{}', is_cancel_pending=False, cancel_mode=None, + error=None, resource_changes=None, changed_ext_connectivity=None, + links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save() + response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs", format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([self.test_single_vnf_lcm_op], response.data) + + def test_get_vnflcmopoccs_with_id_not_exist(self): + response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs?id=dummy", format='json') + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + expected_data = { + "status": 500, + "detail": "LCM Operation Occurances do not exist" + } + self.assertEqual(expected_data, response.data) + + def test_get_vnflcmopoccs_with_filters(self): + lcm_op_id = "a6b9415c-ab99-11e8-9d37-dbb5e0378955" + vnf_instance_id = "cd552c9c-ab6f-11e8-b354-236c32aa91a1" + VNFLcmOpOccModel(id=lcm_op_id, operation_state="STARTING", + state_entered_time="2018-07-09", start_time="2018-07-09", + vnf_instance_id=vnf_instance_id, + grant_id=None, operation="INSTANTIATE", is_automatic_invocation=False, + operation_params='{}', is_cancel_pending=False, cancel_mode=None, + error=None, resource_changes=None, changed_ext_connectivity=None, + links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save() + + lcm_op_id = "99442b18-a5c7-11e8-998c-bf1755941f16" + VNFLcmOpOccModel(id=lcm_op_id, operation_state="STARTING", + state_entered_time="2018-07-09", start_time="2018-07-09", + vnf_instance_id=vnf_instance_id, + grant_id=None, operation="SCALE", is_automatic_invocation=False, + operation_params='{}', is_cancel_pending=False, cancel_mode=None, + error=None, resource_changes=None, changed_ext_connectivity=None, + links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save() + response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs", format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.test_multiple_vnf_lcm_op, response.data) + + response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs?operation=SCALE", format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([self.test_single_vnf_lcm_op], response.data) + + response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs?vnfInstanceId=%s" % vnf_instance_id, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.test_multiple_vnf_lcm_op, response.data) + + def test_get_vnflcmopoccs_with_extra_flags(self): + lcm_op_id = "99442b18-a5c7-11e8-998c-bf1755941f16" + vnf_instance_id = "cd552c9c-ab6f-11e8-b354-236c32aa91a1" + VNFLcmOpOccModel(id=lcm_op_id, operation_state="STARTING", + state_entered_time="2018-07-09", start_time="2018-07-09", + vnf_instance_id=vnf_instance_id, + grant_id=None, operation="SCALE", is_automatic_invocation=False, + operation_params='{}', is_cancel_pending=False, cancel_mode=None, + error=None, resource_changes=None, changed_ext_connectivity=None, + links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save() + response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs?exclude_default", format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.test_vnflcmop_with_exclude_default, response.data) + + def test_get_vnflcmopocc_with_id(self): + lcm_op_id = "99442b18-a5c7-11e8-998c-bf1755941f16" + vnf_instance_id = "cd552c9c-ab6f-11e8-b354-236c32aa91a1" + VNFLcmOpOccModel(id=lcm_op_id, operation_state="STARTING", + state_entered_time="2018-07-09", start_time="2018-07-09", + vnf_instance_id=vnf_instance_id, + grant_id=None, operation="SCALE", is_automatic_invocation=False, + operation_params='{}', is_cancel_pending=False, cancel_mode=None, + error=None, resource_changes=None, changed_ext_connectivity=None, + links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save() + response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs/" + lcm_op_id, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.test_single_vnf_lcm_op, response.data) + + def test_single_vnflcmopocc_with_unknown_id(self): + lcm_op_id = "99442b18-a5c7-11e8-998c-bf1755941f16" + response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs/" + lcm_op_id, format='json') + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + expected_data = { + "status": 500, + "detail": "LCM Operation Occurance does not exist" + } + self.assertEqual(expected_data, response.data) diff --git a/lcm/lcm/nf/tests/test_subscribe_notification.py b/lcm/lcm/nf/tests/test_subscribe_notification.py new file mode 100644 index 00000000..8aeab63f --- /dev/null +++ b/lcm/lcm/nf/tests/test_subscribe_notification.py @@ -0,0 +1,156 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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 mock +from django.test import TestCase +from rest_framework.test import APIClient +import uuid + + +class TestSubscription(TestCase): + def setUp(self): + self.client = APIClient() + + def tearDown(self): + pass + + @mock.patch("requests.get") + @mock.patch.object(uuid, 'uuid4') + def test_subscribe_notification_simple(self, mock_uuid4, mock_requests): + temp_uuid = "99442b18-a5c7-11e8-998c-bf1755941f13" + dummy_subscription = { + "callbackUri": "http://aurl.com" + } + mock_requests.return_value.status_code = 204 + mock_requests.get.status_code = 204 + mock_uuid4.return_value = temp_uuid + response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(201, response.status_code) + self.assertEqual(dummy_subscription["callbackUri"], response.data["callbackUri"]) + self.assertEqual(temp_uuid, response.data["id"]) + + @mock.patch("requests.get") + @mock.patch.object(uuid, 'uuid4') + def test_subscribe_notification(self, mock_uuid4, mock_requests): + temp_uuid = "99442b18-a5c7-11e8-998c-bf1755941f13" + dummy_subscription = { + "callbackUri": "http://aurl.com", + "authentication": { + "authType": ["BASIC"], + "paramsBasic": { + "username": "username", + "password": "password" + } + }, + "filter": { + "notificationTypes": ["VnfLcmOperationOccurrenceNotification"], + "operationTypes": [ + "INSTANTIATE" + ], + "operationStates": [ + "STARTING" + ], + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + mock_uuid4.return_value = temp_uuid + response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(201, response.status_code) + self.assertEqual(dummy_subscription["callbackUri"], response.data["callbackUri"]) + self.assertEqual(temp_uuid, response.data["id"]) + + @mock.patch("requests.get") + def test_invalid_auth_subscription(self, mock_requests): + dummy_subscription = { + "callbackUri": "http://aurl.com", + "authentication": { + "authType": ["OAUTH2_CLIENT_CREDENTIALS"], + "paramsBasic": { + "username": "username", + "password": "password" + } + }, + "filter": { + "notificationTypes": ["VnfLcmOperationOccurrenceNotification"], + "operationTypes": [ + "INSTANTIATE" + ], + "operationStates": [ + "STARTING" + ], + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + expected_data = { + 'error': 'Auth type should be BASIC' + } + response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(500, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch("requests.get") + def test_invalid_notification_type(self, mock_requests): + dummy_subscription = { + "callbackUri": "http://aurl.com", + "filter": { + "notificationTypes": ["VnfIdentifierDeletionNotification"], + "operationTypes": [ + "INSTANTIATE" + ], + "operationStates": [ + "STARTING" + ], + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + expected_data = { + 'error': 'If you are setting operationTypes,then ' + + 'notificationTypes must be VnfLcmOperationOccurrenceNotification' + } + response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(500, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch("requests.get") + @mock.patch.object(uuid, 'uuid4') + def test_duplicate_subscription(self, mock_uuid4, mock_requests): + temp_uuid = str(uuid.uuid4()) + dummy_subscription = { + "callbackUri": "http://aurl.com", + "filter": { + "notificationTypes": ["VnfLcmOperationOccurrenceNotification"], + "operationTypes": [ + "INSTANTIATE" + ], + "operationStates": [ + "STARTING" + ] + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + mock_uuid4.return_value = temp_uuid + response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(201, response.status_code) + self.assertEqual(dummy_subscription["callbackUri"], response.data["callbackUri"]) + self.assertEqual(temp_uuid, response.data["id"]) + response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(303, response.status_code) + expected_data = { + "error": "Already Subscription exists with the same callbackUri and filter" + } + self.assertEqual(expected_data, response.data) diff --git a/lcm/lcm/nf/tests/test_terminate_vnf.py b/lcm/lcm/nf/tests/test_terminate_vnf.py index 23b246d7..5e9a9a84 100644 --- a/lcm/lcm/nf/tests/test_terminate_vnf.py +++ b/lcm/lcm/nf/tests/test_terminate_vnf.py @@ -22,10 +22,11 @@ from rest_framework import status from lcm.nf.biz.terminate_vnf import TerminateVnf from lcm.pub.database.models import NfInstModel, JobStatusModel, VmInstModel, NetworkInstModel, SubNetworkInstModel, \ - PortInstModel, FlavourInstModel, StorageInstModel + PortInstModel, FlavourInstModel, StorageInstModel, SubscriptionModel from lcm.pub.utils import restcall from lcm.pub.utils.jobutil import JobUtil from lcm.pub.utils.timeutil import now_time +from lcm.pub.utils.notificationsutil import NotificationsUtil from lcm.pub.vimapi import api @@ -34,13 +35,13 @@ class TestNFTerminate(TestCase): self.client = Client() StorageInstModel.objects.create(storageid="1", vimid="1", - resouceid="11", + resourceid="11", insttype=0, instid="1111", is_predefined=1) NetworkInstModel.objects.create(networkid='1', vimid='1', - resouceid='1', + resourceid='1', name='pnet_network', is_predefined=1, tenant='admin', @@ -48,7 +49,7 @@ class TestNFTerminate(TestCase): instid='1111') SubNetworkInstModel.objects.create(subnetworkid='1', vimid='1', - resouceid='1', + resourceid='1', networkid='1', is_predefined=1, name='sub_pnet', @@ -59,7 +60,7 @@ class TestNFTerminate(TestCase): networkid='1', subnetworkid='1', vimid='1', - resouceid='1', + resourceid='1', is_predefined=1, name='aaa_pnet_cp', tenant='admin', @@ -67,12 +68,12 @@ class TestNFTerminate(TestCase): instid='1111') FlavourInstModel.objects.create(flavourid="1", vimid="1", - resouceid="11", + resourceid="11", instid="1111", is_predefined=1) VmInstModel.objects.create(vmid="1", vimid="1", - resouceid="11", + resourceid="11", insttype=0, instid="1111", vmname="test_01", @@ -115,7 +116,8 @@ class TestNFTerminate(TestCase): @mock.patch.object(restcall, 'call_req') @mock.patch.object(api, 'call') - def test_terminate_vnf_success(self, mock_call, mock_call_req): + @mock.patch.object(NotificationsUtil, 'post_notification') + def test_terminate_vnf_success(self, mock_post_notification, mock_call, mock_call_req): NfInstModel.objects.create(nfinstid='1111', nf_name='2222', vnfminstid='1', @@ -131,6 +133,32 @@ class TestNFTerminate(TestCase): vnfConfigurableProperties='todo', localizationLanguage='EN_US', create_time=now_time()) + + SubscriptionModel.objects.create( + subscription_id=str(uuid.uuid4()), + callback_uri='api/gvnfmdriver/v1/vnfs/lifecyclechangesnotification', + auth_info=json.JSONEncoder().encode({ + 'authType': ['BASIC'], + 'paramsBasic': { + 'userName': 'username', + 'password': 'password' + } + }), + notification_types=str([ + 'VnfLcmOperationOccurrenceNotification', + 'VnfIdentifierCreationNotification', + 'VnfIdentifierDeletionNotification' + ]), + operation_types=str(['TERMINATE']), + operation_states=str(['COMPLETED']), + vnf_instance_filter=json.JSONEncoder().encode({ + 'vnfdIds': ['111'], + 'vnfProductsFromProviders': [], + 'vnfInstanceIds': ['1111'], + 'vnfInstanceNames': [], + }) + ) + t1_apply_grant_result = [0, json.JSONEncoder().encode( { "id": "1", @@ -143,10 +171,12 @@ class TestNFTerminate(TestCase): } ] }), '200'] - t2_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] + # t2_lcm_notify_result = [0, json.JSONEncoder().encode(''), '200'] t3_delete_flavor = [0, json.JSONEncoder().encode({"vim_id": "vimid_1"}), '200'] - mock_call_req.side_effect = [t1_apply_grant_result, t2_lcm_notify_result, t3_delete_flavor] + # mock_call_req.side_effect = [t1_apply_grant_result, t2_lcm_notify_result, t3_delete_flavor] + mock_call_req.side_effect = [t1_apply_grant_result, t3_delete_flavor] mock_call.return_value = None + mock_post_notification.return_value = None data = { "terminationType": "FORCEFUL", "gracefulTerminationTimeout": 120 diff --git a/lcm/lcm/nf/urls.py b/lcm/lcm/nf/urls.py index f279b551..fd0aaae9 100644 --- a/lcm/lcm/nf/urls.py +++ b/lcm/lcm/nf/urls.py @@ -17,10 +17,19 @@ from django.conf.urls import url from lcm.nf.views.curd_vnf_views import DeleteVnfAndQueryVnf, CreateVnfAndQueryVnfs from lcm.nf.views.instantiate_vnf_view import InstantiateVnfView from lcm.nf.views.terminate_vnf_view import TerminateVnfView +from lcm.nf.views.subscriptions_view import SubscriptionsView +from lcm.nf.views.heal_vnf_view import HealVnfView +from lcm.nf.views.operate_vnf_view import OperateVnfView +from lcm.nf.views.lcm_op_occs_view import QueryMultiVnfLcmOpOccs, QuerySingleVnfLcmOpOcc urlpatterns = [ + url(r'^api/vnflcm/v1/subscriptions$', SubscriptionsView.as_view()), url(r'^api/vnflcm/v1/vnf_instances$', CreateVnfAndQueryVnfs.as_view()), url(r'^api/vnflcm/v1/vnf_instances/(?P[0-9a-zA-Z_-]+)/instantiate$', InstantiateVnfView.as_view()), url(r'^api/vnflcm/v1/vnf_instances/(?P[0-9a-zA-Z_-]+)$', DeleteVnfAndQueryVnf.as_view()), url(r'^api/vnflcm/v1/vnf_instances/(?P[0-9a-zA-Z_-]+)/terminate$', TerminateVnfView.as_view()), + url(r'^api/vnflcm/v1/vnf_instances/(?P[0-9a-zA-Z_-]+)/heal$', HealVnfView.as_view()), + url(r'^api/vnflcm/v1/vnf_instances/(?P[0-9a-zA-Z_-]+)/operate$', OperateVnfView.as_view()), + url(r'^api/vnflcm/v1/vnf_lcm_op_occs$', QueryMultiVnfLcmOpOccs.as_view()), + url(r'^api/vnflcm/v1/vnf_lcm_op_occs/(?P[0-9a-zA-Z_-]+)$', QuerySingleVnfLcmOpOcc.as_view()), ] diff --git a/lcm/lcm/nf/views/heal_vnf_view.py b/lcm/lcm/nf/views/heal_vnf_view.py new file mode 100644 index 00000000..08daa98f --- /dev/null +++ b/lcm/lcm/nf/views/heal_vnf_view.py @@ -0,0 +1,88 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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 logging +import traceback + +from drf_yasg.utils import swagger_auto_schema +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from lcm.nf.biz.heal_vnf import HealVnf +from lcm.nf.serializers.heal_vnf_req import HealVnfRequestSerializer +from lcm.nf.serializers.response import ProblemDetailsSerializer +from lcm.pub.exceptions import NFLCMException, NFLCMExceptionNotFound, NFLCMExceptionConflict +from lcm.pub.utils.jobutil import JobUtil +from lcm.pub.database.models import NfInstModel +from lcm.nf.const import VNF_STATUS + +logger = logging.getLogger(__name__) + + +class HealVnfView(APIView): + @swagger_auto_schema( + request_body=HealVnfRequestSerializer(), + responses={ + status.HTTP_202_ACCEPTED: "Success", + status.HTTP_404_NOT_FOUND: ProblemDetailsSerializer(), + status.HTTP_409_CONFLICT: ProblemDetailsSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal error" + } + ) + def post(self, request, instanceid): + logger.debug("HealVnf--post::> %s" % request.data) + try: + heal_vnf_request_serializer = HealVnfRequestSerializer(data=request.data) + if not heal_vnf_request_serializer.is_valid(): + raise NFLCMException(heal_vnf_request_serializer.errors) + + job_id = JobUtil.create_job('NF', 'HEAL', instanceid) + JobUtil.add_job_status(job_id, 0, "HEAL_VNF_READY") + self.heal_pre_check(instanceid, job_id) + HealVnf(heal_vnf_request_serializer.data, instanceid, job_id).start() + response = Response(data=None, status=status.HTTP_202_ACCEPTED) + response["Location"] = "/vnf_lc_ops/%s" % job_id + return response + except NFLCMExceptionNotFound as e: + probDetail = ProblemDetailsSerializer(data={"status": status.HTTP_404_NOT_FOUND, "detail": "VNF Instance not found"}) + resp_isvalid = probDetail.is_valid() + if not resp_isvalid: + raise NFLCMException(probDetail.errors) + return Response(data=probDetail.data, status=status.HTTP_404_NOT_FOUND) + except NFLCMExceptionConflict as e: + probDetail = ProblemDetailsSerializer(data={"status": status.HTTP_409_CONFLICT, "detail": "VNF Instance not in Instantiated State"}) + resp_isvalid = probDetail.is_valid() + if not resp_isvalid: + raise NFLCMException(probDetail.errors) + return Response(data=probDetail.data, status=status.HTTP_409_CONFLICT) + except NFLCMException as e: + logger.error(e.message) + return Response(data={'error': '%s' % e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + except Exception as e: + logger.error(e.message) + logger.error(traceback.format_exc()) + return Response(data={'error': 'unexpected exception'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def heal_pre_check(self, nf_inst_id, job_id): + vnf_insts = NfInstModel.objects.filter(nfinstid=nf_inst_id) + if not vnf_insts.exists(): + raise NFLCMExceptionNotFound("VNF nf_inst_id does not exist.") + + if vnf_insts[0].status != 'INSTANTIATED': + raise NFLCMExceptionConflict("VNF instantiationState is not INSTANTIATED.") + + NfInstModel.objects.filter(nfinstid=nf_inst_id).update(status=VNF_STATUS.HEALING) + JobUtil.add_job_status(job_id, 15, 'Nf healing pre-check finish') + logger.info("Nf healing pre-check finish") diff --git a/lcm/lcm/nf/views/lcm_op_occs_view.py b/lcm/lcm/nf/views/lcm_op_occs_view.py new file mode 100644 index 00000000..de165317 --- /dev/null +++ b/lcm/lcm/nf/views/lcm_op_occs_view.py @@ -0,0 +1,111 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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 logging +import traceback + +from drf_yasg.utils import swagger_auto_schema +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from lcm.nf.biz.query_vnf_lcm_op_occ import QueryVnfLcmOpOcc +from lcm.nf.serializers.response import ProblemDetailsSerializer +from lcm.nf.serializers.vnf_lcm_op_occ import VNFLCMOpOccSerializer +from lcm.nf.serializers.vnf_lcm_op_occs import VNFLCMOpOccsSerializer +from lcm.pub.exceptions import NFLCMException + +logger = logging.getLogger(__name__) +EXCLUDE_DEFAULT = ['operationParams', 'error', 'resourceChanges', 'changedInfo', 'changedExtConnectivity'] +VALID_FILTERS = ["all_fields", "fields", "exclude_fields", "exclude_default", + "id", "operationState", "stateEnteredTime", "startTime", + "vnfInstanceId", "grantId", "operation"] + + +def get_problem_details_serializer(status_code, error_message): + problem_details = { + "status": status_code, + "detail": error_message + } + problem_details_serializer = ProblemDetailsSerializer(data=problem_details) + problem_details_serializer.is_valid() + return problem_details_serializer + + +class QueryMultiVnfLcmOpOccs(APIView): + @swagger_auto_schema( + responses={ + status.HTTP_200_OK: VNFLCMOpOccsSerializer(), + status.HTTP_400_BAD_REQUEST: ProblemDetailsSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: ProblemDetailsSerializer() + } + ) + def get(self, request): + logger.debug("QueryMultiVnfLcmOpOccs--get::> %s" % request.query_params) + try: + if request.query_params and not set(request.query_params).issubset(set(VALID_FILTERS)): + problem_details_serializer = get_problem_details_serializer(status.HTTP_400_BAD_REQUEST, "Not a valid filter") + return Response(data=problem_details_serializer.data, status=status.HTTP_400_BAD_REQUEST) + resp_data = QueryVnfLcmOpOcc(request.query_params).query_multi_vnf_lcm_op_occ() + + vnf_lcm_op_occs_serializer = VNFLCMOpOccsSerializer(data=resp_data) + if not vnf_lcm_op_occs_serializer.is_valid(): + raise NFLCMException(vnf_lcm_op_occs_serializer.errors) + + logger.debug("QueryMultiVnfLcmOpOccs--get::> Remove default fields if exclude_default" + + " is specified") + # TODO(bharath): Add support for "fields", "exclude_fields" in query parameters + if 'exclude_default' in request.query_params.keys(): + for field in EXCLUDE_DEFAULT: + for lcm_op in vnf_lcm_op_occs_serializer.data: + del lcm_op[field] + return Response(data=vnf_lcm_op_occs_serializer.data, status=status.HTTP_200_OK) + except NFLCMException as e: + logger.error(e.message) + problem_details_serializer = get_problem_details_serializer(status.HTTP_500_INTERNAL_SERVER_ERROR, e.message) + return Response(data=problem_details_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + except Exception as e: + logger.error(e.message) + logger.error(traceback.format_exc()) + problem_details_serializer = get_problem_details_serializer(status.HTTP_500_INTERNAL_SERVER_ERROR, e.message) + return Response(data=problem_details_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +class QuerySingleVnfLcmOpOcc(APIView): + @swagger_auto_schema( + responses={ + status.HTTP_200_OK: VNFLCMOpOccSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: ProblemDetailsSerializer() + } + ) + def get(self, request, lcmopoccid): + logger.debug("QuerySingleVnfLcmOpOcc--get::> %s" % request.query_params) + try: + resp_data = QueryVnfLcmOpOcc(request.query_params, lcm_op_occ_id=lcmopoccid).query_single_vnf_lcm_op_occ() + + vnf_lcm_op_occ_serializer = VNFLCMOpOccSerializer(data=resp_data) + if not vnf_lcm_op_occ_serializer.is_valid(): + raise NFLCMException(vnf_lcm_op_occ_serializer.errors) + + return Response(data=vnf_lcm_op_occ_serializer.data, status=status.HTTP_200_OK) + except NFLCMException as e: + logger.error(e.message) + problem_details_serializer = get_problem_details_serializer(status.HTTP_500_INTERNAL_SERVER_ERROR, e.message) + return Response(data=problem_details_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + except Exception as e: + logger.error(e.message) + logger.error(traceback.format_exc()) + problem_details_serializer = get_problem_details_serializer(status.HTTP_500_INTERNAL_SERVER_ERROR, e.message) + return Response(data=problem_details_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/lcm/lcm/nf/views/operate_vnf_view.py b/lcm/lcm/nf/views/operate_vnf_view.py new file mode 100644 index 00000000..947e983e --- /dev/null +++ b/lcm/lcm/nf/views/operate_vnf_view.py @@ -0,0 +1,88 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved. +# +# 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 logging +import traceback + +from drf_yasg.utils import swagger_auto_schema +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from lcm.nf.biz.operate_vnf import OperateVnf +from lcm.nf.serializers.operate_vnf_req import OperateVnfRequestSerializer +from lcm.nf.serializers.response import ProblemDetailsSerializer +from lcm.pub.exceptions import NFLCMException, NFLCMExceptionNotFound, NFLCMExceptionConflict +from lcm.pub.utils.jobutil import JobUtil +from lcm.pub.database.models import NfInstModel +from lcm.nf.const import VNF_STATUS + +logger = logging.getLogger(__name__) + + +class OperateVnfView(APIView): + @swagger_auto_schema( + request_body=OperateVnfRequestSerializer(), + responses={ + status.HTTP_202_ACCEPTED: "Success", + status.HTTP_404_NOT_FOUND: ProblemDetailsSerializer(), + status.HTTP_409_CONFLICT: ProblemDetailsSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal error" + } + ) + def post(self, request, instanceid): + logger.debug("OperateVnf--post::> %s" % request.data) + try: + operate_vnf_request_serializer = OperateVnfRequestSerializer(data=request.data) + if not operate_vnf_request_serializer.is_valid(): + raise NFLCMException(operate_vnf_request_serializer.errors) + + job_id = JobUtil.create_job('NF', 'OPERATE', instanceid) + JobUtil.add_job_status(job_id, 0, "OPERATE_VNF_READY") + self.operate_pre_check(instanceid, job_id) + OperateVnf(operate_vnf_request_serializer.data, instanceid, job_id).start() + response = Response(data=None, status=status.HTTP_202_ACCEPTED) + response["Location"] = "/vnf_lc_ops/%s" % job_id + return response + except NFLCMExceptionNotFound as e: + probDetail = ProblemDetailsSerializer(data={"status": 404, "detail": "VNF Instance not found"}) + resp_isvalid = probDetail.is_valid() + if not resp_isvalid: + raise NFLCMException(probDetail.errors) + return Response(data=probDetail.data, status=status.HTTP_404_NOT_FOUND) + except NFLCMExceptionConflict as e: + probDetail = ProblemDetailsSerializer(data={"status": 409, "detail": "VNF Instance not in Instantiated State"}) + resp_isvalid = probDetail.is_valid() + if not resp_isvalid: + raise NFLCMException(probDetail.errors) + return Response(data=probDetail.data, status=status.HTTP_409_CONFLICT) + except NFLCMException as e: + logger.error(e.message) + return Response(data={'error': '%s' % e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + except Exception as e: + logger.error(e.message) + logger.error(traceback.format_exc()) + return Response(data={'error': 'unexpected exception'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def operate_pre_check(self, nfInstId, jobId): + vnf_insts = NfInstModel.objects.filter(nfinstid=nfInstId) + if not vnf_insts.exists(): + raise NFLCMExceptionNotFound("VNF nf_inst_id does not exist.") + + if vnf_insts[0].status != 'INSTANTIATED': + raise NFLCMExceptionConflict("VNF instantiationState is not INSTANTIATED.") + NfInstModel.objects.filter(nfinstid=nfInstId).update(status=VNF_STATUS.OPERATING) + + JobUtil.add_job_status(jobId, 15, 'Nf operating pre-check finish') + logger.info("Nf operating pre-check finish") diff --git a/lcm/lcm/nf/views/subscriptions_view.py b/lcm/lcm/nf/views/subscriptions_view.py new file mode 100644 index 00000000..4c013ee4 --- /dev/null +++ b/lcm/lcm/nf/views/subscriptions_view.py @@ -0,0 +1,121 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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 ast +import json +import logging +import traceback + +from drf_yasg.utils import swagger_auto_schema +from lcm.nf.biz.create_subscription import CreateSubscription +from lcm.nf.biz.query_subscription import QuerySubscription +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from lcm.nf.serializers.lccn_subscription_request import LccnSubscriptionRequestSerializer +from lcm.nf.serializers.lccn_subscription import LccnSubscriptionSerializer +from lcm.nf.serializers.lccn_subscriptions import LccnSubscriptionsSerializer +from lcm.nf.serializers.response import ProblemDetailsSerializer +from lcm.pub.exceptions import NFLCMException + +logger = logging.getLogger(__name__) +VALID_FILTERS = ["operationTypes", "operationStates", "notificationTypes", "vnfInstanceId"] + + +def get_problem_details_serializer(status_code, error_message): + problem_details = { + "status": status_code, + "detail": error_message + } + problem_details_serializer = ProblemDetailsSerializer(data=problem_details) + problem_details_serializer.is_valid() + return problem_details_serializer + + +class SubscriptionsView(APIView): + @swagger_auto_schema( + request_body=LccnSubscriptionRequestSerializer(), + responses={ + status.HTTP_201_CREATED: LccnSubscriptionSerializer(), + status.HTTP_303_SEE_OTHER: ProblemDetailsSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: ProblemDetailsSerializer() + } + ) + def post(self, request): + logger.debug("SubscribeNotification--post::> %s" % request.data) + try: + lccn_subscription_request_serializer = LccnSubscriptionRequestSerializer(data=request.data) + if not lccn_subscription_request_serializer.is_valid(): + raise NFLCMException(lccn_subscription_request_serializer.errors) + subscription = CreateSubscription( + lccn_subscription_request_serializer.data).do_biz() + lccn_notifications_filter = { + "notificationTypes": ast.literal_eval(subscription.notification_types), + "operationTypes": ast.literal_eval(subscription.operation_types), + "operationStates": ast.literal_eval(subscription.operation_states), + "vnfInstanceSubscriptionFilter": json.loads(subscription.vnf_instance_filter) + } + subscription_data = { + "id": subscription.subscription_id, + "callbackUri": subscription.callback_uri, + "_links": json.loads(subscription.links), + "filter": lccn_notifications_filter + } + sub_resp_serializer = LccnSubscriptionSerializer(data=subscription_data) + if not sub_resp_serializer.is_valid(): + raise NFLCMException(sub_resp_serializer.errors) + return Response(data=sub_resp_serializer.data, status=status.HTTP_201_CREATED) + except NFLCMException as e: + logger.error(e.message) + if "exists" in e.message: + return Response(data={'error': '%s' % e.message}, status=status.HTTP_303_SEE_OTHER) + return Response(data={'error': '%s' % e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + except Exception as e: + logger.error(e.message) + logger.error(traceback.format_exc()) + return Response(data={'error': e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + @swagger_auto_schema( + responses={ + status.HTTP_200_OK: LccnSubscriptionsSerializer(), + status.HTTP_400_BAD_REQUEST: ProblemDetailsSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: ProblemDetailsSerializer() + } + ) + def get(self, request): + logger.debug("SubscribeNotification--get::> %s" % request.query_params) + try: + if request.query_params and not set(request.query_params).issubset(set(VALID_FILTERS)): + problem_details_serializer = get_problem_details_serializer(status.HTTP_400_BAD_REQUEST, "Not a valid filter") + return Response(data=problem_details_serializer.data, status=status.HTTP_400_BAD_REQUEST) + resp_data = QuerySubscription(request.query_params).query_multi_subscriptions() + + subscriptions_serializer = LccnSubscriptionsSerializer(data=resp_data) + if not subscriptions_serializer.is_valid(): + raise NFLCMException(subscriptions_serializer.errors) + + logger.debug("SubscribeNotification--get::> Remove default fields if exclude_default" + + " is specified") + return Response(data=subscriptions_serializer.data, status=status.HTTP_200_OK) + except NFLCMException as e: + logger.error(e.message) + problem_details_serializer = get_problem_details_serializer(status.HTTP_500_INTERNAL_SERVER_ERROR, traceback.format_exc()) + return Response(data=problem_details_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + except Exception as e: + logger.error(e.message) + logger.error(traceback.format_exc()) + problem_details_serializer = get_problem_details_serializer(status.HTTP_500_INTERNAL_SERVER_ERROR, traceback.format_exc()) + return Response(data=problem_details_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/lcm/lcm/pub/database/models.py b/lcm/lcm/pub/database/models.py index e54fae2b..3ac4a832 100644 --- a/lcm/lcm/pub/database/models.py +++ b/lcm/lcm/pub/database/models.py @@ -39,6 +39,9 @@ class NfInstModel(models.Model): vnfSoftwareVersion = models.CharField(db_column='VNFSOFTWAREVER', max_length=200, blank=True, null=True) vnfConfigurableProperties = models.TextField(db_column='VNFCONFIGURABLEPROPERTIES', max_length=20000, blank=True, null=True) localizationLanguage = models.CharField(db_column='LOCALIZATIONLANGUAGE', max_length=255, null=True) + operationState = models.CharField(db_column='OPERATIONSTATE', max_length=255, null=True) + resInfo = models.TextField(db_column='RESINFO', max_length=20000, blank=True, null=True) + vimInfo = models.TextField(db_column='VIMINFO', max_length=20000, blank=True, null=True) class JobModel(models.Model): @@ -104,7 +107,7 @@ class StorageInstModel(models.Model): storageid = models.CharField(db_column='STORAGEID', primary_key=True, max_length=255) vimid = models.CharField(db_column='VIMID', max_length=255) - resouceid = models.CharField(db_column='RESOURCEID', max_length=255) + resourceid = models.CharField(db_column='RESOURCEID', max_length=255) insttype = models.IntegerField(db_column='INSTTYPE') instid = models.CharField(db_column='INSTID', max_length=255) name = models.CharField(db_column='NAME', max_length=255, null=True) @@ -129,7 +132,7 @@ class VmInstModel(models.Model): vmid = models.CharField(db_column='VMID', primary_key=True, max_length=255) vimid = models.CharField(db_column='VIMID', max_length=255) tenant = models.CharField(db_column='TENANT', max_length=255, null=True) - resouceid = models.CharField(db_column='RESOURCEID', max_length=255) + resourceid = models.CharField(db_column='RESOURCEID', max_length=255) vmname = models.CharField(db_column='VMNAME', max_length=255) nic_array = models.CharField(db_column='NICARRAY', max_length=255) metadata = models.CharField(db_column='METADATA', max_length=255) @@ -164,7 +167,7 @@ class FlavourInstModel(models.Model): flavourid = models.CharField(db_column='FLAVOURID', max_length=255, primary_key=True) vimid = models.CharField(db_column='VIMID', max_length=255) - resouceid = models.CharField(db_column='RESOURCEID', max_length=255) + resourceid = models.CharField(db_column='RESOURCEID', max_length=255) name = models.CharField(db_column='NAME', max_length=255) tenant = models.CharField(db_column='TENANT', max_length=255, null=True) vcpu = models.IntegerField(db_column='VCPU', null=True) @@ -185,7 +188,7 @@ class NetworkInstModel(models.Model): networkid = models.CharField(db_column='NETWORKID', primary_key=True, max_length=255) vimid = models.CharField(db_column='VIMID', max_length=255) - resouceid = models.CharField(db_column='RESOURCEID', max_length=255) + resourceid = models.CharField(db_column='RESOURCEID', max_length=255) insttype = models.IntegerField(db_column='INSTTYPE') instid = models.CharField(db_column='INSTID', max_length=255) name = models.CharField(db_column='NAME', max_length=255) @@ -212,7 +215,7 @@ class SubNetworkInstModel(models.Model): subnetworkid = models.CharField(db_column='SUBNETWORKID', primary_key=True, max_length=255) vimid = models.CharField(db_column='VIMID', max_length=255) - resouceid = models.CharField(db_column='RESOURCEID', max_length=255) + resourceid = models.CharField(db_column='RESOURCEID', max_length=255) networkid = models.CharField(db_column='NETWORKID', max_length=255) insttype = models.IntegerField(db_column='INSTTYPE') instid = models.CharField(db_column='INSTID', max_length=255) @@ -255,7 +258,7 @@ class PortInstModel(models.Model): networkid = models.CharField(db_column='NETWORKID', max_length=255) subnetworkid = models.CharField(db_column='SUBNETWORKID', max_length=255, null=True) vimid = models.CharField(db_column='VIMID', max_length=255) - resouceid = models.CharField(db_column='RESOURCEID', max_length=255) + resourceid = models.CharField(db_column='RESOURCEID', max_length=255) name = models.CharField(db_column='NAME', max_length=255, null=True) insttype = models.IntegerField(db_column='INSTTYPE') instid = models.CharField(db_column='INSTID', max_length=255) @@ -293,3 +296,42 @@ class CPInstModel(models.Model): relatedvl = models.CharField(db_column='RELATEDVL', max_length=255, blank=True, null=True) relatedcp = models.CharField(db_column='RELATEDCP', max_length=255, blank=True, null=True) relatedport = models.CharField(db_column='RELATEDPORT', max_length=255, blank=True, null=True) + + +class SubscriptionModel(models.Model): + class Meta: + db_table = 'SUBSCRIPTION' + subscription_id = models.CharField(db_column='SUBSCRIPTIONID', max_length=255, primary_key=True) + callback_uri = models.CharField(db_column='CALLBACKURI', max_length=255) + auth_info = models.TextField(db_column='AUTHINFO', max_length=20000, blank=True, null=True) + notification_types = models.TextField(db_column='NOTIFICATIONTYPES', + null=True) + operation_types = models.TextField(db_column='OPERATIONTYPES', + null=True) + operation_states = models.TextField(db_column='OPERATIONSTATES', + null=True) + vnf_instance_filter = models.TextField(db_column='VNFINSTANCEFILTER', + null=True) + links = models.TextField(db_column='LINKS', max_length=20000) + + +class VNFLcmOpOccModel(models.Model): + class Meta: + db_table = 'VNFLCMOPOCCS' + + id = models.CharField(db_column='ID', max_length=255, primary_key=True) + operation_state = models.CharField(db_column='OPERATIONSTATE', null=False, max_length=30) + state_entered_time = models.CharField(db_column='STATEENTEREDTIME', null=False, max_length=30) + start_time = models.CharField(db_column='STARTTIME', null=False, max_length=30) + vnf_instance_id = models.CharField(db_column='VNFINSTANCEID', null=False, max_length=255) + grant_id = models.CharField(db_column='GRANTID', null=True, max_length=255) + operation = models.CharField(db_column='OPERATION', null=False, max_length=30) + is_automatic_invocation = models.CharField(db_column='ISAUTOMATICINVOCATION', null=False, max_length=5) + operation_params = models.TextField(db_column='OPERATIONPARAMS', null=False) + is_cancel_pending = models.CharField(db_column='ISCANCELPENDING', null=False, max_length=5) + cancel_mode = models.TextField(db_column='CANCELMODE', null=True) + error = models.TextField(db_column='ERROR', null=True) + resource_changes = models.TextField(db_column='RESOURCECHANGES', null=True) + changed_info = models.TextField(db_column='CHANGEDINFO', null=True) + changed_ext_connectivity = models.TextField(db_column='CHANGEDEXTCONNECTIVITY', null=True) + links = models.TextField(db_column='LINKS', null=False) diff --git a/lcm/lcm/pub/exceptions.py b/lcm/lcm/pub/exceptions.py index 274c0d06..5ff9bc42 100644 --- a/lcm/lcm/pub/exceptions.py +++ b/lcm/lcm/pub/exceptions.py @@ -15,3 +15,11 @@ class NFLCMException(Exception): pass + + +class NFLCMExceptionNotFound(Exception): + pass + + +class NFLCMExceptionConflict(Exception): + pass diff --git a/lcm/lcm/pub/msapi/gvnfmdriver.py b/lcm/lcm/pub/msapi/gvnfmdriver.py index 4b2b304a..13e8dee5 100644 --- a/lcm/lcm/pub/msapi/gvnfmdriver.py +++ b/lcm/lcm/pub/msapi/gvnfmdriver.py @@ -1,22 +1,24 @@ # Copyright 2017 ZTE Corporation. # -# Licensed under the Apache License, Version 2.0 (the "License"); +# 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, +# 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 json import logging +import uuid from lcm.pub.exceptions import NFLCMException from lcm.pub.utils.restcall import req_by_msb +from lcm.pub.utils.timeutil import now_time from lcm.pub.database.models import ( NfInstModel, VmInstModel, NetworkInstModel, PortInstModel, StorageInstModel, VNFCInstModel @@ -26,30 +28,30 @@ logger = logging.getLogger(__name__) def get_packageinfo_by_vnfdid(vnfdid): - ret = req_by_msb("api/gvnfmdriver/v1/vnfpackages", "GET") + ret = req_by_msb('api/gvnfmdriver/v1/vnfpackages', 'GET') if ret[0] != 0: - logger.error("Status code is %s, detail is %s.", ret[2], ret[1]) - raise NFLCMException("Failed to query package_info of vnfdid(%s) from nslcm." % vnfdid) + logger.error('Status code is %s, detail is %s.', ret[2], ret[1]) + raise NFLCMException('Failed to query package_info of vnfdid(%s) from nslcm.' % vnfdid) return json.JSONDecoder().decode(ret[1]) def apply_grant_to_nfvo(data): - ret = req_by_msb("api/gvnfmdriver/v1/resource/grant", "PUT", data) + ret = req_by_msb('api/gvnfmdriver/v1/resource/grant', 'PUT', data) if ret[0] != 0: - logger.error("Status code is %s, detail is %s.", ret[2], ret[1]) - raise NFLCMException("Nf instancing apply grant exception") + logger.error('Status code is %s, detail is %s.', ret[2], ret[1]) + raise NFLCMException('Nf instancing apply grant exception') return json.JSONDecoder().decode(ret[1]) def notify_lcm_to_nfvo(data): - ret = req_by_msb("api/gvnfmdriver/v1/vnfs/lifecyclechangesnotification", "POST", data) + ret = req_by_msb('api/gvnfmdriver/v1/vnfs/lifecyclechangesnotification', 'POST', data) if ret[0] != 0: - logger.error("Status code is %s, detail is %s.", ret[2], ret[1]) - raise NFLCMException("Nf lcm notify exception") + logger.error('Status code is %s, detail is %s.', ret[2], ret[1]) + raise NFLCMException('Nf lcm notify exception') return ret[1] -def prepare_notification_data(nfinstid, jobid, changetype): +def prepare_notification_data(nfinstid, jobid, changetype, operation): logger.info('Send notify request to nfvo') affected_vnfcs = [] vnfcs = VNFCInstModel.objects.filter(instid=nfinstid) @@ -60,7 +62,7 @@ def prepare_notification_data(nfinstid, jobid, changetype): if vm: vm_resource = { 'vimId': vm[0].vimid, - 'resourceId': vm[0].resouceid, + 'resourceId': vm[0].resourceid, 'resourceProviderId': vm[0].vmname, # TODO: is resourceName mapped to resourceProviderId? 'vimLevelResourceType': 'vm' } @@ -75,7 +77,7 @@ def prepare_notification_data(nfinstid, jobid, changetype): for network in networks: network_resource = { 'vimConnectionId': network.vimid, - 'resourceId': network.resouceid, + 'resourceId': network.resourceid, 'resourceProviderId': network.name, # TODO: is resourceName mapped to resourceProviderId? 'vimLevelResourceType': 'network' } @@ -92,7 +94,7 @@ def prepare_notification_data(nfinstid, jobid, changetype): 'id': port.portid, # TODO: port.portid or port.nodeid? 'resourceHandle': { 'vimConnectionId': port.vimid, - 'resourceId': port.resouceid, + 'resourceId': port.resourceid, 'resourceProviderId': port.name, # TODO: is resourceName mapped to resourceProviderId? 'vimLevelResourceType': 'port' }, @@ -107,17 +109,22 @@ def prepare_notification_data(nfinstid, jobid, changetype): 'changeType': changetype, 'storageResource': { 'vimConnectionId': vs.vimid, - 'resourceId': vs.resouceid, + 'resourceId': vs.resourceid, 'resourceProviderId': vs.name, # TODO: is resourceName mapped to resourceProviderId? 'vimLevelResourceType': 'volume' } }) notification_content = { - "notificationType": 'VnfLcmOperationOccurrenceNotification', - "notificationStatus": 'RESULT', - "vnfInstanceId": nfinstid, - "operation": 'INSTANTIATE', - "vnfLcmOpOccId": jobid, + 'id': str(uuid.uuid4()), # shall be the same if sent multiple times due to multiple subscriptions. + 'notificationType': 'VnfLcmOperationOccurrenceNotification', + # set 'subscriptionId' after filtering for subscribers + 'timeStamp': now_time(), + 'notificationStatus': 'RESULT', + 'operationState': 'COMPLETED', + 'vnfInstanceId': nfinstid, + 'operation': operation, + 'isAutomaticInvocation': False, + 'vnfLcmOpOccId': jobid, 'affectedVnfcs': affected_vnfcs, 'affectedVirtualLinks': affected_vls, 'affectedVirtualStorages': affected_vss, @@ -125,7 +132,12 @@ def prepare_notification_data(nfinstid, jobid, changetype): 'id': None, # TODO 'resourceHandle': None, # TODO 'extLinkPorts': ext_link_ports - }] + }], + '_links': { + 'vnfInstance': {'href': '/api/vnflcm/v1/vnf_instances/%s' % nfinstid}, + # set 'subscription' link after filtering for subscribers + 'vnfLcmOpOcc': {'href': '/api/vnflcm/v1/vnf_lcm_op_occs/%s' % jobid} + } } nfInsts = NfInstModel.objects.filter(nfinstid=nfinstid) notification_content['vnfmInstId'] = nfInsts[0].vnfminstid diff --git a/lcm/lcm/pub/utils/notificationsutil.py b/lcm/lcm/pub/utils/notificationsutil.py new file mode 100644 index 00000000..5327dc16 --- /dev/null +++ b/lcm/lcm/pub/utils/notificationsutil.py @@ -0,0 +1,64 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# 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 json +import logging +import requests + +from rest_framework import status +from requests.auth import HTTPBasicAuth + +from lcm.nf import const +from lcm.pub.database.models import SubscriptionModel + +logger = logging.getLogger(__name__) + + +class NotificationsUtil(object): + def __init__(self): + pass + + def send_notification(self, notification): + logger.info("Send Notifications to the callbackUri") + filters = { + "vnfInstanceId": "vnf_instance_filter", + "operationState": "operation_states", + "operation": "operation_types" + } + subscriptions_filter = {v + "__contains": notification[k] for k, v in filters.iteritems()} + + subscriptions = SubscriptionModel.objects.filter(**subscriptions_filter) + if not subscriptions.exists(): + logger.info("No subscriptions created for the filters %s" % notification) + return + logger.info("Start sending notifications") + for subscription in subscriptions: + # set subscription id + notification["subscriptionId"] = subscription.subscription_id + notification['_links']['subscription'] = {'href': '/api/vnflcm/v1/subscriptions/%s' % subscription.subscription_id} + callbackUri = subscription.callback_uri + auth_info = json.loads(subscription.auth_info) + if auth_info["authType"] == const.OAUTH2_CLIENT_CREDENTIALS: + pass + self.post_notification(callbackUri, auth_info, notification) + + def post_notification(self, callbackUri, auth_info, notification): + params = auth_info.get("paramsBasic", {}) + username = params.get("userName") + password = params.get("password") + logger.info("Sending notification to %s", callbackUri) + resp = requests.post(callbackUri, data=notification, auth=HTTPBasicAuth(username, password)) + if resp.status_code != status.HTTP_204_NO_CONTENT: + raise Exception("Unable to send the notification to %s, due to %s" % (callbackUri, resp.text)) + return diff --git a/lcm/lcm/pub/utils/tests.py b/lcm/lcm/pub/utils/tests.py index 87516f07..9bed37c0 100644 --- a/lcm/lcm/pub/utils/tests.py +++ b/lcm/lcm/pub/utils/tests.py @@ -16,14 +16,16 @@ import unittest import mock import enumutil import fileutil +import json import urllib2 import syscomm import timeutil import values import platform -from lcm.pub.database.models import JobStatusModel, JobModel +from lcm.pub.database.models import JobStatusModel, JobModel, SubscriptionModel from lcm.pub.utils.jobutil import JobUtil +from lcm.pub.utils.notificationsutil import NotificationsUtil class MockReq(): @@ -225,3 +227,80 @@ class UtilsTest(unittest.TestCase): self.assertEqual("def", values.ignore_case_get(data, 'abc')) self.assertEqual("klm", values.ignore_case_get(data, 'hig')) self.assertEqual("bbb", values.ignore_case_get(data, 'aaa', 'bbb')) + + +class TestNotificationUtils(unittest.TestCase): + def setUp(self): + subscription_id = 1 + auth_params = { + "authType": ["BASIC"], + "paramsBasic": { + "username": "username", + "password": "password" + } + } + notification_types = ["VnfLcmOperationOccurrenceNotification"] + operation_types = ["INSTANTIATE"] + operation_states = ["STARTING"] + vnf_instance_filter = { + 'vnfdIds': ['99442b18-a5c7-11e8-998c-bf1755941f13', '9fe4080c-b1a3-11e8-bb96-645106374fd3'], + 'vnfInstanceIds': ['99442b18-a5c7-11e8-998c-bf1755941f12'], + 'vnfInstanceNames': ['demo'], + 'vnfProductsFromProviders': { + 'vnfProvider': u'string', + 'vnfProducts': { + 'vnfProductName': 'string', + 'versions': { + 'vnfSoftwareVersion': u'string', + 'vnfdVersions': 'string' + } + } + } + } + links = { + "self": "demo" + } + SubscriptionModel(subscription_id=subscription_id, callback_uri="http://demo", + auth_info=json.dumps(auth_params), + notification_types=json.dumps(notification_types), + operation_types=json.dumps(operation_types), + operation_states=json.dumps(operation_states), + vnf_instance_filter=json.dumps(vnf_instance_filter), + links=json.dumps(links)).save() + + def tearDown(self): + SubscriptionModel.objects.all().delete() + + @mock.patch('requests.post') + def test_send_notification(self, mock_post): + dummy_notification = { + "vnfInstanceId": "99442b18-a5c7-11e8-998c-bf1755941f13", + "operationState": "STARTING", + "operation": "INSTANTIATE", + "_links": {} + } + mock_post.return_value.status_code = 204 + NotificationsUtil().send_notification(dummy_notification) + mock_post.assert_called_once() + + @mock.patch('requests.post') + def test_send_notification_with_empty_filters(self, mock_post): + dummy_notification = { + "vnfInstanceId": "9fe4080c-b1a3-11e8-bb96-645106374fd3", + "operationState": "", + "operation": "", + "_links": {} + } + mock_post.return_value.status_code = 204 + NotificationsUtil().send_notification(dummy_notification) + mock_post.assert_called_once() + + @mock.patch('requests.post') + def test_send_notification_unmatched_filters(self, mock_post): + dummy_notification = { + "vnfInstanceId": "9fe4080c-b1a3-11e8-bb96-xxxxx", + "operationState": "DUMMY", + "operation": "DUMMY", + } + NotificationsUtil().send_notification(dummy_notification) + mock_post.assert_not_called() diff --git a/lcm/lcm/pub/vimapi/adaptor.py b/lcm/lcm/pub/vimapi/adaptor.py index 9ca3e8de..3427aaf4 100644 --- a/lcm/lcm/pub/vimapi/adaptor.py +++ b/lcm/lcm/pub/vimapi/adaptor.py @@ -14,12 +14,14 @@ import logging import time -import ast +import json from lcm.pub.utils.values import ignore_case_get, set_opt_val from lcm.pub.msapi.aai import get_flavor_info from . import api from .exceptions import VimException +from lcm.pub.exceptions import NFLCMException +from lcm.nf.const import ACTION_TYPE, HEAL_ACTION_TYPE logger = logging.getLogger(__name__) @@ -65,8 +67,65 @@ def get_res_id(res_cache, res_type, key): return res_cache[res_type][key] -def create_vim_res(data, do_notify): - vim_cache, res_cache = {}, {} +def action_vm(action_type, server, vimId, tenantId): + param = {} + if action_type == ACTION_TYPE.START: + param = { + "os-start": None, + } + elif action_type == ACTION_TYPE.STOP: + param = { + "os-stop": None, + } + elif action_type == ACTION_TYPE.REBOOT: + param = { + "reboot": {} + } + if server["status"] == "ACTIVE": + param["reboot"]["type"] = "SOFT" + else: + param["reboot"]["type"] = "HARD" + res_id = server["id"] + api.action_vm(vimId, tenantId, res_id, param) + + +# TODO Have to check if the resources should be started and stopped in some order. +def operate_vim_res(data, changeStateTo, stopType, gracefulStopTimeout, do_notify_op): + for res in ignore_case_get(data, "vm"): + try: + if changeStateTo == "STARTED": + action_vm(ACTION_TYPE.START, res, res["vim_id"], res["tenant_id"]) + do_notify_op("ACTIVE", res["id"]) + elif changeStateTo == "STOPPED": + if stopType == "GRACEFUL": + if gracefulStopTimeout > 60: + gracefulStopTimeout = 60 + time.sleep(gracefulStopTimeout) + action_vm(ACTION_TYPE.STOP, res, res["vim_id"], res["tenant_id"]) + do_notify_op("INACTIVE", res["id"]) + except VimException as e: + logger.error("Failed to Operate %s(%s)", RES_VM, res["res_id"]) + logger.error("%s:%s", e.http_code, e.message) + raise NFLCMException("Failed to Operate %s(%s)", RES_VM, res["res_id"]) + + +def heal_vim_res(vdus, vnfd_info, do_notify, data, vim_cache, res_cache): + try: + vimid = data["vimid"] + tenant = data["tenant"] + actionType = data["action"] + if actionType == HEAL_ACTION_TYPE.START: + create_vm(vim_cache, res_cache, vnfd_info, vdus[0], do_notify, RES_VM) + elif actionType == HEAL_ACTION_TYPE.RESTART: + vm_info = api.get_vm(vimid, tenant, vdus[0].resourceid) + action_vm(ACTION_TYPE.REBOOT, vm_info, vimid, tenant) + except VimException as e: + logger.error("Failed to Heal %s(%s)", RES_VM, vdus[0]["vdu_id"]) + logger.error("%s:%s", e.http_code, e.message) + raise NFLCMException("Failed to Heal %s(%s)", RES_VM, vdus[0]["vdu_id"]) + + +def create_vim_res(data, do_notify, vim_cache={}, res_cache={}): for vol in ignore_case_get(data, "volume_storages"): create_volume(vim_cache, res_cache, vol, do_notify, RES_VOLUME) for network in ignore_case_get(data, "vls"): @@ -200,8 +259,17 @@ def create_port(vim_cache, res_cache, data, port, do_notify, res_type): l3_address_data = one_protocol_data["address_data"]["l3_address_data"] # l3 is not 13 fixed_ip_address = ignore_case_get(l3_address_data, "fixed_ip_address") ip_address.extend(fixed_ip_address) + for one_virtual_network_interface in port["properties"]["virtual_network_interface_requirements"]: + interfaceTypeString = one_virtual_network_interface["network_interface_requirements"]["interfaceType"] + interfaceType = json.loads(interfaceTypeString)["configuration-value"] + vnic_type = ignore_case_get(port["properties"], "vnic_type") + if vnic_type == "": + if interfaceType == "SR-IOV": + set_opt_val(param, "vnicType", "direct") + else: + set_opt_val(param, "vnicType", vnic_type) + set_opt_val(param, "ip", ",".join(ip_address)) - set_opt_val(param, "vnicType", ignore_case_get(port["properties"], "vnic_type")) set_opt_val(param, "securityGroups", "") # TODO vim_id, tenant_name = location_info["vimid"], location_info["tenant"] tenant_id = get_tenant_id(vim_cache, vim_id, tenant_name) @@ -222,29 +290,16 @@ def parse_unit(val, base_unit): return int(num) * unit_rate_map[unit.upper()] / unit_rate_map[base_unit.upper()] -def search_flavor_aai(vim_id, memory_page_size, memory_page_unit): +def search_flavor_aai(vim_id, flavor_name): aai_flavors = get_flavor_info(vim_id) if not aai_flavors: return None - logger.debug("aai_flavors:%s" % aai_flavors) aai_flavor = aai_flavors["flavor"] for one_aai_flavor in aai_flavor: - if one_aai_flavor["flavor-name"].find("onap.") == -1: - continue - hpa_capabilities = one_aai_flavor["hpa-capabilities"]["hpa-capability"] - logger.debug("hpa_capabilities=%s", hpa_capabilities) - for one_hpa_capa in hpa_capabilities: - logger.debug("one_hpa_capa=%s", one_hpa_capa) - hpa_feature_attr = one_hpa_capa["hpa-feature-attributes"] - for one_hpa_attr in hpa_feature_attr: - hpa_key = one_hpa_attr["hpa-attribute-key"] - hpa_attr_value = ast.literal_eval(one_hpa_attr["hpa-attribute-value"]) - mem_size = ignore_case_get(hpa_attr_value, 'value') - mem_unit = ignore_case_get(hpa_attr_value, 'unit') - value = mem_size + " " + mem_unit - hpa_mem_size = parse_unit(value, memory_page_unit) - if hpa_key == "memoryPageSize" and hpa_mem_size == memory_page_size: - return one_aai_flavor + if one_aai_flavor["flavor-name"].find(flavor_name) == -1: + return one_aai_flavor + + return None def create_flavor(vim_cache, res_cache, data, flavor, do_notify, res_type): @@ -261,24 +316,19 @@ def create_flavor(vim_cache, res_cache, data, flavor, do_notify, res_type): "isPublic": True } - # just do memory huge page - flavor_extra_specs = "" - vdu_memory_requirements = ignore_case_get(virtual_memory, "vdu_memory_requirements") - if "memoryPageSize" in vdu_memory_requirements: - memory_page_size = int(vdu_memory_requirements["memoryPageSize"].replace('MB', '').strip()) - flavor_extra_specs = {"hw": memory_page_size, } # TODO - logger.debug("flavor_extra_specs:%s" % flavor_extra_specs) - - # FIXME: search aai flavor - aai_flavor = search_flavor_aai(vim_id, memory_page_size, "MB") + # Using flavor name returned by OOF to search falvor + vdu_id = ignore_case_get(flavor, "vdu_id") + for one_vdu in location_info["vduInfo"]: + if one_vdu["vduName"] == vdu_id: + break + aai_flavor = search_flavor_aai(vim_id, one_vdu["flavorName"]) - # add aai flavor + # Add aai flavor if aai_flavor: ret = aai_flavor do_notify(res_type, ret) set_res_cache(res_cache, res_type, flavor["vdu_id"], ret["flavor-id"]) else: - extra_specs = [] disk_type = ignore_case_get(virtual_storage, "type_of_storage") disk_size = int(ignore_case_get(virtual_storage, "size_of_storage").replace('GB', '').strip()) if disk_type == "root": @@ -288,13 +338,10 @@ def create_flavor(vim_cache, res_cache, data, flavor, do_notify, res_type): elif disk_type == "swap": param["swap"] = disk_size - for es in flavor_extra_specs: - extra_specs.append({"keyName": es, "value": flavor_extra_specs[es]}) - - set_opt_val(param, "extraSpecs", extra_specs) tenant_id = get_tenant_id(vim_cache, vim_id, tenant_name) logger.debug("param:%s" % param) ret = api.create_flavor(vim_id, tenant_id, param) + logger.debug("hhb ret:%s" % ret) do_notify(res_type, ret) set_res_cache(res_cache, res_type, flavor["vdu_id"], ret["id"]) diff --git a/lcm/lcm/pub/vimapi/api.py b/lcm/lcm/pub/vimapi/api.py index 0090d66f..2ceb82d0 100644 --- a/lcm/lcm/pub/vimapi/api.py +++ b/lcm/lcm/pub/vimapi/api.py @@ -145,6 +145,11 @@ def get_vm(vim_id, tenant_id, vm_id): def list_vm(vim_id, tenant_id): return call(vim_id, tenant_id, "servers", "GET") + +# Used to start/stop/restart a vm +def action_vm(vim_id, tenant_id, vm_id, data): + return call(vim_id, tenant_id, "servers/%s/action" % vm_id, "POST", data) + ###################################################################### diff --git a/lcm/lcm/samples/tests.py b/lcm/lcm/samples/tests.py index 47e23533..a1d13be4 100644 --- a/lcm/lcm/samples/tests.py +++ b/lcm/lcm/samples/tests.py @@ -87,7 +87,18 @@ inst_res_data = { "vimid": "f1e33529-4a88-4155-9d7a-893cf2c80527", "tenant": "vnfm", "availability_zone": "zone1", - "host": "host1" + "host": "host1", + "vnfId": "", + "vnfName": "", + "cloudOwner": "", + "cloudRegionId": "", + "vduInfo": [ + { + "vduName": "VDU_vbng_0", + "flavorName": "flavor_1", + "directive": "", + }, + ] }, "descrption": "the virtual machine of vNat", "boot_order": [ @@ -154,7 +165,7 @@ inst_res_data = { "external_cps": [ { "key_name": "sriov_plane", - "cp_id": "SRIOV_Port" + "cp_id": "sriov-support" } ], "forward_cps": [ @@ -215,15 +226,15 @@ inst_res_data = { }, } ], - "vnic_type": "normal", + "vnic_type": "direct", "role": "root", "virtual_network_interface_requirements": [ { - "requirement": { - "SRIOV": "true" + "network_interface_requirements": { + "interfaceType": '{"schema-version": "0", "schema-location":"", "platform-id": "generic", "mandatory": false, "configuration-value": "SR-IOV"}' }, "support_mandatory": False, - "name": "sriov", + "name": "sriov-support", "description": "sriov" } ], @@ -304,28 +315,17 @@ c4_data_create_port = { "nodeId": "", "id": "456" } +c5_data_create_flavor = { + "vimId": "f1e33529-4a88-4155-9d7a-893cf2c80527", + "nodeId": "", + "id": "6456", +} c5_data_get_flavor = { "flavor": [ { "flavor-id": "111111", - "flavor-name": "onap.large", - "hpa-capabilities": - { - "hpa-capability": - [ - { - "hpa-capability-id": "1243", - "hpa-feature-attributes": - [ - { - "hpa-attribute-key": "memoryPageSize", - "hpa-attribute-value": '{"value": "2", "unit": "MB"}' - } - ] - } - ] - } + "flavor-name": "flavor_1", } ] } @@ -373,6 +373,7 @@ class SampleViewTest(unittest.TestCase): r3_data_create_subnet = [0, json.JSONEncoder().encode(c3_data_create_subnet), '200'] r4_data_create_port = [0, json.JSONEncoder().encode(c4_data_create_port), '200'] r5_data_get_flavor = [0, json.JSONEncoder().encode(c5_data_get_flavor), '200'] + r5_data_create_flavor = [0, json.JSONEncoder().encode(c5_data_create_flavor), '200'] r6_data_list_image = [0, json.JSONEncoder().encode(c6_data_list_image), '200'] r6_data_create_vm = [0, json.JSONEncoder().encode(c6_data_create_vm), '200'] r6_data_get_vm = [0, json.JSONEncoder().encode(c6_data_get_vm), '200'] @@ -382,7 +383,7 @@ class SampleViewTest(unittest.TestCase): r2_data_create_network, r3_data_create_subnet, r4_data_create_port, - r5_data_get_flavor, + r5_data_get_flavor, r5_data_create_flavor, r6_data_list_image, r6_data_create_vm, r6_data_get_vm] resp = self.client.post(inst_res_url, data=json.dumps(inst_res_data), content_type='application/json') self.failUnlessEqual(status.HTTP_204_NO_CONTENT, resp.status_code)