357 lines
15 KiB
Python
357 lines
15 KiB
Python
# Copyright (C) 2019 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.
|
|
|
|
from io import BytesIO
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
from oslo_utils import uuidutils
|
|
import six
|
|
from six.moves import http_client
|
|
from six.moves import urllib
|
|
import webob
|
|
import zipfile
|
|
from zipfile import ZipFile
|
|
|
|
from tacker._i18n import _
|
|
from tacker.api.schemas import vnf_packages
|
|
from tacker.api import validation
|
|
from tacker.api.views import vnf_packages as vnf_packages_view
|
|
from tacker.common import exceptions
|
|
from tacker.conductor.conductorrpc import vnf_pkgm_rpc
|
|
from tacker.glance_store import store as glance_store
|
|
from tacker.objects import fields
|
|
from tacker.objects import vnf_package as vnf_package_obj
|
|
from tacker.policies import vnf_package as vnf_package_policies
|
|
from tacker import wsgi
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
CONF = cfg.CONF
|
|
|
|
|
|
class VnfPkgmController(wsgi.Controller):
|
|
|
|
_view_builder_class = vnf_packages_view.ViewBuilder
|
|
|
|
def __init__(self):
|
|
super(VnfPkgmController, self).__init__()
|
|
self.rpc_api = vnf_pkgm_rpc.VNFPackageRPCAPI()
|
|
glance_store.initialize_glance_store()
|
|
|
|
def _get_vnf_package(self, 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
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
|
|
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
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
return vnf_package
|
|
|
|
@wsgi.response(http_client.CREATED)
|
|
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN))
|
|
@validation.schema(vnf_packages.create)
|
|
def create(self, request, body):
|
|
context = request.environ['tacker.context']
|
|
context.can(vnf_package_policies.VNFPKGM % 'create')
|
|
|
|
vnf_package = vnf_package_obj.VnfPackage(context=request.context)
|
|
vnf_package.onboarding_state = (
|
|
fields.PackageOnboardingStateType.CREATED)
|
|
vnf_package.operational_state = (
|
|
fields.PackageOperationalStateType.DISABLED)
|
|
vnf_package.usage_state = fields.PackageUsageStateType.NOT_IN_USE
|
|
vnf_package.user_data = body.get('userDefinedData', dict())
|
|
vnf_package.tenant_id = request.context.project_id
|
|
|
|
vnf_package.create()
|
|
|
|
return self._view_builder.create(request, vnf_package)
|
|
|
|
@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_package_policies.VNFPKGM % 'show')
|
|
|
|
# check if id is of type uuid format
|
|
if not uuidutils.is_uuid_like(id):
|
|
msg = _("Can not find requested vnf package: %s") % id
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
|
|
try:
|
|
vnf_package = vnf_package_obj.VnfPackage.get_by_id(
|
|
request.context, id,
|
|
expected_attrs=["vnf_deployment_flavours", "vnfd"])
|
|
except exceptions.VnfPackageNotFound:
|
|
msg = _("Can not find requested vnf package: %s") % id
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
|
|
return self._view_builder.show(request, vnf_package)
|
|
|
|
@wsgi.response(http_client.OK)
|
|
@wsgi.expected_errors((http_client.FORBIDDEN))
|
|
def index(self, request):
|
|
context = request.environ['tacker.context']
|
|
context.can(vnf_package_policies.VNFPKGM % 'index')
|
|
|
|
vnf_packages = vnf_package_obj.VnfPackagesList.get_all(
|
|
request.context,
|
|
expected_attrs=["vnf_deployment_flavours", "vnfd"])
|
|
|
|
return self._view_builder.index(request, vnf_packages)
|
|
|
|
@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']
|
|
context.can(vnf_package_policies.VNFPKGM % 'delete')
|
|
|
|
vnf_package = self._get_vnf_package(id, request)
|
|
|
|
if (vnf_package.operational_state ==
|
|
fields.PackageOperationalStateType.ENABLED or
|
|
vnf_package.usage_state ==
|
|
fields.PackageUsageStateType.IN_USE):
|
|
msg = _("VNF Package %(id)s cannot be deleted as it's "
|
|
"operational state is %(operational_state)s and usage "
|
|
"state is %(usage_state)s.")
|
|
raise webob.exc.HTTPConflict(
|
|
explanation=msg % {
|
|
"id": id,
|
|
"operational_state": vnf_package.operational_state,
|
|
"usage_state": vnf_package.usage_state})
|
|
|
|
# Delete vnf_package
|
|
self.rpc_api.delete_vnf_package(context, vnf_package)
|
|
|
|
@wsgi.response(http_client.ACCEPTED)
|
|
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND,
|
|
http_client.CONFLICT))
|
|
def upload_vnf_package_content(self, request, id, body):
|
|
context = request.environ['tacker.context']
|
|
context.can(vnf_package_policies.VNFPKGM % 'upload_package_content')
|
|
|
|
vnf_package = self._get_vnf_package(id, request)
|
|
|
|
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})
|
|
|
|
vnf_package.onboarding_state = (
|
|
fields.PackageOnboardingStateType.UPLOADING)
|
|
|
|
vnf_package.save()
|
|
|
|
try:
|
|
(location, size, checksum, multihash,
|
|
loc_meta) = glance_store.store_csar(context, id, body)
|
|
except exceptions.UploadFailedToGlanceStore:
|
|
with excutils.save_and_reraise_exception():
|
|
vnf_package.onboarding_state = (
|
|
fields.PackageOnboardingStateType.CREATED)
|
|
vnf_package.save()
|
|
|
|
vnf_package.onboarding_state = (
|
|
fields.PackageOnboardingStateType.PROCESSING)
|
|
|
|
vnf_package.algorithm = CONF.vnf_package.hashing_algorithm
|
|
vnf_package.hash = multihash
|
|
vnf_package.location_glance_store = location
|
|
|
|
vnf_package.save()
|
|
|
|
# process vnf_package
|
|
self.rpc_api.upload_vnf_package_content(context, vnf_package)
|
|
|
|
@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_packages.upload_from_uri)
|
|
def upload_vnf_package_from_uri(self, request, id, body):
|
|
|
|
context = request.environ['tacker.context']
|
|
context.can(vnf_package_policies.VNFPKGM % 'upload_from_uri')
|
|
|
|
vnf_package = self._get_vnf_package(id, request)
|
|
|
|
url = body['addressInformation']
|
|
try:
|
|
data_iter = urllib.request.urlopen(url)
|
|
except Exception:
|
|
data_iter = None
|
|
msg = _("Failed to open URL %s")
|
|
raise webob.exc.HTTPBadRequest(explanation=msg % url)
|
|
finally:
|
|
if hasattr(data_iter, 'close'):
|
|
data_iter.close()
|
|
|
|
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})
|
|
|
|
vnf_package.onboarding_state = (
|
|
fields.PackageOnboardingStateType.UPLOADING)
|
|
|
|
vnf_package.save()
|
|
|
|
# process vnf_package
|
|
self.rpc_api.upload_vnf_package_from_uri(context, vnf_package,
|
|
body['addressInformation'],
|
|
user_name=body.get('userName'),
|
|
password=body.get('password'))
|
|
|
|
@wsgi.response(http_client.OK)
|
|
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
|
|
http_client.NOT_FOUND, http_client.CONFLICT))
|
|
@validation.schema(vnf_packages.patch)
|
|
def patch(self, request, id, body):
|
|
context = request.environ['tacker.context']
|
|
context.can(vnf_package_policies.VNFPKGM % 'patch')
|
|
|
|
old_vnf_package = self._get_vnf_package(id, request)
|
|
vnf_package = old_vnf_package.obj_clone()
|
|
|
|
user_data = body.get('userDefinedData')
|
|
operational_state = body.get('operationalState')
|
|
|
|
if operational_state:
|
|
if vnf_package.onboarding_state == \
|
|
fields.PackageOnboardingStateType.ONBOARDED:
|
|
if vnf_package.operational_state == operational_state:
|
|
msg = _("VNF Package %(id)s is already in "
|
|
"%(operationState)s operational state") % {
|
|
"id": id,
|
|
"operationState": vnf_package.operational_state}
|
|
raise webob.exc.HTTPConflict(explanation=msg)
|
|
else:
|
|
# update vnf_package operational state,
|
|
# if vnf_package Onboarding State is ONBOARDED
|
|
vnf_package.operational_state = operational_state
|
|
else:
|
|
if not user_data:
|
|
msg = _("Updating operational state is not allowed for VNF"
|
|
" Package %(id)s when onboarding state is not "
|
|
"%(onboarded)s")
|
|
raise webob.exc.HTTPBadRequest(
|
|
explanation=msg % {"id": id, "onboarded": fields.
|
|
PackageOnboardingStateType.ONBOARDED})
|
|
# update user data
|
|
if user_data:
|
|
for key, value in list(user_data.items()):
|
|
if vnf_package.user_data.get(key) == value:
|
|
del user_data[key]
|
|
|
|
if not user_data:
|
|
msg = _("The userDefinedData provided in update request is as"
|
|
" the existing userDefinedData of vnf package %(id)s."
|
|
" Nothing to update.")
|
|
raise webob.exc.HTTPConflict(
|
|
explanation=msg % {"id": id})
|
|
vnf_package.user_data = user_data
|
|
|
|
vnf_package.save()
|
|
|
|
return self._view_builder.patch(old_vnf_package, vnf_package)
|
|
|
|
@wsgi.response(http_client.OK)
|
|
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
|
|
http_client.NOT_FOUND, http_client.NOT_ACCEPTABLE,
|
|
http_client.CONFLICT,
|
|
http_client.INTERNAL_SERVER_ERROR))
|
|
def get_vnf_package_vnfd(self, request, id):
|
|
context = request.environ['tacker.context']
|
|
context.can(vnf_package_policies.VNFPKGM % 'get_vnf_package_vnfd')
|
|
|
|
valid_accept_headers = ['application/zip', 'text/plain']
|
|
accept_headers = request.headers['Accept'].split(',')
|
|
for header in accept_headers:
|
|
if header not in valid_accept_headers:
|
|
msg = _("Accept header %(accept)s is invalid, it should be one"
|
|
" of these values: %(valid_values)s")
|
|
raise webob.exc.HTTPNotAcceptable(
|
|
explanation=msg % {"accept": header,
|
|
"valid_values": ",".join(
|
|
valid_accept_headers)})
|
|
|
|
vnf_package = self._get_vnf_package(id, request)
|
|
|
|
if vnf_package.onboarding_state != \
|
|
fields.PackageOnboardingStateType.ONBOARDED:
|
|
msg = _("VNF Package %(id)s state is not "
|
|
"%(onboarded)s")
|
|
raise webob.exc.HTTPConflict(explanation=msg % {"id": id,
|
|
"onboarded": fields.PackageOnboardingStateType.ONBOARDED})
|
|
|
|
try:
|
|
vnfd_files_and_data = self.rpc_api.\
|
|
get_vnf_package_vnfd(context, vnf_package)
|
|
except exceptions.FailedToGetVnfdData as e:
|
|
LOG.error(e.msg)
|
|
raise webob.exc.HTTPInternalServerError(
|
|
explanation=six.text_type(e.msg))
|
|
|
|
if 'text/plain' in accept_headers:
|
|
# Checking for yaml files only. This is required when there is
|
|
# TOSCA.meta file along with single yaml file.
|
|
# In such case we need to return single yaml file.
|
|
file_list = list(vnfd_files_and_data.keys())
|
|
yaml_files = [file for file in file_list if file.endswith(
|
|
('.yaml', '.yml'))]
|
|
if len(yaml_files) == 1:
|
|
request.response.headers['Content-Type'] = 'text/plain'
|
|
return vnfd_files_and_data[yaml_files[0]]
|
|
elif 'application/zip' in accept_headers:
|
|
request.response.headers['Content-Type'] = 'application/zip'
|
|
return self._create_vnfd_zip(vnfd_files_and_data)
|
|
else:
|
|
msg = _("VNFD is implemented as multiple yaml files,"
|
|
" Accept header should be 'application/zip'.")
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
else:
|
|
request.response.headers['Content-Type'] = 'application/zip'
|
|
return self._create_vnfd_zip(vnfd_files_and_data)
|
|
|
|
def _create_vnfd_zip(self, vnfd_files_and_data):
|
|
buff = BytesIO()
|
|
with ZipFile(buff, 'w', zipfile.ZIP_DEFLATED) as zip_archive:
|
|
for file_path, file_data in vnfd_files_and_data.items():
|
|
zip_archive.writestr(file_path, file_data)
|
|
|
|
return buff.getvalue()
|
|
|
|
|
|
def create_resource():
|
|
body_deserializers = {
|
|
'application/zip': wsgi.ZipDeserializer()
|
|
}
|
|
|
|
deserializer = wsgi.RequestDeserializer(
|
|
body_deserializers=body_deserializers)
|
|
return wsgi.Resource(VnfPkgmController(), deserializer=deserializer)
|