diff --git a/tacker/common/exceptions.py b/tacker/common/exceptions.py index fc2d39130..bc9fa38f8 100644 --- a/tacker/common/exceptions.py +++ b/tacker/common/exceptions.py @@ -243,3 +243,17 @@ class InvalidCIDR(BadRequest): class MgmtDriverException(TackerException): message = _("VNF configuration failed") + + +class VnfPolicyNotFound(NotFound): + message = _("Policy %(policy)s does not exist for VNF %(vnf_id)s") + + +class VnfPolicyActionInvalid(BadRequest): + message = _("Invalid action %(action)s for policy %(policy)s, " + "should be one of %(valid_acions)s") + + +class VnfPolicyTypeInvalid(BadRequest): + message = _("Invalid type %(type)s for policy %(policy)s, " + "should be one of %(valid_types)s") diff --git a/tacker/db/vm/vm_db.py b/tacker/db/vm/vm_db.py index 51a0bf628..06b03678b 100644 --- a/tacker/db/vm/vm_db.py +++ b/tacker/db/vm/vm_db.py @@ -402,6 +402,19 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin): device_db.update({'status': new_status}) return device_db + def _update_vnf_scaling_status(self, + context, + policy, + previous_statuses, + status, + mgmt_url=None): + with context.session.begin(subtransactions=True): + device_db = self._get_device_db( + context, policy['vnf']['id'], previous_statuses, status) + if mgmt_url: + device_db.update({'mgmt_url': mgmt_url}) + return self._make_device_dict(device_db) + def _update_device_pre(self, context, device_id): with context.session.begin(subtransactions=True): device_db = self._get_device_db( diff --git a/tacker/extensions/vnfm.py b/tacker/extensions/vnfm.py index 82af19fdc..553e4b469 100644 --- a/tacker/extensions/vnfm.py +++ b/tacker/extensions/vnfm.py @@ -20,8 +20,10 @@ import six from tacker.api import extensions from tacker.api.v1 import attributes as attr +from tacker.api.v1 import base from tacker.api.v1 import resource_helper from tacker.common import exceptions +from tacker import manager from tacker.plugins.common import constants from tacker.services import service_base @@ -305,6 +307,41 @@ RESOURCE_ATTRIBUTE_MAP = { } +SUB_RESOURCE_ATTRIBUTE_MAP = { + 'actions': { + 'parent': { + 'collection_name': 'vnfs', + 'member_name': 'vnf' + }, + 'members': { + 'scale': { + 'parameters': { + 'policy': { + 'allow_post': True, + 'allow_put': False, + 'is_visible': True, + 'validate': {'type:string': None} + }, + 'type': { + 'allow_post': True, + 'allow_put': False, + 'is_visible': True, + 'validate': {'type:string': None} + }, + 'tenant_id': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': False, + 'is_visible': False + }, + } + } + } + } +} + + class Vnfm(extensions.ExtensionDescriptor): @classmethod def get_name(cls): @@ -333,9 +370,31 @@ class Vnfm(extensions.ExtensionDescriptor): special_mappings, RESOURCE_ATTRIBUTE_MAP) plural_mappings['service_types'] = 'service_type' attr.PLURALS.update(plural_mappings) - return resource_helper.build_resource_info( + resources = resource_helper.build_resource_info( plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.VNFM, translate_name=True) + plugin = manager.TackerManager.get_service_plugins()[ + constants.VNFM] + for collection_name in SUB_RESOURCE_ATTRIBUTE_MAP: + parent = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name]['parent'] + + for resource_name in SUB_RESOURCE_ATTRIBUTE_MAP[ + collection_name]['members']: + params = SUB_RESOURCE_ATTRIBUTE_MAP[ + collection_name]['members'][resource_name]['parameters'] + + controller = base.create_resource(collection_name, + resource_name, + plugin, params, + allow_bulk=True, + parent=parent) + + resource = extensions.ResourceExtension( + collection_name, + controller, parent, + attr_map=params) + resources.append(resource) + return resources @classmethod def get_plugin_interface(cls): @@ -397,3 +456,8 @@ class VNFMPluginBase(service_base.NFVPluginBase): @abc.abstractmethod def delete_vnf(self, context, vnf_id): pass + + @abc.abstractmethod + def create_vnf_scale( + self, context, vnf_id, scale): + pass diff --git a/tacker/plugins/common/constants.py b/tacker/plugins/common/constants.py index 6630fa126..c939bd496 100644 --- a/tacker/plugins/common/constants.py +++ b/tacker/plugins/common/constants.py @@ -29,9 +29,13 @@ COMMON_PREFIXES = { # Service operation status constants ACTIVE = "ACTIVE" DOWN = "DOWN" + PENDING_CREATE = "PENDING_CREATE" PENDING_UPDATE = "PENDING_UPDATE" PENDING_DELETE = "PENDING_DELETE" +PENDING_SCALE_IN = "PENDING_SCALE_IN" +PENDING_SCALE_OUT = "PENDING_SCALE_OUT" + INACTIVE = "INACTIVE" DEAD = "DEAD" ERROR = "ERROR" @@ -41,3 +45,8 @@ ACTIVE_PENDING_STATUSES = ( PENDING_CREATE, PENDING_UPDATE ) + +POLICY_SCALING = 'tosca.policy.tacker.Scaling' +POLICY_SCALING_ACTIONS = (ACTION_SCALE_OUT, + ACTION_SCALE_IN) = ('out', 'in') +POLICY_ACTIONS = {POLICY_SCALING: POLICY_SCALING_ACTIONS} diff --git a/tacker/vm/plugin.py b/tacker/vm/plugin.py index 448e3b0ac..c2f86c12e 100644 --- a/tacker/vm/plugin.py +++ b/tacker/vm/plugin.py @@ -17,6 +17,7 @@ import copy import inspect import six +import yaml import eventlet from oslo_config import cfg @@ -27,7 +28,7 @@ from oslo_utils import excutils from tacker._i18n import _LE from tacker.api.v1 import attributes from tacker.common import driver_manager -from tacker.common.exceptions import MgmtDriverException +from tacker.common import exceptions from tacker.db.vm import vm_db from tacker.extensions import vnfm from tacker.plugins.common import constants @@ -243,7 +244,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin): new_status = constants.ACTIVE try: self.mgmt_call(context, device_dict, kwargs) - except MgmtDriverException: + except exceptions.MgmtDriverException: LOG.error(_('VNF configuration failed')) new_status = constants.ERROR self.set_device_error_status_reason(context, device_id, @@ -319,7 +320,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin): context=context, device_id=instance_id, auth_attr=vim_auth, region_name=region_name) self.mgmt_call(context, device_dict, kwargs) - except MgmtDriverException as e: + except exceptions.MgmtDriverException as e: LOG.error(_('VNF configuration failed')) new_status = constants.ERROR self.set_device_error_status_reason(context, device_dict['id'], @@ -415,6 +416,127 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin): self.spawn_n(self._delete_device_wait, context, device_dict, vim_auth) + def _handle_vnf_scaling(self, context, policy): + # validate + def _validate_scaling_policy(): + type = policy['type'] + + if type not in constants.POLICY_ACTIONS.keys(): + raise exceptions.VnfPolicyTypeInvalid( + type=type, + valid_types=constants.POLICY_ACTIONS.keys(), + policy=policy['id'] + ) + action = policy['action'] + + if action not in constants.POLICY_ACTIONS[type]: + raise exceptions.VnfPolicyActionInvalid( + action=action, + valid_actions=constants.POLICY_ACTIONS[type], + policy=policy['id'] + ) + + LOG.debug(_("Policy %s is validated successfully") % policy) + + def _get_status(): + if policy['action'] == constants.ACTION_SCALE_IN: + status = constants.PENDING_SCALE_IN + else: + status = constants.PENDING_SCALE_OUT + + return status + + # pre + def _handle_vnf_scaling_pre(): + status = _get_status() + result = self._update_vnf_scaling_status(context, + policy, + [constants.ACTIVE], + status) + LOG.debug(_("Policy %(policy)s vnf is at %(status)s"), + {'policy': policy, + 'status': status}) + return result + + # post + def _handle_vnf_scaling_post(new_status, mgmt_url=None): + status = _get_status() + result = self._update_vnf_scaling_status(context, + policy, + [status], + new_status, + mgmt_url) + LOG.debug(_("Policy %(policy)s vnf is at %(status)s"), + {'policy': policy, + 'status': new_status}) + return result + + # action + def _vnf_policy_action(): + try: + self._device_manager.invoke( + infra_driver, + 'scale', + plugin=self, + context=context, + auth_attr=vim_auth, + policy=policy, + region_name=region_name + ) + LOG.debug(_("Policy %s action is started successfully") % + policy) + except Exception as e: + LOG.error(_("Policy %s action is failed to start") % + policy) + with excutils.save_and_reraise_exception(): + vnf['status'] = constants.ERROR + self.set_device_error_status_reason( + context, + policy['vnf_id'], + six.text_type(e)) + _handle_vnf_scaling_post(constants.ERROR) + + # wait + def _vnf_policy_action_wait(): + try: + LOG.debug(_("Policy %s action is in progress") % + policy) + mgmt_url = self._device_manager.invoke( + infra_driver, + 'scale_wait', + plugin=self, + context=context, + auth_attr=vim_auth, + policy=policy, + region_name=region_name + ) + LOG.debug(_("Policy %s action is completed successfully") % + policy) + _handle_vnf_scaling_post(constants.ACTIVE, mgmt_url) + # TODO(kanagaraj-manickam): Add support for config and mgmt + except Exception as e: + LOG.error(_("Policy %s action is failed to complete") % + policy) + with excutils.save_and_reraise_exception(): + self.set_device_error_status_reason( + context, + policy['vnf_id'], + six.text_type(e)) + _handle_vnf_scaling_post(constants.ERROR) + + _validate_scaling_policy() + + vnf = _handle_vnf_scaling_pre() + policy['instance_id'] = vnf['instance_id'] + + infra_driver = self._infra_driver_name(vnf) + vim_auth = self.get_vim(context, vnf) + region_name = vnf.get('placement_attr', {}).get('region_name', None) + _vnf_policy_action() + self.spawn_n(_vnf_policy_action_wait) + + return policy + def create_vnf(self, context, vnf): vnf['device'] = vnf.pop('vnf') vnf_attributes = vnf['device'] @@ -436,3 +558,59 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin): vnfd['device_template'] = vnfd.pop('vnfd') new_dict = self.create_device_template(context, vnfd) return new_dict + + def _make_policy_dict(self, vnf, name, policy): + p = {} + p['type'] = policy['type'] + p['properties'] = policy['properties'] + p['vnf'] = vnf + p['name'] = name + p['id'] = p['name'] + return p + + def get_vnf_policies( + self, context, vnf_id, filters=None, fields=None): + vnf = self.get_device(context, vnf_id) + vnfd_tmpl = yaml.load(vnf['device_template']['attributes']['vnfd']) + policy_list = [] + + if vnfd_tmpl.get('tosca_definitions_version'): + polices = vnfd_tmpl['topology_template'].get('policies', []) + for policy_dict in polices: + for name, policy in policy_dict.items(): + def _add(policy): + p = self._make_policy_dict(vnf, name, policy) + p['name'] = name + policy_list.append(p) + + # Check for filters + if filters.get('name'): + if name == filters.get('name'): + _add(policy) + break + else: + continue + + _add(policy) + + return policy_list + + def get_vnf_policy( + self, context, policy_id, vnf_id, fields=None): + policies = self.get_vnf_policies(context, + vnf_id, + filters={'name': policy_id}) + if policies: + return policies[0] + + raise exceptions.VnfPolicyNotFound(policy=policy_id, + vnf_id=vnf_id) + + def create_vnf_scale(self, context, vnf_id, scale): + policy_ = self.get_vnf_policy(context, + scale['scale']['policy'], + vnf_id) + policy_.update({'action': scale['scale']['type']}) + self._handle_vnf_scaling(context, policy_) + + return scale['scale']