Support flow of Getting VNF package

Supported the following operations to get the VNF package
information from NFVO when starting LCM operation.
    - VNF packages (GET)
    - VNF package content (GET)
    - VNFD in an individual VNF package (GET)
    - Individual VNF package artifact (GET)

Implements: blueprint support-vnfm-operations
Spec: https://specs.openstack.org/openstack/tacker-specs/specs/victoria/support-sol003-vnfm-operations.html

Change-Id: Ibdafdda815f8e130226b9d8eef4f18639f01292c
(cherry picked from commit 9d95f91504)
changes/62/756462/2
Aldinson Esto 2 years ago committed by Toshiaki Takahashi
parent b7321d208f
commit e72a66bbba

@ -206,7 +206,8 @@ terminate = {
'properties': {
'terminationType': {'type': 'string',
'enum': ['FORCEFUL', 'GRACEFUL']},
'gracefulTerminationTimeout': {'type': 'integer', 'minimum': 0}
'gracefulTerminationTimeout': {'type': 'integer', 'minimum': 0},
'additionalParams': parameter_types.keyvalue_pairs,
},
'required': ['terminationType'],
'additionalProperties': False,

@ -116,18 +116,11 @@ class ViewBuilder(base.BaseViewBuilder):
return {"_links": _links}
def _get_vnf_instance_info(self,
vnf_instance, api_version=None):
def _get_vnf_instance_info(self, vnf_instance):
vnf_instance_dict = vnf_instance.to_dict()
if vnf_instance_dict.get('vim_connection_info'):
vnf_instance_dict['vim_connection_info'] = \
self._get_vim_conn_info(vnf_instance_dict.get(
'vim_connection_info', []))
if 'vnf_metadata' in vnf_instance_dict:
metadata_val = vnf_instance_dict.pop('vnf_metadata')
vnf_instance_dict['metadata'] = metadata_val
vnf_metadata = vnf_instance_dict.pop("vnf_metadata")
if vnf_metadata:
vnf_instance_dict.update({"metadata": vnf_metadata})
vnf_instance_dict = utils.convert_snakecase_to_camelcase(
vnf_instance_dict)

@ -13,11 +13,20 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import requests
import six
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
import ast
import functools
@ -25,20 +34,18 @@ import json
import re
import traceback
import six
from six.moves import http_client
from six.moves.urllib import parse
import webob
from tacker._i18n import _
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.vnflcm.v1 import sync_resource
from tacker.common import exceptions
from tacker.common import utils
from tacker.conductor.conductorrpc import vnf_lcm_rpc
import tacker.conf
from tacker.db.vnfm import vnfm_db
from tacker.extensions import nfvo
from tacker.extensions import vnfm
from tacker import manager
@ -47,9 +54,11 @@ from tacker.objects import fields
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
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__)
@ -307,73 +316,187 @@ class VnfLcmController(wsgi.Controller):
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.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')
req_body = utils.convert_camelcase_to_snakecase(body)
vnfd_id = req_body.get('vnfd_id')
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 as exc:
raise webob.exc.HTTPBadRequest(explanation=six.text_type(exc))
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')
# get default vim information
vim_client_obj = vim_client.VimClient()
default_vim = vim_client_obj.get_vim(context)
# set vim_connection_info
access_info = {
'username': default_vim.get('vim_auth', {}).get('username'),
'password': default_vim.get('vim_auth', {}).get('password'),
'region': default_vim.get('placement_attr', {}).get('region'),
'tenant': default_vim.get('tenant')
}
vim_con_info = objects.VimConnectionInfo(id=default_vim.get('vim_id'),
vim_id=default_vim.get('vim_id'),
vim_type=default_vim.get('vim_type'),
access_info=access_info)
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'))
vnf_instance.create()
# add default vim to vim_connection_info
setattr(vnf_instance, 'vim_connection_info', [vim_con_info])
# create notification data
notification = {
'notificationType':
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,
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)
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)}}}
'vnfInstanceId': vnf_instance.id,
'links': {
'vnfInstance': {
'href': self._get_vnf_instance_href(vnf_instance)}}}
# call send nootification
self.rpc_api.send_notification(context, notification)
vnf_instance.save()
# call sendNotification
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)
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=six.text_type(exc))
except(sqlexc.SQLAlchemyError, Exception)\
as exc:
raise webob.exc.HTTPInternalServerError(
explanation=six.text_type(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))
@ -401,8 +524,16 @@ class VnfLcmController(wsgi.Controller):
@check_vnf_state(action="delete",
instantiation_state=[fields.VnfInstanceState.NOT_INSTANTIATED],
task_state=[None])
def _delete(self, context, vnf_instance):
@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,
@ -411,7 +542,8 @@ class VnfLcmController(wsgi.Controller):
context = request.environ['tacker.context']
vnf_instance = self._get_vnf_instance(context, id)
self._delete(context, vnf_instance)
vnf = self._get_vnf(context, id)
self._delete(context, vnf_instance, vnf)
notification = {
"notificationType": "VnfIdentifierDeletionNotification",

@ -0,0 +1,138 @@
#
# 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_config import cfg
from oslo_log import log as logging
from tacker.common import csar_utils
from tacker.common import exceptions
from tacker.common import utils
from tacker.conductor.conductorrpc import vnf_pkgm_rpc
from tacker.glance_store import store as glance_store
from tacker import objects
from tacker.objects import fields
import tacker.vnfm.nfvo_client as nfvo_client
import time
import webob
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class SyncVnfPackage:
vnf_package_rpc_api = vnf_pkgm_rpc.VNFPackageRPCAPI()
@classmethod
def create_package(cls, context, vnf_package_info):
"""vnf_package, create a vnf_package_vnfd table."""
vnf_package_info = utils.convert_camelcase_to_snakecase(
vnf_package_info)
try:
vnf_package = cls.__create_vnf_package(context, vnf_package_info)
except Exception as exc:
raise webob.exc.HTTPInternalServerError(
explanation=exc)
try:
artifact_paths = cls._get_artifact_paths(vnf_package_info)
vnf_package_binary = \
nfvo_client.VnfPackageRequest.download_vnf_packages(
vnf_package.id, artifact_paths)
except nfvo_client.UndefinedExternalSettingException as exc:
raise webob.exc.HTTPNotFound(explanation=exc)
except (nfvo_client.FaliedDownloadContentException, Exception) as exc:
raise webob.exc.HTTPInternalServerError(
explanation=exc)
try:
(location, size, _, multihash, _) = glance_store.store_csar(
context, vnf_package.id, vnf_package_binary)
cls.__update_vnf_package(vnf_package, location, size, multihash)
cls.vnf_package_rpc_api.upload_vnf_package_content(
context, vnf_package)
vnf_package_vnfd = cls._get_vnf_package_vnfd(
context, vnf_package_info.get('vnfd_id'))
except Exception as exc:
raise webob.exc.HTTPInternalServerError(
explanation=exc)
return vnf_package_vnfd
@classmethod
def _get_artifact_paths(cls, vnf_package_info):
additional_artifacts = vnf_package_info.get('additional_artifacts')
if additional_artifacts is None:
return None
return [artifact.get('artifact_path')
for artifact in additional_artifacts
if 'artifact_path' in artifact]
@classmethod
def __store_csar(cls, context, id, body):
(location, size, checksum, multihash,
loc_meta) = glance_store.store_csar(context, id, body)
return location, size, checksum, multihash, loc_meta
@classmethod
def __load_csar(cls, context, vnf_package):
location = vnf_package.location_glance_store
zip_path = glance_store.load_csar(vnf_package.id, location)
vnf_data, flavours = csar_utils.load_csar_data(
context.elevated(), vnf_package.id, zip_path)
return vnf_data, flavours
@classmethod
def __create_vnf_package(cls, context, vnf_package_info):
"""VNF Package Table Registration."""
vnf_package = objects.VnfPackage(
context=context,
id=vnf_package_info.get('id'),
onboarding_state=fields.PackageOnboardingStateType.CREATED,
operational_state=fields.PackageOperationalStateType.DISABLED,
usage_state=fields.PackageUsageStateType.NOT_IN_USE,
tenant_id=context.project_id
)
vnf_package.create()
return vnf_package
@classmethod
def __update_vnf_package(cls, vnf_package, location, size, multihash):
"""VNF Package Table Update."""
vnf_package.algorithm = CONF.vnf_package.hashing_algorithm
vnf_package.location_glance_store = location
vnf_package.hash = multihash
vnf_package.size = size
vnf_package.save()
@classmethod
def _get_vnf_package_vnfd(cls, context, vnfd_id):
"""Get VNF Package VNFD."""
for num in range(CONF.vnf_lcm.retry_num):
try:
vnfd = objects.VnfPackageVnfd.get_by_id(
context,
vnfd_id)
return vnfd
except exceptions.VnfPackageVnfdNotFound:
LOG.debug("retry_wait %s" %
CONF.vnf_lcm.retry_wait)
time.sleep(CONF.vnf_lcm.retry_wait)
return None

@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from io import BytesIO
import json
import mimetypes
import os
@ -302,19 +302,37 @@ class VnfPkgmController(wsgi.Controller):
context = request.environ['tacker.context']
context.can(vnf_package_policies.VNFPKGM % 'upload_package_content')
vnf_package = self._get_vnf_package(id, request)
# check if id is of type uuid format
if not uuidutils.is_uuid_like(id):
msg = _("Can not find requested vnf package: %s") % id
return self._make_problem_detail('Not Found', msg, 404)
try:
vnf_package = vnf_package_obj.VnfPackage.get_by_id(
request.context, id)
except exceptions.VnfPackageNotFound:
msg = _("Can not find requested vnf package: %s") % id
return self._make_problem_detail('Not Found', msg, 404)
except Exception as e:
return self._make_problem_detail(
'Internal Server Error', str(e), 500)
if vnf_package.onboarding_state != \
fields.PackageOnboardingStateType.CREATED:
msg = _("VNF Package %(id)s onboarding state "
"is not %(onboarding)s")
raise webob.exc.HTTPConflict(explanation=msg % {"id": id,
"onboarding": fields.PackageOnboardingStateType.CREATED})
return self._make_problem_detail('Conflict', msg % {"id": id,
"onboarding": fields.PackageOnboardingStateType.CREATED},
409)
vnf_package.onboarding_state = (
fields.PackageOnboardingStateType.UPLOADING)
vnf_package.save()
try:
vnf_package.save()
except Exception as e:
return self._make_problem_detail(
'Internal Server Error', str(e), 500)
try:
(location, size, checksum, multihash,
@ -323,16 +341,29 @@ class VnfPkgmController(wsgi.Controller):
with excutils.save_and_reraise_exception():
vnf_package.onboarding_state = (
fields.PackageOnboardingStateType.CREATED)
vnf_package.save()
vnf_package.onboarding_state = (
fields.PackageOnboardingStateType.PROCESSING)
try:
vnf_package.save()
except Exception as e:
return self._make_problem_detail(
'Internal Server Error', str(e), 500)
vnf_package.algorithm = CONF.vnf_package.hashing_algorithm
vnf_package.hash = multihash
vnf_package.location_glance_store = location
vnf_package.size = size
vnf_package.save()
try:
vnf_package.save()
except Exception as e:
vnf_package.onboarding_state = (
fields.PackageOnboardingStateType.CREATED)
try:
vnf_package.save()
except Exception as e:
return self._make_problem_detail(
'Internal Server Error', str(e), 500)
return self._make_problem_detail(
'Internal Server Error', str(e), 500)
# process vnf_package
self.rpc_api.upload_vnf_package_content(context, vnf_package)
@ -618,6 +649,16 @@ class VnfPkgmController(wsgi.Controller):
return buff.getvalue()
def _make_problem_detail(self, title, detail, status):
res = webob.Response(content_type='application/problem+json')
problemDetails = {}
problemDetails['title'] = title
problemDetails['detail'] = detail
problemDetails['status'] = status
res.text = json.dumps(problemDetails)
res.status_int = status
return res
def create_resource():
body_deserializers = {

@ -12,6 +12,7 @@
# 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 copy import deepcopy
import hashlib
import os
import re
@ -29,6 +30,7 @@ import zipfile
from tacker.common import exceptions
import tacker.conf
from tacker.extensions import vnfm
import urllib.request as urllib2
@ -124,8 +126,10 @@ def _get_software_image(custom_defs, nodetemplate_name, node_tpl):
properties = node_tpl['properties']
sw_image_artifact = _get_sw_image_artifact(node_tpl.get('artifacts'))
if sw_image_artifact:
image_path = sw_image_artifact['file'].lstrip("./")
properties['sw_image_data'].update(
{'software_image_id': nodetemplate_name})
{'software_image_id': nodetemplate_name,
'image_path': image_path})
sw_image_data = properties['sw_image_data']
if 'metadata' in sw_image_artifact:
sw_image_data.update({'metadata':
@ -137,7 +141,79 @@ def _populate_flavour_data(tosca):
flavours = []
if tosca.nested_tosca_templates_with_topology:
for tp in tosca.nested_tosca_templates_with_topology:
_get_flavour_data(tp, flavours)
sw_image_list = []
# Setting up flavour data
flavour_id = tp.substitution_mappings.properties.get('flavour_id')
if flavour_id:
flavour = {'flavour_id': flavour_id}
tpl_dict = dict()
# get from top-vnfd data
for key, value in tosca.tpl.items():
if key in CONF.vnf_package.get_top_list:
tpl_dict[key] = value
# get from lower-vnfd data
tpl_dict['topology_template'] = dict()
tpl_dict['topology_template']['policies'] = \
tp.tpl.get('policies')
tpl_dict['topology_template']['node_templates'] = \
deepcopy(tp.tpl.get('node_templates'))
for e_node in CONF.vnf_package.exclude_node:
if tpl_dict['topology_template']['node_templates'].\
get(e_node):
del (tpl_dict['topology_template']
['node_templates'][e_node])
tpl_dict['topology_template']['inputs'] = \
deepcopy(tp.tpl.get('inputs'))
for del_input in CONF.vnf_package.del_input_list:
if tpl_dict['topology_template']['inputs'].get(del_input):
del tpl_dict['topology_template']['inputs'][del_input]
if len(tpl_dict['topology_template']['inputs']) < 1:
del tpl_dict['topology_template']['inputs']
flavour.update({'tpl_dict': tpl_dict})
instantiation_levels = _get_instantiation_levels(tp.policies)
if instantiation_levels:
flavour.update(
{'instantiation_levels': instantiation_levels})
mgmt_driver = None
for template_name, node_tpl in \
tp.tpl.get('node_templates').items():
# check the flavour property in vnf data
_update_flavour_data_from_vnf(
tp.custom_defs, node_tpl, flavour)
if node_tpl['type'] in CONF.vnf_package.get_lower_list:
if node_tpl['type'] == "tosca.nodes.nfv.VDU.Tacker":
# get mgmt_driver
mgmt_driver_flavour = \
node_tpl['properties'].get('mgmt_driver')
if mgmt_driver_flavour:
if mgmt_driver and \
mgmt_driver_flavour != mgmt_driver:
raise vnfm.MultipleMGMTDriversSpecified()
mgmt_driver = mgmt_driver_flavour
flavour.update({'mgmt_driver': mgmt_driver})
for template_name, node_tpl in \
tp.tpl.get('node_templates').items():
# Update the software image data
sw_image = _get_software_image(tp.custom_defs,
template_name,
node_tpl)
if sw_image:
sw_image_list.append(sw_image)
# Add software images for flavour
if sw_image_list:
flavour.update({'sw_images': sw_image_list})
if flavour:
flavours.append(flavour)
else:
_get_flavour_data(tosca.topology_template, flavours)

@ -356,5 +356,9 @@ class UserDataUpdateCreateFailed(TackerException):
"or created after %(retries)d retries.")
class DBAccessError(TackerException):
message = _("DB Access Error")
class SeeOther(TackerException):
code = 303

@ -18,6 +18,7 @@ import functools
import inspect
import json
import os
import oslo_messaging
import shutil
import sys
import time
@ -28,7 +29,6 @@ import yaml
from glance_store import exceptions as store_exceptions
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
from oslo_serialization import jsonutils
from oslo_service import periodic_task
from oslo_service import service
@ -42,20 +42,25 @@ from sqlalchemy.orm import exc as orm_exc
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.common import log
from tacker.common import safe_utils
from tacker.common import topics
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.nfvo import nfvo_db
from tacker.db.vnfm import vnfm_db
from tacker.extensions import nfvo
from tacker.glance_store import store as glance_store
from tacker import manager
from tacker import objects
from tacker.objects import fields
from tacker.objects.vnf_package import VnfPackagesList
from tacker.objects import vnfd as vnfd_db
from tacker.objects import vnfd_attribute as vnfd_attribute_db
from tacker.plugins.common import constants
from tacker import service as tacker_service
from tacker import version
@ -63,7 +68,7 @@ from tacker.vnflcm import utils as vnflcm_utils
from tacker.vnflcm import vnflcm_driver
from tacker.vnfm import plugin
CONF = cfg.CONF
CONF = tacker.conf.CONF
# NOTE(tpatil): keystone_authtoken opts registered explicitly as conductor
# service doesn't use the keystonemiddleware.authtoken middleware as it's
@ -97,6 +102,13 @@ cfg.CONF.register_opts(OPTS, 'keystone_authtoken')
LOG = logging.getLogger(__name__)
_INACTIVE_STATUS = ('INACTIVE')
_ACTIVE_STATUS = ('ACTIVE')
_PENDING_STATUS = ('PENDING_CREATE',
'PENDING_TERMINATE',
'PENDING_DELETE',
'PENDING_HEAL')
def _delete_csar(context, vnf_package):
# Delete from glance store
@ -154,6 +166,9 @@ class Conductor(manager.Manager):
super(Conductor, self).__init__(host=self.conf.host)
self.vnfm_plugin = plugin.VNFMPlugin()
self.vnflcm_driver = vnflcm_driver.VnfLcmDriver()
self.vnf_manager = driver_manager.DriverManager(
'tacker.tacker.vnfm.drivers',
cfg.CONF.tacker.infra_driver)
def start(self):
coordination.COORDINATOR.start()
@ -223,7 +238,7 @@ class Conductor(manager.Manager):
vnf_sw_image.min_ram = 0
vnf_sw_image.min_disk = int(sw_image.get('min_disk').split()[0])
vnf_sw_image.size = int(sw_image.get('size').split()[0])
vnf_sw_image.image_path = ''
vnf_sw_image.image_path = sw_image['image_path']
vnf_sw_image.software_image_id = sw_image['software_image_id']
vnf_sw_image.metadata = sw_image.get('metadata', dict())
vnf_sw_image.create()
@ -233,8 +248,9 @@ class Conductor(manager.Manager):
deploy_flavour.package_uuid = package_uuid
deploy_flavour.flavour_id = flavour['flavour_id']
deploy_flavour.flavour_description = flavour['flavour_description']
deploy_flavour.instantiation_levels = \
flavour.get('instantiation_levels')
if flavour.get('instantiation_levels'):
deploy_flavour.instantiation_levels = \
flavour.get('instantiation_levels')
deploy_flavour.create()
sw_images = flavour.get('sw_images')
@ -273,27 +289,55 @@ class Conductor(manager.Manager):
package_vnfd.vnfd_version = vnf_data.get('descriptor_version')
package_vnfd.create()
self._onboard_vnfd(context, vnf_package, vnf_data, flavours)
for flavour in flavours:
self._create_flavour(context, vnf_package.id, flavour)
def _onboard_vnfd(self, context, vnf_package, vnf_data, flavours):
vnfd = vnfd_db.Vnfd(context=context)
vnfd.id = vnf_data.get('descriptor_id')
vnfd.tenant_id = context.tenant_id
vnfd.name = vnf_data.get('product_name') + '-' + \
vnf_data.get('descriptor_version')
vnfd.discription = vnf_data.get('discription')
for flavour in flavours:
if flavour.get('mgmt_driver'):
vnfd.mgmt_driver = flavour.get('mgmt_driver')
break
vnfd.create()
for flavour in flavours:
vnfd_attribute = vnfd_attribute_db.VnfdAttribute(context=context)
vnfd_attribute.id = uuidutils.generate_uuid()
vnfd_attribute.vnfd_id = vnf_data.get('descriptor_id')
vnfd_attribute.key = 'vnfd_' + flavour['flavour_id']
vnfd_attribute.value = \
yaml.dump(flavour.get('tpl_dict'), default_flow_style=False)
vnfd_attribute.create()
break
@revert_upload_vnf_package
def upload_vnf_package_content(self, context, vnf_package):
location = vnf_package.location_glance_store
zip_path = glance_store.load_csar(vnf_package.id, location)
vnf_data, flavours, vnf_artifacts = csar_utils.load_csar_data(
context.elevated(), vnf_package.id, zip_path)
self._onboard_vnf_package(
context,
vnf_package,
vnf_data,
flavours,
vnf_artifacts)
vnf_package.onboarding_state = (
fields.PackageOnboardingStateType.ONBOARDED)
vnf_package.operational_state = (
fields.PackageOperationalStateType.ENABLED)
fields.PackageOnboardingStateType.PROCESSING)
try:
vnf_package.save()
vnf_package.save()
location = vnf_package.location_glance_store
zip_path = glance_store.load_csar(vnf_package.id, location)
vnf_data, flavours, vnf_artifacts = csar_utils.load_csar_data(
context.elevated(), vnf_package.id, zip_path)
self._onboard_vnf_package(context, vnf_package, vnf_data, flavours)
vnf_package.onboarding_state = (
fields.PackageOnboardingStateType.ONBOARDED)
vnf_package.operational_state = (
fields.PackageOperationalStateType.ENABLED)
vnf_package.save()
except Exception as msg:
raise Exception(msg)
@revert_upload_vnf_package
def upload_vnf_package_from_uri(self, context, vnf_package,
@ -433,6 +477,180 @@ class Conductor(manager.Manager):
vnf_package.save()
@log.log
def _change_vnf_status(self, context, vnf_id,
current_statuses, new_status, error_reason=None):
'''Change vnf status and add error reason if error'''
LOG.debug("Change status of vnf %s from %s to %s", vnf_id,
current_statuses, new_status)
with context.session.begin(subtransactions=True):
updated_values = {
'status': new_status, 'updated_at': timeutils.utcnow()}
vnf_model = (context.session.query(
vnfm_db.VNF).filter_by(id=vnf_id).first())
if not vnf_model:
raise exceptions.VnfInstanceNotFound(
message="VNF {} not found".format(vnf_id))
if vnf_model.status not in current_statuses:
raise exceptions.VnfConflictState(
message='Cannot change status to {} \
while in {}'.format(
updated_values['status'], vnf_model.status))
vnf_model.update(updated_values)
def _update_vnf_attributes(self, context, vnf_dict, current_statuses,
new_status):
with context.session.begin(subtransactions=True):
try:
modified_attributes = {}
added_attributes = {}
updated_values = {
'mgmt_ip_address': vnf_dict['mgmt_ip_address'],
'status': new_status,
'updated_at': timeutils.utcnow()}
vnf_model = (context.session.query(vnfm_db.VNF).filter_by(
id=vnf_dict['id']).first())
if not vnf_model:
raise exceptions.VnfInstanceNotFound(
message="VNF {} not found".format(vnf_dict['id']))
if vnf_model.status not in current_statuses:
raise exceptions.VnfConflictState(
message='Cannot change status to {} while \
in {}'.format(updated_values['status'],
vnf_model.status))
vnf_model.update(updated_values)
for key, val in vnf_dict['attributes'].items():
vnf_attr_model = (context.session.query(
vnfm_db.VNFAttribute).
filter_by(vnf_id=vnf_dict['id']).
filter_by(key=key).first())
if vnf_attr_model:
modified_attributes.update(
{vnf_attr_model.key: vnf_attr_model.value})
vnf_attr_model.update({'value': val})
else:
added_attributes.update({key: val})
vnf_attr_model = vnfm_db.VNFAttribute(
id=uuidutils.generate_uuid(),
vnf_id=vnf_dict['id'],
key=key, value=val)
context.session.add(vnf_attr_model)
except Exception as exc:
with excutils.save_and_reraise_exception():
LOG.error("Error in updating tables {}".format(str(exc)))
# Roll back modified/added vnf attributes
for key, val in modified_attributes.items():
vnf_attr_model = (context.session.query(
vnfm_db.VNFAttribute).
filter_by(vnf_id=vnf_dict['id']).
filter_by(key=key).first())
if vnf_attr_model:
vnf_attr_model.update({'value': val})
for key, val in added_attributes.items():
vnf_attr_model = (context.session.query(
vnfm_db.VNFAttribute).
filter_by(vnf_id=vnf_dict['id']).
filter_by(key=key).first())
if vnf_attr_model:
vnf_attr_model.delete()
@log.log
def _build_instantiated_vnf_info(self, context, vnf_instance,
instantiate_vnf_req=None):
try:
# if instantiate_vnf_req is not present, create from vnf_instance
if not instantiate_vnf_req:
instantiate_vnf_req = objects.InstantiateVnfRequest.\
from_vnf_instance(vnf_instance)
# update instantiated vnf info based on created stack resources
if hasattr(vnf_instance.instantiated_vnf_info, 'instance_id'):
# get final vnfd_dict
vnfd_dict = vnflcm_utils._get_vnfd_dict(context,
vnf_instance.vnfd_id,
instantiate_vnf_req.flavour_id)
# get vim_connection info from request
vim_info = vnflcm_utils._get_vim(context,
instantiate_vnf_req.vim_connection_info)
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 vnf_instance.instantiated_vnf_info.instance_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)
except Exception as ex:
try:
vnf_instance.instantiated_vnf_info.reinitialize()
vnf_instance.instantiated_vnf_info.save()
finally:
error_msg = "Failed to build instantiation information \
for vnf {} because {}".\
format(vnf_instance.id, encodeutils.
exception_to_unicode(ex))
LOG.error("_build_instantiated_vnf_info error {}".
format(error_msg))
raise exceptions.TackerException(message=error_msg)
@log.log
def _update_instantiated_vnf_info(
self, context, vnf_instance, heal_vnf_request):
try:
vim_info = vnflcm_utils._get_vim(context,
vnf_instance.vim_connection_info)
vim_connection_info = \
objects.VimConnectionInfo.obj_from_primitive(
vim_info, context)
self.vnf_manager.invoke(
vim_connection_info.vim_type, 'post_heal_vnf',
context=context, vnf_instance=vnf_instance,
vim_connection_info=vim_connection_info,
heal_vnf_request=heal_vnf_request)
except Exception as exp:
error_msg = \
"Failed to update instantiation information for vnf {}: {}".\
format(vnf_instance.id, encodeutils.exception_to_unicode(exp))
LOG.error("_update_instantiated_vnf_info error {}".
format(error_msg))
raise exceptions.TackerException(message=error_msg)
@log.log
def _add_additional_vnf_info(self, context, vnf_instance):
'''this method adds misc info to 'vnf' table'''
try:
if hasattr(vnf_instance.instantiated_vnf_info, 'instance_id'):
if vnf_instance.instantiated_vnf_info.instance_id:
# add instance_id info
instance_id = vnf_instance.instantiated_vnf_info.\
instance_id
with context.session.begin(subtransactions=True):
updated_values = {'instance_id': instance_id}
context.session.query(vnfm_db.VNF).filter_by(
id=vnf_instance.id).update(updated_values)
except Exception as ex:
# with excutils.save_and_reraise_exception():
error_msg = "Failed to add additional vnf info to vnf {}. Details -\
{}".format(
vnf_instance.id, str(ex))
LOG.error("_add_additional_vnf_info error {}".format(error_msg))
raise exceptions.TackerException(message=error_msg)
@periodic_task.periodic_task(spacing=CONF.vnf_package_delete_interval)
def _run_cleanup_vnf_packages(self, context):
"""Delete orphan extracted csar zip and files from extracted path
@ -490,7 +708,6 @@ class Conductor(manager.Manager):
fields.LcmOccsOperationType.INSTANTIATE)
operation_state = kwargs.get('operation_state',
fields.LcmOccsOperationState.PROCESSING)
evacuate_end_list = kwargs.get('evacuate_end_list', None)
is_automatic_invocation = \
kwargs.get('is_automatic_invocation', False)
error = kwargs.get('error', None)
@ -546,8 +763,7 @@ class Conductor(manager.Manager):
operation_state == fields.LcmOccsOperationState.FAILED_TEMP):
affected_resources = vnflcm_utils._get_affected_resources(
old_vnf_instance=old_vnf_instance,
new_vnf_instance=vnf_instance,
extra_list=evacuate_end_list)
new_vnf_instance=vnf_instance)
affected_resources_snake_case = \
utils.convert_camelcase_to_snakecase(affected_resources)
resource_change_obj = \
@ -666,6 +882,7 @@ class Conductor(manager.Manager):
self,
context,
vnf_instance,
vnf_dict,
instantiate_vnf,
vnf_lcm_op_occs_id):
@ -679,30 +896,23 @@ class Conductor(manager.Manager):
request_obj=instantiate_vnf
)
# Check if vnf is already instantiated.
vnf_instance = objects.VnfInstance.get_by_id(context,
vnf_instance.id)
if vnf_instance.instantiation_state == \
fields.VnfInstanceState.INSTANTIATED:
LOG.error("Vnf instance %(id)s is already in %(state)s state.",
{"id": vnf_instance.id,
"state": vnf_instance.instantiation_state})
return
# change vnf_status
if vnf_dict['status'] == 'INACTIVE':
vnf_dict['status'] = 'PENDING_CREATE'
self._change_vnf_status(context, vnf_instance.id,
_INACTIVE_STATUS, 'PENDING_CREATE')
self.vnflcm_driver.instantiate_vnf(context, vnf_instance,
instantiate_vnf)
vnf_dict, instantiate_vnf)
self._build_instantiated_vnf_info(context,
vnf_instance,
instantiate_vnf_req=instantiate_vnf)
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,
expected_attrs=['vnfd'])
try:
self._update_package_usage_state(context, vnf_package)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error("Failed to update usage_state of vnf package %s",
vnf_package.id)
self._update_vnf_attributes(context, vnf_dict,
_PENDING_STATUS, _ACTIVE_STATUS)
self.vnflcm_driver._vnf_instance_update(context, vnf_instance,
instantiation_state=fields.VnfInstanceState.
INSTANTIATED, task_state=None)
# Update vnf_lcm_op_occs table and send notification "COMPLETED"
self._send_lcm_op_occ_notification(
@ -715,6 +925,12 @@ class Conductor(manager.Manager):
)
except Exception as ex:
self._change_vnf_status(context, vnf_instance.id,
_PENDING_STATUS, 'ERROR')
self._build_instantiated_vnf_info(context, vnf_instance,
instantiate_vnf)
# Update vnf_lcm_op_occs table and send notification "FAILED_TEMP"
self._send_lcm_op_occ_notification(
context=context,
@ -730,17 +946,6 @@ class Conductor(manager.Manager):
def terminate(self, context, vnf_lcm_op_occs_id,
vnf_instance, terminate_vnf_req, vnf_dict):
try:
# 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("Terminate action cannot be performed on vnf %(id)s "
"which is in %(state)s state.",
{"id": vnf_instance.id,
"state": vnf_instance.instantiation_state})
return
old_vnf_instance = copy.deepcopy(vnf_instance)
# Update vnf_lcm_op_occs table and send notification "PROCESSING"
@ -753,22 +958,18 @@ class Conductor(manager.Manager):
operation=fields.LcmOccsOperationType.TERMINATE
)
self._change_vnf_status(context, vnf_instance.id,
_ACTIVE_STATUS, 'PENDING_TERMINATE')
self.vnflcm_driver.terminate_vnf(context, vnf_instance,
terminate_vnf_req,
vnf_lcm_op_occs_id)
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,
expected_attrs=['vnfd'])
try:
self._update_package_usage_state(context, vnf_package)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error("Failed to update usage_state of vnf package %s",
vnf_package.id)
terminate_vnf_req)
self._change_vnf_status(context, vnf_instance.id,
_PENDING_STATUS, 'INACTIVE')
self.vnflcm_driver._vnf_instance_update(context, vnf_instance,
vim_connection_info=[], task_state=None,
instantiation_state=fields.VnfInstanceState.NOT_INSTANTIATED)
vnf_instance.instantiated_vnf_info.reinitialize()
# Update vnf_lcm_op_occs table and send notification "COMPLETED"
self._send_lcm_op_occ_notification(
@ -782,6 +983,10 @@ class Conductor(manager.Manager):
)
except Exception as exc:
# set vnf_status to error
self._change_vnf_status(context, vnf_instance.id,
_PENDING_STATUS, 'ERROR')
# Update vnf_lcm_op_occs table and send notification "FAILED_TEMP"
self._send_lcm_op_occ_notification(
context=context,
@ -803,19 +1008,6 @@ class Conductor(manager.Manager):
vnf_lcm_op_occs_id):
try:
evacuate_end_list = []
# 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("Heal action cannot be performed on vnf %(id)s "
"which is in %(state)s state.",
{"id": vnf_instance.id,
"state": vnf_instance.instantiation_state})
return
old_vnf_instance = copy.deepcopy(vnf_instance)
# Update vnf_lcm_op_occs table and send notification "PROCESSING"
@ -828,15 +1020,25 @@ class Conductor(manager.Manager):
operation=fields.LcmOccsOperationType.HEAL
)
heal_result = \
self.vnflcm_driver.heal_vnf(context, vnf_instance, vnf_dict,
heal_vnf_request,
vnf_lcm_op_occs_id)
# update vnf status to PENDING_HEAL
self._change_vnf_status(context, vnf_instance.id,
_ACTIVE_STATUS, constants.PENDING_HEAL)
self.vnflcm_driver.heal_vnf(context, vnf_instance,
vnf_dict, heal_vnf_request)
self._update_instantiated_vnf_info(context, vnf_instance,
heal_vnf_request)
# update vnf_lcm_op_occs and send notification "COMPLETED"
if heal_result:
evacuate_end_list = heal_result.get('evacuate_end_list')
# update instance_in in vnf_table
self._add_additional_vnf_info(context, vnf_instance)
# update vnf status to ACTIVE
self._change_vnf_status(context, vnf_instance.id,
_PENDING_STATUS, constants.ACTIVE)
# during .save() ,instantiated_vnf_info is also saved to DB
self.vnflcm_driver._vnf_instance_update(context, vnf_instance,
task_state=None)
# update vnf_lcm_op_occs and send notification "COMPLETED"
self._send_lcm_op_occ_notification(
context=context,
vnf_lcm_op_occs_id=vnf_lcm_op_occs_id,
@ -844,10 +1046,16 @@ class Conductor(manager.Manager):
vnf_instance=vnf_instance,
request_obj=heal_vnf_request,
operation=fields.LcmOccsOperationType.HEAL,
operation_state=fields.LcmOccsOperationState.COMPLETED,
evacuate_end_list=evacuate_end_list
operation_state=fields.LcmOccsOperationState.COMPLETED
)
except Exception as ex:
# update vnf_status to 'ERROR' and create event with 'ERROR' status
self._change_vnf_status(context, vnf_instance,
_PENDING_STATUS, constants.ERROR, str(ex))
# call _update_instantiated_vnf_info for notification
self._update_instantiated_vnf_info(context, vnf_instance,
heal_vnf_request)
# update vnf_lcm_op_occs and send notification "FAILED_TEMP"
self._send_lcm_op_occ_notification(
@ -858,7 +1066,6 @@ class Conductor(manager.Manager):
request_obj=heal_vnf_request,
operation=fields.LcmOccsOperationType.HEAL,
operation_state=fields.LcmOccsOperationState.FAILED_TEMP,
evacuate_end_list=evacuate_end_list,
error=str(ex)
)

@ -56,7 +56,28 @@ Possible values:
Related options:
* None
"""))]
""")),
cfg.ListOpt('get_top_list',
default=['tosca_definitions_version',
'description', 'metadata'],
help=_("List of items to get from top-vnfd")),
cfg.ListOpt('exclude_node',
default=['VNF'],
help=_("Exclude node from node_template")),
cfg.ListOpt('get_lower_list',
default=['tosca.nodes.nfv.VNF', 'tosca.nodes.nfv.VDU.Tacker'],
help=_("List of types to get from lower-vnfd")),
cfg.ListOpt('del_input_list',
default=['descriptor_id', 'descriptor_version'
'provider', 'product_name', 'software_version',
'vnfm_info', 'flavour_id', 'flavour_description'],
help=_("List of del inputs from lower-vnfd")),
]
vnf_package_group = cfg.OptGroup('vnf_package',
title='vnf_package options',

@ -201,8 +201,8 @@ class VnfInstance(model_base.BASE, models.SoftDeleteMixin,
task_state = sa.Column(sa.String(255), nullable=True)
vim_connection_info = sa.Column(sa.JSON(), nullable=True)
tenant_id = sa.Column('tenant_id', sa.String(length=64), nullable=False)
vnf_metadata = sa.Column(sa.JSON(), nullable=True)
vnf_pkg_id = sa.Column(types.Uuid, nullable=False)
vnf_metadata = sa.Column(sa.JSON(), nullable=True)
class VnfInstantiatedInfo(model_base.BASE, models.SoftDeleteMixin,