tacker/tacker/api/vnflcm/v1/controller.py

1672 lines
69 KiB
Python

# Copyright (C) 2020 NTT DATA
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import datetime
import requests
import tacker.conf
import webob
from oslo_db.exception import DBDuplicateEntry
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_utils import excutils
from oslo_utils import timeutils
from oslo_utils import uuidutils
from sqlalchemy import exc as sqlexc
from toscaparser import tosca_template
import ast
import functools
import json
import re
import traceback
from http import client as http_client
from urllib import parse
from tacker._i18n import _
from tacker.api import api_common
from tacker.api.schemas import vnf_lcm
from tacker.api import validation
from tacker.api.views import vnf_lcm as vnf_lcm_view
from tacker.api.views import vnf_lcm_op_occs as vnf_op_occs_view
from tacker.api.views import vnf_subscriptions as vnf_subscription_view
from tacker.api.vnflcm.v1 import sync_resource
from tacker.common import exceptions
from tacker.common import utils
from tacker.conductor.conductorrpc import vnf_lcm_rpc
from tacker.db.vnfm import vnfm_db
from tacker.extensions import nfvo
from tacker.extensions import vnfm
from tacker import manager
from tacker import objects
from tacker.objects import fields
from tacker.objects.fields import ErrorPoint as EP
from tacker.objects import vnf_lcm_op_occs as vnf_lcm_op_occs_obj
from tacker.objects import vnf_lcm_subscriptions as subscription_obj
from tacker.plugins.common import constants
from tacker.policies import vnf_lcm as vnf_lcm_policies
from tacker.tosca import utils as toscautils
from tacker.vnflcm import utils as vnflcm_utils
import tacker.vnfm.nfvo_client as nfvo_client
from tacker.vnfm import vim_client
from tacker import wsgi
CONF = tacker.conf.CONF
LOG = logging.getLogger(__name__)
def check_vnf_state(action, instantiation_state=None, task_state=(None,)):
"""Decorator to check vnf states are valid for particular action.
If the vnf is in the wrong state, it will raise conflict exception.
"""
if instantiation_state is not None and not \
isinstance(instantiation_state, set):
instantiation_state = set(instantiation_state)
if task_state is not None and not isinstance(task_state, set):
task_state = set(task_state)
def outer(f):
@functools.wraps(f)
def inner(self, context, vnf_instance, *args, **kw):
if instantiation_state is not None and \
vnf_instance.instantiation_state not in \
instantiation_state:
raise exceptions.VnfInstanceConflictState(
attr='instantiation_state',
uuid=vnf_instance.id,
state=vnf_instance.instantiation_state,
action=action)
if (task_state is not None and
vnf_instance.task_state not in task_state):
raise exceptions.VnfInstanceConflictState(
attr='task_state',
uuid=vnf_instance.id,
state=vnf_instance.task_state,
action=action)
return f(self, context, vnf_instance, *args, **kw)
return inner
return outer
def check_vnf_status(action, status=None):
"""Decorator to check vnf status are valid for particular action.
If the vnf is in the wrong state, it will raise conflict exception.
"""
if status is not None and not isinstance(status, set):
status = set(status)
def outer(f):
@functools.wraps(f)
def inner(self, context, vnf_instance, vnf, *args, **kw):
if status is not None and \
vnf['status'] not in \
status:
raise exceptions.VnfConflictState(
attr='status',
uuid=vnf['id'],
state=vnf['status'],
action=action)
return f(self, context, vnf_instance, vnf, *args, **kw)
return inner
return outer
def check_vnf_status_and_error_point(action, status=None):
"""Decorator to check vnf status are valid for particular action.
If the vnf has the wrong status with the wrong error point,
it will raise conflict exception.
"""
if status is not None and not isinstance(status, set):
status = set(status)
def outer(f):
@functools.wraps(f)
def inner(self, context, vnf_instance, vnf, *args, **kw):
vnf['current_error_point'] = fields.ErrorPoint.INITIAL
if 'before_error_point' not in vnf:
vnf['before_error_point'] = fields.ErrorPoint.INITIAL
if status is not None and vnf['status'] not in status and \
vnf['before_error_point'] == fields.ErrorPoint.INITIAL:
raise exceptions.VnfConflictStateWithErrorPoint(
uuid=vnf['id'],
state=vnf['status'],
action=action,
error_point=vnf['before_error_point'])
return f(self, context, vnf_instance, vnf, *args, **kw)
return inner
return outer
class VnfLcmController(wsgi.Controller):
notification_type_list = ['VnfLcmOperationOccurrenceNotification',
'VnfIdentifierCreationNotification',
'VnfIdentifierDeletionNotification']
operation_type_list = ['INSTANTIATE',
'SCALE',
'SCALE_TO_LEVEL',
'CHANGE_FLAVOUR',
'TERMINATE',
'HEAL',
'OPERATE',
'CHANGE_EXT_CONN',
'MODIFY_INFO']
operation_state_list = ['STARTING',
'PROCESSING',
'COMPLETED',
'FAILED_TEMP',
'FAILED',
'ROLLING_BACK',
'ROLLED_BACK']
_view_builder_class = vnf_lcm_view.ViewBuilder
def __init__(self):
super(VnfLcmController, self).__init__()
self.rpc_api = vnf_lcm_rpc.VNFLcmRPCAPI()
self._vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM']
self._view_builder_op_occ = vnf_op_occs_view.ViewBuilder()
self._view_builder_subscription = vnf_subscription_view.ViewBuilder()
def _get_vnf_instance_href(self, vnf_instance):
return '{}vnflcm/v1/vnf_instances/{}'.format(
CONF.vnf_lcm.endpoint_url, vnf_instance.id)
def _get_vnf_lcm_op_occs_href(self, vnf_lcm_op_occs_id):
return '{}vnflcm/v1/vnf_lcm_op_occs/{}'.format(
CONF.vnf_lcm.endpoint_url, vnf_lcm_op_occs_id)
def _get_vnf_instance(self, context, id):
# check if id is of type uuid format
if not uuidutils.is_uuid_like(id):
msg = _("Can not find requested vnf instance: %s") % id
raise webob.exc.HTTPNotFound(explanation=msg)
try:
vnf_instance = objects.VnfInstance.get_by_id(context, id)
except exceptions.VnfInstanceNotFound:
msg = _("Can not find requested vnf instance: %s") % id
raise webob.exc.HTTPNotFound(explanation=msg)
return vnf_instance
def _get_vnf(self, context, id):
# check if id is of type uuid format
if not uuidutils.is_uuid_like(id):
msg = _("Can not find requested vnf: %s") % id
raise webob.exc.HTTPNotFound(explanation=msg)
try:
vnf = self._vnfm_plugin.get_vnf(context, id)
except vnfm.VNFNotFound:
msg = _("Can not find requested vnf: %s") % id
raise webob.exc.HTTPNotFound(explanation=msg)
except Exception as exc:
msg = _("Encountered error while fetching vnf: %s") % id
LOG.debug("{}: {}".format(msg, str(exc)))
raise webob.exc.HTTPInternalServerError(explanation=str(exc))
return vnf
def _validate_flavour_and_inst_level(self, context, req_body,
vnf_instance):
inst_levels = {}
flavour_list = []
vnf_package_vnfd = objects.VnfPackageVnfd.get_by_id(
context, vnf_instance.vnfd_id)
vnf_package = objects.VnfPackage.get_by_id(
context, vnf_package_vnfd.package_uuid)
deployment_flavour = vnf_package.vnf_deployment_flavours
for dep_flavour in deployment_flavour.objects:
flavour_list.append(dep_flavour.flavour_id)
if dep_flavour.instantiation_levels:
inst_levels.update({
dep_flavour.flavour_id: dep_flavour.instantiation_levels})
if req_body['flavour_id'] not in flavour_list:
raise exceptions.FlavourNotFound(flavour_id=req_body['flavour_id'])
req_inst_level_id = req_body.get('instantiation_level_id')
if req_inst_level_id is None:
return
if not inst_levels:
raise exceptions.InstantiationLevelNotFound(
inst_level_id=req_inst_level_id)
for flavour, inst_level in inst_levels.items():
if flavour != req_body['flavour_id']:
continue
if req_inst_level_id in inst_level.get('levels').keys():
# Found instantiation level
return
raise exceptions.InstantiationLevelNotFound(
inst_level_id=req_body['instantiation_level_id'])
def _validate_vim_connection(self, context, instantiate_vnf_request):
if instantiate_vnf_request.vim_connection_info:
vim_id = instantiate_vnf_request.vim_connection_info[0].vim_id
access_info = \
instantiate_vnf_request.vim_connection_info[0].access_info
if access_info:
region_name = access_info.get('region')
else:
region_name = None
else:
vim_id = None
region_name = None
vim_client_obj = vim_client.VimClient()
try:
vim_client_obj.get_vim(
context, vim_id, region_name=region_name)
except nfvo.VimDefaultNotDefined as exp:
raise webob.exc.HTTPBadRequest(explanation=exp.message)
except nfvo.VimNotFoundException:
msg = _("VimConnection id is not found: %s")\
% vim_id
raise webob.exc.HTTPBadRequest(explanation=msg)
except nfvo.VimRegionNotFoundException:
msg = _("Region not found for the VimConnection: %s")\
% vim_id
raise webob.exc.HTTPBadRequest(explanation=msg)
def _notification_process(
self, context, vnf_instance, lcm_operation, request, body,
vnf_lcm_op_occs=None,
operation_state=fields.LcmOccsOperationState.STARTING,
notification_status=fields.LcmOccsNotificationStatus.START,
affected_resources=None, is_auto=False):
LOG.debug('START NOTIFICATION PROCESS')
vnf_url = self._get_vnf_instance_href(vnf_instance)
notification = {
'notificationType':
fields.LcmOccsNotificationType.VNF_OP_OCC_NOTIFICATION,
'notificationStatus': notification_status,
'operationState': operation_state,
'vnfInstanceId': vnf_instance.id,
'operation': lcm_operation,
'isAutomaticInvocation': is_auto,
'_links': {
'vnfInstance': {
'href': vnf_url},
'vnfLcmOpOcc': {}}}
if operation_state is fields.LcmOccsOperationState.FAILED:
vnf_lcm_op_occs_id = vnf_lcm_op_occs.id
notification['affectedVnfcs'] = affected_resources.get(
'affectedVnfcs', [])
notification['affectedVirtualLinks'] = affected_resources.get(
'affectedVirtualLinks', [])
notification['affectedVirtualStorages'] = affected_resources.get(
'affectedVirtualStorages', [])
notification['error'] = str(vnf_lcm_op_occs.error)
else:
vnf_lcm_op_occs_id = uuidutils.generate_uuid()
error_point = 0
operation_params = jsonutils.dumps(body)
try:
# call create lcm op occs here
LOG.debug('Create LCM OP OCCS')
vnf_lcm_op_occs = objects.VnfLcmOpOcc(
context=context,
id=vnf_lcm_op_occs_id,
operation_state=operation_state,
start_time=timeutils.utcnow(),
state_entered_time=timeutils.utcnow(),
vnf_instance_id=vnf_instance.id,
is_cancel_pending=is_auto,
operation=lcm_operation,
is_automatic_invocation=is_auto,
operation_params=operation_params,
error_point=error_point)
vnf_lcm_op_occs.create()
except Exception:
msg = _("Failed to create LCM occurrence")
raise webob.exc.HTTPInternalServerError(explanation=msg)
vnf_lcm_url = self._get_vnf_lcm_op_occs_href(vnf_lcm_op_occs_id)
notification['vnfLcmOpOccId'] = vnf_lcm_op_occs_id
notification['_links']['vnfLcmOpOcc']['href'] = vnf_lcm_url
# call send notification
try:
self.rpc_api.send_notification(context, notification)
except Exception as ex:
LOG.error(
"Encoutered problem sending notification {}".format(
encodeutils.exception_to_unicode(ex)))
return vnf_lcm_op_occs_id
def _create_vnf(self, context, vnf_instance, default_vim, attributes=None):
tenant_id = vnf_instance.tenant_id
vnfd_id = vnf_instance.vnfd_id
name = vnf_instance.vnf_instance_name
description = vnf_instance.vnf_instance_description
vnf_id = vnf_instance.id
vim_id = default_vim.get('vim_id')
placement_attr = default_vim.get('placement_attr', {})
try:
with context.session.begin(subtransactions=True):
vnf_db = vnfm_db.VNF(id=vnf_id,
tenant_id=tenant_id,
name=name,
description=description,
instance_id=None,
vnfd_id=vnfd_id,
vim_id=vim_id,
placement_attr=placement_attr,
status=constants.INACTIVE,
error_reason=None,
deleted_at=datetime.datetime.min)
context.session.add(vnf_db)
for key, value in attributes.items():
arg = vnfm_db.VNFAttribute(
id=uuidutils.generate_uuid(), vnf_id=vnf_id,
key=key, value=str(value))
context.session.add(arg)
except DBDuplicateEntry as e:
raise exceptions.DuplicateEntity(
_type="vnf",
entry=e.columns)
def _destroy_vnf(self, context, vnf_instance):
with context.session.begin(subtransactions=True):
if vnf_instance.id:
now = timeutils.utcnow()
updated_values = {'deleted_at': now, 'status':
'PENDING_DELETE'}
context.session.query(vnfm_db.VNFAttribute).filter_by(
vnf_id=vnf_instance.id).delete()
context.session.query(vnfm_db.VNF).filter_by(
id=vnf_instance.id).update(updated_values)
def _update_package_usage_state(self, context, vnf_package):
"""Update vnf package usage state to IN_USE/NOT_IN_USE
If vnf package is not used by any of the vnf instances, it's usage
state should be set to NOT_IN_USE otherwise it should be set to
IN_USE.
"""
result = vnf_package.is_package_in_use(context)
if result:
vnf_package.usage_state = fields.PackageUsageStateType.IN_USE
else:
vnf_package.usage_state = fields.PackageUsageStateType.NOT_IN_USE
vnf_package.save()
@wsgi.response(http_client.CREATED)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN))
@validation.schema(vnf_lcm.create)
def create(self, request, body):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'create')
try:
req_body = utils.convert_camelcase_to_snakecase(body)
vnfd_id = req_body.get('vnfd_id')
vnfd = objects.VnfPackageVnfd.get_by_id(request.context,
vnfd_id)
except exceptions.VnfPackageVnfdNotFound:
vnf_package_info = self._find_vnf_package_info('vnfdId',
vnfd_id)
if not vnf_package_info:
msg = (
_("Can not find requested to NFVO, \
vnf package info: vnfdId=[%s]") %
vnfd_id)
return self._make_problem_detail(
msg, 404, title='Not Found')
vnfd = sync_resource.SyncVnfPackage.create_package(
context,
vnf_package_info)
if not vnfd:
msg = (
_("Can not find requested to NFVO, \
vnf package vnfd: %s") %
vnfd_id)
return self._make_problem_detail(
msg, 500, 'Internal Server Error')
try:
# get default vim information
vim_client_obj = vim_client.VimClient()
default_vim = vim_client_obj.get_vim(context)
vnf_instance = objects.VnfInstance(
context=request.context,
vnf_instance_name=req_body.get('vnf_instance_name'),
vnf_instance_description=req_body.get(
'vnf_instance_description'),
vnfd_id=vnfd_id,
instantiation_state=fields.VnfInstanceState.NOT_INSTANTIATED,
vnf_provider=vnfd.vnf_provider,
vnf_product_name=vnfd.vnf_product_name,
vnf_software_version=vnfd.vnf_software_version,
vnfd_version=vnfd.vnfd_version,
vnf_pkg_id=vnfd.package_uuid,
tenant_id=request.context.project_id,
vnf_metadata=req_body.get('metadata'))
try:
vnf_instance.create()
# create entry to 'vnf' table and 'vnf_attribute' table
attributes = {'placement_attr': default_vim.
get('placement_attr', {})}
self._create_vnf(context, vnf_instance,
default_vim, attributes)
# get vnf package
vnf_package = objects.VnfPackage.get_by_id(context,
vnfd.package_uuid, expected_attrs=['vnfd'])
# Update VNF Package to IN_USE
self._update_package_usage_state(context, vnf_package)
except Exception:
with excutils.save_and_reraise_exception():
# roll back db changes
self._destroy_vnf(context, vnf_instance)
vnf_instance.destroy(context)
if 'vnf_package' not in locals():
LOG.error("vnf_package is not assigned")
else:
self._update_package_usage_state(context, vnf_package)
# create notification data
notification = {
'notificationType':
fields.LcmOccsNotificationType.VNF_ID_CREATION_NOTIFICATION,
'vnfInstanceId': vnf_instance.id,
'links': {
'vnfInstance': {
'href': self._get_vnf_instance_href(vnf_instance)}}}
# call send_notification
self.rpc_api.send_notification(context, notification)
result = self._view_builder.create(vnf_instance)
headers = {"location": self._get_vnf_instance_href(vnf_instance)}
return wsgi.ResponseObject(result, headers=headers)
except nfvo.VimDefaultNotDefined as exc:
raise webob.exc.HTTPBadRequest(explanation=str(exc))
except(sqlexc.SQLAlchemyError, Exception) as exc:
raise webob.exc.HTTPInternalServerError(
explanation=str(exc))
except webob.exc.HTTPNotFound as e:
return self._make_problem_detail(str(e), 404,
'Not Found')
except webob.exc.HTTPInternalServerError as e:
return self._make_problem_detail(str(e), 500,
'Internal Server Error')
except Exception as e:
return self._make_problem_detail(str(e), 500,
'Internal Server Error')
def _find_vnf_package_info(self, filter_key, filter_val):
try:
vnf_package_info_res = \
nfvo_client.VnfPackageRequest.index(params={
"filter":
"(eq,{},{})".format(filter_key, filter_val)
})
except requests.exceptions.RequestException as e:
LOG.exception(e)
return None
if not vnf_package_info_res.ok:
return None
vnf_package_info = vnf_package_info_res.json()
if (not vnf_package_info or len(vnf_package_info) == 0):
return None
return vnf_package_info[0]
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND))
def show(self, request, id):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'show')
vnf_instance = self._get_vnf_instance(context, id)
return self._view_builder.show(vnf_instance)
@wsgi.response(http_client.OK)
def api_versions(self, request):
return {'uriPrefix': '/vnflcm/v1',
'apiVersions': [{'version': '1.3.0', 'isDeprecated': False}]}
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.BAD_REQUEST))
@api_common.validate_supported_params({'filter'})
def index(self, request):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'index')
filters = request.GET.get('filter')
filters = self._view_builder.validate_filter(filters)
vnf_instances = objects.VnfInstanceList.get_by_filters(
request.context, filters=filters)
return self._view_builder.index(vnf_instances)
@check_vnf_state(action="delete",
instantiation_state=[fields.VnfInstanceState.NOT_INSTANTIATED],
task_state=[None])
@check_vnf_status(action="delete",
status=[constants.INACTIVE])
def _delete(self, context, vnf_instance, vnf):
vnf_package_vnfd = objects.VnfPackageVnfd.get_by_id(context,
vnf_instance.vnfd_id)
vnf_instance.destroy(context)
self._destroy_vnf(context, vnf_instance)
vnf_package = objects.VnfPackage.get_by_id(context,
vnf_package_vnfd.package_uuid, expected_attrs=['vnfd'])
self._update_package_usage_state(context, vnf_package)
@wsgi.response(http_client.NO_CONTENT)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND,
http_client.CONFLICT))
def delete(self, request, id):
context = request.environ['tacker.context']
vnf_instance = self._get_vnf_instance(context, id)
vnf = self._get_vnf(context, id)
self._delete(context, vnf_instance, vnf)
notification = {
"notificationType": "VnfIdentifierDeletionNotification",
"vnfInstanceId": vnf_instance.id,
"links": {
"vnfInstance":
"href:{apiRoot}/vnflcm/v1/vnf_instances/{vnfInstanceId}"}}
# send notification
self.rpc_api.send_notification(context, notification)
@check_vnf_state(action="instantiate",
instantiation_state=[fields.VnfInstanceState.NOT_INSTANTIATED],
task_state=[None])
@check_vnf_status_and_error_point(action="instantiate",
status=[constants.INACTIVE])
def _instantiate(self, context, vnf_instance, vnf, request_body):
req_body = utils.convert_camelcase_to_snakecase(request_body)
vnf_lcm_op_occs_id = vnf.get('vnf_lcm_op_occs_id')
try:
self._validate_flavour_and_inst_level(context, req_body,
vnf_instance)
except exceptions.NotFound as ex:
raise webob.exc.HTTPBadRequest(explanation=str(ex))
instantiate_vnf_request = \
objects.InstantiateVnfRequest.obj_from_primitive(
req_body, context=context)
# validate the vim connection id passed through request is exist or not
self._validate_vim_connection(context, instantiate_vnf_request)
vnf_instance.task_state = fields.VnfInstanceTaskState.INSTANTIATING
vnf_instance.save()
# lcm op process
if vnf['before_error_point'] == fields.ErrorPoint.INITIAL:
vnf_lcm_op_occs_id = \
self._notification_process(context, vnf_instance,
fields.LcmOccsOperationType.INSTANTIATE,
instantiate_vnf_request, request_body)
if vnf_lcm_op_occs_id:
self.rpc_api.instantiate(context, vnf_instance, vnf,
instantiate_vnf_request,
vnf_lcm_op_occs_id)
# set response header
res = webob.Response()
res.status_int = 202
location = ('Location',
self._get_vnf_lcm_op_occs_href(vnf_lcm_op_occs_id))
res.headerlist.append(location)
return res
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND,
http_client.CONFLICT, http_client.BAD_REQUEST))
@validation.schema(vnf_lcm.instantiate)
def instantiate(self, request, id, body):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'instantiate')
vnf = self._get_vnf(context, id)
vnf_instance = self._get_vnf_instance(context, id)
return self._instantiate(context, vnf_instance, vnf, body)
@check_vnf_state(action="terminate",
instantiation_state=[
fields.VnfInstanceState.INSTANTIATED,
fields.VnfInstanceState.NOT_INSTANTIATED],
task_state=[None])
@check_vnf_status_and_error_point(action="terminate",
status=[constants.ACTIVE])
def _terminate(self, context, vnf_instance, vnf, request_body):
req_body = utils.convert_camelcase_to_snakecase(request_body)
terminate_vnf_req = \
objects.TerminateVnfRequest.obj_from_primitive(
req_body, context=context)
vnf_instance.task_state = fields.VnfInstanceTaskState.TERMINATING
vnf_instance.save()
vnf_lcm_op_occs_id = vnf.get('vnf_lcm_op_occs_id')
# lcm op process
if vnf['before_error_point'] == fields.ErrorPoint.INITIAL:
vnf_lcm_op_occs_id = \
self._notification_process(context, vnf_instance,
fields.LcmOccsOperationType.TERMINATE,
terminate_vnf_req, request_body)
if vnf_lcm_op_occs_id:
self.rpc_api.terminate(context, vnf_instance, vnf,
terminate_vnf_req, vnf_lcm_op_occs_id)
# set response header
res = webob.Response()
res.status_int = 202
location = ('Location',
self._get_vnf_lcm_op_occs_href(vnf_lcm_op_occs_id))
res.headerlist.append(location)
return res
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
http_client.NOT_FOUND, http_client.CONFLICT))
@validation.schema(vnf_lcm.terminate)
def terminate(self, request, id, body):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'terminate')
vnf = self._get_vnf(context, id)
vnf_instance = self._get_vnf_instance(context, id)
return self._terminate(context, vnf_instance, vnf, body)
@check_vnf_status_and_error_point(action="heal",
status=[constants.ACTIVE])
def _heal(self, context, vnf_instance, vnf_dict, request_body):
req_body = utils.convert_camelcase_to_snakecase(request_body)
heal_vnf_request = objects.HealVnfRequest(context=context, **req_body)
inst_vnf_info = vnf_instance.instantiated_vnf_info
vnfc_resource_info_ids = [
vnfc_resource_info.id for vnfc_resource_info in
inst_vnf_info.vnfc_resource_info]
vnf_lcm_op_occs_id = vnf_dict.get('vnf_lcm_op_occs_id')
for vnfc_id in heal_vnf_request.vnfc_instance_id:
# check if vnfc_id exists in vnfc_resource_info
if vnfc_id not in vnfc_resource_info_ids:
msg = _("Vnfc id %(vnfc_id)s not present in vnf instance "
"%(id)s")
raise webob.exc.HTTPBadRequest(
explanation=msg % {"vnfc_id": vnfc_id,
"id": vnf_instance.id})
vnf_instance.task_state = fields.VnfInstanceTaskState.HEALING
vnf_instance.save()
# call notification process
if vnf_dict['before_error_point'] == fields.ErrorPoint.INITIAL:
vnf_lcm_op_occs_id = \
self._notification_process(context, vnf_instance,
fields.LcmOccsOperationType.HEAL,
heal_vnf_request, request_body)
if vnf_lcm_op_occs_id:
self.rpc_api.heal(context, vnf_instance, vnf_dict,
heal_vnf_request, vnf_lcm_op_occs_id)
# set response header
res = webob.Response()
res.status_int = 202
location = ('Location',
self._get_vnf_lcm_op_occs_href(vnf_lcm_op_occs_id))
res.headerlist.append(location)
return res
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
http_client.NOT_FOUND, http_client.CONFLICT))
@validation.schema(vnf_lcm.heal)
def heal(self, request, id, body):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'heal')
vnf = self._get_vnf(context, id)
vnf_instance = self._get_vnf_instance(context, id)
if vnf_instance.instantiation_state not in \
[fields.VnfInstanceState.INSTANTIATED]:
raise exceptions.VnfInstanceConflictState(
attr='instantiation_state',
uuid=vnf_instance.id,
state=vnf_instance.instantiation_state,
action='heal')
if vnf_instance.task_state not in [None]:
raise exceptions.VnfInstanceConflictState(
attr='task_state',
uuid=vnf_instance.id,
state=vnf_instance.task_state,
action='heal')
return self._heal(context, vnf_instance, vnf, body)
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND))
def show_lcm_op_occs(self, request, id):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'show_lcm_op_occs')
try:
vnf_lcm_op_occs = objects.VnfLcmOpOcc.get_by_id(context, id)
except exceptions.NotFound as occ_e:
return self._make_problem_detail(str(occ_e),
404, title='VnfLcmOpOcc NOT FOUND')
except Exception as e:
LOG.error(traceback.format_exc())
return self._make_problem_detail(str(e),
500, title='Internal Server Error')
return self._view_builder.show_lcm_op_occs(vnf_lcm_op_occs)
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND))
def update(self, request, id, body):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'update_vnf')
# get body
body_data = {}
body_data['vnf_instance_name'] = body.get('vnfInstanceName')
body_data['vnf_instance_description'] = body.get(
'vnfInstanceDescription')
body_data['vnfd_id'] = body.get('vnfdId')
if (body.get('vnfdId') is None and body.get('vnfPkgId')):
body_data['vnf_pkg_id'] = body.get('vnfPkgId')
# According to the ETSI NFV SOL document,
# there is no API request/response
# specification for Etag yet,
# and transactions using Etag are not defined
# by standardization. Therefore, the Victoria release does not support
# `Error Code: 412 Precondition Failed`. Once a standard specification
# for this is established, it will be installed on the tacker.
# Confirmation of update target
try:
vnf_data = objects.VNF.vnf_index_list(id, context)
if not vnf_data:
msg = _("Can not find requested vnf data: %s") % id
return self._make_problem_detail(msg, 404, title='Not Found')
except Exception as e:
return self._make_problem_detail(
str(e), 500, 'Internal Server Error')
if (vnf_data.get("status") != fields.VnfStatus.ACTIVE and
vnf_data.get("status") != fields.VnfStatus.INACTIVE):
msg = _("VNF %(id)s status is %(state)s")
return self._make_problem_detail(
msg % {
"id": id,
"state": vnf_data.get("status")},
409,
'Conflict')
try:
vnf_instance_data = objects.VnfInstanceList.vnf_instance_list(
vnf_data.get('vnfd_id'), context)
if not vnf_instance_data:
msg = _("Can not find requested vnf instance data: %s") \
% vnf_data.get('vnfd_id')
return self._make_problem_detail(msg, 404, title='Not Found')
except Exception as e:
return self._make_problem_detail(
str(e), 500, 'Internal Server Error')
vnfd_pkg_data = {}
if (body_data.get('vnfd_id') or body_data.get('vnf_pkg_id')):
pkg_obj = objects.VnfPackageVnfd(context=context)
try:
if (body.get('vnfdId')):
input_id = 'vnfd_id'
filter_id = 'vnfdId'
vnfd_pkg = pkg_obj.get_vnf_package_vnfd(
body_data[input_id])
elif (body_data.get('vnf_pkg_id')):
input_id = 'vnf_pkg_id'
filter_id = 'id'
vnfd_pkg = pkg_obj.get_vnf_package_vnfd(
body_data[input_id], package_uuid=True)
except exceptions.VnfPackageVnfdNotFound:
vnf_package_info = self._find_vnf_package_info(filter_id,
body_data[input_id])
if not vnf_package_info:
msg = _(
"Can not find requested vnf package vnfd: %s") %\
body_data[input_id]
return self._make_problem_detail(msg, 400,
'Bad Request')
vnfd_pkg = sync_resource.SyncVnfPackage.create_package(
context, vnf_package_info)
if not vnfd_pkg:
msg = (
_("Can not find requested to NFVO,\
vnf package vnfd: %s") %
body_data[input_id])
return self._make_problem_detail(
msg, 500, 'Internal Server Error')
vnfd_pkg_data['vnf_provider'] = vnfd_pkg.get('vnf_provider')
vnfd_pkg_data['vnf_product_name'] = vnfd_pkg.get(
'vnf_product_name')
vnfd_pkg_data['vnf_software_version'] = vnfd_pkg.get(
'vnf_software_version')
vnfd_pkg_data['vnfd_version'] = vnfd_pkg.get('vnfd_version')
vnfd_pkg_data['package_uuid'] = vnfd_pkg.get('package_uuid')
vnfd_pkg_data['vnfd_id'] = vnfd_pkg.get('vnfd_id')
# make op_occs_uuid
op_occs_uuid = uuidutils.generate_uuid()
# process vnf
vnf_lcm_opoccs = {
'vnf_instance_id': id,
'id': op_occs_uuid,
'state_entered_time': timeutils.utcnow(),
'operationParams': str(body)}
self.rpc_api.update(
context,
vnf_lcm_opoccs,
body_data,
vnfd_pkg_data,
vnf_data.get('vnfd_id'))
# make response
res = webob.Response(content_type='application/json')
res.status_int = 202
loc_url = CONF.vnf_lcm.endpoint_url.rstrip("/") + \
'/vnflcm/v1/vnf_lcm_op_occs/' + op_occs_uuid
location = ('Location', loc_url)
res.headerlist.append(location)
return res
@wsgi.response(http_client.CREATED)
@validation.schema(vnf_lcm.register_subscription)
def register_subscription(self, request, body):
subscription_request_data = body
subscription_id = uuidutils.generate_uuid()
vnf_lcm_subscription = subscription_obj.LccnSubscriptionRequest(
context=request.context)
vnf_lcm_subscription.id = subscription_id
vnf_lcm_subscription.callback_uri = subscription_request_data.get(
'callbackUri')
vnf_lcm_subscription.subscription_authentication = \
subscription_request_data.get('subscriptionAuthentication')
LOG.debug("filter %s " % subscription_request_data.get('filter'))
LOG.debug(
"filter type %s " %
type(
subscription_request_data.get('filter')))
filter_uni = subscription_request_data.get('filter')
filter = ast.literal_eval(str(filter_uni).replace("'", "'"))
if CONF.vnf_lcm.test_callback_uri:
resp = self._test_notification(request.context,
vnf_lcm_subscription)
if resp == -1:
LOG.exception(traceback.format_exc())
return self._make_problem_detail(
'Failed to Test Notification', 400,
title='Bad Request')
try:
vnf_lcm_subscription = vnf_lcm_subscription.create(filter)
LOG.debug("vnf_lcm_subscription %s" % vnf_lcm_subscription)
except exceptions.SeeOther as e:
res = webob.Response(content_type='application/json')
res.status_int = http_client.SEE_OTHER.value
location = (
'Location',
CONF.vnf_lcm.endpoint_url.rstrip("/") +
"/vnflcm/v1/subscriptions/" + str(e))
res.headerlist.append(location)
return res
result = self._view_builder.subscription_create(vnf_lcm_subscription,
filter)
location = result.get('_links', {}).get('self', {}).get('href')
headers = {"location": location}
return wsgi.ResponseObject(result, headers=headers)
@wsgi.response(http_client.OK)
def subscription_show(self, request, subscriptionId):
try:
vnf_lcm_subscriptions = (
subscription_obj.LccnSubscriptionRequest.
vnf_lcm_subscriptions_show(request.context, subscriptionId))
if not vnf_lcm_subscriptions:
msg = (
_("Can not find requested vnf lcm subscriptions: %s") %
subscriptionId)
return self._make_problem_detail(msg, 404, title='Not Found')
except Exception as e:
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
return self._view_builder.subscription_show(vnf_lcm_subscriptions)
@wsgi.response(http_client.OK)
def subscription_list(self, request):
nextpage_opaque_marker = ""
paging = 1
filter_string = ""
re_url = request.path_url
query_params = request.query_string
if query_params:
query_params = parse.unquote(query_params)
query_param_list = query_params.split('&')
for query_param in query_param_list:
query_param_key_value = query_param.split('=')
if len(query_param_key_value) != 2:
msg = _("Request query parameter error")
return self._make_problem_detail(
msg, 400, title='Bad Request')
if query_param_key_value[0] == 'filter':
filter_string = query_param_key_value[1]
if query_param_key_value[0] == 'nextpage_opaque_marker':
nextpage_opaque_marker = query_param_key_value[1]
if query_param_key_value[0] == 'page':
paging = int(query_param_key_value[1])
if filter_string:
# check enumerations columns
for filter_segment in filter_string.split(';'):
filter_segment = re.sub(
r'\(|\)|\'', '', filter_segment)
filter_name = filter_segment.split(',')[1]
filter_value = filter_segment.split(',')[2]
if filter_name == 'notificationTypes':
if filter_value not in self.notification_type_list:
msg = (_("notificationTypes value mismatch: %s")
% filter_value)
return self._make_problem_detail(msg, 400,
title='Bad Request')
elif filter_name == 'operationTypes':
if filter_value not in self.operation_type_list:
msg = (_("operationTypes value mismatch: %s")
% filter_value)
return self._make_problem_detail(msg, 400,
title='Bad Request')
elif filter_name == 'operationStates':
if filter_value not in self.operation_state_list:
msg = (_("operationStates value mismatch: %s")
% filter_value)
return self._make_problem_detail(msg, 400,
title='Bad Request')
try:
filter_string_parsed = self._view_builder_subscription. \
validate_filter(filter_string)
if nextpage_opaque_marker:
start_index = paging - 1
else:
start_index = None
vnf_lcm_subscriptions, last = (
subscription_obj.LccnSubscriptionList.
get_by_filters(request.context,
read_deleted='no',
filters=filter_string_parsed,
nextpage_opaque_marker=start_index))
LOG.debug("vnf_lcm_subscriptions %s" % vnf_lcm_subscriptions)
subscription_data = self._view_builder_subscription. \
subscription_list(vnf_lcm_subscriptions)
LOG.debug("last %s" % last)
except Exception as e:
LOG.error(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
if subscription_data == 400:
msg = _("Number of records exceeds nextpage_opaque_marker")
return self._make_problem_detail(msg, 400, title='Bad Request')
# make response
res = webob.Response(content_type='application/json')
res.body = jsonutils.dump_as_bytes(subscription_data)
res.status_int = 200
if nextpage_opaque_marker:
if not last:
ln = '<%s?page=%s>;rel="next"; title*="next chapter"' % (
re_url, paging + 1)
# Regarding the setting in http header related to
# nextpage control, RFC8288 and NFV-SOL013
# specifications have not been confirmed.
# Therefore, it is implemented by setting "page",
# which is a general control method of WebAPI,
# as "URI-Reference" of Link header.
links = ('Link', ln)
res.headerlist.append(links)
LOG.debug("subscription_list res %s" % res)
return res
@wsgi.response(http_client.NO_CONTENT)
def delete_subscription(self, request, subscriptionId):
try:
vnf_lcm_subscription = \
subscription_obj.LccnSubscriptionRequest.destroy(
request.context, subscriptionId)
if vnf_lcm_subscription == 404:
msg = (
_("Can not find requested vnf lcm subscription: %s") %
subscriptionId)
return self._make_problem_detail(msg, 404, title='Not Found')
except Exception as e:
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
def _get_scale_max_level_from_vnfd(self, context, vnf_instance, aspect_id):
vnfd_dict = vnflcm_utils._get_vnfd_dict(context,
vnf_instance.vnfd_id,
vnf_instance.instantiated_vnf_info.flavour_id)
tosca = tosca_template.ToscaTemplate(parsed_params={}, a_file=False,
yaml_dict_tpl=vnfd_dict)
tosca_policies = tosca.topology_template.policies
aspect_max_level_dict = {}
toscautils._extract_policy_info(
tosca_policies, {}, {}, {}, {}, {}, aspect_max_level_dict)
return aspect_max_level_dict.get(aspect_id)
@check_vnf_state(action="scale",
instantiation_state=[fields.VnfInstanceState.INSTANTIATED],
task_state=[None])
@check_vnf_status_and_error_point(action="scale",
status=[constants.ACTIVE])
def _scale(self, context, vnf_instance, vnf_info, request_body):
req_body = utils.convert_camelcase_to_snakecase(request_body)
scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive(
req_body, context=context)
inst_vnf_info = vnf_instance.instantiated_vnf_info
if 'vnf_lcm_op_occs_id' in vnf_info:
vnf_lcm_op_occs_id = vnf_info['vnf_lcm_op_occs_id']
aspect = False
current_level = 0
for scale in inst_vnf_info.scale_status:
if scale_vnf_request.aspect_id == scale.aspect_id:
aspect = True
current_level = scale.scale_level
break
if not aspect:
return self._make_problem_detail(
'aspectId not in ScaleStatus',
400,
title='aspectId not in ScaleStatus')
if not scale_vnf_request.number_of_steps:
scale_vnf_request.number_of_steps = 1
if not scale_vnf_request.additional_params:
scale_vnf_request.additional_params = {"is_reverse": "False",
"is_auto": "False"}
if not scale_vnf_request.additional_params.get('is_reverse'):
scale_vnf_request.additional_params['is_reverse'] = "False"
if not scale_vnf_request.additional_params.get('is_auto'):
scale_vnf_request.additional_params['is_auto'] = "False"
vim_type = vnf_instance.vim_connection_info[0].vim_type
if scale_vnf_request.type == 'SCALE_IN':
if current_level == 0 or\
current_level < scale_vnf_request.number_of_steps:
return self._make_problem_detail(
'can not scale_in', 400, title='can not scale_in')
if vim_type == "kubernetes" and\
scale_vnf_request.additional_params['is_reverse'] == "True":
return self._make_problem_detail(
'is_reverse option is not supported when Kubernetes '
'scale operation',
400,
title='is_reverse option is not supported when Kubernetes '
'scale operation')
scale_level = current_level - scale_vnf_request.number_of_steps
elif scale_vnf_request.type == 'SCALE_OUT':
if vim_type == "kubernetes":
max_level = self._get_scale_max_level_from_vnfd(
context=context,
vnf_instance=vnf_instance,
aspect_id=scale_vnf_request.aspect_id)
else:
scaleGroupDict = jsonutils.loads(
vnf_info['attributes']['scale_group'])
max_level = (scaleGroupDict['scaleGroupDict']
[scale_vnf_request.aspect_id]['maxLevel'])
scale_level = current_level + scale_vnf_request.number_of_steps
if max_level < scale_level:
return self._make_problem_detail(
'can not scale_out', 400, title='can not scale_out')
if 'vnf_lcm_op_occs_id' in vnf_info:
num = (scaleGroupDict['scaleGroupDict']
[scale_vnf_request.aspect_id]['num'])
default = (scaleGroupDict['scaleGroupDict']
[scale_vnf_request.aspect_id]['default'])
vnf_info['res_num'] = (num *
scale_vnf_request.number_of_steps + default)
if vnf_info['before_error_point'] == fields.ErrorPoint.INITIAL:
vnf_lcm_op_occs_id = uuidutils.generate_uuid()
timestamp = datetime.datetime.utcnow()
operation_params = {
'type': scale_vnf_request.type,
'aspect_id': scale_vnf_request.aspect_id,
'number_of_steps': scale_vnf_request.number_of_steps,
'additional_params': scale_vnf_request.additional_params}
vnf_lcm_op_occ = objects.VnfLcmOpOcc(
context=context,
id=vnf_lcm_op_occs_id,
operation_state='STARTING',
state_entered_time=timestamp,
start_time=timestamp,
vnf_instance_id=inst_vnf_info.vnf_instance_id,
operation='SCALE',
is_automatic_invocation=scale_vnf_request.additional_params.get('\
is_auto'),
operation_params=json.dumps(operation_params),
error_point=1)
vnf_lcm_op_occ.create()
else:
try:
vnf_lcm_op_occ = objects.VnfLcmOpOcc.get_by_id(
context, vnf_lcm_op_occs_id)
except exceptions.NotFound as lcm_e:
return self._make_problem_detail(str(lcm_e),
404, title='Not Found')
except (sqlexc.SQLAlchemyError, Exception) as exc:
LOG.exception(exc)
return self._make_problem_detail(str(exc),
500, title='Internal Server Error')
vnf_instance.task_state = fields.VnfInstanceTaskState.SCALING
vnf_instance.save()
vnflcm_url = CONF.vnf_lcm.endpoint_url.rstrip("/") + \
"/vnflcm/v1/vnf_lcm_op_occs/" + vnf_lcm_op_occs_id
insta_url = CONF.vnf_lcm.endpoint_url.rstrip("/") + \
"/vnflcm/v1/vnf_instances/" + inst_vnf_info.vnf_instance_id
vnf_info['vnflcm_id'] = vnf_lcm_op_occs_id
vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ
vnf_info['after_scale_level'] = scale_level
vnf_info['scale_level'] = current_level
vnf_info['instance_id'] = inst_vnf_info.instance_id
notification = {}
notification['notificationType'] = \
'VnfLcmOperationOccurrenceNotification'
notification['vnfInstanceId'] = inst_vnf_info.vnf_instance_id
notification['notificationStatus'] = 'START'
notification['operation'] = 'SCALE'
notification['operationState'] = 'STARTING'
notification['isAutomaticInvocation'] = \
scale_vnf_request.additional_params.get('is_auto')
notification['vnfLcmOpOccId'] = vnf_lcm_op_occs_id
notification['_links'] = {}
notification['_links']['vnfInstance'] = {}
notification['_links']['vnfInstance']['href'] = insta_url
notification['_links']['vnfLcmOpOcc'] = {}
notification['_links']['vnfLcmOpOcc']['href'] = vnflcm_url
vnf_info['notification'] = notification
if vnf_info['before_error_point'] == fields.ErrorPoint.INITIAL:
self.rpc_api.send_notification(context, notification)
self.rpc_api.scale(context, vnf_info, vnf_instance, scale_vnf_request)
res = webob.Response()
res.status_int = 202
location = ('Location', vnflcm_url)
res.headerlist.append(location)
return res
@validation.schema(vnf_lcm.scale)
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
http_client.NOT_FOUND, http_client.CONFLICT))
def scale(self, request, id, body):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'scale')
try:
vnf_info = self._vnfm_plugin.get_vnf(context, id)
if vnf_info['status'] != constants.ACTIVE:
return self._make_problem_detail(
'VNF IS NOT ACTIVE', 409, title='VNF IS NOT ACTIVE')
vnf_instance = self._get_vnf_instance(context, id)
if not vnf_instance.instantiated_vnf_info.scale_status:
return self._make_problem_detail(
'NOT SCALE VNF', 409, title='NOT SCALE VNF')
return self._scale(context, vnf_instance, vnf_info, body)
except vnfm.VNFNotFound as vnf_e:
return self._make_problem_detail(
str(vnf_e), 404, title='VNF NOT FOUND')
except webob.exc.HTTPNotFound as inst_e:
return self._make_problem_detail(
str(inst_e), 404, title='VNF NOT FOUND')
except Exception as e:
LOG.error(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
def _rollback(
self,
context,
vnf_info,
vnf_instance,
vnf_lcm_op_occs,
operation_params):
self.rpc_api.rollback(
context,
vnf_info,
vnf_instance,
operation_params)
vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs
vnflcm_url = CONF.vnf_lcm.endpoint_url.rstrip("/") + \
"/vnflcm/v1/vnf_lcm_op_occs/" + vnf_lcm_op_occs.id
res = webob.Response()
res.status_int = 202
location = ('Location', vnflcm_url)
res.headerlist.append(location)
return res
def _get_rollback_vnf(self, context, vnf_instance_id):
return self._vnfm_plugin.get_vnf(context, vnf_instance_id)
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
http_client.NOT_FOUND, http_client.CONFLICT))
def rollback(self, request, id):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'rollback')
try:
vnf_lcm_op_occs = objects.VnfLcmOpOcc.get_by_id(context, id)
if vnf_lcm_op_occs.operation_state != 'FAILED_TEMP':
return self._make_problem_detail(
'OperationState IS NOT FAILED_TEMP',
409,
title='OperationState IS NOT FAILED_TEMP')
if vnf_lcm_op_occs.operation != 'INSTANTIATE' \
and vnf_lcm_op_occs.operation != 'SCALE':
return self._make_problem_detail(
'OPERATION IS NOT INSTANTIATE/SCALE',
409,
title='OPERATION IS NOT INSTANTIATE/SCALE')
operation_params = jsonutils.loads(
vnf_lcm_op_occs.operation_params)
if vnf_lcm_op_occs.operation == 'SCALE' \
and operation_params['type'] == 'SCALE_IN':
return self._make_problem_detail(
'SCALE_IN CAN NOT ROLLBACK', 409,
title='SCALE_IN CAN NOT ROLLBACK')
vnf_info = self._get_rollback_vnf(
context, vnf_lcm_op_occs.vnf_instance_id)
vnf_instance = self._get_vnf_instance(
context, vnf_lcm_op_occs.vnf_instance_id)
inst_vnf_info = vnf_instance.instantiated_vnf_info
if inst_vnf_info is not None:
vnf_info['instance_id'] = inst_vnf_info.instance_id
vnf_lcm_op_occs.changed_info = None
vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs
return self._rollback(
context,
vnf_info,
vnf_instance,
vnf_lcm_op_occs,
operation_params)
except vnfm.VNFNotFound as vnf_e:
return self._make_problem_detail(
str(vnf_e), 404, title='VNF NOT FOUND')
except exceptions.NotFound as occ_e:
return self._make_problem_detail(
str(occ_e), 404, title='VNF NOT FOUND')
except webob.exc.HTTPNotFound as inst_e:
return self._make_problem_detail(
str(inst_e), 404, title='VNF NOT FOUND')
except Exception as e:
LOG.error(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
# TODO(esto-aln): For adding it to make it consistent
# We change vnf status here. In near future, we plan to
# delete this method.
def _update_vnf_fail_status(self, context, vnf_instance_id,
new_status):
self._vnfm_plugin.update_vnf_fail_status(
context, vnf_instance_id, new_status)
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
http_client.NOT_FOUND))
def fail(self, request, id):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'fail')
try:
vnf_lcm_op_occs = objects.VnfLcmOpOcc.get_by_id(context, id)
operation = vnf_lcm_op_occs.operation
if vnf_lcm_op_occs.operation_state != 'FAILED_TEMP':
return self._make_problem_detail(
'State is not FAILED_TEMP', 409, title='Conflict')
vnf_instance_id = vnf_lcm_op_occs.vnf_instance_id
vnf_instance = self._get_vnf_instance(context, vnf_instance_id)
except webob.exc.HTTPNotFound as e:
return self._make_problem_detail(
str(e), 404, title='VNF NOT FOUND')
except exceptions.NotFound as e:
return self._make_problem_detail(
str(e), 404, title='VNF LCM NOT FOUND')
except Exception as e:
LOG.error(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
try:
old_vnf_instance = copy.deepcopy(vnf_instance)
vnf_lcm_op_occs.operation_state = "FAILED"
vnf_lcm_op_occs.state_entered_time = \
datetime.datetime.utcnow().isoformat()
vnf_lcm_op_occs.updated_at = vnf_lcm_op_occs.state_entered_time
error_details = objects.ProblemDetails(
context=context,
status=500,
detail=str(vnf_lcm_op_occs.error)
)
vnf_lcm_op_occs.error = error_details
# TODO(esto-aln): For adding it to make it consistent
# We change vnf status here. In near future, we plan to
# delete this branch.
if vnf_instance.instantiation_state == \
fields.VnfInstanceState.INSTANTIATED:
new_status = constants.ACTIVE
else:
new_status = constants.INACTIVE
self._update_vnf_fail_status(context, vnf_instance.id,
new_status)
vnf_instance.task_state = None
vnf_instance.save()
affected_resources = vnflcm_utils._get_affected_resources(
old_vnf_instance=old_vnf_instance,
new_vnf_instance=vnf_instance)
resource_change_obj = jsonutils.dumps(
utils.convert_camelcase_to_snakecase(affected_resources))
changed_resource = objects.ResourceChanges.obj_from_primitive(
resource_change_obj, context)
vnf_lcm_op_occs.resource_changes = changed_resource
vnf_lcm_op_occs.save()
except Exception as ex:
error_msg = "Error in VNF Fail for vnf {} because {}".format(
vnf_instance.id, encodeutils.exception_to_unicode(ex))
LOG.error(error_msg)
raise exceptions.TackerException(message=error_msg)
return self._fail(context, vnf_instance, vnf_lcm_op_occs,
operation, affected_resources)
def _fail(self, context, vnf_instance, vnf_lcm_op_occs,
operation, affected_resources):
self._notification_process(
context, vnf_instance, operation, {}, {},
vnf_lcm_op_occs=vnf_lcm_op_occs,
operation_state=fields.LcmOccsOperationState.FAILED,
notification_status=fields.LcmOccsNotificationStatus.RESULT,
affected_resources=affected_resources)
return self._view_builder.show_lcm_op_occs(vnf_lcm_op_occs)
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
http_client.NOT_FOUND, http_client.CONFLICT))
def retry(self, request, id):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'retry')
try:
vnf_lcm_op_occs = objects.VnfLcmOpOcc.get_by_id(context, id)
except exceptions.NotFound as lcm_e:
return self._make_problem_detail(str(lcm_e),
404, title='Not Found')
except (sqlexc.SQLAlchemyError, Exception) as exc:
LOG.exception(exc)
return self._make_problem_detail(str(exc),
500, title='Internal Server Error')
# operation state checking
if vnf_lcm_op_occs.operation_state != \
fields.LcmOccsOperationState.FAILED_TEMP:
error_msg = ('Cannot proceed with operation_state %s'
% vnf_lcm_op_occs.operation_state)
return self._make_problem_detail(error_msg,
409, title='Conflict')
# get vnf
try:
vnf = self._get_vnf(context, vnf_lcm_op_occs.vnf_instance_id)
except webob.exc.HTTPNotFound as lcm_e:
return self._make_problem_detail(str(lcm_e),
404, title='Not Found')
except Exception as exc:
LOG.exception(exc)
return self._make_problem_detail(str(exc),
500, title='Internal Server Error')
# get vnf instance
try:
vnf_instance = objects.VnfInstance.get_by_id(
context, vnf_lcm_op_occs.vnf_instance_id)
except exceptions.VnfInstanceNotFound:
msg = (_("Can not find requested vnf instance: %s")
% vnf_lcm_op_occs.vnf_instance_id)
return self._make_problem_detail(msg,
404, title='Not Found')
except Exception as exc:
LOG.exception(exc)
return self._make_problem_detail(str(exc),
500, title='Internal Server Error')
operation = vnf_lcm_op_occs.operation
body = jsonutils.loads(vnf_lcm_op_occs.operation_params)
vnf['before_error_point'] = vnf_lcm_op_occs.error_point
vnf['vnf_lcm_op_occs_id'] = id
if operation == fields.LcmOccsOperationType.INSTANTIATE:
self._instantiate(context, vnf_instance, vnf, body)
elif operation == fields.LcmOccsOperationType.TERMINATE:
self._terminate(context, vnf_instance, vnf, body)
elif operation == fields.LcmOccsOperationType.HEAL:
self._heal(context, vnf_instance, vnf, body)
elif operation == fields.LcmOccsOperationType.SCALE:
self._scale(context, vnf_instance, vnf, body)
elif operation == fields.LcmOccsOperationType.CHANGE_EXT_CONN:
self._change_ext_conn(context, vnf_instance, vnf, body)
else:
error_msg = 'Operation type %s is inavalid' % operation
return self._make_problem_detail(error_msg,
500, title='Internal Server Error')
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.BAD_REQUEST))
def list_lcm_op_occs(self, request):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'list_lcm_op_occs')
all_fields = request.GET.get('all_fields')
exclude_default = request.GET.get('exclude_default')
fields = request.GET.get('fields')
exclude_fields = request.GET.get('exclude_fields')
filters = request.GET.get('filter')
if not (all_fields or fields or exclude_fields):
exclude_default = True
self._view_builder_op_occ.validate_attribute_fields(
all_fields=all_fields, fields=fields,
exclude_fields=exclude_fields,
exclude_default=exclude_default)
filters = self._view_builder_op_occ.validate_filter(filters)
try:
vnf_lcm_op_occs = \
vnf_lcm_op_occs_obj.VnfLcmOpOccList.get_by_filters(
request.context, read_deleted='no', filters=filters)
except Exception as e:
LOG.exception(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
return self._view_builder_op_occ.index(request, vnf_lcm_op_occs,
all_fields=all_fields, exclude_fields=exclude_fields,
fields=fields, exclude_default=exclude_default)
def _make_problem_detail(
self,
detail,
status,
title=None,
type=None,
instance=None):
'''This process returns the problem_detail to the caller'''
LOG.error(detail)
res = webob.Response(content_type='application/problem+json')
problem_details = {}
if type:
problem_details['type'] = type
if title:
problem_details['title'] = title
problem_details['detail'] = detail
problem_details['status'] = status
if instance:
problem_details['instance'] = instance
res.text = json.dumps(problem_details)
res.status_int = status
return res
def _test_notification(self, context, vnf_lcm_subscription):
resp = self.rpc_api.test_notification(context,
vnf_lcm_subscription, cast=False)
return resp
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
http_client.NOT_FOUND, http_client.CONFLICT))
@validation.schema(vnf_lcm.change_ext_conn)
def change_ext_conn(self, request, id, body):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'change_ext_conn')
vnf = self._get_vnf(context, id)
vnf_instance = self._get_vnf_instance(context, id)
if (vnf_instance.instantiation_state !=
fields.VnfInstanceState.INSTANTIATED):
return self._make_problem_detail(
'VNF is not instantiated',
409,
title='VNF IS NOT INSTANTIATED')
vnf['before_error_point'] = EP.INITIAL
self._change_ext_conn(context, vnf_instance, vnf, body)
def _change_ext_conn(self, context, vnf_instance, vnf, request_body):
req_body = utils.convert_camelcase_to_snakecase(request_body)
change_ext_conn_req = objects.ChangeExtConnRequest.obj_from_primitive(
req_body, context)
# call notification process
if vnf['before_error_point'] == EP.INITIAL:
vnf_lcm_op_occs_id = self._notification_process(
context,
vnf_instance,
fields.LcmOccsOperationType.CHANGE_EXT_CONN,
change_ext_conn_req,
request_body)
else:
vnf_lcm_op_occs_id = vnf['vnf_lcm_op_occs_id']
# Call Conductor server.
self.rpc_api.change_ext_conn(
context,
vnf_instance,
vnf,
change_ext_conn_req,
vnf_lcm_op_occs_id)
def create_resource():
return wsgi.Resource(VnfLcmController())