diff --git a/tacker/api/vnflcm/v1/controller.py b/tacker/api/vnflcm/v1/controller.py index 410fc7a07..915a610ed 100644 --- a/tacker/api/vnflcm/v1/controller.py +++ b/tacker/api/vnflcm/v1/controller.py @@ -455,7 +455,7 @@ class VnfLcmController(wsgi.Controller): 'vnfInstance': { 'href': self._get_vnf_instance_href(vnf_instance)}}} - # call sendNotification + # call send_notification self.rpc_api.send_notification(context, notification) result = self._view_builder.create(vnf_instance) diff --git a/tacker/conductor/conductor_server.py b/tacker/conductor/conductor_server.py index 37d798bf4..70edf464a 100644 --- a/tacker/conductor/conductor_server.py +++ b/tacker/conductor/conductor_server.py @@ -25,7 +25,6 @@ import time import traceback import yaml - from glance_store import exceptions as store_exceptions from oslo_config import cfg from oslo_log import log as logging @@ -51,6 +50,7 @@ from tacker.common import utils import tacker.conf from tacker import context as t_context from tacker.db.common_services import common_services_db +from tacker.db.db_sqlalchemy import models from tacker.db.nfvo import nfvo_db from tacker.db.vnfm import vnfm_db from tacker.extensions import nfvo @@ -66,6 +66,7 @@ from tacker import service as tacker_service from tacker import version from tacker.vnflcm import utils as vnflcm_utils from tacker.vnflcm import vnflcm_driver +from tacker.vnfm import nfvo_client from tacker.vnfm import plugin CONF = tacker.conf.CONF @@ -230,6 +231,65 @@ def revert_update_lcm(function): return decorated_function +@utils.expects_func_args('vnf_instance', 'vnf_lcm_op_occ_id') +def grant_error_common(function): + """Decorator to revert upload_vnf_package on failure.""" + + @functools.wraps(function) + def decorated_function(self, context, *args, **kwargs): + try: + return function(self, context, *args, **kwargs) + except Exception: + with excutils.save_and_reraise_exception(): + wrapped_func = safe_utils.get_wrapped_function(function) + keyed_args = inspect.getcallargs(wrapped_func, self, context, + *args, **kwargs) + context = keyed_args['context'] + vnf_instance = keyed_args['vnf_instance'] + vnf_lcm_op_occ_id = keyed_args['vnf_lcm_op_occ_id'] + try: + vnf_lcm_op_occs = objects.VnfLcmOpOcc.get_by_id( + context, vnf_lcm_op_occ_id) + timestamp = datetime.datetime.utcnow() + vnf_lcm_op_occs.operation_state = 'ROLLED_BACK' + vnf_lcm_op_occs.state_entered_time = timestamp + vnf_lcm_op_occs.save() + except Exception as e: + LOG.warning("Failed to update vnf_lcm_op_occ for vnf " + "instance %(id)s. Error: %(error)s", + {"id": vnf_instance.id, "error": e}) + + try: + notification = {} + notification['notificationType'] = \ + 'VnfLcmOperationOccurrenceNotification' + notification['vnfInstanceId'] = vnf_instance.id + notification['notificationStatus'] = 'RESULT' + notification['operation'] = vnf_lcm_op_occs.operation + notification['operationState'] = 'ROLLED_BACK' + notification['isAutomaticInvocation'] = \ + vnf_lcm_op_occs.is_automatic_invocation + notification['vnfLcmOpOccId'] = vnf_lcm_op_occ_id + insta_url = CONF.vnf_lcm.endpoint_url + \ + "/vnflcm/v1/vnf_instances/" + \ + vnf_instance.id + vnflcm_url = CONF.vnf_lcm.endpoint_url + \ + "/vnflcm/v1/vnf_lcm_op_occs/" + \ + vnf_lcm_op_occ_id + notification['_links'] = {} + notification['_links']['vnfInstance'] = {} + notification['_links']['vnfInstance']['href'] = insta_url + notification['_links']['vnfLcmOpOcc'] = {} + notification['_links']['vnfLcmOpOcc']['href'] = vnflcm_url + self.send_notification(context, notification) + except Exception as e: + LOG.warning("Failed notification for vnf " + "instance %(id)s. Error: %(error)s", + {"id": vnf_instance.id, "error": e}) + + return decorated_function + + class Conductor(manager.Manager): def __init__(self, host, conf=None): if conf: @@ -661,9 +721,10 @@ class Conductor(manager.Manager): vim_connection_info = objects.VimConnectionInfo.\ obj_from_primitive(vim_info, context) - vnflcm_utils._build_instantiated_vnf_info(vnfd_dict, - instantiate_vnf_req, vnf_instance, - vim_id=vim_connection_info.vim_id) + if not vnf_instance.instantiated_vnf_info.instance_id: + vnflcm_utils._build_instantiated_vnf_info( + vnfd_dict, instantiate_vnf_req, vnf_instance, + vim_id=vim_connection_info.vim_id) if vnf_instance.instantiated_vnf_info.instance_id: self.vnf_manager.invoke(vim_connection_info.vim_type, @@ -761,6 +822,522 @@ class Conductor(manager.Manager): {'zip': csar_path, 'folder': csar_zip_temp_path, 'uuid': vnf_pack.id}) + def _grant(self, context, grant_request): + LOG.info( + "grant start grant_request[%s]" % + grant_request.to_request_body()) + + response = nfvo_client.GrantRequest().grants( + json=grant_request.to_request_body()) + + res_body = response.json() + res_dict = utils.convert_camelcase_to_snakecase(res_body) + LOG.info("grant end res_body[%s]" % res_dict) + grant_obj = objects.Grant.obj_from_primitive( + res_dict, context=context) + if len(grant_request.add_resources) != len(grant_obj.add_resources): + msg = "grant add resource error" + raise exceptions.ValidationError(detail=msg) + if len( + grant_request.remove_resources) != len( + grant_obj.remove_resources): + msg = "grant remove resource error" + raise exceptions.ValidationError(detail=msg) + + self._check_res_add_remove_rsc(context, grant_request, grant_obj) + + return grant_obj + + def _check_res_add_remove_rsc(self, context, grant_request, grant_obj): + for add_resource in grant_request.add_resources: + match_flg = False + for rsc in grant_obj.add_resources: + if add_resource.id == rsc.resource_definition_id: + match_flg = True + break + if not match_flg: + msg = "grant add resource error" + raise exceptions.ValidationError(detail=msg) + + for remove_resource in grant_request.remove_resources: + match_flg = False + for rsc in grant_obj.remove_resources: + if remove_resource.id == rsc.resource_definition_id: + match_flg = True + break + if not match_flg: + msg = "grant remove resource error" + raise exceptions.ValidationError(detail=msg) + + @grant_error_common + def _instantiate_grant(self, + context, + vnf_instance, + vnf_dict, + instantiate_vnf_request, + vnf_lcm_op_occ_id): + vnfd_dict = vnflcm_utils._get_vnfd_dict(context, + vnf_dict['vnfd']['id'], + instantiate_vnf_request.flavour_id) + inst_level = instantiate_vnf_request.instantiation_level_id + vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id=instantiate_vnf_request.flavour_id, + instantiation_level_id=inst_level, + vnf_instance_id=vnf_instance.id) + vnf_instance.instantiated_vnf_info.reinitialize() + vnflcm_utils._build_instantiated_vnf_info(vnfd_dict, + instantiate_vnf_request, vnf_instance, '') + if not self._get_grant_execute(): + return + + add_resources = [] + vnf_inf = vnf_instance.instantiated_vnf_info + for vnfc_resource in vnf_inf.vnfc_resource_info: + resource = objects.ResourceDefinition() + resource.id = vnfc_resource.id + resource.type = constants.TYPE_COMPUTE + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = vnfc_resource.vdu_id + add_resources.append(resource) + + for vl_resource in vnf_inf.vnf_virtual_link_resource_info: + resource = objects.ResourceDefinition() + resource.id = vl_resource.id + resource.type = constants.TYPE_VL + resource.resource_template_id = \ + vl_resource.vnf_virtual_link_desc_id + add_resources.append(resource) + for cp_resource in vl_resource.vnf_link_ports: + for vnfc_resource in vnf_inf.vnfc_resource_info: + for vnfc_cp_resource in vnfc_resource.vnfc_cp_info: + if cp_resource.cp_instance_id == vnfc_cp_resource.id: + resource = objects.ResourceDefinition() + resource.id = cp_resource.id + resource.type = constants.TYPE_LINKPORT + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = \ + vnfc_cp_resource.cpd_id + add_resources.append(resource) + + for storage_resource in vnf_inf.virtual_storage_resource_info: + for vnfc_resource in vnf_inf.vnfc_resource_info: + if storage_resource.id in vnfc_resource.storage_resource_ids: + resource = objects.ResourceDefinition() + resource.id = storage_resource.id + resource.type = constants.TYPE_STORAGE + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = \ + storage_resource.virtual_storage_desc_id + add_resources.append(resource) + + p_c_list = [] + placement_obj_list = [] + topo_temp = vnfd_dict.get('topology_template', {}) + for policy in topo_temp.get('policies', []): + for policy_name, policy_dict in policy.items(): + key_type = 'tosca.policies.nfv.AntiAffinityRule' + if policy_dict['type'] == key_type: + placement_constraint = objects.PlacementConstraint() + placement_constraint.affinity_or_anti_affinity = \ + 'ANTI_AFFINITY' + placement_constraint.scope = 'ZONE' + placement_constraint.resource = [] + placement_constraint.fallback_best_effort = True + for target in policy_dict.get('targets', []): + if target in topo_temp.get('groups', []): + for member in topo_temp['groups']['members']: + for vnfc_rsc in vnf_inf.vnfc_resource_info: + if member == vnfc_rsc.vdu_id: + resource = \ + objects.ConstraintResourceRef() + resource.id_type = 'GRANT' + resource.resource_id = vnfc_rsc.id + p_rsc = \ + placement_constraint.resource + p_rsc.append(resource) + break + else: + for vnfc_rsc in vnf_inf.vnfc_resource_info: + if target == vnfc_rsc.vdu_id: + resource = \ + objects.ConstraintResourceRef() + resource.id_type = 'GRANT' + resource.resource_id = vnfc_rsc.id + p_rsc = placement_constraint.resource + p_rsc.append(resource) + break + p_c_list.append(placement_constraint) + placement_obj = models.PlacementConstraint() + placement_obj.id = uuidutils.generate_uuid() + placement_obj.vnf_instance_id = vnf_instance.id + placement_obj.affinity_or_anti_affinity = \ + placement_constraint.affinity_or_anti_affinity + placement_obj.scope = placement_constraint.scope + placement_obj.server_group_name = policy_name + p_c_dict = placement_constraint.to_dict() + res_dict = p_c_dict.get('resource', {}) + res_json = json.dumps(res_dict) + placement_obj.resource = res_json + placement_obj.created_at = timeutils.utcnow() + placement_obj.deleted_at = datetime.datetime.min + placement_obj_list.append(placement_obj) + + g_request = self._make_grant_request(context, + vnf_instance, + vnf_lcm_op_occ_id, + 'INSTANTIATE', + False, + add_resources=add_resources, + placement_constraints=p_c_list) + + vnf_dict['placement_obj_list'] = placement_obj_list + vnf_dict['grant'] = self._grant(context, g_request) + + def _get_placement(self, context, vnf_instance): + return self.vnfm_plugin.get_placement_constraint(context, + vnf_instance.id) + + @grant_error_common + def _scale_grant( + self, + context, + vnf_dict, + vnf_instance, + scale_vnf_request, + vnf_lcm_op_occ_id): + # Check if vnf is in instantiated state. + vnf_instance = objects.VnfInstance.get_by_id(context, + vnf_instance.id) + if vnf_instance.instantiation_state == \ + fields.VnfInstanceState.NOT_INSTANTIATED: + LOG.error("Scale action cannot be performed on vnf %(id)s " + "which is in %(state)s state.", + {"id": vnf_instance.id, + "state": vnf_instance.instantiation_state}) + raise Exception("Scale action cannot be performed on vnf") + + vim_info = vnflcm_utils._get_vim( + context, vnf_instance.vim_connection_info) + vim_connection_info = objects.VimConnectionInfo.obj_from_primitive( + vim_info, context) + if scale_vnf_request.type == 'SCALE_IN': + vnf_dict['action'] = 'in' + reverse = scale_vnf_request.additional_params.get('is_reverse') + region_name = vim_connection_info.access_info.get('region_name') + scale_id_list, scale_name_list, grp_id, res_num = \ + self.vnf_manager.invoke(vim_connection_info.vim_type, + 'get_scale_in_ids', + plugin=self, + context=context, + vnf_dict=vnf_dict, + is_reverse=reverse, + auth_attr=vim_connection_info.access_info, + region_name=region_name, + number_of_steps=scale_vnf_request.number_of_steps) + vnf_dict['res_num'] = res_num + else: + scale_id_list = [] + if not self._get_grant_execute(): + return None, [] + + placement_obj_list = self.vnfm_plugin.get_placement_constraint( + context, vnf_instance.id) + self.vnf_manager.invoke( + vim_connection_info.vim_type, + 'get_grant_resource', + plugin=self, + vnf_instance=vnf_instance, + vnf_info=vnf_dict, + scale_vnf_request=scale_vnf_request, + placement_obj_list=placement_obj_list, + vim_connection_info=vim_connection_info, + del_list=scale_id_list + ) + vnf_dict['placement_obj_list'] = placement_obj_list + + grant_request = self._make_grant_request( + context, + vnf_instance, + vnf_lcm_op_occ_id, + 'SCALE', + False, + add_resources=vnf_dict['addResources'], + remove_resources=vnf_dict['removeResources'], + placement_constraints=vnf_dict['placement_constraint_list']) + + vnf_dict['grant'] = self._grant(context, grant_request) + + @grant_error_common + def _heal_grant(self, + context, + vnf_instance, + vnf_dict, + heal_vnf_request, + vnf_lcm_op_occ_id): + vnf_inf = vnf_instance.instantiated_vnf_info + if not self._get_grant_execute(): + return + + placement_obj_list = self._get_placement(context, vnf_instance) + vim_info = vnflcm_utils._get_vim(context, + vnf_instance.vim_connection_info) + vim_connection_info = \ + objects.VimConnectionInfo.obj_from_primitive(vim_info, context) + + cinder_list = self.vnf_manager.invoke(vim_connection_info.vim_type, + 'get_cinder_list', + vnf_info=vnf_dict) + + vnf_instantiated_info_after = copy.deepcopy(vnf_inf) + del_cre_vdu_list = [] + add_resources = [] + rm_resources = [] + affinity_list = [] + for vnfc_resource in vnf_instantiated_info_after.vnfc_resource_info: + vnfc_key = vnfc_resource.compute_resource.resource_id + if not heal_vnf_request.vnfc_instance_id or \ + vnfc_key in heal_vnf_request.vnfc_instance_id: + if vnfc_resource.vdu_id not in del_cre_vdu_list: + del_cre_vdu_list.append(vnfc_resource.vdu_id) + for vnfc_resource in vnf_instantiated_info_after.vnfc_resource_info: + if vnfc_resource.vdu_id in del_cre_vdu_list: + resource = objects.ResourceDefinition() + resource.id = vnfc_resource.id + resource.type = constants.TYPE_COMPUTE + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = vnfc_resource.vdu_id + vim_id = vnfc_resource.compute_resource.vim_connection_id + rsc_id = vnfc_resource.compute_resource.resource_id + vnfc_rh = objects.ResourceHandle( + vim_connection_id=vim_id, + resource_id=rsc_id) + resource.resource = vnfc_rh + rm_resources.append(resource) + add_uuid = uuidutils.generate_uuid() + resource = objects.ResourceDefinition() + resource.id = add_uuid + resource.type = constants.TYPE_COMPUTE + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = vnfc_resource.vdu_id + add_resources.append(resource) + + key_id = vnfc_resource.compute_resource.resource_id + for placement_obj in placement_obj_list: + resource_dict = jsonutils.loads(placement_obj.resource) + set_flg = False + for resource in resource_dict: + if resource.get('resource_id') == key_id: + resource['id_type'] = 'GRANT' + resource['resource_id'] = add_uuid + g_name = placement_obj.server_group_name + affinity_list.append(g_name) + set_flg = True + res_json = jsonutils.dump_as_bytes(resource_dict) + placement_obj.resource = res_json + break + if set_flg: + break + vnfc_resource.id = add_uuid + vnfc_resource.compute_resource = objects.ResourceHandle() + + st_info = vnf_instantiated_info_after.virtual_storage_resource_info + for storage_resource in st_info: + if storage_resource.virtual_storage_desc_id in cinder_list: + for vnfc_resource in vnf_inf.vnfc_resource_info: + id_list = vnfc_resource.storage_resource_ids + if storage_resource.id in id_list: + resource = objects.ResourceDefinition() + resource.id = storage_resource.id + resource.type = constants.TYPE_STORAGE + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = \ + storage_resource.virtual_storage_desc_id + st_rh = objects.ResourceHandle() + st_rh.vim_connection_id = \ + storage_resource.storage_resource.vim_connection_id + st_rh.resource_id = \ + storage_resource.storage_resource.resource_id + resource.resource = st_rh + rm_resources.append(resource) + + add_uuid = uuidutils.generate_uuid() + resource = objects.ResourceDefinition() + resource = objects.ResourceDefinition() + resource.id = add_uuid + resource.type = constants.TYPE_STORAGE + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = \ + storage_resource.virtual_storage_desc_id + add_resources.append(resource) + storage_resource.id = add_uuid + storage_resource.storage_resource = \ + objects.ResourceHandle() + + p_c_list = [] + for placement_obj in placement_obj_list: + p_constraint = objects.PlacementConstraint() + p_constraint.affinity_or_anti_affinity = \ + placement_obj.affinity_or_anti_affinity + p_constraint.scope = placement_obj.scope + resource_dict = jsonutils.loads(placement_obj.resource) + p_constraint.resource = [] + for rsc in resource_dict: + rsc_obj = objects.ConstraintResourceRef() + rsc_obj.id_type = rsc.get('id_type') + rsc_obj.resource_id = rsc.get('resource_id') + p_constraint.resource.append(rsc_obj) + p_constraint.fallback_best_effort = True + p_c_list.append(p_constraint) + + g_request = self._make_grant_request(context, + vnf_instance, + vnf_lcm_op_occ_id, + 'HEAL', + False, + add_resources=add_resources, + remove_resources=rm_resources, + placement_constraints=p_c_list) + + vnf_dict['placement_obj_list'] = placement_obj_list + vnf_dict['grant'] = self._grant(context, g_request) + vnf_dict['vnf_instantiated_info_after'] = vnf_instantiated_info_after + + @grant_error_common + def _terminate_grant(self, context, vnf_instance, vnf_lcm_op_occ_id): + vnf_inf = vnf_instance.instantiated_vnf_info + if not self._get_grant_execute(): + return + + rm_resources = [] + for vnfc_resource in vnf_inf.vnfc_resource_info: + resource = objects.ResourceDefinition() + resource.id = vnfc_resource.id + resource.type = constants.TYPE_COMPUTE + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = vnfc_resource.vdu_id + vim_id = vnfc_resource.compute_resource.vim_connection_id + rsc_id = vnfc_resource.compute_resource.resource_id + vnfc_rh = objects.ResourceHandle( + vim_connection_id=vim_id, + resource_id=rsc_id) + resource.resource = vnfc_rh + rm_resources.append(resource) + + for vl_resource in vnf_inf.vnf_virtual_link_resource_info: + resource = objects.ResourceDefinition() + resource.id = vl_resource.id + resource.type = constants.TYPE_VL + resource.resource_template_id = \ + vl_resource.vnf_virtual_link_desc_id + vim_id = vl_resource.network_resource.vim_connection_id + rsc_id = vl_resource.network_resource.resource_id + vl_rh = objects.ResourceHandle( + vim_connection_id=vim_id, + resource_id=rsc_id) + resource.resource = vl_rh + rm_resources.append(resource) + for cp_resource in vl_resource.vnf_link_ports: + for vnfc_resource in vnf_inf.vnfc_resource_info: + for vnfc_cp_resource in vnfc_resource.vnfc_cp_info: + if cp_resource.cp_instance_id == vnfc_cp_resource.id: + resource = objects.ResourceDefinition() + resource.id = cp_resource.id + resource.type = constants.TYPE_LINKPORT + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = \ + vnfc_cp_resource.cpd_id + vim_id = \ + cp_resource.resource_handle.vim_connection_id + rsc_id = cp_resource.resource_handle.resource_id + cp_rh = objects.ResourceHandle( + vim_connection_id=vim_id, + resource_id=rsc_id) + resource.resource = cp_rh + rm_resources.append(resource) + + for storage_resource in vnf_inf.virtual_storage_resource_info: + for vnfc_resource in vnf_inf.vnfc_resource_info: + if storage_resource.id in vnfc_resource.storage_resource_ids: + resource = objects.ResourceDefinition() + resource.id = storage_resource.id + resource.type = constants.TYPE_STORAGE + resource.vdu_id = vnfc_resource.vdu_id + resource.resource_template_id = \ + storage_resource.virtual_storage_desc_id + vim_id = \ + storage_resource.storage_resource.vim_connection_id + rsc_id = storage_resource.storage_resource.resource_id + st_rh = objects.ResourceHandle( + vim_connection_id=vim_id, + resource_id=rsc_id) + resource.resource = st_rh + rm_resources.append(resource) + + grant_request = self._make_grant_request(context, + vnf_instance, + vnf_lcm_op_occ_id, + 'TERMINATE', + False, + remove_resources=rm_resources) + self._grant(context, grant_request) + + def _get_grant_execute(self): + try: + nfvo_client.GrantRequest().validate() + except nfvo_client.UndefinedExternalSettingException: + return False + + return True + + def _make_grant_request(self, context, vnf_instance, + vnf_lcm_op_occ_id, operation, + is_automatic_invocation, + add_resources=[], + remove_resources=[], + placement_constraints=[]): + grant_request = objects.GrantRequest() + grant_request.vnf_instance_id = vnf_instance.id + grant_request.vnf_lcm_op_occ_id = vnf_lcm_op_occ_id + grant_request.vnfd_id = vnf_instance.vnfd_id + grant_request.flavour_id = \ + vnf_instance.instantiated_vnf_info.flavour_id + grant_request.operation = operation + grant_request.is_automatic_invocation = is_automatic_invocation + vnflcm_url = CONF.vnf_lcm.endpoint_url + \ + "/vnflcm/v1/vnf_lcm_op_occs/" + vnf_lcm_op_occ_id + insta_url = CONF.vnf_lcm.endpoint_url + \ + "/vnflcm/v1/vnf_instances/" + vnf_instance.id + link_vnflcm = objects.Link(href=vnflcm_url) + link_insta = objects.Link(href=insta_url) + link = objects.Links(vnf_lcm_op_occ=link_vnflcm, + vnf_instance=link_insta) + grant_request._links = link + + if add_resources: + grant_request.add_resources = add_resources + if remove_resources: + grant_request.remove_resources = remove_resources + if placement_constraints: + grant_request.placement_constraints = placement_constraints + + return grant_request + + def _create_placement(self, context, vnf_dict): + p_list = vnf_dict.get('placement_obj_list', []) + if len(p_list) == 0: + return + self.vnfm_plugin.create_placement_constraint(context, + p_list) + + def _update_placement(self, context, vnf_dict, vnf_instance): + self.vnfm_plugin.update_placement_constraint(context, + vnf_dict, + vnf_instance) + + def _delete_placement(self, context, vnf_instance_id): + self.vnfm_plugin.delete_placement_constraint(context, + vnf_instance_id) + @log.log def _get_vnf_notify(self, context, id): try: @@ -790,6 +1367,7 @@ class Conductor(manager.Manager): is_automatic_invocation = \ kwargs.get('is_automatic_invocation', False) error = kwargs.get('error', None) + # Used for timing control when a failure occurs if old_vnf_instance: vnf_instance_id = old_vnf_instance.id @@ -918,7 +1496,7 @@ class Conductor(manager.Manager): self.__set_auth_subscription(line) for num in range(CONF.vnf_lcm.retry_num): - LOG.info("send notify[%s]" % json.dumps(notification)) + LOG.warn("send notify[%s]" % json.dumps(notification)) auth_client = auth.auth_manager.get_auth_client( notification['subscriptionId']) response = auth_client.post( @@ -965,6 +1543,12 @@ class Conductor(manager.Manager): instantiate_vnf, vnf_lcm_op_occs_id): + self._instantiate_grant(context, + vnf_instance, + vnf_dict, + instantiate_vnf, + vnf_lcm_op_occs_id) + try: # Update vnf_lcm_op_occs table and send notification "PROCESSING" self._send_lcm_op_occ_notification( @@ -993,6 +1577,8 @@ class Conductor(manager.Manager): instantiation_state=fields.VnfInstanceState. INSTANTIATED, task_state=None) + self._create_placement(context, vnf_dict) + # Update vnf_lcm_op_occs table and send notification "COMPLETED" self._send_lcm_op_occ_notification( context=context, @@ -1024,6 +1610,11 @@ class Conductor(manager.Manager): @coordination.synchronized('{vnf_instance[id]}') def terminate(self, context, vnf_lcm_op_occs_id, vnf_instance, terminate_vnf_req, vnf_dict): + + self._terminate_grant(context, + vnf_instance, + vnf_lcm_op_occs_id) + try: old_vnf_instance = copy.deepcopy(vnf_instance) @@ -1042,6 +1633,9 @@ class Conductor(manager.Manager): self.vnflcm_driver.terminate_vnf(context, vnf_instance, terminate_vnf_req) + + self._delete_placement(context, vnf_instance.id) + self._change_vnf_status(context, vnf_instance.id, _PENDING_STATUS, 'INACTIVE') @@ -1086,6 +1680,12 @@ class Conductor(manager.Manager): heal_vnf_request, vnf_lcm_op_occs_id): + self._heal_grant(context, + vnf_instance, + vnf_dict, + heal_vnf_request, + vnf_lcm_op_occs_id) + try: old_vnf_instance = copy.deepcopy(vnf_instance) @@ -1117,6 +1717,8 @@ class Conductor(manager.Manager): self.vnflcm_driver._vnf_instance_update(context, vnf_instance, task_state=None) + self._update_placement(context, vnf_dict, vnf_instance) + # update vnf_lcm_op_occs and send notification "COMPLETED" self._send_lcm_op_occ_notification( context=context, @@ -1150,16 +1752,15 @@ class Conductor(manager.Manager): @coordination.synchronized('{vnf_instance[id]}') def scale(self, context, vnf_info, vnf_instance, scale_vnf_request): - # Check if vnf is in instantiated state. - vnf_instance = objects.VnfInstance.get_by_id(context, - vnf_instance.id) - if vnf_instance.instantiation_state == \ - fields.VnfInstanceState.NOT_INSTANTIATED: - LOG.error("Scale action cannot be performed on vnf %(id)s " - "which is in %(state)s state.", - {"id": vnf_instance.id, - "state": vnf_instance.instantiation_state}) - return + vnf_lcm_op_occ = vnf_info['vnf_lcm_op_occ'] + vnf_lcm_op_occ_id = vnf_lcm_op_occ.id + + self._scale_grant( + context, + vnf_info, + vnf_instance, + scale_vnf_request, + vnf_lcm_op_occ_id) self.vnflcm_driver.scale_vnf( context, vnf_info, vnf_instance, scale_vnf_request) diff --git a/tacker/db/db_sqlalchemy/models.py b/tacker/db/db_sqlalchemy/models.py index f6ca7b7cc..351c15a7e 100644 --- a/tacker/db/db_sqlalchemy/models.py +++ b/tacker/db/db_sqlalchemy/models.py @@ -301,7 +301,7 @@ class VnfLcmOpOccs(model_base.BASE, models.SoftDeleteMixin, """VNF LCM OP OCCS Fields""" __tablename__ = 'vnf_lcm_op_occs' - id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + id = sa.Column(sa.String(16), primary_key=True) vnf_instance_id = sa.Column(sa.String(36), sa.ForeignKey('vnf_instances.id'), nullable=False) @@ -316,3 +316,17 @@ class VnfLcmOpOccs(model_base.BASE, models.SoftDeleteMixin, resource_changes = sa.Column(sa.JSON(), nullable=True) changed_info = sa.Column(sa.JSON(), nullable=True) error_point = sa.Column(sa.Integer, nullable=False) + + +class PlacementConstraint(model_base.BASE, models.SoftDeleteMixin, + models.TimestampMixin, models_v1.HasId): + """Represents a Vnf Placement Constraint.""" + + __tablename__ = 'placement_constraint' + vnf_instance_id = sa.Column(sa.String(36), + sa.ForeignKey('vnf_instances.id'), + nullable=False) + affinity_or_anti_affinity = sa.Column(sa.String(255), nullable=False) + scope = sa.Column(sa.String(255), nullable=False) + server_group_name = sa.Column(sa.String(255), nullable=False) + resource = sa.Column(sa.JSON(), nullable=True) diff --git a/tacker/db/migration/alembic_migrations/versions/2c5211036579_add_placement_table.py b/tacker/db/migration/alembic_migrations/versions/2c5211036579_add_placement_table.py new file mode 100644 index 000000000..63a663cc4 --- /dev/null +++ b/tacker/db/migration/alembic_migrations/versions/2c5211036579_add_placement_table.py @@ -0,0 +1,52 @@ +# Copyright 2020 OpenStack Foundation +# +# 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. +# + +"""add placement table + +Revision ID: 2c5211036579 +Revises: ee98bbc0789d +Create Date: 2020-09-11 20:47:46.345771 + +""" +# flake8: noqa: E402 + +# revision identifiers, used by Alembic. +revision = '2c5211036579' +down_revision = 'ee98bbc0789d' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy import Boolean + +from tacker.db import types + + +def upgrade(active_plugins=None, options=None): + op.create_table( + 'placement_constraint', + sa.Column('id', types.Uuid(length=36), nullable=False), + sa.Column('vnf_instance_id', types.Uuid(length=36), nullable=False), + sa.Column('affinity_or_anti_affinity', + sa.String(length=255), nullable=False), + sa.Column('scope', sa.String(length=255), nullable=False), + sa.Column('server_group_name', sa.String(length=255), nullable=False), + sa.Column('resource', sa.JSON(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.Column('deleted_at', sa.DateTime(), nullable=True), + sa.Column('deleted', Boolean, default=False), + sa.PrimaryKeyConstraint('id'), + mysql_engine='InnoDB' + ) diff --git a/tacker/db/migration/alembic_migrations/versions/HEAD b/tacker/db/migration/alembic_migrations/versions/HEAD index 4c0d2f349..7f99de5cd 100644 --- a/tacker/db/migration/alembic_migrations/versions/HEAD +++ b/tacker/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -ee98bbc0789d \ No newline at end of file +2c5211036579 \ No newline at end of file diff --git a/tacker/db/vnfm/vnfm_db.py b/tacker/db/vnfm/vnfm_db.py index efd2ba943..0ab8933df 100644 --- a/tacker/db/vnfm/vnfm_db.py +++ b/tacker/db/vnfm/vnfm_db.py @@ -18,6 +18,7 @@ from datetime import datetime from oslo_db.exception import DBDuplicateEntry from oslo_log import log as logging +from oslo_serialization import jsonutils from oslo_utils import timeutils from oslo_utils import uuidutils @@ -29,9 +30,11 @@ from sqlalchemy import schema from tacker._i18n import _ from tacker.api.v1 import attributes from tacker.common import exceptions +import tacker.conf from tacker import context as t_context from tacker.db.common_services import common_services_db_plugin from tacker.db import db_base +from tacker.db.db_sqlalchemy import models from tacker.db import model_base from tacker.db import models_v1 from tacker.db.nfvo import ns_db @@ -40,6 +43,8 @@ from tacker.extensions import vnfm from tacker import manager from tacker.plugins.common import constants +CONF = tacker.conf.CONF + LOG = logging.getLogger(__name__) _ACTIVE_UPDATE = (constants.ACTIVE, constants.PENDING_UPDATE, constants.PENDING_HEAL) @@ -812,3 +817,51 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin): constants.ERROR] return self._mark_vnf_status( vnf_id, exclude_status, constants.DEAD) + + def create_placement_constraint(self, context, placement_obj_list): + context.session.add_all(placement_obj_list) + + def get_placement_constraint(self, context, vnf_instance_id): + placement_constraint = ( + self._model_query(context, models.PlacementConstraint).filter( + models.PlacementConstraint.vnf_instance_id == vnf_instance_id). + filter(models.PlacementConstraint.deleted == 0).all()) + return placement_constraint + + def update_placement_constraint_heal(self, context, + vnf_info, + vnf_instance): + if not vnf_info.get('grant'): + return + placement_obj_list = vnf_info['placement_obj_list'] + inst_info = vnf_instance.instantiated_vnf_info + for vnfc in inst_info.vnfc_resource_info: + for placement_obj in placement_obj_list: + rsc_dict = jsonutils.loads(placement_obj.resource) + for rsc in rsc_dict: + if vnfc.id == rsc.get('resource_id') and\ + rsc.get('id_type') == 'GRANT': + rsc['id_type'] = 'RES_MGMT' + rsc['resource_id'] = vnfc.\ + compute_resource.resource_id + rsc['vim_connection_id'] = vnfc.\ + compute_resource.vim_connection_id + placement_obj.resource = jsonutils.dumps(rsc_dict) + self.update_placement_constraint(context, placement_obj) + + def delete_placement_constraint(self, context, vnf_instance_id): + (self._model_query(context, models.PlacementConstraint). + filter( + models.PlacementConstraint.vnf_instance_id == vnf_instance_id). + filter(models.PlacementConstraint.deleted == 0). + update({'deleted': 0, 'deleted_at': timeutils.utcnow()})) + + def update_placement_constraint(self, context, placement_obj): + (self._model_query( + context, + models.PlacementConstraint).filter( + models.PlacementConstraint.id == placement_obj.id). + filter(models.PlacementConstraint.deleted == 0). + update({ + 'resource': placement_obj.resource, + 'updated_at': timeutils.utcnow()})) diff --git a/tacker/objects/__init__.py b/tacker/objects/__init__.py index 8f990abb6..9ce0634f2 100644 --- a/tacker/objects/__init__.py +++ b/tacker/objects/__init__.py @@ -42,3 +42,7 @@ def register_all(): __import__('tacker.objects.vnf_artifact') __import__('tacker.objects.vnf_lcm_subscriptions') __import__('tacker.objects.scale_vnf_request') + __import__('tacker.objects.grant') + __import__('tacker.objects.grant_request') + __import__('tacker.objects.vnfd') + __import__('tacker.objects.vnfd_attribute') diff --git a/tacker/objects/grant.py b/tacker/objects/grant.py new file mode 100644 index 000000000..c62b3ef59 --- /dev/null +++ b/tacker/objects/grant.py @@ -0,0 +1,287 @@ +# 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 tacker import objects +from tacker.objects import base +from tacker.objects import fields + + +@base.TackerObjectRegistry.register +class Grant(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'id': fields.StringField(nullable=False), + 'vnf_instance_id': fields.StringField(nullable=False), + 'vnf_lcm_op_occ_id': fields.StringField(nullable=False), + 'vim_connections': fields.ListOfObjectsField( + 'VimConnectionInfo', nullable=True, default=[]), + 'zones': fields.ListOfObjectsField( + 'ZoneInfo', nullable=True, default=[]), + 'add_resources': fields.ListOfObjectsField( + 'GrantInfo', nullable=True, default=[]), + 'remove_resources': fields.ListOfObjectsField( + 'GrantInfo', nullable=True, default=[]), + 'vim_assets': fields.ObjectField( + 'VimAssets', nullable=True) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_grant = super( + Grant, cls).obj_from_primitive(primitive, context) + else: + if 'vim_connections' in primitive.keys(): + obj_data = [objects.VimConnectionInfo._from_dict( + vim_conn) for vim_conn in primitive.get( + 'vim_connections', [])] + primitive.update({'vim_connections': obj_data}) + + if 'zones' in primitive.keys(): + obj_data = [ZoneInfo._from_dict( + zone) for zone in primitive.get( + 'zones', [])] + primitive.update({'zones': obj_data}) + + if 'add_resources' in primitive.keys(): + obj_data = [GrantInfo._from_dict( + add_rsc) for add_rsc in primitive.get( + 'add_resources', [])] + primitive.update({'add_resources': obj_data}) + if 'remove_resources' in primitive.keys(): + obj_data = [GrantInfo._from_dict( + remove_rsc) for remove_rsc in primitive.get( + 'remove_resources', [])] + primitive.update({'remove_resources': obj_data}) + if 'vim_assets' in primitive.keys(): + obj_data = VimAssets.obj_from_primitive( + primitive.get('vim_assets'), context) + primitive.update({'vim_assets': obj_data}) + + obj_grant = Grant._from_dict(primitive) + + return obj_grant + + @classmethod + def _from_dict(cls, data_dict): + id = data_dict.get('id') + vnf_instance_id = data_dict.get('vnf_instance_id') + vnf_lcm_op_occ_id = data_dict.get('vnf_lcm_op_occ_id') + vim_connections = data_dict.get('vim_connections', []) + zones = data_dict.get('zones', []) + add_resources = data_dict.get('add_resources', []) + remove_resources = data_dict.get('remove_resources', []) + vim_assets = data_dict.get('vim_assets') + + obj = cls( + id=id, + vnf_instance_id=vnf_instance_id, + vnf_lcm_op_occ_id=vnf_lcm_op_occ_id, + vim_connections=vim_connections, + zones=zones, + add_resources=add_resources, + remove_resources=remove_resources, + vim_assets=vim_assets) + return obj + + +@base.TackerObjectRegistry.register +class ZoneInfo(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'id': fields.StringField(nullable=False), + 'zone_id': fields.StringField(nullable=False), + 'vim_connection_id': fields.StringField(nullable=True) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_zone_info = super( + ZoneInfo, cls).obj_from_primitive(primitive, context) + else: + obj_zone_info = ZoneInfo._from_dict(primitive) + + return obj_zone_info + + @classmethod + def _from_dict(cls, data_dict): + id = data_dict.get('id') + zone_id = data_dict.get('zone_id') + vim_connection_id = data_dict.get('vim_connection_id') + + obj = cls( + id=id, + zone_id=zone_id, + vim_connection_id=vim_connection_id) + return obj + + +@base.TackerObjectRegistry.register +class GrantInfo(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'resource_definition_id': fields.StringField(nullable=False), + 'vim_connection_id': fields.StringField(nullable=True), + 'zone_id': fields.StringField(nullable=True) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_grant_info = super( + GrantInfo, cls).obj_from_primitive(primitive, context) + else: + obj_grant_info = GrantInfo._from_dict(primitive) + + return obj_grant_info + + @classmethod + def _from_dict(cls, data_dict): + resource_definition_id = data_dict.get('resource_definition_id') + vim_connection_id = data_dict.get('vim_connection_id') + zone_id = data_dict.get('zone_id') + + obj = cls( + resource_definition_id=resource_definition_id, + vim_connection_id=vim_connection_id, + zone_id=zone_id) + return obj + + +@base.TackerObjectRegistry.register +class VimAssets(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'compute_resource_flavours': fields.ListOfObjectsField( + 'VimComputeResourceFlavour', nullable=True, default=[]), + 'software_images': fields.ListOfObjectsField( + 'VimSoftwareImage', nullable=True, default=[]) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_vim_assets = super( + VimAssets, cls).obj_from_primitive(primitive, context) + else: + if 'compute_resource_flavours' in primitive.keys(): + obj_data = [VimComputeResourceFlavour._from_dict( + flavour) for flavour in primitive.get( + 'compute_resource_flavours', [])] + primitive.update({'compute_resource_flavours': obj_data}) + + if 'software_images' in primitive.keys(): + obj_data = [VimSoftwareImage._from_dict( + img) for img in primitive.get( + 'software_images', [])] + primitive.update({'software_images': obj_data}) + obj_vim_assets = VimAssets._from_dict(primitive) + + return obj_vim_assets + + @classmethod + def _from_dict(cls, data_dict): + compute_resource_flavours = data_dict.get( + 'compute_resource_flavours', []) + software_images = data_dict.get('software_images', []) + + obj = cls( + compute_resource_flavours=compute_resource_flavours, + software_images=software_images) + return obj + + +@base.TackerObjectRegistry.register +class VimComputeResourceFlavour(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'vim_connection_id': fields.StringField(nullable=True), + 'vnfd_virtual_compute_desc_id': fields.StringField(nullable=False), + 'vim_flavour_id': fields.StringField(nullable=False) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_flavour = super( + VimComputeResourceFlavour, + cls).obj_from_primitive( + primitive, + context) + else: + obj_flavour = VimComputeResourceFlavour._from_dict(primitive) + + return obj_flavour + + @classmethod + def _from_dict(cls, data_dict): + vim_connection_id = data_dict.get('vim_connection_id') + vnfd_virtual_compute_desc_id = data_dict.get( + 'vnfd_virtual_compute_desc_id') + vim_flavour_id = data_dict.get('vim_flavour_id') + + obj = cls( + vim_connection_id=vim_connection_id, + vnfd_virtual_compute_desc_id=vnfd_virtual_compute_desc_id, + vim_flavour_id=vim_flavour_id) + return obj + + +@base.TackerObjectRegistry.register +class VimSoftwareImage(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'vim_connection_id': fields.StringField(nullable=True), + 'vnfd_software_image_id': fields.StringField(nullable=False), + 'vim_software_image_id': fields.StringField(nullable=False) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_img = super( + VimSoftwareImage, cls).obj_from_primitive(primitive, context) + else: + obj_img = VimSoftwareImage._from_dict(primitive) + + return obj_img + + @classmethod + def _from_dict(cls, data_dict): + vim_connection_id = data_dict.get('vim_connection_id') + vnfd_software_image_id = data_dict.get('vnfd_software_image_id') + vim_software_image_id = data_dict.get('vim_software_image_id') + + obj = cls( + vim_connection_id=vim_connection_id, + vnfd_software_image_id=vnfd_software_image_id, + vim_software_image_id=vim_software_image_id) + return obj diff --git a/tacker/objects/grant_request.py b/tacker/objects/grant_request.py new file mode 100644 index 000000000..088e7c13a --- /dev/null +++ b/tacker/objects/grant_request.py @@ -0,0 +1,407 @@ +# 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 oslo_serialization import jsonutils + +from tacker.common import utils +from tacker.objects import base +from tacker.objects import fields + + +@base.TackerObjectRegistry.register +class GrantRequest(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'vnf_instance_id': fields.StringField(nullable=False), + 'vnf_lcm_op_occ_id': fields.StringField(nullable=False), + 'vnfd_id': fields.StringField(nullable=False), + 'flavour_id': fields.StringField(nullable=True), + 'operation': fields.StringField(nullable=False), + 'is_automatic_invocation': fields.BooleanField(nullable=False, + default=False), + 'add_resources': fields.ListOfObjectsField( + 'ResourceDefinition', nullable=True, default=[]), + 'remove_resources': fields.ListOfObjectsField( + 'ResourceDefinition', nullable=True, default=[]), + 'placement_constraints': fields.ListOfObjectsField( + 'PlacementConstraint', nullable=True, default=[]), + '_links': fields.ObjectField( + 'Links', nullable=False) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_grant_req = super( + GrantRequest, cls).obj_from_primitive(primitive, context) + else: + if 'add_resources' in primitive.keys(): + obj_data = [ResourceDefinition._from_dict( + add_rsc) for add_rsc in primitive.get( + 'add_resources', [])] + primitive.update({'add_resources': obj_data}) + if 'remove_resources' in primitive.keys(): + obj_data = [ResourceDefinition._from_dict( + remove_rsc) for remove_rsc in primitive.get( + 'remove_resources', [])] + primitive.update({'add_resources': obj_data}) + if 'placement_constraints' in primitive.keys(): + obj_data = [PlacementConstraint._from_dict( + place) for place in primitive.get( + 'placement_constraints', [])] + primitive.update({'add_resources': obj_data}) + obj_grant_req = GrantRequest._from_dict(primitive) + + return obj_grant_req + + @classmethod + def _from_dict(cls, data_dict): + vnf_instance_id = data_dict.get('vnf_instance_id') + vnf_lcm_op_occ_id = data_dict.get('vnf_lcm_op_occ_id') + vnfd_id = data_dict.get('vnfd_id') + flavour_id = data_dict.get('flavour_id') + operation = data_dict.get('operation') + is_automatic_invocation = data_dict.get('is_automatic_invocation') + add_resources = data_dict.get('add_resources', []) + remove_resources = data_dict.get('remove_resources', []) + placement_constraints = data_dict.get('placement_constraints', []) + links = data_dict.get('_links') + + obj = cls( + vnf_instance_id=vnf_instance_id, + vnf_lcm_op_occ_id=vnf_lcm_op_occ_id, + vnfd_id=vnfd_id, + flavour_id=flavour_id, + operation=operation, + is_automatic_invocation=is_automatic_invocation, + add_resources=add_resources, + remove_resources=remove_resources, + placement_constraints=placement_constraints, + _links=links) + return obj + + def to_dict(self): + data = {'vnf_instance_id': self.vnf_instance_id, + 'vnf_lcm_op_occ_id': self.vnf_lcm_op_occ_id, + 'vnfd_id': self.vnfd_id, + 'flavour_id': self.flavour_id, + 'operation': self.operation, + 'is_automatic_invocation': self.is_automatic_invocation, + '_links': self._links.to_dict()} + if self.add_resources: + add_resources_list = [] + for add_resource in self.add_resources: + add_resources_list.append(add_resource.to_dict()) + + data.update({'add_resources': add_resources_list}) + if self.remove_resources: + remove_resources_list = [] + for remove_resource in self.remove_resources: + remove_resources_list.append(remove_resource.to_dict()) + + data.update({'remove_resources': remove_resources_list}) + if self.placement_constraints: + placement_constraints_list = [] + for placement_constraint in self.placement_constraints: + placement_constraints_list.append( + placement_constraint.to_dict()) + + data.update({'placement_constraints': placement_constraints_list}) + return data + + def to_request_body(self): + req_dict = self.to_dict() + req_dict = utils.convert_snakecase_to_camelcase(req_dict) + return jsonutils.dumps(req_dict).replace('Links', '_links') + + +@base.TackerObjectRegistry.register +class ResourceDefinition(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'id': fields.StringField(nullable=False), + 'type': fields.StringField(nullable=False), + 'vdu_id': fields.StringField(nullable=True, default=None), + 'resource_template_id': fields.StringField(nullable=False), + 'resource': fields.ObjectField( + 'ResourceHandle', nullable=True, default=None) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_grant_req = super( + ResourceDefinition, cls).\ + obj_from_primitive(primitive, context) + else: + if 'resource' in primitive.keys(): + obj_data = ResourceHandle._from_dict( + primitive.get('resource')) + primitive.update({'resource': obj_data}) + obj_grant_req = ResourceDefinition._from_dict(primitive) + + return obj_grant_req + + @classmethod + def _from_dict(cls, data_dict): + id = data_dict.get('id') + type = data_dict.get('type') + vdu_id = data_dict.get('vdu_id') + resource_template_id = data_dict.get('resource_template_id') + resource = data_dict.get('resource') + + obj = cls( + id=id, + type=type, + vdu_id=vdu_id, + resource_template_id=resource_template_id, + resource=resource) + return obj + + def to_dict(self): + data = {'id': self.id, + 'type': self.type, + 'resource_template_id': self.resource_template_id} + if self.vdu_id: + data.update({'vdu_id': self.vdu_id}) + if self.resource: + data.update({'resource': self.resource.to_dict()}) + return data + + +@base.TackerObjectRegistry.register +class PlacementConstraint(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'affinity_or_anti_affinity': fields.StringField(nullable=False), + 'scope': fields.StringField(nullable=False), + 'resource': fields.ListOfObjectsField( + 'ConstraintResourceRef', nullable=False, default=[]), + 'fallback_best_effort': fields.BooleanField(nullable=False), + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_placement_constraint = super( + PlacementConstraint, cls).obj_from_primitive( + primitive, context) + else: + if 'resource' in primitive.keys(): + obj_data = [ConstraintResourceRef._from_dict( + add_rsc) for add_rsc in primitive.get( + 'resource', [])] + primitive.update({'resource': obj_data}) + obj_placement_constraint = PlacementConstraint._from_dict( + primitive) + + return obj_placement_constraint + + @classmethod + def _from_dict(cls, data_dict): + affinity_or_anti_affinity = data_dict.get('affinity_or_anti_affinity') + scope = data_dict.get('scope') + resource = data_dict.get('resource') + fallback_best_effort = data_dict.get('fallback_best_effort') + + obj = cls( + affinity_or_anti_affinity=affinity_or_anti_affinity, + scope=scope, + resource=resource, + fallback_best_effort=fallback_best_effort) + return obj + + def to_dict(self): + data = {'affinity_or_anti_affinity': self.affinity_or_anti_affinity, + 'scope': self.scope, + 'fallback_best_effort': self.fallback_best_effort} + if self.resource: + resource_list = [] + for rsc in self.resource: + resource_list.append(rsc.to_dict()) + + data.update({'resource': resource_list}) + return data + + +@base.TackerObjectRegistry.register +class ConstraintResourceRef(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'id_type': fields.StringField(nullable=False), + 'resource_id': fields.StringField(nullable=False), + 'vim_connection_id': fields.StringField(nullable=True, default=None), + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_placement_constraint = super( + ConstraintResourceRef, cls).obj_from_primitive( + primitive, context) + else: + obj_placement_constraint = ConstraintResourceRef._from_dict( + primitive) + + return obj_placement_constraint + + @classmethod + def _from_dict(cls, data_dict): + id_type = data_dict.get('id_type') + resource_id = data_dict.get('resource_id') + vim_connection_id = data_dict.get('vim_connection_id') + + obj = cls( + id_type=id_type, + resource_id=resource_id, + vim_connection_id=vim_connection_id) + return obj + + def to_dict(self): + data = {'id_type': self.id_type, + 'resource_id': self.resource_id} + if self.vim_connection_id: + data.update({'vim_connection_id': self.vim_connection_id}) + return data + + +@base.TackerObjectRegistry.register +class Links(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'vnf_lcm_op_occ': fields.ObjectField( + 'Link', nullable=False), + 'vnf_instance': fields.ObjectField( + 'Link', nullable=False) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_links = super( + Links, cls).obj_from_primitive(primitive, context) + else: + if 'vnf_lcm_op_occ' in primitive.keys(): + obj_data = Link._from_dict( + primitive.get('vnf_lcm_op_occ')) + primitive.update({'vnf_lcm_op_occ': obj_data}) + if 'vnf_instance' in primitive.keys(): + obj_data = Link._from_dict( + primitive.get('vnf_instance')) + primitive.update({'vnf_instance': obj_data}) + obj_links = Links._from_dict(primitive) + + return obj_links + + @classmethod + def _from_dict(cls, data_dict): + vnf_lcm_op_occ = data_dict.get('vnf_lcm_op_occ') + vnf_instance = data_dict.get('vnf_instance') + + obj = cls( + vnf_lcm_op_occ=vnf_lcm_op_occ, + vnf_instance=vnf_instance) + return obj + + def to_dict(self): + return {'vnf_lcm_op_occ': self.vnf_lcm_op_occ.to_dict(), + 'vnf_instance': self.vnf_instance.to_dict()} + + +@base.TackerObjectRegistry.register +class Link(base.TackerObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'href': fields.StringField(nullable=False) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_link = super( + Link, cls).obj_from_primitive(primitive, context) + else: + obj_link = Link._from_dict(primitive) + + return obj_link + + @classmethod + def _from_dict(cls, data_dict): + href = data_dict.get('href') + + obj = cls( + href=href) + return obj + + def to_dict(self): + return {'href': self.href} + + +@base.TackerObjectRegistry.register +class ResourceHandle(base.TackerObject, + base.TackerPersistentObject): + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'vim_connection_id': fields.StringField(nullable=True, + default=None), + 'resource_id': fields.StringField(nullable=False, default=""), + 'vim_level_resource_type': fields.StringField(nullable=True, + default=None) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + resource_handle = super( + ResourceHandle, cls).obj_from_primitive( + primitive, context) + else: + resource_handle = ResourceHandle._from_dict(primitive) + + return resource_handle + + @classmethod + def _from_dict(cls, data_dict): + vim_connection_id = data_dict.get('vim_connection_id') + resource_id = data_dict.get('resource_id', "") + vim_level_resource_type = data_dict.get('vim_level_resource_type') + + obj = cls(vim_connection_id=vim_connection_id, + resource_id=resource_id, + vim_level_resource_type=vim_level_resource_type) + + return obj + + def to_dict(self): + return {'vim_connection_id': self.vim_connection_id, + 'resource_id': self.resource_id, + 'vim_level_resource_type': self.vim_level_resource_type} diff --git a/tacker/plugins/common/constants.py b/tacker/plugins/common/constants.py index f559d8522..3814a2d61 100644 --- a/tacker/plugins/common/constants.py +++ b/tacker/plugins/common/constants.py @@ -32,14 +32,14 @@ COMMON_PREFIXES = { ACTIVE = "ACTIVE" ACK = "ACK" INACTIVE = "INACTIVE" - +PENDING_INSTANTIATE = "PENDING_INSTANTIATE" PENDING_CREATE = "PENDING_CREATE" PENDING_UPDATE = "PENDING_UPDATE" PENDING_DELETE = "PENDING_DELETE" PENDING_SCALE_IN = "PENDING_SCALE_IN" PENDING_SCALE_OUT = "PENDING_SCALE_OUT" PENDING_HEAL = "PENDING_HEAL" - +PENDING_TERMINATE = "PENDING_TERMINATE" DEAD = "DEAD" ERROR = "ERROR" NACK = "NACK" @@ -55,6 +55,7 @@ POLICY_SCALING_ACTIONS = (ACTION_SCALE_OUT, ACTION_SCALE_IN) = ('out', 'in') POLICY_ACTIONS = {POLICY_SCALING: POLICY_SCALING_ACTIONS} POLICY_ALARMING = 'tosca.policies.tacker.Alarming' +POLICY_EVENT_ALARMING = 'tosca.policies.tacker.EventAlarming' VALID_POLICY_TYPES = [POLICY_SCALING, POLICY_ALARMING] POLICY_RESERVATION = 'tosca.policies.tacker.Reservation' RESERVATION_POLICY_ACTIONS = ['start_actions', @@ -68,6 +69,8 @@ RES_TYPE_VNF = "vnf" RES_TYPE_VIM = "vim" RES_EVT_CREATE = "CREATE" +RES_EVT_INSTANTIATE = "INSTANTIATE" +RES_EVT_TERMINATE = "TERMINATE" RES_EVT_DELETE = "DELETE" RES_EVT_UPDATE = "UPDATE" RES_EVT_MONITOR = "MONITOR" @@ -90,3 +93,8 @@ VNF_STATUS_TO_EVT_TYPES = {PENDING_CREATE: RES_EVT_CREATE, RES_EVT_CREATED_FLD = "created_at" RES_EVT_DELETED_FLD = "deleted_at" RES_EVT_UPDATED_FLD = "updated_at" + +TYPE_COMPUTE = "COMPUTE" +TYPE_LINKPORT = "LINKPORT" +TYPE_STORAGE = "STORAGE" +TYPE_VL = "VL" diff --git a/tacker/tests/contrib/post_test_hook.sh b/tacker/tests/contrib/post_test_hook.sh old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/common/Files/images/cirros-0.4.0-x86_64-disk.img b/tacker/tests/etc/samples/etsi/nfv/common/Files/images/cirros-0.4.0-x86_64-disk.img old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/vnflcm1/Definitions/helloworld3_df_simple.yaml b/tacker/tests/etc/samples/etsi/nfv/vnflcm1/Definitions/helloworld3_df_simple.yaml old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/vnflcm1/Definitions/helloworld3_top.vnfd.yaml b/tacker/tests/etc/samples/etsi/nfv/vnflcm1/Definitions/helloworld3_top.vnfd.yaml old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/vnflcm1/Definitions/helloworld3_types.yaml b/tacker/tests/etc/samples/etsi/nfv/vnflcm1/Definitions/helloworld3_types.yaml old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/vnflcm1/TOSCA-Metadata/TOSCA.meta b/tacker/tests/etc/samples/etsi/nfv/vnflcm1/TOSCA-Metadata/TOSCA.meta old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/vnflcm2/Definitions/helloworld3_df_simple.yaml b/tacker/tests/etc/samples/etsi/nfv/vnflcm2/Definitions/helloworld3_df_simple.yaml old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/vnflcm2/Definitions/helloworld3_top.vnfd.yaml b/tacker/tests/etc/samples/etsi/nfv/vnflcm2/Definitions/helloworld3_top.vnfd.yaml old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/vnflcm2/Definitions/helloworld3_types.yaml b/tacker/tests/etc/samples/etsi/nfv/vnflcm2/Definitions/helloworld3_types.yaml old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/vnflcm2/TOSCA-Metadata/TOSCA.meta b/tacker/tests/etc/samples/etsi/nfv/vnflcm2/TOSCA-Metadata/TOSCA.meta old mode 100755 new mode 100644 diff --git a/tacker/tests/etc/samples/etsi/nfv/vnflcm4/TOSCA-Metadata/TOSCA.meta b/tacker/tests/etc/samples/etsi/nfv/vnflcm4/TOSCA-Metadata/TOSCA.meta old mode 100755 new mode 100644 diff --git a/tacker/tests/unit/conductor/test_conductor_server.py b/tacker/tests/unit/conductor/test_conductor_server.py index 035e45508..fd36eb11a 100644 --- a/tacker/tests/unit/conductor/test_conductor_server.py +++ b/tacker/tests/unit/conductor/test_conductor_server.py @@ -28,18 +28,23 @@ import yaml from glance_store import exceptions as store_exceptions from oslo_config import cfg +from oslo_serialization import jsonutils from six.moves import urllib + from tacker import auth from tacker.common import coordination from tacker.common import csar_utils +from tacker.common import driver_manager from tacker.common import exceptions from tacker.conductor import conductor_server from tacker import context from tacker import context as t_context +from tacker.db.db_sqlalchemy import models from tacker.glance_store import store as glance_store from tacker import objects from tacker.objects import fields from tacker.plugins.common import constants +from tacker.tests import constants as test_constants from tacker.tests.unit import base as unit_base from tacker.tests.unit.conductor import fakes from tacker.tests.unit.db.base import SqlTestCase @@ -52,6 +57,8 @@ from tacker.tests.unit.vnfm.infra_drivers.openstack.fixture_data import \ import tacker.tests.unit.vnfm.test_nfvo_client as nfvo_client from tacker.tests import utils from tacker.tests import uuidsentinel +from tacker.vnfm import nfvo_client as test_nfvo_client +from tacker.vnfm import vim_client import unittest from unittest import mock @@ -67,6 +74,14 @@ class FakeVNFMPlugin(mock.Mock): pass +class MockResponse: + def __init__(self, json_data): + self.json_data = json_data + + def json(self): + return self.json_data + + class TestConductor(SqlTestCase, unit_base.FixturedTestCase): client_fixture_class = client.ClientFixture sdk_connection_fixure_class = client.SdkConnectionFixture @@ -302,11 +317,17 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): '._update_vnf_attributes') @mock.patch('tacker.conductor.conductor_server.Conductor' '._change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._build_instantiated_vnf_info') @mock.patch.object(objects.VnfLcmOpOcc, "save") @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.utils._convert_desired_capacity') @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") def test_instantiate_vnf_instance(self, mock_vnf_by_id, - mock_get_lock, mock_save, mock_change_vnf_status, + mock_des, mock_vnfd_dict, + mock_get_lock, mock_save, + mock_build_info, mock_change_vnf_status, mock_update_vnf_attributes): lcm_op_occs_data = fakes.get_lcm_op_occs_data() mock_vnf_by_id.return_value = \ @@ -321,7 +342,11 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): vnf_instance.create() instantiate_vnf_req = vnflcm_fakes.get_instantiate_vnf_request_obj() vnf_lcm_op_occs_id = uuidsentinel.vnf_lcm_op_occs_id - vnf_dict = {"status": "ACTIVE"} + vnf_dict = db_utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour=instantiate_vnf_req.flavour_id) + vnfd_key = 'vnfd_' + instantiate_vnf_req.flavour_id + vnfd_yaml = vnf_dict['vnfd']['attributes'].get(vnfd_key, '') + mock_vnfd_dict.return_value = yaml.safe_load(vnfd_yaml) self.conductor.instantiate(self.context, vnf_instance, vnf_dict, instantiate_vnf_req, vnf_lcm_op_occs_id) self.vnflcm_driver.instantiate_vnf.assert_called_once_with( @@ -332,6 +357,217 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): mock.ANY, 'PENDING_CREATE') mock_update_vnf_attributes.assert_called_once() + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._build_instantiated_vnf_info') + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.utils._convert_desired_capacity') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._check_res_add_remove_rsc') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_instantiate_vnf_instance_grant(self, + mock_vnf_by_id, + mock_save, + mock_check, + mock_des, mock_vnfd_dict, mock_grants, + mock_exec, mock_get_lock, + mock_change_vnf_status, + mock_build_info): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + instantiate_vnf_req = vnflcm_fakes.get_instantiate_vnf_request_obj() + vnf_dict = db_utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour=instantiate_vnf_req.flavour_id) + vnf_lcm_op_occs_id = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + mock_vnf_by_id.return_value = \ + objects.VnfLcmOpOcc(context=self.context, + **lcm_op_occs_data) + mock_exec.return_value = True + vimAssets = {'computeResourceFlavours': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdVirtualComputeDescId': 'CDU1', + 'vimFlavourId': 'm1.tiny'}], + 'softwareImages': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdSoftwareImageId': 'VDU1', + 'vimSoftwareImageId': 'cirros'}]} + resAddResource = [] + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351280', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa9', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa0', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa1', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = vnf_instance.id + grant_dict['vnfLcmOpOccId'] = vnf_lcm_op_occs_id + grant_dict['addResources'] = [] + grant_dict['addResources'].extend(resAddResource) + grant_dict['vimAssets'] = vimAssets + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + vnfd_key = 'vnfd_' + instantiate_vnf_req.flavour_id + vnfd_yaml = vnf_dict['vnfd']['attributes'].get(vnfd_key, '') + mock_vnfd_dict.return_value = yaml.safe_load(vnfd_yaml) + self.conductor.instantiate(self.context, vnf_instance, vnf_dict, + instantiate_vnf_req, vnf_lcm_op_occs_id) + self.vnflcm_driver.instantiate_vnf.assert_called_once_with( + self.context, vnf_instance, vnf_dict, + instantiate_vnf_req) + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._build_instantiated_vnf_info') + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.utils._convert_desired_capacity') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._check_res_add_remove_rsc') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_instantiate_vnf_instance_grant_resource_exception(self, + mock_vnf_by_id, + mock_save, + mock_check, + mock_des, mock_vnfd_dict, mock_grants, + mock_exec, mock_get_lock, + mock_build_info, + mock_send): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + # mock_package_in_use.return_value = False + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + instantiate_vnf_req = vnflcm_fakes.get_instantiate_vnf_request_obj() + vnf_dict = db_utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour=instantiate_vnf_req.flavour_id) + vnf_lcm_op_occs_id = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + mock_vnf_by_id.return_value = \ + objects.VnfLcmOpOcc(context=self.context, + **lcm_op_occs_data) + mock_exec.return_value = True + vimAssets = {'computeResourceFlavours': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdVirtualComputeDescId': 'CDU1', + 'vimFlavourId': 'm1.tiny'}], + 'softwareImages': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdSoftwareImageId': 'VDU1', + 'vimSoftwareImageId': 'cirros'}]} + resAddResource = [] + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351280', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa9', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa0', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = vnf_instance.id + grant_dict['vnfLcmOpOccId'] = vnf_lcm_op_occs_id + grant_dict['addResources'] = [] + grant_dict['addResources'].extend(resAddResource) + grant_dict['vimAssets'] = vimAssets + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + self.assertRaises(exceptions.ValidationError, + self.conductor.instantiate, + self.context, vnf_instance, vnf_dict, + instantiate_vnf_req, vnf_lcm_op_occs_id) + self.assertEqual( + mock_send.call_args[0][1].get('operationState'), + 'ROLLED_BACK') + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._build_instantiated_vnf_info') + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.utils._convert_desired_capacity') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._check_res_add_remove_rsc') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_instantiate_vnf_instance_grant_exception(self, + mock_vnf_by_id, + mock_save, + mock_check, + mock_des, mock_vnfd_dict, mock_grants, + mock_exec, mock_get_lock, + mock_build_info, + mock_send): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + # mock_package_in_use.return_value = False + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + instantiate_vnf_req = vnflcm_fakes.get_instantiate_vnf_request_obj() + vnf_dict = db_utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour=instantiate_vnf_req.flavour_id) + vnf_lcm_op_occs_id = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + vnfd_key = 'vnfd_' + instantiate_vnf_req.flavour_id + vnfd_yaml = vnf_dict['vnfd']['attributes'].get(vnfd_key, '') + mock_vnfd_dict.return_value = yaml.safe_load(vnfd_yaml) + mock_vnf_by_id.return_value = \ + objects.VnfLcmOpOcc(context=self.context, + **lcm_op_occs_data) + mock_exec.return_value = True + mock_grants.side_effect = \ + requests.exceptions.HTTPError("MockException") + self.assertRaises(requests.exceptions.HTTPError, + self.conductor.instantiate, + self.context, vnf_instance, vnf_dict, + instantiate_vnf_req, vnf_lcm_op_occs_id) + self.assertEqual( + mock_send.call_args[0][1].get('operationState'), + 'ROLLED_BACK') + @unittest.skip("Such test is no longer feasible.") @mock.patch.object(objects.VnfLcmOpOcc, "save") @mock.patch.object(coordination.Coordinator, 'get_lock') @@ -410,17 +646,23 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): '._update_vnf_attributes') @mock.patch('tacker.conductor.conductor_server.Conductor' '._change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._build_instantiated_vnf_info') @mock.patch.object(objects.VnfLcmOpOcc, "save") @mock.patch.object(coordination.Coordinator, 'get_lock') @mock.patch.object(objects.LccnSubscriptionRequest, 'vnf_lcm_subscriptions_get') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.utils._convert_desired_capacity') @mock.patch('tacker.conductor.conductor_server.LOG') @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") @mock.patch('tacker.vnflcm.utils._get_affected_resources') def test_instantiate_vnf_instance_failed_with_exception( self, mock_res, mock_vnf_by_id, mock_log, + mock_des, mock_vnfd_dict, mock_vnf_lcm_subscriptions_get, - mock_get_lock, mock_save, mock_change_vnf_status, + mock_get_lock, mock_save, mock_build_info, + mock_change_vnf_status, mock_update_vnf_attributes): lcm_op_occs_data = fakes.get_lcm_op_occs_data() mock_vnf_by_id.return_value = \ @@ -435,9 +677,13 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): vnf_instance.create() instantiate_vnf_req = vnflcm_fakes.get_instantiate_vnf_request_obj() vnf_lcm_op_occs_id = uuidsentinel.vnf_lcm_op_occs_id - vnf_dict = {"status": "ACTIVE"} + vnf_dict = db_utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour=instantiate_vnf_req.flavour_id) m_vnf_lcm_subscriptions = \ [mock.MagicMock(**fakes.get_vnf_lcm_subscriptions())] + vnfd_key = 'vnfd_' + instantiate_vnf_req.flavour_id + vnfd_yaml = vnf_dict['vnfd']['attributes'].get(vnfd_key, '') + mock_vnfd_dict.return_value = yaml.safe_load(vnfd_yaml) mock_vnf_lcm_subscriptions_get.return_value = \ m_vnf_lcm_subscriptions mock_update_vnf_attributes.side_effect = Exception @@ -476,6 +722,261 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): self.assertEqual(mock_send_notification.call_count, 2) self.assertEqual(mock_change_vnf_status.call_count, 2) + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._change_vnf_status') + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_terminate_vnf_instance_grant(self, mock_vnf_by_id, + mock_grants, + mock_exec, + mock_get_lock, + mock_change_vnf_status): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + # mock_package_in_use.return_value = True + vnf_instance_data['instantiation_state'] =\ + fields.VnfInstanceState.INSTANTIATED + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id='simple', + vnf_instance_id=vnf_instance.id) + vnf_instance.instantiated_vnf_info.reinitialize() + + terminate_vnf_req = objects.TerminateVnfRequest( + termination_type=fields.VnfInstanceTerminationType.GRACEFUL) + vnfLcmOpOccId = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + vnf_dict = db_utils.get_dummy_vnf(instance_id=self.instance_uuid) + + mock_exec.return_value = True + resRemResource = [] + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = vnf_instance.id + grant_dict['vnfLcmOpOccId'] = vnfLcmOpOccId + grant_dict['removeResources'] = [] + grant_dict['removeResources'].extend(resRemResource) + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + self.conductor.terminate(self.context, vnfLcmOpOccId, + vnf_instance, terminate_vnf_req, vnf_dict) + + self.vnflcm_driver.terminate_vnf.assert_called_once_with( + self.context, mock.ANY, terminate_vnf_req) + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._change_vnf_status') + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_terminate_vnf_instance_grant_1(self, mock_vnf_by_id, + mock_grants, + mock_exec, + mock_get_lock, + mock_change_vnf_status): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + # mock_package_in_use.return_value = True + vnf_instance_data['instantiation_state'] =\ + fields.VnfInstanceState.INSTANTIATED + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id='simple', + vnf_instance_id=vnf_instance.id) + vnf_instance.instantiated_vnf_info.reinitialize() + vnfc_obj = objects.VnfcResourceInfo() + vnfc_obj.id = '2c6e5cc7-240d-4458-a683-1fe648351280' + vnfc_obj.vdu_id = 'VDU1' + vnfc_obj.storage_resource_ids = \ + ['faf14707-da7c-4eec-be99-8099fa1e9fa0'] + compute_resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce2') + vnfc_obj.compute_resource = compute_resource + cp_obj = objects.VnfcCpInfo() + cp_obj.id = 'faf14707-da7c-4eec-be99-8099fa1e9fa9' + cp_obj.cpd_id = 'PORT1' + cp_obj.vnf_link_port_id = 'faf14707-da7c-4eec-be99-8099fa1e9fb9' + vnfc_obj.vnfc_cp_info = [cp_obj] + st_obj = objects.VirtualStorageResourceInfo() + st_obj.id = 'faf14707-da7c-4eec-be99-8099fa1e9fa0' + st_obj.virtual_storage_desc_id = 'ST1' + storage_resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce4') + st_obj.storage_resource = storage_resource + vl_obj = objects.VnfVirtualLinkResourceInfo() + vl_obj.id = 'faf14707-da7c-4eec-be99-8099fa1e9fa1' + vl_obj.vnf_virtual_link_desc_id = 'VL1' + network_resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce5') + vl_obj.network_resource = network_resource + port_obj = objects.VnfLinkPortInfo() + port_obj.id = 'faf14707-da7c-4eec-be99-8099fa1e9fb9' + port_obj.cp_instance_id = 'faf14707-da7c-4eec-be99-8099fa1e9fa9' + resource_handle = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce3') + port_obj.resource_handle = resource_handle + vl_obj.vnf_link_ports = [port_obj] + vnf_instance.instantiated_vnf_info.vnfc_resource_info = [vnfc_obj] + vnf_instance.instantiated_vnf_info.virtual_storage_resource_info = \ + [st_obj] + vnf_instance.instantiated_vnf_info.vnf_virtual_link_resource_info = \ + [vl_obj] + + resRemResource = [] + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351280', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resRemResource.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fb9', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resRemResource.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa0', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resRemResource.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa1', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resRemResource.append(resource) + + terminate_vnf_req = objects.TerminateVnfRequest( + termination_type=fields.VnfInstanceTerminationType.GRACEFUL) + vnfLcmOpOccId = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + vnf_dict = db_utils.get_dummy_vnf(instance_id=self.instance_uuid) + + mock_exec.return_value = True + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = vnf_instance.id + grant_dict['vnfLcmOpOccId'] = vnfLcmOpOccId + grant_dict['removeResources'] = [] + grant_dict['removeResources'].extend(resRemResource) + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + self.conductor.terminate(self.context, vnfLcmOpOccId, + vnf_instance, terminate_vnf_req, vnf_dict) + + self.vnflcm_driver.terminate_vnf.assert_called_once_with( + self.context, mock.ANY, terminate_vnf_req) + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_terminate_vnf_instance_grant_resource_exception(self, + mock_vnf_by_id, + mock_save, + mock_grants, + mock_exec, + mock_get_lock, + mock_send): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + # mock_package_in_use.return_value = True + vnf_instance_data['instantiation_state'] =\ + fields.VnfInstanceState.INSTANTIATED + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id='simple', + vnf_instance_id=vnf_instance.id) + vnf_instance.instantiated_vnf_info.reinitialize() + + terminate_vnf_req = objects.TerminateVnfRequest( + termination_type=fields.VnfInstanceTerminationType.GRACEFUL) + vnfLcmOpOccId = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + vnf_dict = db_utils.get_dummy_vnf(instance_id=self.instance_uuid) + + mock_exec.return_value = True + resRemResource = [] + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351280', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resRemResource.append(resource) + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = vnf_instance.id + grant_dict['vnfLcmOpOccId'] = vnfLcmOpOccId + grant_dict['removeResources'] = [] + grant_dict['removeResources'].extend(resRemResource) + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + + self.assertRaises(exceptions.ValidationError, + self.conductor.terminate, + self.context, vnfLcmOpOccId, + vnf_instance, terminate_vnf_req, vnf_dict) + self.assertEqual( + mock_send.call_args[0][1].get('operationState'), + 'ROLLED_BACK') + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_terminate_vnf_instance_grant_exception(self, + mock_vnf_by_id, + mock_save, + mock_grants, + mock_exec, + mock_get_lock, + mock_send): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + # mock_package_in_use.return_value = True + vnf_instance_data['instantiation_state'] =\ + fields.VnfInstanceState.INSTANTIATED + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id='simple', + vnf_instance_id=vnf_instance.id) + vnf_instance.instantiated_vnf_info.reinitialize() + + terminate_vnf_req = objects.TerminateVnfRequest( + termination_type=fields.VnfInstanceTerminationType.GRACEFUL) + vnfLcmOpOccId = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + vnf_dict = db_utils.get_dummy_vnf(instance_id=self.instance_uuid) + + mock_exec.return_value = True + mock_grants.side_effect = \ + requests.exceptions.HTTPError("MockException") + self.assertRaises(requests.exceptions.HTTPError, + self.conductor.terminate, + self.context, vnfLcmOpOccId, + vnf_instance, terminate_vnf_req, vnf_dict) + self.assertEqual( + mock_send.call_args[0][1].get('operationState'), + 'ROLLED_BACK') + @mock.patch('tacker.conductor.conductor_server.Conductor' '._change_vnf_status') @mock.patch('tacker.conductor.conductor_server.Conductor' @@ -674,6 +1175,422 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): mock_add_additional_vnf_info. \ assert_called_once_with(self.context, vnf_instance) + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_instantiated_vnf_info') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._add_additional_vnf_info') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch('tacker.vnflcm.utils._get_vnflcm_interface') + @mock.patch.object(vim_client.VimClient, "get_vim") + @mock.patch.object(driver_manager.DriverManager, "__init__") + @mock.patch.object(driver_manager.DriverManager, "register") + @mock.patch.object(driver_manager.DriverManager, "invoke") + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._get_placement') + def test_heal_vnf_instance_grant(self, + mock_placement, + mock_d1, + mock_d2, + mock_d3, + mock_vim, + mock_act, + mock_grants, + mock_exec, + mock_vnf_by_id, + mock_get_lock, + mock_save, + mock_add_vnf_info, + mock_update_vnf_info, + mock_change_status): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiation_state = \ + fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id='simple') + heal_vnf_req = objects.HealVnfRequest(cause="healing request") + vnf_dict = db_utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') + vnf_lcm_op_occs_id = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + mock_exec.return_value = True + mock_act.return_value = None + vim_obj = {'vim_id': uuidsentinel.vim_id, + 'vim_name': 'fake_vim', + 'vim_type': 'openstack', + 'vim_auth': { + 'auth_url': 'http://localhost/identity', + 'password': 'test_pw', + 'username': 'test_user', + 'project_name': 'test_project'}} + vimAssets = {'computeResourceFlavours': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdVirtualComputeDescId': 'CDU1', + 'vimFlavourId': 'm1.tiny'}], + 'softwareImages': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdSoftwareImageId': 'VDU1', + 'vimSoftwareImageId': 'cirros'}]} + resAddResource = [] + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = vnf_instance.id + grant_dict['vnfLcmOpOccId'] = vnf_lcm_op_occs_id + grant_dict['addResources'] = [] + grant_dict['addResources'].extend(resAddResource) + grant_dict['vimAssets'] = vimAssets + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + + mock_vim.return_value = vim_obj + mock_d1.return_value = [] + mock_placement.return_value = [] + self.conductor.heal(self.context, vnf_instance, vnf_dict, + heal_vnf_req, vnf_lcm_op_occs_id) + mock_add_vnf_info.assert_called_once() + mock_update_vnf_info.assert_called_once() + self.vnflcm_driver.heal_vnf.assert_called_once_with( + self.context, mock.ANY, vnf_dict, heal_vnf_req) + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_instantiated_vnf_info') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._add_additional_vnf_info') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch('tacker.vnflcm.utils._get_vnflcm_interface') + @mock.patch.object(vim_client.VimClient, "get_vim") + @mock.patch.object(driver_manager.DriverManager, "__init__") + @mock.patch.object(driver_manager.DriverManager, "register") + @mock.patch.object(driver_manager.DriverManager, "invoke") + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._get_placement') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._check_res_add_remove_rsc') + def test_heal_vnf_instance_grant_1(self, + mock_check, + mock_placement, + mock_d1, + mock_d2, + mock_d3, + mock_vim, + mock_act, + mock_grants, + mock_exec, + mock_vnf_by_id, + mock_get_lock, + mock_save, + mock_add_vnf_info, + mock_update_vnf_info, + mock_change_status): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiation_state = \ + fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id='simple', + vnf_instance_id=vnf_instance.id) + vnf_instance.instantiated_vnf_info.reinitialize() + vnfc_obj = objects.VnfcResourceInfo() + vnfc_obj.id = '2c6e5cc7-240d-4458-a683-1fe648351280' + vnfc_obj.vdu_id = 'VDU1' + vnfc_obj.storage_resource_ids = \ + ['faf14707-da7c-4eec-be99-8099fa1e9fa0'] + compute_resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce2') + vnfc_obj.compute_resource = compute_resource + cp_obj = objects.VnfcCpInfo() + cp_obj.id = 'faf14707-da7c-4eec-be99-8099fa1e9fa9' + cp_obj.cpd_id = 'PORT1' + cp_obj.vnf_link_port_id = 'faf14707-da7c-4eec-be99-8099fa1e9fb9' + vnfc_obj.vnfc_cp_info = [cp_obj] + st_obj = objects.VirtualStorageResourceInfo() + st_obj.id = 'faf14707-da7c-4eec-be99-8099fa1e9fa0' + st_obj.virtual_storage_desc_id = 'ST1' + storage_resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce4') + st_obj.storage_resource = storage_resource + vl_obj = objects.VnfVirtualLinkResourceInfo() + vl_obj.id = 'faf14707-da7c-4eec-be99-8099fa1e9fa1' + vl_obj.vnf_virtual_link_desc_id = 'VL1' + network_resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce5') + vl_obj.network_resource = network_resource + port_obj = objects.VnfLinkPortInfo() + port_obj.id = 'faf14707-da7c-4eec-be99-8099fa1e9fb9' + port_obj.cp_instance_id = 'faf14707-da7c-4eec-be99-8099fa1e9fa9' + resource_handle = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce3') + port_obj.resource_handle = resource_handle + vl_obj.vnf_link_ports = [port_obj] + vnf_instance.instantiated_vnf_info.vnfc_resource_info = [vnfc_obj] + vnf_instance.instantiated_vnf_info.virtual_storage_resource_info = \ + [st_obj] + vnf_instance.instantiated_vnf_info.vnf_virtual_link_resource_info = \ + [vl_obj] + heal_vnf_req = objects.HealVnfRequest(cause="healing request") + vnf_dict = db_utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') + vnf_lcm_op_occs_id = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + mock_exec.return_value = True + mock_act.return_value = None + vim_obj = {'vim_id': uuidsentinel.vim_id, + 'vim_name': 'fake_vim', + 'vim_type': 'openstack', + 'vim_auth': { + 'auth_url': 'http://localhost/identity', + 'password': 'test_pw', + 'username': 'test_user', + 'project_name': 'test_project'}} + vimAssets = {'computeResourceFlavours': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdVirtualComputeDescId': 'CDU1', + 'vimFlavourId': 'm1.tiny'}], + 'softwareImages': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdSoftwareImageId': 'VDU1', + 'vimSoftwareImageId': 'cirros'}]} + resAddResource = [] + resRemResource = [] + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351280'} + resRemResource.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa0'} + resRemResource.append(resource) + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351281', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351282', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = vnf_instance.id + grant_dict['vnfLcmOpOccId'] = vnf_lcm_op_occs_id + grant_dict['addResources'] = [] + grant_dict['addResources'].extend(resAddResource) + grant_dict['removeResources'] = [] + grant_dict['removeResources'].extend(resRemResource) + grant_dict['vimAssets'] = vimAssets + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + + mock_vim.return_value = vim_obj + mock_d1.return_value = ['ST1'] + res_str = '[{"id_type": "RES_MGMT", "resource_id": ' + \ + '"2c6e5cc7-240d-4458-a683-1fe648351200", ' + \ + '"vim_connection_id": ' + \ + '"2a63bee3-0c43-4568-bcfa-b0cb733e064c"}]' + placemnt = models.PlacementConstraint( + id='c2947d8a-2c67-4e8f-ad6f-c0889b351c17', + vnf_instance_id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + affinity_or_anti_affinity='ANTI_AFFINITY', + scope='ZONE', + server_group_name='my_compute_placement_policy', + resource=res_str, + deleted_at=datetime.datetime.min) + mock_placement.return_value = [placemnt] + self.conductor.heal(self.context, vnf_instance, vnf_dict, + heal_vnf_req, vnf_lcm_op_occs_id) + mock_add_vnf_info.assert_called_once() + mock_update_vnf_info.assert_called_once() + self.vnflcm_driver.heal_vnf.assert_called_once_with( + self.context, mock.ANY, vnf_dict, heal_vnf_req) + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_instantiated_vnf_info') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._add_additional_vnf_info') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch('tacker.vnflcm.utils._get_vnflcm_interface') + @mock.patch.object(vim_client.VimClient, "get_vim") + @mock.patch.object(driver_manager.DriverManager, "__init__") + @mock.patch.object(driver_manager.DriverManager, "register") + @mock.patch.object(driver_manager.DriverManager, "invoke") + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._get_placement') + def test_heal_vnf_instance_grant_resource_exception(self, + mock_placement, + mock_d1, + mock_d2, + mock_d3, + mock_vim, + mock_act, + mock_grants, + mock_exec, + mock_vnf_by_id, + mock_get_lock, + mock_save, + mock_add_vnf_info, + mock_update_vnf_info, + mock_send): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiation_state = \ + fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id='simple') + heal_vnf_req = objects.HealVnfRequest(cause="healing request") + vnf_dict = db_utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') + vnf_lcm_op_occs_id = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + mock_exec.return_value = True + mock_act.return_value = None + vim_obj = {'vim_id': uuidsentinel.vim_id, + 'vim_name': 'fake_vim', + 'vim_type': 'openstack', + 'vim_auth': { + 'auth_url': 'http://localhost/identity', + 'password': 'test_pw', + 'username': 'test_user', + 'project_name': 'test_project'}} + vimAssets = {'computeResourceFlavours': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdVirtualComputeDescId': 'CDU1', + 'vimFlavourId': 'm1.tiny'}], + 'softwareImages': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdSoftwareImageId': 'VDU1', + 'vimSoftwareImageId': 'cirros'}]} + resAddResource = [] + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351281', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = vnf_instance.id + grant_dict['vnfLcmOpOccId'] = vnf_lcm_op_occs_id + grant_dict['addResources'] = [] + grant_dict['addResources'].extend(resAddResource) + grant_dict['vimAssets'] = vimAssets + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + + mock_vim.return_value = vim_obj + mock_d1.return_value = [] + mock_placement.return_value = [] + self.assertRaises(exceptions.ValidationError, + self.conductor.heal, + self.context, vnf_instance, vnf_dict, + heal_vnf_req, vnf_lcm_op_occs_id) + self.assertEqual( + mock_send.call_args[0][1].get('operationState'), + 'ROLLED_BACK') + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_instantiated_vnf_info') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._add_additional_vnf_info') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch('tacker.vnflcm.utils._get_vnflcm_interface') + @mock.patch.object(vim_client.VimClient, "get_vim") + @mock.patch.object(driver_manager.DriverManager, "__init__") + @mock.patch.object(driver_manager.DriverManager, "register") + @mock.patch.object(driver_manager.DriverManager, "invoke") + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._get_placement') + def test_heal_vnf_instance_grant_exception(self, + mock_placement, + mock_d1, + mock_d2, + mock_d3, + mock_vim, + mock_act, + mock_grants, + mock_exec, + mock_vnf_by_id, + mock_get_lock, + mock_save, + mock_add_vnf_info, + mock_update_vnf_info, + mock_send): + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiation_state = \ + fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id='simple') + heal_vnf_req = objects.HealVnfRequest(cause="healing request") + vnf_dict = db_utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') + vnf_lcm_op_occs_id = 'a9c36d21-21aa-4692-8922-7999bbcae08c' + mock_exec.return_value = True + mock_act.return_value = None + vim_obj = {'vim_id': uuidsentinel.vim_id, + 'vim_name': 'fake_vim', + 'vim_type': 'openstack', + 'vim_auth': { + 'auth_url': 'http://localhost/identity', + 'password': 'test_pw', + 'username': 'test_user', + 'project_name': 'test_project'}} + mock_grants.side_effect = \ + requests.exceptions.HTTPError("MockException") + + mock_vim.return_value = vim_obj + mock_d1.return_value = [] + mock_placement.return_value = [] + self.assertRaises(requests.exceptions.HTTPError, + self.conductor.heal, + self.context, vnf_instance, vnf_dict, + heal_vnf_req, vnf_lcm_op_occs_id) + self.assertEqual( + mock_send.call_args[0][1].get('operationState'), + 'ROLLED_BACK') + @mock.patch('tacker.conductor.conductor_server.Conductor.' '_send_lcm_op_occ_notification') @mock.patch('tacker.conductor.conductor_server.Conductor.' @@ -800,16 +1717,65 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): self.assertEqual('CREATED', self.vnf_package.onboarding_state) @mock.patch.object(coordination.Coordinator, 'get_lock') - @mock.patch.object(objects.VnfInstance, "get_by_id") - def test_scale(self, mock_vnf_by_id, mock_get_lock): + @mock.patch.object(objects.VnfInstance, 'get_by_id') + @mock.patch.object(vim_client.VimClient, "get_vim") + @mock.patch.object(driver_manager.DriverManager, "__init__") + @mock.patch.object(driver_manager.DriverManager, "register") + @mock.patch.object(driver_manager.DriverManager, "invoke") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + def test_scale_in( + self, + mock_grants, + mock_d1, + mock_d2, + mock_d3, + mock_vim, + mock_vnf_by_id, + mock_get_lock): mock_vnf_by_id.return_value = fakes.return_vnf_instance( fields.VnfInstanceState.INSTANTIATED) vnf_info = fakes._get_vnf() + vnf_lcm_op_occ = objects.VnfLcmOpOcc( + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=uuidsentinel.vnf_instance_id, + operation='SCALE', + operation_state='ACTIVE', + is_automatic_invocation=False, + operation_params='{"type": "SCALE_IN", "aspect_id": "SP1"}', + error_point=0, + id=test_constants.UUID, + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ vnf_instance = fakes.return_vnf_instance( fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status") scale_vnf_request = fakes.scale_request("SCALE_IN", 1) + vim_obj = {'vim_id': uuidsentinel.vim_id, + 'vim_name': 'fake_vim', + 'vim_type': 'openstack', + 'vim_auth': { + 'auth_url': 'http://localhost/identity', + 'password': 'test_pw', + 'username': 'test_user', + 'project_name': 'test_project'}} + + mock_vim.return_value = vim_obj + mock_d1.return_value = ([], [], "", "") + vnf_info['addResources'] = [] + vnf_info['removeResources'] = [] + vnf_info['affinity_list'] = [] + vnf_info['placement_constraint_list'] = [] + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnf_instance_id'] = uuidsentinel.vnf_instance_id + grant_dict['vnf_lcm_op_occ_id'] = test_constants.UUID + mock_grants.return_value = MockResponse( + json_data=jsonutils.dumps(grant_dict)) self.conductor.scale( self.context, @@ -818,6 +1784,473 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): scale_vnf_request) self.vnflcm_driver.scale_vnf.assert_called_once_with( self.context, vnf_info, mock.ANY, scale_vnf_request) + self.assertEqual(0, mock_grants.call_count) + + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.VnfInstance, 'get_by_id') + @mock.patch.object(vim_client.VimClient, "get_vim") + @mock.patch.object(driver_manager.DriverManager, "__init__") + @mock.patch.object(driver_manager.DriverManager, "register") + @mock.patch.object(driver_manager.DriverManager, "invoke") + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + def test_scale_in_grants( + self, + mock_grants, + moch_exec, + mock_d1, + mock_d2, + mock_d3, + mock_vim, + mock_vnf_by_id, + mock_get_lock): + mock_vnf_by_id.return_value = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_info = fakes._get_vnf() + vnf_lcm_op_occ = objects.VnfLcmOpOcc( + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=uuidsentinel.vnf_instance_id, + operation='SCALE', + operation_state='ACTIVE', + is_automatic_invocation=False, + operation_params='{"type": "SCALE_IN", "aspect_id": "SP1"}', + error_point=0, + id=test_constants.UUID, + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED, + scale_status="scale_status") + scale_vnf_request = fakes.scale_request("SCALE_IN", 1) + vim_obj = {'vim_id': uuidsentinel.vim_id, + 'vim_name': 'fake_vim', + 'vim_type': 'openstack', + 'vim_auth': { + 'auth_url': 'http://localhost/identity', + 'password': 'test_pw', + 'username': 'test_user', + 'project_name': 'test_project'}} + + mock_vim.return_value = vim_obj + removeResources = [] + resRemoveResource = [] + resource = objects.ResourceDefinition( + id='2c6e5cc7-240d-4458-a683-1fe648351280', + type='COMPUTE', + vdu_id='VDU1', + resource_template_id='VDU1') + resource.resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce2') + removeResources.append(resource) + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351280', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resRemoveResource.append(resource) + resource = objects.ResourceDefinition( + id='faf14707-da7c-4eec-be99-8099fa1e9fa9', + type='LINKPORT', + vdu_id='VDU1', + resource_template_id='PORT1') + resource.resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce3') + removeResources.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa9', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resRemoveResource.append(resource) + resource = objects.ResourceDefinition( + id='faf14707-da7c-4eec-be99-8099fa1e9fa9', + type='STORAGE', + vdu_id='VDU1', + resource_template_id='ST1') + resource.resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce4') + removeResources.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa9', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resRemoveResource.append(resource) + mock_d1.return_value = ([], [], "", "") + vnf_info['addResources'] = [] + vnf_info['removeResources'] = removeResources + vnf_info['affinity_list'] = [] + vnf_info['placement_constraint_list'] = [] + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = uuidsentinel.vnf_instance_id + grant_dict['vnfLcmOpOccId'] = test_constants.UUID + grant_dict['removeResources'] = [] + grant_dict['removeResources'].extend(resRemoveResource) + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + moch_exec.return_value = True + + self.conductor.scale( + self.context, + vnf_info, + vnf_instance, + scale_vnf_request) + self.vnflcm_driver.scale_vnf.assert_called_once_with( + self.context, vnf_info, mock.ANY, scale_vnf_request) + self.assertEqual(1, mock_grants.call_count) + + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.VnfInstance, 'get_by_id') + @mock.patch.object(vim_client.VimClient, "get_vim") + @mock.patch.object(driver_manager.DriverManager, "__init__") + @mock.patch.object(driver_manager.DriverManager, "register") + @mock.patch.object(driver_manager.DriverManager, "invoke") + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + def test_scale_grants_out( + self, + mock_grants, + moch_exec, + mock_d1, + mock_d2, + mock_d3, + mock_vim, + mock_vnf_by_id, + mock_get_lock): + mock_vnf_by_id.return_value = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + + vnf_info = fakes._get_vnf() + vnf_lcm_op_occ = objects.VnfLcmOpOcc( + context=self.context, + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=vnf_instance.id, + operation='SCALE', + operation_state='ACTIVE', + is_automatic_invocation=False, + operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}', + is_cancel_pending=False, + error_point=0, + id='00e1314d-2a82-40bd-b318-cc881243842d', + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + vnf_lcm_op_occ.create() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED, + scale_status="scale_status") + scale_vnf_request = fakes.scale_request("SCALE_OUT", 1) + vim_obj = {'vim_id': uuidsentinel.vim_id, + 'vim_name': 'fake_vim', + 'vim_type': 'openstack', + 'vim_auth': { + 'auth_url': 'http://localhost/identity', + 'password': 'test_pw', + 'username': 'test_user', + 'project_name': 'test_project'}} + + mock_vim.return_value = vim_obj + addResources = [] + resAddResource = [] + resource = objects.ResourceDefinition( + id='2c6e5cc7-240d-4458-a683-1fe648351280', + type='COMPUTE', + vdu_id='VDU1', + resource_template_id='VDU1') + addResources.append(resource) + resource = { + 'resourceDefinitionId': '2c6e5cc7-240d-4458-a683-1fe648351280', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = objects.ResourceDefinition( + id='faf14707-da7c-4eec-be99-8099fa1e9fa9', + type='LINKPORT', + vdu_id='VDU1', + resource_template_id='PORT1') + addResources.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa9', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = objects.ResourceDefinition( + id='faf14707-da7c-4eec-be99-8099fa1e9fa9', + type='STORAGE', + vdu_id='VDU1', + resource_template_id='ST1') + addResources.append(resource) + resource = { + 'resourceDefinitionId': 'faf14707-da7c-4eec-be99-8099fa1e9fa9', + 'vimConnectionId': uuidsentinel.vim_id, + 'zoneId': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + placement = objects.PlacementConstraint( + affinity_or_anti_affinity='ANTI_AFFINITY', + scope='ZONE', + resource=[], + fallback_best_effort=True) + resource = objects.ConstraintResourceRef( + id_type='RES_MGMT', + resource_id='2c6e5cc7-240d-4458-a683-1fe648351200', + vim_connection_id=uuidsentinel.vim_id) + placement.resource.append(resource) + resource = objects.ConstraintResourceRef( + id_type='GRANT', + resource_id='2c6e5cc7-240d-4458-a683-1fe648351280') + placement.resource.append(resource) + vimAssets = {'computeResourceFlavours': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdVirtualComputeDescId': 'CDU1', + 'vimFlavourId': 'm1.tiny'}], + 'softwareImages': [ + {'vimConnectionId': uuidsentinel.vim_id, + 'vnfdSoftwareImageId': 'VDU1', + 'vimSoftwareImageId': 'cirros'}]} + + mock_d1.return_value = ([], [], "", "") + vnf_info['addResources'] = addResources + vnf_info['removeResources'] = [] + vnf_info['affinity_list'] = [] + vnf_info['placement_constraint_list'] = [] + vnf_info['placement_constraint_list'].append(placement) + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnfInstanceId'] = uuidsentinel.vnf_instance_id + grant_dict['vnfLcmOpOccId'] = test_constants.UUID + grant_dict['addResources'] = [] + grant_dict['addResources'].extend(resAddResource) + grant_dict['vimAssets'] = vimAssets + moch_exec.return_value = True + json_data = grant_dict + mock_grants.return_value = MockResponse(json_data=json_data) + + self.conductor.scale( + self.context, + vnf_info, + vnf_instance, + scale_vnf_request) + self.vnflcm_driver.scale_vnf.assert_called_once_with( + self.context, vnf_info, mock.ANY, scale_vnf_request) + self.assertEqual(1, mock_grants.call_count) + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.VnfInstance, 'get_by_id') + @mock.patch.object(vim_client.VimClient, "get_vim") + @mock.patch.object(driver_manager.DriverManager, "__init__") + @mock.patch.object(driver_manager.DriverManager, "register") + @mock.patch.object(driver_manager.DriverManager, "invoke") + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + def test_scale_grants_out_resource_exception( + self, + mock_grants, + moch_exec, + mock_d1, + mock_d2, + mock_d3, + mock_vim, + mock_vnf_by_id, + mock_get_lock, + mock_send): + mock_vnf_by_id.return_value = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + + vnf_info = fakes._get_vnf() + vnf_lcm_op_occ = objects.VnfLcmOpOcc( + context=self.context, + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=vnf_instance.id, + operation='SCALE', + operation_state='ACTIVE', + is_automatic_invocation=False, + operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}', + is_cancel_pending=False, + error_point=0, + id='00e1314d-2a82-40bd-b318-cc881243843d', + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + vnf_lcm_op_occ.create() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED, + scale_status="scale_status") + scale_vnf_request = fakes.scale_request("SCALE_OUT", 1) + vim_obj = {'vim_id': uuidsentinel.vim_id, + 'vim_name': 'fake_vim', + 'vim_type': 'openstack', + 'vim_auth': { + 'auth_url': 'http://localhost/identity', + 'password': 'test_pw', + 'username': 'test_user', + 'project_name': 'test_project'}} + + mock_vim.return_value = vim_obj + addResources = [] + resource = objects.ResourceDefinition( + id='2c6e5cc7-240d-4458-a683-1fe648351280', + type='COMPUTE', + vdu_id='VDU1', + resource_template_id='VDU1') + addResources.append(resource) + resource = objects.ResourceDefinition( + id='faf14707-da7c-4eec-be99-8099fa1e9fa9', + type='LINKPORT', + vdu_id='VDU1', + resource_template_id='PORT1') + addResources.append(resource) + resource = objects.ResourceDefinition( + id='faf14707-da7c-4eec-be99-8099fa1e9fa9', + type='STORAGE', + vdu_id='VDU1', + resource_template_id='ST1') + addResources.append(resource) + mock_d1.return_value = ([], [], "", "") + vnf_info['addResources'] = addResources + vnf_info['removeResources'] = [] + vnf_info['affinity_list'] = [] + vnf_info['placement_constraint_list'] = [] + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnf_instance_id'] = uuidsentinel.vnf_instance_id + grant_dict['vnf_lcm_op_occ_id'] = test_constants.UUID + moch_exec.return_value = True + mock_grants.return_value = MockResponse(json_data=grant_dict) + + self.assertRaises(exceptions.ValidationError, + self.conductor.scale, + self.context, vnf_info, vnf_instance, scale_vnf_request) + self.assertEqual( + mock_send.call_args[0][1].get('operationState'), + 'ROLLED_BACK') + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.VnfInstance, 'get_by_id') + @mock.patch.object(vim_client.VimClient, "get_vim") + @mock.patch.object(driver_manager.DriverManager, "__init__") + @mock.patch.object(driver_manager.DriverManager, "register") + @mock.patch.object(driver_manager.DriverManager, "invoke") + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + def test_scale_grants_exception( + self, + mock_grants, + moch_exec, + mock_d1, + mock_d2, + mock_d3, + mock_vim, + mock_vnf_by_id, + mock_get_lock, + mock_send): + mock_vnf_by_id.return_value = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + + vnf_info = fakes._get_vnf() + vnf_lcm_op_occ = objects.VnfLcmOpOcc( + context=self.context, + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=vnf_instance.id, + operation='SCALE', + operation_state='ACTIVE', + is_automatic_invocation=False, + operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}', + error_point=0, + id='00e1314d-2a82-40bd-b318-cc881243843d', + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + vnf_lcm_op_occ.create() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED, + scale_status="scale_status") + scale_vnf_request = fakes.scale_request("SCALE_OUT", 1) + vim_obj = {'vim_id': uuidsentinel.vim_id, + 'vim_name': 'fake_vim', + 'vim_type': 'openstack', + 'vim_auth': { + 'auth_url': 'http://localhost/identity', + 'password': 'test_pw', + 'username': 'test_user', + 'project_name': 'test_project'}} + + mock_vim.return_value = vim_obj + addResources = [] + resource = objects.ResourceDefinition( + id='2c6e5cc7-240d-4458-a683-1fe648351280', + type='COMPUTE', + vdu_id='VDU1', + resource_template_id='VDU1') + addResources.append(resource) + resource = objects.ResourceDefinition( + id='faf14707-da7c-4eec-be99-8099fa1e9fa9', + type='LINKPORT', + vdu_id='VDU1', + resource_template_id='PORT1') + addResources.append(resource) + resource = objects.ResourceDefinition( + id='faf14707-da7c-4eec-be99-8099fa1e9fa9', + type='STORAGE', + vdu_id='VDU1', + resource_template_id='ST1') + addResources.append(resource) + mock_d1.return_value = ([], [], "", "") + vnf_info['addResources'] = addResources + vnf_info['removeResources'] = [] + vnf_info['affinity_list'] = [] + vnf_info['placement_constraint_list'] = [] + moch_exec.return_value = True + mock_grants.side_effect = \ + requests.exceptions.HTTPError("MockException") + + self.assertRaises(requests.exceptions.HTTPError, + self.conductor.scale, + self.context, vnf_info, vnf_instance, scale_vnf_request) + print("test_log rollback") + print(mock_send.call_args) + self.assertEqual( + mock_send.call_args[0][1].get('operationState'), + 'ROLLED_BACK') @mock.patch.object(objects.LccnSubscriptionRequest, 'vnf_lcm_subscriptions_get') diff --git a/tacker/tests/unit/db/utils.py b/tacker/tests/unit/db/utils.py index 728dcbd10..05eeba4b4 100644 --- a/tacker/tests/unit/db/utils.py +++ b/tacker/tests/unit/db/utils.py @@ -34,6 +34,7 @@ tosca_cvnf_vnfd = _get_template('test_tosca_cvnf.yaml') tosca_vnfd_openwrt = _get_template('test_tosca_openwrt.yaml') tosca_vnfd_openwrt_param = _get_template('test_tosca_openwrt_param.yaml') tosca_invalid_vnfd = _get_template('test_tosca_parser_failure.yaml') +etsi_vnfd = _get_template('etsi_nfv/tosca_vnfd.yaml') config_data = _get_template('config_data.yaml') update_config_data = _get_template('update_config_data.yaml') hot_data = _get_template('hot_data.yaml') @@ -74,6 +75,11 @@ vnffgd_wrong_cp_number_template = yaml.safe_load(_get_template( 'tosca_vnffgd_wrong_cp_number_template.yaml')) vnfd_instance_reservation_alarm_scale_tosca_template = _get_template( 'test_tosca-vnfd-instance-reservation.yaml') +hot_grant = _get_template('hot_grant.yaml') +hot_scale_grant = _get_template('hot_scale_grant.yaml') +hot_scale_nest_grant = _get_template('hot_scale_nest_grant.yaml') +hot_scale_initial = _get_template('hot_scale_initial.yaml') +hot_scale_nest_initial = _get_template('hot_scale_nest_initial.yaml') def get_dummy_vnfd_obj(): @@ -190,6 +196,57 @@ def get_dummy_vnf(status='PENDING_CREATE', scaling_group=False, return dummy_vnf +def get_dummy_vnf_test(status='PENDING_CREATE', scaling_group=False, + instance_id=None): + dummy_vnf = {'status': status, 'instance_id': instance_id, 'name': + u'test_openwrt', 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437', + 'vnfd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e', + 'vnfd': { + 'service_types': [{'service_type': u'vnfd', + 'id': u'4a4c2d44-8a52-4895-9a75-9d1c76c3e738'}], + 'description': u'OpenWRT with services', + 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437', + 'mgmt_driver': u'openwrt', + 'attributes': {u'vnfd_simple': tosca_vnfd_openwrt}, + 'id': u'fb048660-dc1b-4f0f-bd89-b023666650ec', + 'name': u'openwrt_services'}, + 'mgmt_ip_address': None, 'service_context': [], + 'attributes': {u'param_values': u''}, + 'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123', + 'description': u'OpenWRT with services'} + if scaling_group: + dummy_vnf['attributes'].update({'scaling_group_names': + '{"SP1": "SP1_group"}', + 'heat_template': 'test'}) + return dummy_vnf + + +def get_dummy_vnf_etsi(status='PENDING_CREATE', scaling_group=False, + instance_id=None, flavour='Simple'): + vnfd_key = 'vnfd_' + flavour + dummy_vnf = {'status': status, 'instance_id': instance_id, 'name': + u'test_openwrt', 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437', + 'vnfd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e', + 'vnfd': { + 'service_types': [{'service_type': u'vnfd', + 'id': u'4a4c2d44-8a52-4895-9a75-9d1c76c3e738'}], + 'description': u'OpenWRT with services', + 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437', + 'mgmt_driver': u'openwrt', + 'attributes': {vnfd_key: etsi_vnfd}, + 'id': u'fb048660-dc1b-4f0f-bd89-b023666650ec', + 'name': u'openwrt_services'}, + 'mgmt_ip_address': None, 'service_context': [], + 'attributes': {u'param_values': u''}, + 'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123', + 'description': u'OpenWRT with services'} + if scaling_group: + dummy_vnf['attributes'].update({'scaling_group_names': + '{"SP1": "SP1_group"}', + 'heat_template': 'test'}) + return dummy_vnf + + def get_dummy_vnf_config_attr(): return {'status': 'PENDING_CREATE', 'instance_id': None, 'name': u'test_openwrt', 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437', @@ -483,3 +540,23 @@ def get_dummy_vim_connection_info(): 'created_at': '', 'deleted': False, 'deleted_at': '', 'id': 'fake_id', 'updated_at': '', 'vim_id': 'fake_vim_id', 'vim_type': 'openstack'} + + +def get_dummy_grant_hot(): + return str(hot_grant) + + +def get_dummy_scale_grant_hot(): + return str(hot_scale_grant) + + +def get_dummy_scale_nest_grant_hot(): + return str(hot_scale_nest_grant) + + +def get_dummy_scale_initial_hot(): + return str(hot_scale_initial) + + +def get_dummy_scale_nest_initial_hot(): + return str(hot_scale_nest_initial) diff --git a/tacker/tests/unit/objects/fakes.py b/tacker/tests/unit/objects/fakes.py index ced1380fe..bc7394bd9 100644 --- a/tacker/tests/unit/objects/fakes.py +++ b/tacker/tests/unit/objects/fakes.py @@ -245,8 +245,9 @@ def get_vnf_instance_data_with_id(vnfd_id): } -def get_lcm_op_occs_data(vnf_instance_id): +def get_lcm_op_occs_data(id, vnf_instance_id): return { + "id": id, "tenant_id": uuidsentinel.tenant_id, 'operation_state': 'PROCESSING', 'state_entered_time': datetime.datetime(1900, 1, 1, 1, 1, 1, diff --git a/tacker/tests/unit/objects/test_vnf_instance.py b/tacker/tests/unit/objects/test_vnf_instance.py index 0c49c7a18..6b3bf915b 100644 --- a/tacker/tests/unit/objects/test_vnf_instance.py +++ b/tacker/tests/unit/objects/test_vnf_instance.py @@ -16,6 +16,8 @@ import ddt from unittest import mock +from oslo_utils import uuidutils + from tacker.common import exceptions from tacker import context from tacker.db import api as sqlalchemy_api @@ -231,7 +233,9 @@ class TestVnfInstance(SqlTestCase): vnf_instance = objects.VnfInstance(context=self.context, **vnf_instance_data) vnf_instance.create() + id = uuidutils.generate_uuid() vnf_lcm_oppccs = fakes.get_lcm_op_occs_data( + id, vnf_instance.id) vnf_instance.update( diff --git a/tacker/tests/unit/objects/test_vnf_lcm_op_occs.py b/tacker/tests/unit/objects/test_vnf_lcm_op_occs.py index ef0a1d975..d5e74b84a 100644 --- a/tacker/tests/unit/objects/test_vnf_lcm_op_occs.py +++ b/tacker/tests/unit/objects/test_vnf_lcm_op_occs.py @@ -11,6 +11,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from oslo_utils import uuidutils + from tacker import context from tacker import objects from tacker.tests.unit.db.base import SqlTestCase @@ -58,21 +60,27 @@ class TestVnfLcmOpOcc(SqlTestCase): return vnf_instance def _create_vnf_lcm_op_occs(self): - vnf_lcm_op_occs_data = fakes.get_lcm_op_occs_data(self.vnf_instance.id) + id = uuidutils.generate_uuid() + vnf_lcm_op_occs_data = \ + fakes.get_lcm_op_occs_data(id, self.vnf_instance.id) vnf_lcm_op_occs = objects.vnf_lcm_op_occs.VnfLcmOpOcc( context=self.context, **vnf_lcm_op_occs_data) vnf_lcm_op_occs.create() return vnf_lcm_op_occs def test_create(self): - vnf_lcm_op_occs_data = fakes.get_lcm_op_occs_data(self.vnf_instance.id) + id = uuidutils.generate_uuid() + vnf_lcm_op_occs_data = \ + fakes.get_lcm_op_occs_data(id, self.vnf_instance.id) vnf_lcm_op_occs = objects.vnf_lcm_op_occs.VnfLcmOpOcc( context=self.context, **vnf_lcm_op_occs_data) vnf_lcm_op_occs.create() self.assertTrue(vnf_lcm_op_occs.vnf_instance_id) def test_save(self): - vnf_lcm_op_occs_data = fakes.get_lcm_op_occs_data(self.vnf_instance.id) + id = uuidutils.generate_uuid() + vnf_lcm_op_occs_data = \ + fakes.get_lcm_op_occs_data(id, self.vnf_instance.id) vnf_lcm_op_occs = objects.vnf_lcm_op_occs.VnfLcmOpOcc( context=self.context, **vnf_lcm_op_occs_data) vnf_lcm_op_occs.create() diff --git a/tacker/tests/unit/vnflcm/fakes.py b/tacker/tests/unit/vnflcm/fakes.py index 27f31b6a1..cda32fe6e 100644 --- a/tacker/tests/unit/vnflcm/fakes.py +++ b/tacker/tests/unit/vnflcm/fakes.py @@ -473,6 +473,10 @@ def get_instantiate_vnf_request_obj(): ext_managed_virtual_link_data = ExtManagedVirtualLinkData() vim_connection_info = VimConnectionInfo() ext_virtual_link_data = ExtVirtualLinkData() + ext_managed_virtual_link_data.id = uuidsentinel.ext_id + ext_managed_virtual_link_data.vnf_virtual_link_desc_id = 'VL3' + ext_managed_virtual_link_data.resource_id = \ + 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa' instantiate_vnf_req.additional_params = None instantiate_vnf_req.deleted = 0 instantiate_vnf_req.ext_managed_virtual_links = \ diff --git a/tacker/tests/unit/vnflcm/test_vnflcm_driver.py b/tacker/tests/unit/vnflcm/test_vnflcm_driver.py index b200901cf..6891f28c8 100644 --- a/tacker/tests/unit/vnflcm/test_vnflcm_driver.py +++ b/tacker/tests/unit/vnflcm/test_vnflcm_driver.py @@ -113,6 +113,10 @@ class FakeDriverManager(mock.Mock): if self.fail_method_name and \ self.fail_method_name == 'heal_vnf': raise InfraDriverException("heal_vnf failed") + elif 'heal_vnf_standard' in args: + if self.fail_method_name and \ + self.fail_method_name == 'heal_vnf_standard': + raise InfraDriverException("heal_vnf_standard failed") elif 'heal_vnf_wait' in args: if self.fail_method_name and \ self.fail_method_name == 'heal_vnf_wait': @@ -196,10 +200,8 @@ class TestVnflcmDriver(db_base.SqlTestCase): driver.instantiate_vnf(self.context, vnf_instance_obj, vnf_dict, instantiate_vnf_req_obj) - self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state) - self.assertEqual(2, mock_vnf_instance_save.call_count) - self.assertEqual(4, self._vnf_manager.invoke.call_count) - mock_final_vnf_dict.assert_called_once() + self.assertEqual(1, mock_vnf_instance_save.call_count) + self.assertEqual(3, self._vnf_manager.invoke.call_count) shutil.rmtree(fake_csar) @mock.patch('tacker.vnflcm.utils._make_final_vnf_dict') @@ -232,10 +234,8 @@ class TestVnflcmDriver(db_base.SqlTestCase): driver.instantiate_vnf(self.context, vnf_instance_obj, vnf_dict, instantiate_vnf_req_obj) - self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state) - self.assertEqual(2, mock_vnf_instance_save.call_count) - self.assertEqual(4, self._vnf_manager.invoke.call_count) - mock_final_vnf_dict.assert_called_once() + self.assertEqual(1, mock_vnf_instance_save.call_count) + self.assertEqual(3, self._vnf_manager.invoke.call_count) shutil.rmtree(fake_csar) @mock.patch('tacker.vnflcm.utils._make_final_vnf_dict') @@ -268,10 +268,8 @@ class TestVnflcmDriver(db_base.SqlTestCase): driver.instantiate_vnf(self.context, vnf_instance_obj, vnf_dict, instantiate_vnf_req_obj) - self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state) - self.assertEqual(2, mock_vnf_instance_save.call_count) - self.assertEqual(4, self._vnf_manager.invoke.call_count) - mock_final_vnf_dict.assert_called_once() + self.assertEqual(1, mock_vnf_instance_save.call_count) + self.assertEqual(3, self._vnf_manager.invoke.call_count) shutil.rmtree(fake_csar) @mock.patch('tacker.vnflcm.utils._make_final_vnf_dict') @@ -335,6 +333,12 @@ class TestVnflcmDriver(db_base.SqlTestCase): objects.InstantiateVnfRequest.obj_from_primitive( instantiate_vnf_req_dict, self.context) vnf_instance_obj = fakes.return_vnf_instance() + level = instantiate_vnf_req_obj.instantiation_level_id + vnf_instance_obj.instantiated_vnf_info = objects.InstantiatedVnfInfo( + flavour_id=instantiate_vnf_req_obj.flavour_id, + instantiation_level_id=level, + vnf_instance_id=vnf_instance_obj.id, + ext_cp_info=[]) fake_csar = os.path.join(self.temp_dir, vnf_package_id) cfg.CONF.set_override('vnf_package_csar_path', self.temp_dir, @@ -386,7 +390,8 @@ class TestVnflcmDriver(db_base.SqlTestCase): driver.instantiate_vnf(self.context, vnf_instance_obj, vnf_dict, instantiate_vnf_req_obj) self.assertEqual(2, mock_create.call_count) - self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state) + self.assertEqual("NOT_INSTANTIATED", + vnf_instance_obj.instantiation_state) mock_final_vnf_dict.assert_called_once() shutil.rmtree(fake_csar) @@ -419,7 +424,6 @@ class TestVnflcmDriver(db_base.SqlTestCase): driver.instantiate_vnf(self.context, vnf_instance_obj, vnf_dict, instantiate_vnf_req_obj) self.assertEqual(2, mock_create.call_count) - self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state) mock_final_vnf_dict.assert_called_once() shutil.rmtree(fake_csar) @@ -540,6 +544,7 @@ class TestVnflcmDriver(db_base.SqlTestCase): @mock.patch('tacker.vnflcm.utils._make_final_vnf_dict') @mock.patch.object(TackerManager, 'get_service_plugins', return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch('tacker.vnflcm.utils._make_final_vnf_dict') @mock.patch.object(objects.VnfPackageVnfd, 'get_by_id') @mock.patch.object(vim_client.VimClient, "get_vim") @mock.patch.object(objects.VnfResource, "create") @@ -550,7 +555,8 @@ class TestVnflcmDriver(db_base.SqlTestCase): def test_heal_vnf_without_vnfc_instance(self, mock_log, mock_save, mock_vnf_resource_list, mock_resource_destroy, mock_resource_create, mock_vim, mock_vnf_package_vnfd, - mock_get_service_plugins, mock_final_vnf_dict): + mock_make_final_vnf_dict, mock_get_service_plugins, + mock_final_vnf_dict): vnf_package_vnfd = fakes.return_vnf_package_vnfd() vnf_package_id = vnf_package_vnfd.package_uuid mock_vnf_package_vnfd.return_value = vnf_package_vnfd @@ -582,6 +588,7 @@ class TestVnflcmDriver(db_base.SqlTestCase): self._mock_vnf_manager() driver = vnflcm_driver.VnfLcmDriver() vnf_dict = {"attributes": {}} + mock_make_final_vnf_dict.return_value = {} driver.heal_vnf(self.context, vnf_instance, vnf_dict, heal_vnf_req) self.assertEqual(1, mock_save.call_count) # vnf resource software images will be deleted during @@ -590,9 +597,9 @@ class TestVnflcmDriver(db_base.SqlTestCase): # Vnf resource software images will be created during # instantiation. self.assertEqual(1, mock_resource_create.call_count) - # Invoke will be called 7 times, 3 for deleting the vnf - # resources and 4 during instantiation. - self.assertEqual(7, self._vnf_manager.invoke.call_count) + # Invoke will be called 6 times, 3 for deleting the vnf + # resources and 3 during instantiation. + self.assertEqual(6, self._vnf_manager.invoke.call_count) expected_msg = ("Request received for healing vnf '%s' " "is completed successfully") mock_log.info.assert_called_with(expected_msg, @@ -632,6 +639,7 @@ class TestVnflcmDriver(db_base.SqlTestCase): @mock.patch('tacker.vnflcm.utils._make_final_vnf_dict') @mock.patch.object(TackerManager, 'get_service_plugins', return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch('tacker.vnflcm.utils._make_final_vnf_dict') @mock.patch.object(objects.VnfPackageVnfd, 'get_by_id') @mock.patch.object(vim_client.VimClient, "get_vim") @mock.patch.object(objects.VnfResource, "create") @@ -642,8 +650,8 @@ class TestVnflcmDriver(db_base.SqlTestCase): def test_heal_vnf_without_vnfc_instance_infra_instantiate_vnf_fail(self, mock_log, mock_save, mock_vnf_resource_list, mock_resource_destroy, mock_resource_create, mock_vim, - mock_vnf_package_vnfd, mock_get_service_plugins, - mock_final_vnf_dict): + mock_vnf_package_vnfd, mock_make_final_vnf_dict, + mock_get_service_plugins, mock_final_vnf_dict): vnf_package_vnfd = fakes.return_vnf_package_vnfd() vnf_package_id = vnf_package_vnfd.package_uuid mock_vnf_package_vnfd.return_value = vnf_package_vnfd @@ -664,6 +672,7 @@ class TestVnflcmDriver(db_base.SqlTestCase): self._mock_vnf_manager(fail_method_name='instantiate_vnf') driver = vnflcm_driver.VnfLcmDriver() vnf_dict = {"fake": "fake_dict"} + mock_make_final_vnf_dict.return_value = {} self.assertRaises(exceptions.VnfHealFailed, driver.heal_vnf, self.context, vnf_instance, vnf_dict, heal_vnf_req) @@ -814,6 +823,10 @@ class TestVnflcmDriver(db_base.SqlTestCase): @mock.patch.object(driver_manager.DriverManager, "invoke") def test_scale_true(self, mock_invoke, mock_get_service_plugins): vnf_info = fakes._get_vnf() + vnf_info['attributes']['scale_group'] = '{\"scaleGroupDict\": ' + \ + '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ + '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ + '\"initialLevel\": 0, \"default\": 0 }}}' scale_vnf_request = fakes.scale_request("SCALE_IN", 1, "True") vim_connection_info = vim_connection.VimConnectionInfo( vim_type="fake_type") @@ -830,6 +843,10 @@ class TestVnflcmDriver(db_base.SqlTestCase): def test_scale_false_in(self, mock_invoke, mock_safe_load, mock_get_service_plugins): vnf_info = fakes._get_vnf() + vnf_info['attributes']['scale_group'] = '{\"scaleGroupDict\": ' + \ + '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ + '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ + '\"initialLevel\": 0, \"default\": 0 }}}' scale_vnf_request = fakes.scale_request("SCALE_IN", 1, "False") vim_connection_info = vim_connection.VimConnectionInfo( vim_type="fake_type") @@ -846,9 +863,36 @@ class TestVnflcmDriver(db_base.SqlTestCase): return_value={'VNFM': FakeVNFMPlugin()}) @mock.patch.object(yaml, "safe_load") @mock.patch.object(driver_manager.DriverManager, "invoke") - def test_scale_false_out(self, mock_invoke, mock_safe_load, - mock_get_service_plugins): + def test_scale_false_out_initial(self, mock_invoke, mock_safe_load, + mock_get_service_plugins): vnf_info = fakes._get_vnf() + vnf_info['attributes']['scale_group'] = '{\"scaleGroupDict\": ' + \ + '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ + '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ + '\"initialLevel\": 0, \"default\": 0 }}}' + scale_vnf_request = fakes.scale_request("SCALE_OUT", 1, "False") + vim_connection_info = vim_connection.VimConnectionInfo( + vim_type="fake_type") + scale_name_list = ["fake"] + grp_id = "fake_id" + with open(vnf_info["attributes"]["heat_template"], "r") as f: + mock_safe_load.return_value = yaml.safe_load(f) + print(mock_safe_load.return_value) + driver = vnflcm_driver.VnfLcmDriver() + driver.scale(self.context, vnf_info, scale_vnf_request, + vim_connection_info, scale_name_list, grp_id) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(yaml, "safe_load") + @mock.patch.object(driver_manager.DriverManager, "invoke") + def test_scale_false_out_level_up(self, mock_invoke, mock_safe_load, + mock_get_service_plugins): + vnf_info = fakes._get_vnf() + vnf_info['attributes']['scale_group'] = '{\"scaleGroupDict\": ' + \ + '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ + '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ + '\"initialLevel\": 0, \"default\": 1 }}}' scale_vnf_request = fakes.scale_request("SCALE_OUT", 1, "False") vim_connection_info = vim_connection.VimConnectionInfo( vim_type="fake_type") diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_vnfd.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_vnfd.yaml new file mode 100644 index 000000000..4f6bc7905 --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_vnfd.yaml @@ -0,0 +1,98 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: > + Template for test _generate_hot_from_tosca(). + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + +node_types: +topology_template: + node_templates: + VDU1: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 1 + sw_image_data: + name: Software of VDU1 + version: '0.4.0' + checksum: + algorithm: sha-256 + hash: b9c3036539fd7a5f87a1bf38eb05fdde8b556a1a7e664dbeda90ed3cd74b4f9d + container_format: bare + disk_format: qcow2 + min_disk: 1 GiB + size: 1 GiB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: Files/images/cirros-0.4.0-x86_64-disk.img + capabilities: + virtual_compute: + properties: + virtual_memory: + virtual_mem_size: 512 MiB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 1 GiB + requirements: + - virtual_storage: VB1 + + VB1: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 100 GB + rdma_enabled: true + sw_image_data: + name: cirros + version: '0.0.0' + checksum: + algorithm: sha512 + hash: f0fd1b50420dce4ca382ccfbb528eef3a38bbeff00b54e95e3876b9bafe7ed2d6f919ca35d9046d437c6d2d8698b1174a335fbd66035bb3edc525d2cdb187232 + container_format: bare + disk_format: qcow2 + min_disk: 0 B + min_ram: 0 B + size: 13267968 B + + CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + - virtual_link: VL3 + + VL3: + type: tosca.nodes.nfv.VnfVirtualLink + properties: + connectivity_type: + layer_protocols: [ ipv4 ] + description: Internal Virtual link in the VNF + vl_profile: + max_bitrate_requirements: + root: 1048576 + leaf: 1048576 + min_bitrate_requirements: + root: 1048576 + leaf: 1048576 + virtual_link_protocol_data: + - associated_layer_protocol: ipv4 + l3_protocol_data: + ip_version: ipv4 + cidr: 33.33.0.0/24 + + policies: + - policy_affinity_local_VDU1: + type: tosca.policies.nfv.AntiAffinityRule + targets: [ VDU1 ] + properties: + scope: zone diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_grant.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_grant.yaml new file mode 100644 index 000000000..1d58dc38a --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_grant.yaml @@ -0,0 +1,27 @@ +heat_template_version: 2016-10-14 +description: test +parameters: + DB11_image: + type: string + default: cirros +resources: + DB11: + type: OS::Nova::Server + properties: + user_data_format: SOFTWARE_CONFIG + availability_zone: nova + block_device_mapping_v2: + - device_name: vda + volume_id: {get_resource: ST1} + flavor: m1.tiny + networks: + - port: {get_resource: DB11-CP} + config_drive: false + DB11-CP: + type: OS::Neutron::Port + properties: + network: net_mgmt +outputs: + id-DB11: + value: + get_attr: [DB11-CP, fixed_ips, 0, ip_address] diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_grant.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_grant.yaml new file mode 100644 index 000000000..084b69b9e --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_grant.yaml @@ -0,0 +1,31 @@ +heat_template_version: 2013-05-23 +description: test +parameters: + current_num: + type: number + nfv: + type: json +resources: + SP1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + auto_scaling_group_id: {get_resource: SP1_group} + adjustment_type: change_in_capacity + scaling_adjustment: 1 + SP1_group: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 0 + desired_capacity: {get_param: current_num} + resource: + type: SP1_res.yaml + properties: + nfv: {get_param: nfv} + max_size: 3 + SP1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + auto_scaling_group_id: {get_resource: SP1_group} + adjustment_type: change_in_capacity + scaling_adjustment: -1 +outputs: {} diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_initial.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_initial.yaml new file mode 100644 index 000000000..084b69b9e --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_initial.yaml @@ -0,0 +1,31 @@ +heat_template_version: 2013-05-23 +description: test +parameters: + current_num: + type: number + nfv: + type: json +resources: + SP1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + auto_scaling_group_id: {get_resource: SP1_group} + adjustment_type: change_in_capacity + scaling_adjustment: 1 + SP1_group: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 0 + desired_capacity: {get_param: current_num} + resource: + type: SP1_res.yaml + properties: + nfv: {get_param: nfv} + max_size: 3 + SP1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + auto_scaling_group_id: {get_resource: SP1_group} + adjustment_type: change_in_capacity + scaling_adjustment: -1 +outputs: {} diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_nest_grant.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_nest_grant.yaml new file mode 100644 index 000000000..89a99848a --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_nest_grant.yaml @@ -0,0 +1,57 @@ +heat_template_version: 2013-05-23 +description: test +parameters: + DB11_image: + type: string + default: cirros + my_compute_placement_policy: + type: string + default: ssss +resources: + DB11: + type: OS::Nova::Server + properties: + user_data_format: SOFTWARE_CONFIG + availability_zone: nova + scheduler_hints: + group: {get_resource: my_compute_placement_policy} + block_device_mapping_v2: + - device_name: vda + volume_id: {get_resource: ENUMm0-VB} + flavor: m1.tiny + networks: + - port: {get_resource: DB11-CP} + config_drive: false + DB11-CP: + type: OS::Neutron::Port + properties: + network: {get_resource: DB11-VL} + DB12: + type: OS::Nova::Server + properties: + user_data_format: SOFTWARE_CONFIG + availability_zone: nova + image: cirros + flavor: m1.tiny + networks: + - port: {get_resource: DB12-CP} + config_drive: false + DB12-CP: + type: OS::Neutron::Port + properties: + network: {get_resource: DB11-VL} + DB12-VB: + properties: {image: cirros, size: \'1\'} + type: OS::Cinder::Volume + ENUMm0-VB: + properties: + image: {get_param: DB11_image} + size: \'1\' + type: OS::Cinder::Volume + DB12-CB: + properties: + instance_uuid: {get_resource: DB12} + mountpoint: /dev/vdb + volume_id: {get_resource: DB12-VB} + type: OS::Cinder::VolumeAttachment +outputs: {} diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_nest_initial.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_nest_initial.yaml new file mode 100644 index 000000000..1d58dc38a --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_scale_nest_initial.yaml @@ -0,0 +1,27 @@ +heat_template_version: 2016-10-14 +description: test +parameters: + DB11_image: + type: string + default: cirros +resources: + DB11: + type: OS::Nova::Server + properties: + user_data_format: SOFTWARE_CONFIG + availability_zone: nova + block_device_mapping_v2: + - device_name: vda + volume_id: {get_resource: ST1} + flavor: m1.tiny + networks: + - port: {get_resource: DB11-CP} + config_drive: false + DB11-CP: + type: OS::Neutron::Port + properties: + network: net_mgmt +outputs: + id-DB11: + value: + get_attr: [DB11-CP, fixed_ips, 0, ip_address] diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py index 974b75b5a..f2a68b3b5 100644 --- a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py @@ -23,8 +23,12 @@ import ddt import requests import yaml +from heatclient.v1 import resources +from oslo_serialization import jsonutils from tacker.common import exceptions +from tacker.common import utils as cutils from tacker import context +from tacker.db.db_sqlalchemy import models from tacker.extensions import vnfm from tacker import objects from tacker.tests.common import helpers @@ -39,6 +43,21 @@ from tacker.vnfm.infra_drivers.openstack import heat_client as hc from tacker.vnfm.infra_drivers.openstack import openstack +vnf_dict = { + 'instance_id': 'd1121d3c-368b-4ac2-b39d-835aa3e4ccd8' +} + + +class FakeAlarmPlugin(): + def add_alarm_url_to_vnf(self, context, vnf): + return + + +class FakePlugin(): + def get_vnf(self, context, id): + return vnf_dict + + @ddt.ddt class TestOpenStack(base.FixturedTestCase): client_fixture_class = client.ClientFixture @@ -144,7 +163,8 @@ class TestOpenStack(base.FixturedTestCase): mock_get_base_hot_dict, mock_get_vnflcm_interface, mock_format_base_hot): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -156,7 +176,7 @@ class TestOpenStack(base.FixturedTestCase): 'instantiate_vnf_request_lcm_userdata.json') inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} @@ -171,6 +191,105 @@ class TestOpenStack(base.FixturedTestCase): grant_info=grant_info_test, vnf_instance=vnf_instance) + @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack' + '.OpenStack._format_base_hot') + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') + @mock.patch('tacker.common.clients.OpenstackClients') + def test_create_grant(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict, + mock_format_base_hot): + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') + vnf['placement_attr'] = {'region_name': 'dummy_region'} + base_hot_dict_test = self._read_file() + vnf_package_path_test = os.path.abspath( + os.path.join(os.path.dirname(__file__), + "../../../../etc/samples/etsi/nfv", + "user_data_sample_normal")) + inst_req_info_test = type('', (), {}) + test_json = self._json_load( + 'instantiate_vnf_request_lcm_userdata.json') + inst_req_info_test.additional_params = test_json['additionalParams'] + inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = 'simple' + vnf_resource = type('', (), {}) + vnf_resource.resource_identifier = constants.INVALID_UUID + grant_info_test = {'vdu_name': {vnf_resource}} + nested_hot_dict = {'parameters': {'vnf': 'test'}} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vimAssets = {'compute_resource_flavours': [ + {'vim_connection_id': uuidsentinel.vim_id, + 'vnfd_virtual_compute_desc_id': 'VDU1', + 'vim_flavour_id': 'm1.tiny'}], + 'softwareImages': [ + {'vim_connection_id': uuidsentinel.vim_id, + 'vnfd_software_image_id': 'VDU1', + 'vim_software_image_id': 'cirros'}]} + resAddResource = [] + resource = { + 'resource_definition_id': '2c6e5cc7-240d-4458-a683-1fe648351280', + 'vim_connection_id': uuidsentinel.vim_id, + 'zone_id': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = { + 'resource_definition_id': 'faf14707-da7c-4eec-be99-8099fa1e9fa9', + 'vim_connection_id': uuidsentinel.vim_id, + 'zone_id': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = { + 'resource_definition_id': 'faf14707-da7c-4eec-be99-8099fa1e9fa0', + 'vim_connection_id': uuidsentinel.vim_id, + 'zone_id': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + resource = { + 'resource_definition_id': 'faf14707-da7c-4eec-be99-8099fa1e9fa1', + 'vim_connection_id': uuidsentinel.vim_id, + 'zone_id': '5e4da3c3-4a55-412a-b624-843921f8b51d'} + resAddResource.append(resource) + zone = { + 'id': '5e4da3c3-4a55-412a-b624-843921f8b51d', + 'zone_id': 'nova', + 'vim_connection_id': uuidsentinel.vim_id} + vim_obj = {'id': '0b9c66bb-9e1f-4bb2-92c3-913074e52e2b', + 'vim_id': uuidsentinel.vim_id, + 'vim_type': 'openstack', + 'access_info': { + 'password': 'test_pw', + 'username': 'test_user', + 'region': 'test_region', + 'tenant': uuidsentinel.tenant}} + grant_dict = {} + grant_dict['id'] = 'c213e465-8220-487e-9464-f79104e81e96' + grant_dict['vnf_instance_id'] = uuidsentinel.vnf_instance_id + grant_dict['vnf_lcm_op_occ_id'] = uuidsentinel.vnf_lcm_op_occ_id + grant_dict['add_resources'] = [] + grant_dict['add_resources'].extend(resAddResource) + grant_dict['vim_assets'] = vimAssets + grant_dict['zones'] = [zone] + grant_dict['vim_connections'] = [vim_obj] + grant_obj = objects.Grant.obj_from_primitive( + grant_dict, context=self.context) + vnf['grant'] = grant_obj + vnf_instance = fd_utils.get_vnf_instance_object() + vnf_instance.instantiated_vnf_info.reinitialize() + vnfc_obj = objects.VnfcResourceInfo() + vnfc_obj.id = '2c6e5cc7-240d-4458-a683-1fe648351280' + vnfc_obj.vdu_id = 'VDU1' + vnfc_obj.storage_resource_ids = \ + ['faf14707-da7c-4eec-be99-8099fa1e9fa0'] + compute_resource = objects.ResourceHandle( + vim_connection_id=uuidsentinel.vim_id, + resource_id='6e1c286d-c023-4b34-8369-831c6e84cce2') + vnfc_obj.compute_resource = compute_resource + vnf_instance.instantiated_vnf_info.vnfc_resource_info = [vnfc_obj] + self.openstack.create(self.plugin, self.context, vnf, + self.auth_attr, inst_req_info=inst_req_info_test, + vnf_package_path=vnf_package_path_test, + base_hot_dict=base_hot_dict_test, + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.common.clients.OpenstackClients') def test_create_heat_stack(self, mock_OpenstackClients_heat): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) @@ -236,7 +355,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_userdata_null(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -251,7 +371,7 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' grant_info_test = None nested_hot_dict = {'test': 'test'} mock_get_base_hot_dict.return_value = \ @@ -270,7 +390,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_userdataclass_null(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -285,7 +406,7 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' grant_info_test = None nested_hot_dict = {'test': 'test'} mock_get_base_hot_dict.return_value = \ @@ -304,7 +425,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_import_module_exception(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -316,7 +438,7 @@ class TestOpenStack(base.FixturedTestCase): 'instantiate_vnf_request_lcm_userdata.json') inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' grant_info_test = None nested_hot_dict = {'test': 'test'} mock_get_base_hot_dict.return_value = \ @@ -337,7 +459,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_getattr_none(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -370,7 +493,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_missing_file(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -382,7 +506,7 @@ class TestOpenStack(base.FixturedTestCase): 'instantiate_vnf_request_lcm_userdata.json') inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} @@ -403,7 +527,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_return_none_dict(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -417,7 +542,7 @@ class TestOpenStack(base.FixturedTestCase): 'UserData/lcm_user_data_non_dict.py' inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} @@ -438,7 +563,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_none_base_hot_dict(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} inst_req_info_test = type('', (), {}) test_json = self._json_load( @@ -465,7 +591,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_invalid_user_data(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -481,7 +608,7 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' grant_info_test = None nested_hot_dict = {'test': 'test'} mock_get_base_hot_dict.return_value = \ @@ -501,7 +628,8 @@ class TestOpenStack(base.FixturedTestCase): def test_create_invalid_user_data_class(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -517,7 +645,7 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' grant_info_test = None nested_hot_dict = {'test': 'test'} mock_get_base_hot_dict.return_value = \ @@ -536,7 +664,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_lcm_user_data_and_user_data_class_no_value(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -553,7 +682,7 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = test_json['extVirtualLinks'] - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} @@ -620,7 +749,8 @@ class TestOpenStack(base.FixturedTestCase): @mock.patch('tacker.common.clients.OpenstackClients') def test_create_instance_exception(self, mock_OpenstackClients_heat, mock_get_base_hot_dict): - vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + flavour='simple') vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() vnf_package_path_test = os.path.abspath( @@ -634,7 +764,7 @@ class TestOpenStack(base.FixturedTestCase): 'UserData/lcm_user_data_invalid_script.py' inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None - inst_req_info_test.flavour_id = test_json['flavourId'] + inst_req_info_test.flavour_id = 'simple' vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} @@ -1633,3 +1763,190 @@ class TestOpenStack(base.FixturedTestCase): "stack_id %s") % (vnf_instance.id, uuidsentinel.stack_id) self.assertEqual(expected_msg, str(result)) + + def test_get_grant_resource_scale_out(self): + vnfc_resource_info = fd_utils.get_vnfc_resource_info() + + inst_vnf_info = fd_utils.get_vnf_instantiated_info( + vnfc_resource_info=[vnfc_resource_info]) + + vnf_instance = fd_utils.get_vnf_instance_object( + instantiated_vnf_info=inst_vnf_info) + + vim_connection_info = fd_utils.get_vim_connection_info_object() + scale_vnf_request = objects.ScaleVnfRequest(type='SCALE_OUT', + aspect_id='SP1', + number_of_steps=1) + test_res = '[{"id_type": "RES_MGMT", "resource_id": ' + \ + '"2c6e5cc7-240d-4458-a683-1fe648351200",' + \ + ' "vim_connection_id": ' + \ + '"2a63bee3-0c43-4568-bcfa-b0cb733e064c"}]' + placemnt = models.PlacementConstraint( + id='c2947d8a-2c67-4e8f-ad6f-c0889b351c17', + vnf_instance_id=uuidsentinel.vnf_instance_id, + affinity_or_anti_affinity='ANTI_AFFINITY', + scope='ZONE', + server_group_name='my_compute_placement_policy', + resource=test_res) + placement_obj_list = [placemnt] + del_list = [] + vnf_info = {} + vnf_info['attributes'] = {} + vnf_info['attributes']['scale_group'] = '{\"scaleGroupDict\": ' + \ + '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ + '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ + '\"initialLevel\": 0, \"default\": 0 }}}' + vnf_info['attributes']['heat_template'] = utils.\ + get_dummy_scale_grant_hot() + vnf_info['attributes']['SP1_res.yaml'] = utils.\ + get_dummy_scale_nest_grant_hot() + self.openstack.get_grant_resource( + vnf_instance, + vnf_info, + scale_vnf_request, + placement_obj_list, + vim_connection_info, + del_list) + self.assertEqual(1, len(vnf_info['placement_constraint_list'])) + + @mock.patch.object(hc.HeatClient, "resource_get_list") + def test_get_grant_resource_scale_in(self, mock_list): + v_s_resource_info = fd_utils.get_virtual_storage_resource_info( + desc_id="storage1", set_resource_id=False) + + storage_resource_ids = [v_s_resource_info.id] + vnfc_resource_info = fd_utils.get_vnfc_resource_info(vdu_id="VDU_VNF", + storage_resource_ids=storage_resource_ids, set_resource_id=False) + + v_l_resource_info = fd_utils.get_virtual_link_resource_info( + vnfc_resource_info.vnfc_cp_info[0].vnf_link_port_id, + vnfc_resource_info.vnfc_cp_info[0].id) + + inst_vnf_info = fd_utils.get_vnf_instantiated_info( + virtual_storage_resource_info=[v_s_resource_info], + vnf_virtual_link_resource_info=[v_l_resource_info], + vnfc_resource_info=[vnfc_resource_info]) + + vnf_instance = fd_utils.get_vnf_instance_object( + instantiated_vnf_info=inst_vnf_info) + + vim_connection_info = fd_utils.get_vim_connection_info_object() + scale_vnf_request = objects.ScaleVnfRequest(type='SCALE_IN', + aspect_id='SP1', + number_of_steps=1) + placement_obj_list = [] + del_list = ['58337c61-3148-4c29-892f-68b043c009ea'] + vnf_info = {} + res_list = [] + vdu_id = uuidsentinel.vdu_resource_id + vnf_instance.instantiated_vnf_info.vnfc_resource_info[0].\ + compute_resource.resource_id = vdu_id + resource2 = resources.Resource(None, { + 'resource_name': 'aaaaaaaa', + 'resource_type': 'OS::Nova::Server', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': vdu_id, + 'id': '1111' + }) + res_list.append(resource2) + port_id = uuidsentinel.virtual_link_port_resource_id + vnf_instance.instantiated_vnf_info.vnf_virtual_link_resource_info[ + 0].vnf_link_ports[0].resource_handle.resource_id = port_id + resource3 = resources.Resource(None, { + 'resource_name': 'bbbbbbbb', + 'resource_type': 'OS::Neutron::Port', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': port_id, + 'id': '1111' + }) + res_list.append(resource3) + st_id = uuidsentinel.vdu_resource_id + vnf_instance.instantiated_vnf_info.virtual_storage_resource_info[ + 0].storage_resource.resource_id = st_id + resource4 = resources.Resource(None, { + 'resource_name': 'cccccccc', + 'resource_type': 'OS::Cinder::Volume', + 'creation_time': '2020-01-01T00:00:01', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': st_id, + 'id': '1111' + }) + res_list.append(resource4) + mock_list.return_value = res_list + self.openstack.get_grant_resource( + vnf_instance, + vnf_info, + scale_vnf_request, + placement_obj_list, + vim_connection_info, + del_list) + self.assertEqual(3, len(vnf_info['removeResources'])) + + @mock.patch.object(hc.HeatClient, "update") + def test_scale_out_initial(self, mock_update): + scale_vnf_request = objects.ScaleVnfRequest(type='SCALE_OUT', + aspect_id='SP1', + number_of_steps=1) + vnf_info = {} + vnf_info['attributes'] = {} + vnf_info['attributes']['scale_group'] = '{\"scaleGroupDict\": ' + \ + '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ + '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ + '\"initialLevel\": 0, \"default\": 0 }}}' + vnf_info['attributes']['heat_template'] = \ + utils.get_dummy_scale_initial_hot() + vnf_info['attributes']['SP1_res.yaml'] = \ + utils.get_dummy_scale_nest_initial_hot() + stack_param_dict = {} + stack_param_dict['nfv'] = {} + stack_param_dict['nfv']['VDU'] = {} + stack_param_dict['nfv']['VDU']['VDU1'] = {} + stack_param_dict['nfv']['VDU']['VDU1']['zone'] = '' + stack_param_dict['nfv']['VDU']['VDU1']['flavor'] = '' + stack_param_dict['nfv']['VDU']['VDU1']['image'] = '' + vnf_info['attributes'].update({'stack_param': str(stack_param_dict)}) + vnf_info['instance_id'] = uuidsentinel.stack_id + testjson = '{"id": "c213e465-8220-487e-9464-f79104e81e96", ' + \ + '"vnf_instance_id": ' + \ + '"47101fb6-bd18-4e04-b2b5-22370a023448", ' + \ + '"vnf_lcm_op_occ_id": ' + \ + '"f26f181d-7891-4720-b022-b074ec1733ef", ' + \ + '"add_resources": [{"resource_definition_id": ' + \ + '"2c6e5cc7-240d-4458-a683-1fe648351280", ' + \ + '"vim_connection_id": ' + \ + '"b6eacd1b-5a9e-41ea-a33b-9d7196cd9187", ' + \ + '"zone_id": "5e4da3c3-4a55-412a-b624-843921f8b51d"}' + \ + ', {"resource_definition_id": ' + \ + '"faf14707-da7c-4eec-be99-8099fa1e9fa9", ' + \ + '"vim_connection_id": ' + \ + '"b6eacd1b-5a9e-41ea-a33b-9d7196cd9187", ' + \ + '"zone_id": "5e4da3c3-4a55-412a-b624-843921f8b51d"}' + \ + ', {"resource_definition_id": ' + \ + '"faf14707-da7c-4eec-be99-8099fa1e9fa9", ' + \ + '"vim_connection_id": ' + \ + '"b6eacd1b-5a9e-41ea-a33b-9d7196cd9187", ' + \ + '"zone_id": ' + \ + '"5e4da3c3-4a55-412a-b624-843921f8b51d"}], ' + \ + '"vim_assets": {"compute_resource_flavours": ' + \ + '[{"vim_connection_id": ' + \ + '"b6eacd1b-5a9e-41ea-a33b-9d7196cd9187", ' + \ + '"vnfd_virtual_compute_desc_id": "VDU1", ' + \ + '"vim_flavour_id": "m1.tiny"}], "software_images": ' + \ + '[{"vim_connection_id": ' + \ + '"b6eacd1b-5a9e-41ea-a33b-9d7196cd9187", ' + \ + '"vnfd_software_image_id": "VDU1", ' + \ + '"vim_software_image_id": "cirros"}]}}' + res_body = jsonutils.loads(testjson) + res_dict = cutils.convert_camelcase_to_snakecase(res_body) + grant_obj = objects.Grant.obj_from_primitive( + res_dict, context=context) + vnf_info['grant'] = grant_obj + self.openstack.scale_out_initial(context=self.context, + plugin=self, + auth_attr=None, + vnf_info=vnf_info, + scale_vnf_request=scale_vnf_request, + region_name=None + ) diff --git a/tacker/tests/unit/vnfm/test_plugin.py b/tacker/tests/unit/vnfm/test_plugin.py index 5678f08f1..b9f58d5b6 100644 --- a/tacker/tests/unit/vnfm/test_plugin.py +++ b/tacker/tests/unit/vnfm/test_plugin.py @@ -18,6 +18,7 @@ from unittest import mock from unittest.mock import patch import ddt +import iso8601 from oslo_utils import uuidutils import yaml @@ -25,12 +26,15 @@ from tacker._i18n import _ from tacker.common import exceptions from tacker import context from tacker.db.common_services import common_services_db_plugin +from tacker.db.db_sqlalchemy import models from tacker.db.nfvo import nfvo_db from tacker.db.nfvo import ns_db from tacker.db.vnfm import vnfm_db from tacker.extensions import vnfm +from tacker import objects from tacker.objects import heal_vnf_request from tacker.plugins.common import constants +from tacker.tests.unit.conductor import fakes from tacker.tests.unit.db import base as db_base from tacker.tests.unit.db import utils from tacker.vnfm import monitor @@ -1160,3 +1164,165 @@ class TestVNFMPlugin(db_base.SqlTestCase): mock_get_vnf.return_value = dummy_vnf self._test_create_vnf_trigger(policy_name="start_actions", action_value="SP_RSV-out") + + def test_create_placement_constraint(self): + res_str = '[{"id_type": "RES_MGMT", "resource_id": ' + \ + '"2c6e5cc7-240d-4458-a683-1fe648351200", ' + \ + '"vim_connection_id": ' + \ + '"2a63bee3-0c43-4568-bcfa-b0cb733e064c"}]' + placemnt = models.PlacementConstraint( + id='c2947d8a-2c67-4e8f-ad6f-c0889b351c17', + vnf_instance_id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + affinity_or_anti_affinity='ANTI_AFFINITY', + scope='ZONE', + server_group_name='my_compute_placement_policy', + resource=res_str, + deleted_at=datetime.min) + pls_list = [placemnt] + vnf_inst = models.VnfInstance( + id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + vnf_provider=' ', + vnf_product_name=' ', + vnf_software_version=' ', + vnfd_version=' ', + vnfd_id='8d86480e-d4e6-4ee0-ba4d-08217118d6cb', + instantiation_state=' ', + tenant_id='9b3f0518-bf6b-4982-af32-d282ce577c8f', + created_at=datetime( + 2020, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_pkg_id=uuidutils.generate_uuid()) + self.context.session.add(vnf_inst) + self.context.session.flush() + + self.vnfm_plugin.create_placement_constraint( + self.context, pls_list) + + def test_get_placement_constraint(self): + res_str = '[{"id_type": "RES_MGMT", "resource_id": ' + \ + '"2c6e5cc7-240d-4458-a683-1fe648351200", ' + \ + '"vim_connection_id": ' + \ + '"2a63bee3-0c43-4568-bcfa-b0cb733e064c"}]' + placemnt = models.PlacementConstraint( + id='c2947d8a-2c67-4e8f-ad6f-c0889b351c17', + vnf_instance_id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + affinity_or_anti_affinity='ANTI_AFFINITY', + scope='ZONE', + server_group_name='my_compute_placement_policy', + resource=res_str, + deleted_at=datetime.min) + vnf_inst = models.VnfInstance( + id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + vnf_provider=' ', + vnf_product_name=' ', + vnf_software_version=' ', + vnfd_version=' ', + vnfd_id='8d86480e-d4e6-4ee0-ba4d-08217118d6cb', + instantiation_state=' ', + tenant_id='9b3f0518-bf6b-4982-af32-d282ce577c8f', + created_at=datetime( + 2020, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_pkg_id=uuidutils.generate_uuid()) + self.context.session.add(vnf_inst) + self.context.session.flush() + self.context.session.add(placemnt) + self.context.session.flush() + + res = self.vnfm_plugin.get_placement_constraint( + self.context, '7ddc38c3-a116-48b0-bfc1-68d7f306f467') + self.assertEqual(1, len(res)) + + def test_update_placement_constraint_heal(self): + res_str = '[{"id_type": "RES_MGMT", "resource_id": ' + \ + '"2c6e5cc7-240d-4458-a683-1fe648351200", ' + \ + '"vim_connection_id": ' + \ + '"2a63bee3-0c43-4568-bcfa-b0cb733e064c"}]' + placemnt = models.PlacementConstraint( + id='c2947d8a-2c67-4e8f-ad6f-c0889b351c17', + vnf_instance_id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + affinity_or_anti_affinity='ANTI_AFFINITY', + scope='ZONE', + server_group_name='my_compute_placement_policy', + resource=res_str, + deleted_at=datetime.min) + res_str2 = '[{"id_type": "GRANT", "resource_id": ' + \ + '"4cef1b7e-8e5f-430e-b32e-a7585a61d61c"}]' + placemnt2 = models.PlacementConstraint( + id='c2947d8a-2c67-4e8f-ad6f-c0889b351c17', + vnf_instance_id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + affinity_or_anti_affinity='ANTI_AFFINITY', + scope='ZONE', + server_group_name='my_compute_placement_policy', + resource=res_str2, + deleted_at=datetime.min) + vnf_inst = models.VnfInstance( + id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + vnf_provider=' ', + vnf_product_name=' ', + vnf_software_version=' ', + vnfd_version=' ', + vnfd_id='8d86480e-d4e6-4ee0-ba4d-08217118d6cb', + instantiation_state=' ', + tenant_id='9b3f0518-bf6b-4982-af32-d282ce577c8f', + created_at=datetime( + 2020, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_pkg_id=uuidutils.generate_uuid()) + self.context.session.add(vnf_inst) + self.context.session.flush() + self.context.session.add(placemnt) + self.context.session.flush() + + vnf_info = {} + vnf_info['grant'] = objects.Grant() + placement_obj_list = [] + placement_obj_list.append(placemnt2) + vnf_info['placement_obj_list'] = placement_obj_list + + insta = fakes.return_vnf_instance('INSTANTIATED') + vnfc = objects.VnfcResourceInfo() + vnfc.id = '4cef1b7e-8e5f-430e-b32e-a7585a61d61c' + vnfc.vdu_id = 'VDU1' + c_rsc = objects.ResourceHandle() + c_rsc.vim_connection_id = '2a63bee3-0c43-4568-bcfa-b0cb733e064c' + c_rsc.resource_id = '9aa1e075-2aa1-46ce-a27a-35a581190219' + vnfc.compute_resource = c_rsc + insta.instantiated_vnf_info.vnfc_resource_info.append(vnfc) + + self.vnfm_plugin.update_placement_constraint_heal( + self.context, vnf_info, insta) + + def test_delete_placement_constraint(self): + res_str = '[{"id_type": "RES_MGMT", "resource_id": ' + \ + '"2c6e5cc7-240d-4458-a683-1fe648351200", ' + \ + '"vim_connection_id": ' + \ + '"2a63bee3-0c43-4568-bcfa-b0cb733e064c"}]' + placemnt = models.PlacementConstraint( + id='c2947d8a-2c67-4e8f-ad6f-c0889b351c17', + vnf_instance_id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + affinity_or_anti_affinity='ANTI_AFFINITY', + scope='ZONE', + server_group_name='my_compute_placement_policy', + resource=res_str, + deleted_at=datetime.min) + vnf_inst = models.VnfInstance( + id='7ddc38c3-a116-48b0-bfc1-68d7f306f467', + vnf_provider=' ', + vnf_product_name=' ', + vnf_software_version=' ', + vnfd_version=' ', + vnfd_id='8d86480e-d4e6-4ee0-ba4d-08217118d6cb', + instantiation_state=' ', + tenant_id='9b3f0518-bf6b-4982-af32-d282ce577c8f', + created_at=datetime( + 2020, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_pkg_id=uuidutils.generate_uuid()) + self.context.session.add(vnf_inst) + self.context.session.flush() + self.context.session.add(placemnt) + self.context.session.flush() + + self.vnfm_plugin.delete_placement_constraint( + self.context, '7ddc38c3-a116-48b0-bfc1-68d7f306f467') diff --git a/tacker/vnflcm/utils.py b/tacker/vnflcm/utils.py index dae900055..169679c1b 100644 --- a/tacker/vnflcm/utils.py +++ b/tacker/vnflcm/utils.py @@ -95,9 +95,11 @@ def _get_vnflcm_interface(context, interface, vnf_instance, flavour_id): if vnfd_dict.get('topology_template'): topology_template = vnfd_dict.get('topology_template') if topology_template.get('node_templates'): - for a_val in topology_template.get('node_templates').values(): - if 'interfaces' in a_val.keys(): - interfaces = a_val.get('interfaces') + node_templates = topology_template.get('node_templates') + if node_templates.get('VNF'): + vnf = node_templates.get('VNF') + if vnf.get('interfaces'): + interfaces = vnf.get('interfaces') if interfaces.get('Vnflcm'): vnflcm = interfaces.get('Vnflcm') if vnflcm: diff --git a/tacker/vnflcm/vnflcm_driver.py b/tacker/vnflcm/vnflcm_driver.py index 401e05950..280db96e1 100644 --- a/tacker/vnflcm/vnflcm_driver.py +++ b/tacker/vnflcm/vnflcm_driver.py @@ -276,7 +276,8 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): try: instance_id = self._vnf_manager.invoke( vim_connection_info.vim_type, 'instantiate_vnf', - context=context, vnf_instance=vnf_instance, + context=context, plugin=self._vnfm_plugin, + vnf_instance=vnf_instance, vnfd_dict=final_vnf_dict, grant_response=vnf_resources, vim_connection_info=vim_connection_info, base_hot_dict=base_hot_dict, @@ -293,12 +294,9 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): id=vnf_instance.id, error=encodeutils.exception_to_unicode(exp)) - vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo( - flavour_id=instantiate_vnf_req.flavour_id, - instantiation_level_id=instantiate_vnf_req.instantiation_level_id, - vnf_instance_id=vnf_instance.id, - instance_id=instance_id, - ext_cp_info=[]) + if vnf_instance.instantiated_vnf_info and\ + not vnf_instance.instantiated_vnf_info.instance_id: + vnf_instance.instantiated_vnf_info.instance_id = instance_id if vnf_dict['attributes'].get('scaling_group_names'): vnf_instance.instantiated_vnf_info.scale_status = \ vnf_dict['scale_status'] @@ -306,7 +304,7 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): try: self._vnf_manager.invoke( vim_connection_info.vim_type, 'create_wait', - plugin=self, context=context, + plugin=self._vnfm_plugin, context=context, vnf_dict=final_vnf_dict, vnf_id=final_vnf_dict['instance_id'], auth_attr=vim_connection_info.access_info) @@ -322,14 +320,6 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): id=vnf_instance.id, error=encodeutils.exception_to_unicode(exp)) - vnflcm_utils._build_instantiated_vnf_info(vnfd_dict, - instantiate_vnf_req, vnf_instance, vim_connection_info.vim_id) - - self._vnf_manager.invoke(vim_connection_info.vim_type, - 'post_vnf_instantiation', context=context, - vnf_instance=vnf_instance, - vim_connection_info=vim_connection_info) - @log.log @rollback_vnf_instantiated_resources def instantiate_vnf(self, context, vnf_instance, vnf_dict, @@ -351,10 +341,6 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): self._instantiate_vnf(context, vnf_instance, vnf_dict, vim_connection_info, instantiate_vnf_req) - self._vnf_instance_update(context, vnf_instance, - instantiation_state=fields.VnfInstanceState.INSTANTIATED, - task_state=None) - @log.log @revert_to_error_task_state def terminate_vnf(self, context, vnf_instance, terminate_vnf_req): @@ -929,7 +915,12 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): LOG.debug( "is_reverse: %s", scale_vnf_request.additional_params.get('is_reverse')) - if scale_vnf_request.additional_params['is_reverse'] == 'True': + scale_json = vnf_info['attributes']['scale_group'] + scaleGroupDict = jsonutils.loads(scale_json) + key_aspect = scale_vnf_request.aspect_id + default = scaleGroupDict['scaleGroupDict'][key_aspect]['default'] + if (scale_vnf_request.type == 'SCALE_IN' and + scale_vnf_request.additional_params['is_reverse'] == 'True'): self._vnf_manager.invoke( vim_connection_info.vim_type, 'scale_in_reverse', @@ -944,7 +935,27 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): ) self._vnf_manager.invoke( vim_connection_info.vim_type, - 'scale_in_reverse_wait', + 'scale_update_wait', + plugin=self, + context=context, + auth_attr=vim_connection_info.access_info, + vnf_info=vnf_info, + region_name=vim_connection_info.access_info.get('region_name') + ) + elif scale_vnf_request.type == 'SCALE_OUT' and default == 0: + self._vnf_manager.invoke( + vim_connection_info.vim_type, + 'scale_out_initial', + plugin=self, + context=context, + auth_attr=vim_connection_info.access_info, + vnf_info=vnf_info, + scale_vnf_request=scale_vnf_request, + region_name=vim_connection_info.access_info.get('region_name') + ) + self._vnf_manager.invoke( + vim_connection_info.vim_type, + 'scale_update_wait', plugin=self, context=context, auth_attr=vim_connection_info.access_info, diff --git a/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py b/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py index 8ed25e783..8917c0d37 100644 --- a/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py +++ b/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py @@ -1350,10 +1350,32 @@ class Kubernetes(abstract_driver.VnfAbstractDriver, grp_id): pass - def scale_in_reverse_wait(self, + def scale_out_initial(self, + context, + plugin, + auth_attr, + vnf_info, + scale_vnf_request, + region_name): + pass + + def scale_update_wait(self, context, plugin, auth_attr, vnf_info, region_name): pass + + def get_cinder_list(self, + vnf_info): + pass + + def get_grant_resource(self, + vnf_instance, + vnf_info, + scale_vnf_request, + placement_obj_list, + vim_connection_info, + del_list): + pass diff --git a/tacker/vnfm/infra_drivers/openstack/openstack.py b/tacker/vnfm/infra_drivers/openstack/openstack.py index a28b6a0f0..d199a6bf6 100644 --- a/tacker/vnfm/infra_drivers/openstack/openstack.py +++ b/tacker/vnfm/infra_drivers/openstack/openstack.py @@ -34,10 +34,12 @@ from tacker._i18n import _ from tacker.common import exceptions from tacker.common import log from tacker.common import utils +from tacker.db.common_services import common_services_db_plugin from tacker.extensions import vnflcm from tacker.extensions import vnfm from tacker import objects from tacker.objects import fields +from tacker.plugins.common import constants from tacker.tosca.utils import represent_odict from tacker.vnflcm import utils as vnflcm_utils from tacker.vnfm.infra_drivers import abstract_driver @@ -52,6 +54,11 @@ from tacker.vnfm.lcm_user_data.constants import USER_DATA_TIMEOUT eventlet.monkey_patch(time=True) +SCALING_GROUP_RESOURCE = "OS::Heat::AutoScalingGroup" +NOVA_SERVER_RESOURCE = "OS::Nova::Server" + +VNF_PACKAGE_HOT_DIR = 'Files' + LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -108,6 +115,9 @@ class OpenStack(abstract_driver.VnfAbstractDriver, self.STACK_RETRY_WAIT = cfg.CONF.openstack_vim.stack_retry_wait self.IMAGE_RETRIES = 10 self.IMAGE_RETRY_WAIT = 10 + self.LOCK_RETRIES = 10 + self.LOCK_RETRY_WAIT = 10 + self._cos_db_plg = common_services_db_plugin.CommonServicesPluginDb() def get_type(self): return 'openstack' @@ -124,7 +134,17 @@ class OpenStack(abstract_driver.VnfAbstractDriver, inst_req_info=None, grant_info=None, vnf_instance=None): LOG.debug('vnf %s', vnf) - region_name = vnf.get('placement_attr', {}).get('region_name', None) + if vnf.get('grant'): + if vnf['grant'].vim_connections: + vim_con = vnf['grant'].vim_connections[0] + auth_attr = vim_con.access_info + region_name = auth_attr.get('region') + else: + region_name = vnf.get('placement_attr', {}).\ + get('region_name', None) + else: + region_name = vnf.get('placement_attr', {}).\ + get('region_name', None) heatclient = hc.HeatClient(auth_attr, region_name) additional_param = None if inst_req_info is not None: @@ -157,7 +177,8 @@ class OpenStack(abstract_driver.VnfAbstractDriver, for name, hot in nested_hot_dict.items(): vnf['attributes'][name] = self._format_base_hot(hot) - vnfd_str = vnf['vnfd']['attributes']['vnfd'] + vnfd_str = vnf['vnfd']['attributes']['vnfd_' + + inst_req_info.flavour_id] vnfd_dict = yaml.safe_load(vnfd_str) LOG.debug('VNFD: %s', vnfd_dict) LOG.debug('VNF package path: %s', vnf_package_path) @@ -220,6 +241,30 @@ class OpenStack(abstract_driver.VnfAbstractDriver, for name, value in scaleGroupDict['scaleGroupDict'].items(): hot_param_dict[name + '_desired_capacity'] = \ value['default'] + if vnf.get('grant'): + grant = vnf['grant'] + ins_inf = vnf_instance.instantiated_vnf_info.vnfc_resource_info + for addrsc in grant.add_resources: + for zone in grant.zones: + if zone.id == addrsc.zone_id: + vdu_name = None + for rsc in ins_inf: + if addrsc.resource_definition_id == rsc.id: + vdu_name = rsc.vdu_id + break + if not vdu_name: + continue + hot_param_dict['nfv']['VDU'][vdu_name]['zone'] = \ + zone.zone_id + if 'vim_assets' in grant and grant.vim_assets: + for flavour in grant.vim_assets.compute_resource_flavours: + vdu_name = flavour.vnfd_virtual_compute_desc_id + hot_param_dict['nfv']['VDU'][vdu_name]['flavor'] = \ + flavour.vim_flavour_id + for image in grant.vim_assets.software_images: + vdu_name = image.vnfd_software_image_id + hot_param_dict['nfv']['VDU'][vdu_name]['image'] = \ + image.vim_software_image_id # Add stack param to vnf_attributes vnf['attributes'].update({'stack_param': str(hot_param_dict)}) @@ -1482,15 +1527,63 @@ class OpenStack(abstract_driver.VnfAbstractDriver, mark_unhealthy=True, resource_status_reason='Scale') paramDict = {} - paramDict[scale_vnf_request.aspect_id + - '_desired_capacity'] = vnf_info['res_num'] + scale_json = vnf_info['attributes']['scale_group'] + scaleGroupDict = jsonutils.loads(scale_json) + for name, value in scaleGroupDict['scaleGroupDict'].items(): + paramDict[name + '_desired_capacity'] = value['default'] + paramDict[scale_vnf_request.aspect_id + '_desired_capacity'] = \ + vnf_info['res_num'] stack_update_param = { 'parameters': paramDict, 'existing': True} heatclient.update(vnf_info['instance_id'], **stack_update_param) + stack_param = jsonutils.loads(vnf_info['attributes']['stack_param']) + stack_param.update(paramDict) + vnf_info['attributes'].update({'stack_param': str(paramDict)}) @log.log - def scale_in_reverse_wait( + def scale_out_initial(self, context, plugin, auth_attr, vnf_info, + scale_vnf_request, region_name): + scale_json = vnf_info['attributes']['scale_group'] + scaleGroupDict = jsonutils.loads(scale_json) + key_aspect = scale_vnf_request.aspect_id + num = scaleGroupDict['scaleGroupDict'][key_aspect]['num'] + vnf_info['res_num'] = num * scale_vnf_request.number_of_steps + heatclient = hc.HeatClient(auth_attr, region_name) + paramDict = {} + for name, value in scaleGroupDict['scaleGroupDict'].items(): + paramDict[name + '_desired_capacity'] = value['default'] + paramDict[scale_vnf_request.aspect_id + + '_desired_capacity'] = vnf_info['res_num'] + stack_param = yaml.safe_load(vnf_info['attributes']['stack_param']) + grant = vnf_info['grant'] + for addrsc in grant.add_resources: + for zone in grant.zones: + if zone.id == addrsc.zone_id: + for rsc in vnf_info['addResources']: + if addrsc.id == rsc.id: + vdu_name = rsc.vdu_id + break + stack_param['nfv']['VDU'][vdu_name]['zone'] = zone.zone_id + if 'vim_assets' in grant and grant.vim_assets: + for flavour in grant.vim_assets.compute_resource_flavours: + vdu_name = flavour.vnfd_virtual_compute_desc_id + stack_param['nfv']['VDU'][vdu_name]['flavor'] = \ + flavour.vim_flavour_id + for image in grant.vim_assets.software_images: + vdu_name = image.vnfd_software_image_id + stack_param['nfv']['VDU'][vdu_name]['image'] = \ + image.vim_software_image_id + + paramDict['nfv'] = stack_param['nfv'] + stack_update_param = { + 'parameters': paramDict, + 'existing': True} + heatclient.update(vnf_info['instance_id'], **stack_update_param) + vnf_info['attributes'].update({'stack_param': str(paramDict)}) + + @log.log + def scale_update_wait( self, context, plugin, @@ -1501,3 +1594,251 @@ class OpenStack(abstract_driver.VnfAbstractDriver, auth_attr, infra_cnst.STACK_UPDATE_IN_PROGRESS, infra_cnst.STACK_UPDATE_COMPLETE, vnfm.VNFScaleWaitFailed, region_name=region_name) + + def get_cinder_list(self, vnf_info): + cinder_list = [] + block_key = 'block_device_mapping_v2' + if not vnf_info['attributes'].get('scale_group'): + heat_yaml = vnf_info['attributes']['heat_template'] + heat_dict = yaml.safe_load(heat_yaml) + for resource_name, resource in heat_dict['resources'].items(): + if resource.get('properties') and resource.get( + 'properties').get(block_key): + for cinder in resource['properties'][block_key]: + if cinder['volume_id'].get('get_resource'): + cinder_list.append( + cinder['volume_id']['get_resource']) + else: + for resource_name, resource in vnf_info['attributes'].items(): + if '.yaml' in resource_name: + heat_dict = yaml.safe_load(resource) + for resource_name, resource in heat_dict['resources'].\ + items(): + if resource.get('properties') and resource.get( + 'properties').get(block_key): + for cinder in resource['properties'][block_key]: + if cinder['volume_id'].get('get_resource'): + cinder_list.append( + cinder['volume_id']['get_resource']) + return cinder_list + + def get_grant_resource( + self, + vnf_instance, + vnf_info, + scale_vnf_request, + placement_obj_list, + vim_connection_info, + del_list): + if scale_vnf_request.type == 'SCALE_OUT': + self._get_grant_resource_scale_out(vnf_info, + scale_vnf_request, + placement_obj_list) + else: + self.get_grant_resource_scale_in(vnf_instance, + vnf_info, + vim_connection_info, + del_list) + + def _get_grant_resource_scale_out( + self, + vnf_info, + scale_vnf_request, + placement_obj_list): + add_resources = [] + affinity_list = [] + placement_constraint_list = [] + uuid_list = [] + port_uuid_list = [] + storage_uuid_list = [] + heat_template = vnf_info['attributes']['heat_template'] + heat_resource = yaml.safe_load(heat_template) + key_vnfd = scale_vnf_request.aspect_id + '_scale_out' + ajust_prop = heat_resource['resources'][key_vnfd]['properties'] + ajust = ajust_prop['scaling_adjustment'] + size = ajust * scale_vnf_request.number_of_steps + yaml_name = scale_vnf_request.aspect_id + '_res.yaml' + if not vnf_info['attributes'].get(yaml_name): + yaml_name = scale_vnf_request.aspect_id + '.hot.yaml' + nested_hot = yaml.safe_load( + vnf_info['attributes'][yaml_name]) + for resource_name, resource in nested_hot['resources'].items(): + if resource['type'] == 'OS::Nova::Server': + for i in range(size): + add_uuid = uuidutils.generate_uuid() + rsc = objects.ResourceDefinition( + id=add_uuid, + type=constants.TYPE_COMPUTE, + vdu_id=resource_name, + resource_template_id=resource_name) + add_resources.append(rsc) + uuid_list.append(add_uuid) + for net in resource.get('networks', []): + add_uuid = uuidutils.generate_uuid() + port_rsc = net['port']['get_resource'] + rsc = objects.ResourceDefinition( + id=add_uuid, + type=constants.TYPE_LINKPORT, + vdu_id=resource_name, + resource_template_id=port_rsc) + add_resources.append(rsc) + port_uuid_list.append(add_uuid) + if resource['properties'].get('block_device_mapping_v2'): + for i in range(size): + for cinder in resource['properties'].get( + 'block_device_mapping_v2', []): + add_uuid = uuidutils.generate_uuid() + vol_rsc = cinder['volume_id']['get_resource'] + rsc = objects.ResourceDefinition( + id=add_uuid, + type=constants.TYPE_STORAGE, + vdu_id=resource_name, + resource_template_id=vol_rsc) + add_resources.append(rsc) + storage_uuid_list.append(add_uuid) + if resource['properties'].get('scheduler_hints'): + sch_hint = resource['properties']['scheduler_hints'] + if sch_hint['group'].get('get_param'): + affinity_name = sch_hint['group']['get_param'] + else: + affinity_name = sch_hint['group']['get_resource'] + for placement in placement_obj_list: + if placement.server_group_name == affinity_name: + for uuid in uuid_list: + rsc = objects.ConstraintResourceRef( + id_type='GRANT', resource_id=uuid) + plm = jsonutils.loads(placement.resource) + plm.append(rsc.to_dict()) + placement.resource = jsonutils.dumps(plm) + affinity_list.append(affinity_name) + break + if resource['type'] == 'OS::Cinder::VolumeAttachment': + for i in range(size): + add_uuid = uuidutils.generate_uuid() + vol_rsc = resource['properties']['instance_uuid'] + rsc = objects.ResourceDefinition( + id=add_uuid, + type=constants.TYPE_STORAGE, + vdu_id=vol_rsc['get_resource'], + resource_template_id=resource_name) + add_resources.append(rsc) + storage_uuid_list.append(add_uuid) + vnf_info['uuid_list'] = uuid_list + vnf_info['port_uuid_list'] = port_uuid_list + vnf_info['storage_uuid_list'] = storage_uuid_list + for placement in placement_obj_list: + if placement.server_group_name in affinity_list: + plm = jsonutils.loads(placement.resource) + addRsc = [] + for pl in plm: + vim_id = pl.get('vim_connection_id') + addRsc.append( + objects.ConstraintResourceRef( + id_type=pl['id_type'], + resource_id=pl['resource_id'], + vim_connection_id=vim_id)) + placement_constraint = objects.PlacementConstraint( + affinity_or_anti_affinity='ANTI_AFFINITY', + scope='ZONE', + resource=addRsc, + fallback_best_effort=True) + placement_constraint_list.append(placement_constraint) + vnf_info['addResources'] = add_resources + vnf_info['removeResources'] = [] + vnf_info['affinity_list'] = affinity_list + vnf_info['placement_constraint_list'] = placement_constraint_list + + def get_grant_resource_scale_in( + self, + vnf_instance, + vnf_info, + vim_connection_info, + del_list): + remove_resources = [] + access_info = vim_connection_info.access_info + + heatclient = hc.HeatClient(access_info, + region_name=access_info.get('region')) + inst_info = vnf_instance.instantiated_vnf_info + for del_rsc in del_list: + scale_resurce_list = heatclient.resource_get_list(del_rsc) + for rsc in scale_resurce_list: + if rsc.resource_type == 'OS::Nova::Server': + for vnfc_resource in inst_info.vnfc_resource_info: + if vnfc_resource.\ + compute_resource.resource_id == \ + rsc.physical_resource_id: + cmo_rsc = vnfc_resource.compute_resource + vim_id = cmo_rsc.vim_connection_id + rsc_id = cmo_rsc.resource_id + resource = objects.ResourceDefinition( + id=vnfc_resource.id, + type=constants.TYPE_COMPUTE, + vdu_id=vnfc_resource.vdu_id, + resource_template_id=vnfc_resource.vdu_id, + resource=objects.ResourceHandle( + vim_connection_id=vim_id, + resource_id=rsc_id)) + remove_resources.append(resource) + if rsc.resource_type == 'OS::Neutron::Port': + for vl_resource in \ + inst_info.vnf_virtual_link_resource_info: + for cp_resource in vl_resource.vnf_link_ports: + cp_handl = cp_resource.resource_handle + cp_id = cp_handl.resource_id + vim_id = cp_handl.vim_connection_id + if cp_id == rsc.physical_resource_id: + for vnfc_resource in inst_info.\ + vnfc_resource_info: + for vnfc_cp_rsc in vnfc_resource.\ + vnfc_cp_info: + if cp_resource.\ + cp_instance_id == \ + vnfc_cp_rsc.id: + p_id = cp_resource.id + v_id = vnfc_resource.vdu_id + d_id = vnfc_cp_rsc.cpd_id + r_hd = objects.\ + ResourceHandle() + r_hd.\ + vim_connection_id = \ + vim_id + r_hd.resource_id = cp_id + rs = objects.\ + ResourceDefinition() + rs.id = p_id + rs.type = 'constants.\ + TYPE_LINKPORT' + rs.vdu_id = v_id + rs.resource_template_id = d_id + rs.resource = r_hd + remove_resources.append( + rs) + if rsc.resource_type == 'OS::Cinder::Volume': + st_info = inst_info.virtual_storage_resource_info + for storage_resource in st_info: + st_rsc = storage_resource.storage_resource + st_id = st_rsc.resource_id + if st_id == rsc.physical_resource_id: + ins_vnfc = inst_info.vnfc_resource_info + for vnfc_resource in ins_vnfc: + s_ids = vnfc_resource.storage_resource_ids + if storage_resource.id in s_ids: + rs = objects.ResourceDefinition() + rs.id = storage_resource.id + rs.type = 'STORAGE' + rs.vdu_id = vnfc_resource.vdu_id + tmp_id = storage_resource.\ + virtual_storage_desc_id + rs.resource_template_id = tmp_id + r_hd = objects.ResourceHandle() + vim_id = st_rsc.vim_connection_id + rsc_id = st_rsc.resource_id + r_hd.vim_connection_id = vim_id + r_hd.resource_id = rsc_id + rs.resource = r_hd + remove_resources.append(rs) + vnf_info['addResources'] = [] + vnf_info['removeResources'] = remove_resources + vnf_info['affinity_list'] = [] + vnf_info['placement_constraint_list'] = [] diff --git a/tacker/vnfm/infra_drivers/scale_driver.py b/tacker/vnfm/infra_drivers/scale_driver.py index 5459fee03..1bb34adc7 100644 --- a/tacker/vnfm/infra_drivers/scale_driver.py +++ b/tacker/vnfm/infra_drivers/scale_driver.py @@ -80,10 +80,35 @@ class VnfScaleAbstractDriver(extensions.PluginInterface): pass @abc.abstractmethod - def scale_in_reverse_wait(self, + def scale_out_initial(self, + context, + plugin, + auth_attr, + vnf_info, + scale_vnf_request, + region_name): + pass + + @abc.abstractmethod + def scale_update_wait(self, context, plugin, auth_attr, vnf_info, region_name): pass + + @abc.abstractmethod + def get_cinder_list(self, + vnf_info): + pass + + @abc.abstractmethod + def get_grant_resource(self, + vnf_instance, + vnf_info, + scale_vnf_request, + placement_obj_list, + vim_connection_info, + del_list): + pass