Implement RestFul API to read VNFD of an on-boarded VNF package
The VNFD can be implemented as a single file or as a collection of multiple files. If the VNFD is implemented in the form of multiple files, a ZIP file embedding these files shall be returned. If the VNFD is implemented as a single file, either that file or a ZIP file embedding that file shall be returned. Implemented below API:- * GET /vnf_packages/{vnfPkgId}/vnfd Change-Id: I4af9c8126fb7478da294bc99a176eabba3944564 Implements: bp enhance-vnf-package-support-part1
This commit is contained in:
parent
848ab62a8d
commit
f03b615bfe
@ -1,4 +1,12 @@
|
||||
# variables in header
|
||||
content_type:
|
||||
description: |
|
||||
If the VNFD is implemented in the form of multiple files, zip file will
|
||||
be returned with Content-Type set as `application/zip` otherwise it will
|
||||
be set to `text/plain` in the response header.
|
||||
in: header
|
||||
required: true
|
||||
type: string
|
||||
|
||||
# variables in path
|
||||
alias_path:
|
||||
|
@ -46,6 +46,11 @@
|
||||
405:
|
||||
default: |
|
||||
Method is not valid for this endpoint.
|
||||
406:
|
||||
default: |
|
||||
Not Acceptable, the requested resource is only capable of generating
|
||||
content not acceptable according to the 'Accept' headers sent in the
|
||||
request.
|
||||
409:
|
||||
default: |
|
||||
This operation conflicted with another operation on this resource.
|
||||
|
@ -305,3 +305,59 @@ Response Example
|
||||
|
||||
.. literalinclude:: samples/vnf_packages/vnf-packages-patch-response.json
|
||||
:language: javascript
|
||||
|
||||
Read VNFD of an individual VNF package
|
||||
======================================
|
||||
|
||||
.. rest_method:: GET /vnfpkgm/v1/vnf_packages/{vnf_package_id}/vnfd
|
||||
|
||||
Read VNFD of an on-boarded VNF package.
|
||||
|
||||
The VNFD can be implemented as a single file or as a collection of multiple
|
||||
files. If the VNFD is implemented in the form of multiple files, a ZIP file
|
||||
embedding these files shall be returned. If the VNFD is implemented as a
|
||||
single file, either that file or a ZIP file embedding that file shall be
|
||||
returned. The selection of the format is controlled by the "Accept" HTTP
|
||||
header passed in the GET request.
|
||||
|
||||
If the "Accept" header contains only "text/plain" and the VNFD is implemented
|
||||
as a single file, the file shall be returned; otherwise, an error message shall
|
||||
be returned.
|
||||
If the "Accept" header contains only "application/zip", the single file or
|
||||
the multiple files that make up the VNFD shall be returned embedded in a ZIP
|
||||
file.
|
||||
If the "Accept" header contains both "text/plain" and "application/zip", it
|
||||
is up to the NFVO to choose the format to return for a single-file VNFD; for a
|
||||
multi-file VNFD, a ZIP file shall be returned.
|
||||
|
||||
Response Codes
|
||||
--------------
|
||||
|
||||
.. rest_status_code:: success status.yaml
|
||||
|
||||
- 200
|
||||
|
||||
.. rest_status_code:: error status.yaml
|
||||
|
||||
- 400
|
||||
- 401
|
||||
- 403
|
||||
- 404
|
||||
- 406
|
||||
- 409
|
||||
- 500
|
||||
|
||||
Request Parameters
|
||||
------------------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- vnf_package_id: vnf_package_id_path
|
||||
- Accept: content_type
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- Content-Type: content_type
|
||||
|
@ -13,13 +13,17 @@
|
||||
# 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
|
||||
@ -275,6 +279,72 @@ class VnfPkgmController(wsgi.Controller):
|
||||
|
||||
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 = {
|
||||
|
@ -74,3 +74,9 @@ class VnfpkgmAPIRouter(wsgi.Router):
|
||||
self._setup_route(mapper,
|
||||
"/vnf_packages/{id}/package_content/upload_from_uri",
|
||||
methods, controller, default_resource)
|
||||
|
||||
# Allowed methods on /vnf_packages/{id}/vnfd
|
||||
methods = {"GET": "get_vnf_package_vnfd"}
|
||||
self._setup_route(mapper,
|
||||
"/vnf_packages/{id}/vnfd",
|
||||
methods, controller, default_resource)
|
||||
|
@ -269,7 +269,7 @@ def _get_data_from_csar(tosca, context, id):
|
||||
return vnf_data, flavours
|
||||
|
||||
|
||||
def _extract_csar_zip_file(file_path, extract_path):
|
||||
def extract_csar_zip_file(file_path, extract_path):
|
||||
try:
|
||||
with zipfile.ZipFile(file_path, 'r') as zf:
|
||||
zf.extractall(extract_path)
|
||||
@ -287,7 +287,7 @@ def load_csar_data(context, package_uuid, zip_path):
|
||||
|
||||
extract_zip_path = os.path.join(CONF.vnf_package.vnf_package_csar_path,
|
||||
package_uuid)
|
||||
_extract_csar_zip_file(zip_path, extract_zip_path)
|
||||
extract_csar_zip_file(zip_path, extract_zip_path)
|
||||
|
||||
try:
|
||||
tosca = ToscaTemplate(zip_path, None, True)
|
||||
|
@ -240,6 +240,10 @@ class UploadFailedToGlanceStore(Invalid):
|
||||
"%(error)s")
|
||||
|
||||
|
||||
class FailedToGetVnfdData(Invalid):
|
||||
message = _("Failed to get csar zip file from glance store: %(error)s")
|
||||
|
||||
|
||||
class InvalidCSAR(Invalid):
|
||||
message = _("Invalid csar: %(error)s")
|
||||
|
||||
|
@ -16,17 +16,21 @@
|
||||
import datetime
|
||||
import functools
|
||||
import inspect
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from glance_store import exceptions as store_exceptions
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging
|
||||
from oslo_service import periodic_task
|
||||
from oslo_service import service
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import timeutils
|
||||
from sqlalchemy.orm import exc as orm_exc
|
||||
import yaml
|
||||
|
||||
from tacker.common import csar_utils
|
||||
from tacker.common import exceptions
|
||||
@ -255,6 +259,79 @@ class Conductor(manager.Manager):
|
||||
|
||||
vnf_package.destroy(context)
|
||||
|
||||
def get_vnf_package_vnfd(self, context, vnf_package):
|
||||
csar_path = os.path.join(CONF.vnf_package.vnf_package_csar_path,
|
||||
vnf_package.id)
|
||||
if not os.path.isdir(csar_path):
|
||||
location = vnf_package.location_glance_store
|
||||
try:
|
||||
zip_path = glance_store.load_csar(vnf_package.id, location)
|
||||
csar_utils.extract_csar_zip_file(zip_path, csar_path)
|
||||
except (store_exceptions.GlanceStoreException) as e:
|
||||
exc_msg = encodeutils.exception_to_unicode(e)
|
||||
msg = (_("Exception raised from glance store can be "
|
||||
"unrecoverable if it is not related to connection"
|
||||
" error. Error: %s.") % exc_msg)
|
||||
raise exceptions.FailedToGetVnfdData(error=msg)
|
||||
try:
|
||||
return self._read_vnfd_files(csar_path)
|
||||
except Exception as e:
|
||||
exc_msg = encodeutils.exception_to_unicode(e)
|
||||
msg = (_("Exception raised while reading csar file"
|
||||
" Error: %s.") % exc_msg)
|
||||
raise exceptions.FailedToGetVnfdData(error=msg)
|
||||
|
||||
def _read_vnfd_files(self, csar_path):
|
||||
"""Creating a dictionary with file path as key and file data as value.
|
||||
|
||||
It will contain YAML files representing the VNFD, and information
|
||||
necessary to navigate the ZIP file and to identify the file that is
|
||||
the entry point for parsing the VNFD such as TOSCA-meta is included.
|
||||
"""
|
||||
|
||||
def _add_recursively_imported_files(imported_yamls, file_path_and_data,
|
||||
dir_of_parent_definition_file=''):
|
||||
for file in imported_yamls:
|
||||
file_path = os.path.join(
|
||||
csar_path, dir_of_parent_definition_file, file)
|
||||
file_data = yaml.safe_load(io.open(file_path))
|
||||
dest_file_path = os.path.abspath(file_path).split(
|
||||
csar_path + '/')[-1]
|
||||
file_path_and_data[dest_file_path] = yaml.dump(file_data)
|
||||
|
||||
if file_data.get('imports'):
|
||||
dir_of_parent_definition_file = '/'.join(
|
||||
file_path.split('/')[:-1])
|
||||
_add_recursively_imported_files(
|
||||
file_data['imports'], file_path_and_data,
|
||||
dir_of_parent_definition_file)
|
||||
|
||||
file_path_and_data = {}
|
||||
if 'TOSCA-Metadata' in os.listdir(csar_path) and os.path.isdir(
|
||||
os.path.join(csar_path, 'TOSCA-Metadata')):
|
||||
# This is CSAR containing a TOSCA-Metadata directory, which
|
||||
# includes the TOSCA.meta metadata file providing an entry
|
||||
# information for processing a CSAR file.
|
||||
tosca_meta_data = yaml.safe_load(io.open(os.path.join(
|
||||
csar_path, 'TOSCA-Metadata', 'TOSCA.meta')))
|
||||
file_path_and_data['TOSCA-Metadata/TOSCA.meta'] = yaml.dump(
|
||||
tosca_meta_data)
|
||||
entry_defination_file = tosca_meta_data['Entry-Definitions']
|
||||
_add_recursively_imported_files([entry_defination_file],
|
||||
file_path_and_data)
|
||||
else:
|
||||
# This is a CSAR without a TOSCA-Metadata directory and containing
|
||||
# a single yaml file with a .yml or .yaml extension at the root of
|
||||
# the archive.
|
||||
root_yaml_file = sorted(
|
||||
os.listdir(csar_path),
|
||||
key=lambda item: item.endswith(('yaml', '.yml')))[-1]
|
||||
src_path = os.path.join(csar_path, root_yaml_file)
|
||||
file_data = yaml.safe_load(io.open(src_path))
|
||||
file_path_and_data[root_yaml_file] = yaml.dump(file_data)
|
||||
|
||||
return file_path_and_data
|
||||
|
||||
@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
|
||||
|
@ -61,3 +61,12 @@ class VNFPackageRPCAPI(object):
|
||||
rpc_method = cctxt.cast if cast else cctxt.call
|
||||
return rpc_method(context, 'delete_vnf_package',
|
||||
vnf_package=vnf_package)
|
||||
|
||||
def get_vnf_package_vnfd(self, context, vnf_package, cast=False):
|
||||
serializer = objects_base.TackerObjectSerializer()
|
||||
client = rpc.get_client(self.target, version_cap=None,
|
||||
serializer=serializer)
|
||||
cctxt = client.prepare()
|
||||
rpc_method = cctxt.cast if cast else cctxt.call
|
||||
return rpc_method(context, 'get_vnf_package_vnfd',
|
||||
vnf_package=vnf_package)
|
||||
|
@ -94,7 +94,16 @@ rules = [
|
||||
'path': '/vnf_packages/{vnf_package_id}'
|
||||
}
|
||||
]),
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name=VNFPKGM % 'get_vnf_package_vnfd',
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="reads the content of the VNFD within a VNF package.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/vnf_packages/{vnf_package_id}/vnfd'
|
||||
}
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
|
@ -60,7 +60,9 @@ class SessionClient(adapter.Adapter):
|
||||
|
||||
def _decode_json(self, response):
|
||||
body = response.text
|
||||
if body:
|
||||
if body and response.headers['Content-Type'] == 'text/plain':
|
||||
return body
|
||||
elif body:
|
||||
return jsonutils.loads(body)
|
||||
else:
|
||||
return ""
|
||||
@ -68,6 +70,8 @@ class SessionClient(adapter.Adapter):
|
||||
def do_request(self, url, method, **kwargs):
|
||||
kwargs.setdefault('authenticated', True)
|
||||
resp = self.request(url, method, **kwargs)
|
||||
if resp.headers['Content-Type'] == 'application/zip':
|
||||
return resp, resp.content
|
||||
body = self._decode_json(resp)
|
||||
return resp, body
|
||||
|
||||
|
@ -13,14 +13,22 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import zipfile
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
import tacker.conf
|
||||
from tacker.tests.functional import base
|
||||
|
||||
|
||||
CONF = tacker.conf.CONF
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VnfPackageTest(base.BaseTackerTest):
|
||||
|
||||
VNF_PACKAGE_DELETE_TIMEOUT = 120
|
||||
@ -169,3 +177,68 @@ class VnfPackageTest(base.BaseTackerTest):
|
||||
self.assertEqual(expected_result, resp_body)
|
||||
self._delete_vnf_package(vnf_package['id'])
|
||||
self._wait_for_delete(vnf_package['id'])
|
||||
|
||||
def _create_and_onboard_vnf_package(self, file_name=None):
|
||||
body = jsonutils.dumps({"userDefinedData": {"foo": "bar"}})
|
||||
vnf_package = self._create_vnf_package(body)
|
||||
if file_name is None:
|
||||
file_name = "sample_vnf_package_csar.zip"
|
||||
file_path = self._get_csar_file_path(file_name)
|
||||
with open(file_path, 'rb') as file_object:
|
||||
resp, resp_body = self.http_client.do_request(
|
||||
'{base_path}/{id}/package_content'.format(
|
||||
id=vnf_package['id'],
|
||||
base_path=self.base_url),
|
||||
"PUT", body=file_object, content_type='application/zip')
|
||||
self.assertEqual(202, resp.status_code)
|
||||
self._wait_for_onboard(vnf_package['id'])
|
||||
|
||||
return vnf_package['id']
|
||||
|
||||
def test_get_vnfd_from_onboarded_vnf_package_for_content_type_zip(self):
|
||||
vnf_package_id = self._create_and_onboard_vnf_package()
|
||||
self.addCleanup(self._delete_vnf_package, vnf_package_id)
|
||||
resp, resp_body = self.http_client.do_request(
|
||||
'{base_path}/{id}/vnfd'.format(id=vnf_package_id,
|
||||
base_path=self.base_url),
|
||||
"GET", content_type='application/zip')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual('application/zip', resp.headers['Content-Type'])
|
||||
self.assert_resp_contents(resp)
|
||||
|
||||
def assert_resp_contents(self, resp):
|
||||
expected_file_list = ['Definitions/helloworld3_top.vnfd.yaml',
|
||||
'Definitions/helloworld3_df_simple.yaml',
|
||||
'Definitions/etsi_nfv_sol001_vnfd_types.yaml',
|
||||
'Definitions/etsi_nfv_sol001_common_types.yaml',
|
||||
'Definitions/helloworld3_types.yaml',
|
||||
'TOSCA-Metadata/TOSCA.meta']
|
||||
|
||||
tmp = tempfile.NamedTemporaryFile(delete=False)
|
||||
try:
|
||||
tmp.write(resp.content)
|
||||
finally:
|
||||
# checking response.content is valid zip file
|
||||
self.assertTrue(zipfile.is_zipfile(tmp))
|
||||
with zipfile.ZipFile(tmp, 'r') as zipObj:
|
||||
# Get list of files names in zip
|
||||
actual_file_list = zipObj.namelist()
|
||||
self.assertEqual(expected_file_list, actual_file_list)
|
||||
|
||||
tmp.close()
|
||||
|
||||
@ddt.data('text/plain', 'application/zip,text/plain')
|
||||
def test_get_vnfd_from_onboarded_vnf_package_for_content_type_text(
|
||||
self, accept_header):
|
||||
# Uploading vnf package with single yaml file csar.
|
||||
single_yaml_csar = "sample_vnfpkg_no_meta_single_vnfd.zip"
|
||||
vnf_package_id = self._create_and_onboard_vnf_package(
|
||||
single_yaml_csar)
|
||||
self.addCleanup(self._delete_vnf_package, vnf_package_id)
|
||||
resp, resp_body = self.http_client.do_request(
|
||||
'{base_path}/{id}/vnfd'.format(id=vnf_package_id,
|
||||
base_path=self.base_url),
|
||||
"GET", content_type=accept_header)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual('text/plain', resp.headers['Content-Type'])
|
||||
self.assertIsNotNone(resp.text)
|
||||
|
@ -34,7 +34,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
return os.path.join(
|
||||
self.base_path, "../../etc/samples", file_name)
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data(self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path("sample_vnf_package_csar.zip")
|
||||
vnf_data, flavours = csar_utils.load_csar_data(
|
||||
@ -44,7 +44,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
self.assertEqual(flavours[0]['flavour_id'], 'simple')
|
||||
self.assertIsNotNone(flavours[0]['sw_images'])
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_with_single_yaml(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path(
|
||||
@ -56,7 +56,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
self.assertEqual(flavours[0]['flavour_id'], 'simple')
|
||||
self.assertIsNotNone(flavours[0]['sw_images'])
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_without_instantiation_level(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path(
|
||||
@ -68,7 +68,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
' "tosca.policies.nfv.InstantiationLevels is not defined.')
|
||||
self.assertEqual(msg, exc.format_message())
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_with_invalid_instantiation_level(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path(
|
||||
@ -81,7 +81,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
"defined levels %s") % ",".join(sorted(levels))
|
||||
self.assertEqual(msg, exc.format_message())
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_with_invalid_default_instantiation_level(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path(
|
||||
@ -94,7 +94,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
"defined levels %s") % ",".join(sorted(levels))
|
||||
self.assertEqual(msg, exc.format_message())
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_without_vnfd_info(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path(
|
||||
@ -104,7 +104,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
self.context, constants.UUID, file_path)
|
||||
self.assertEqual("VNF properties are mandatory", exc.format_message())
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_with_artifacts_and_without_sw_image_data(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path(
|
||||
@ -116,7 +116,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
' type tosca.artifacts.nfv.SwImage for node VDU1.')
|
||||
self.assertEqual(msg, exc.format_message())
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_with_multiple_sw_image_data(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path(
|
||||
@ -128,7 +128,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
' is added more than one time for node VDU1.')
|
||||
self.assertEqual(msg, exc.format_message())
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_csar_with_missing_sw_image_data_in_main_template(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path(
|
||||
@ -140,7 +140,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
' type tosca.artifacts.nfv.SwImage for node VDU1.')
|
||||
self.assertEqual(msg, exc.format_message())
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_without_flavour_info(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path("csar_without_flavour_info.zip")
|
||||
@ -149,7 +149,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
self.context, constants.UUID, file_path)
|
||||
self.assertEqual("No VNF flavours are available", exc.format_message())
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_without_flavour_info_in_main_template(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path(
|
||||
@ -167,7 +167,7 @@ class TestCSARUtils(testtools.TestCase):
|
||||
mock_rmtree.assert_called()
|
||||
mock_remove.assert_called()
|
||||
|
||||
@mock.patch('tacker.common.csar_utils._extract_csar_zip_file')
|
||||
@mock.patch('tacker.common.csar_utils.extract_csar_zip_file')
|
||||
def test_load_csar_data_without_policies(
|
||||
self, mock_extract_csar_zip_file):
|
||||
file_path = self._get_csar_file_path("csar_without_policies.zip")
|
||||
|
@ -79,3 +79,18 @@ class VnfPackageRPCTestCase(base.BaseTestCase):
|
||||
self.context, 'delete_vnf_package',
|
||||
vnf_package=vnf_package_obj)
|
||||
_test()
|
||||
|
||||
def test_get_vnf_package_vnfd(self):
|
||||
|
||||
@mock.patch.object(BackingOffClient, 'prepare')
|
||||
def _test(prepare_mock):
|
||||
prepare_mock.return_value = self.cctxt_mock
|
||||
vnf_package_obj = vnf_package.VnfPackage(self.context,
|
||||
**fakes.VNF_DATA)
|
||||
self.rpc_api.get_vnf_package_vnfd(self.context,
|
||||
vnf_package_obj, cast=False)
|
||||
prepare_mock.assert_called()
|
||||
self.cctxt_mock.call.assert_called_once_with(
|
||||
self.context, 'get_vnf_package_vnfd',
|
||||
vnf_package=vnf_package_obj)
|
||||
_test()
|
||||
|
@ -13,6 +13,14 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import io
|
||||
import os
|
||||
from oslo_config import cfg
|
||||
import shutil
|
||||
import tempfile
|
||||
import yaml
|
||||
import zipfile
|
||||
|
||||
from tacker.tests import uuidsentinel
|
||||
|
||||
|
||||
@ -49,3 +57,59 @@ VNF_PACKAGE_DATA = {'algorithm': None, 'hash': None,
|
||||
'usage_state': 'NOT_IN_USE',
|
||||
'user_data': {'abc': 'xyz'}
|
||||
}
|
||||
|
||||
|
||||
def make_vnfd_files_list(csar_path):
|
||||
files_list = []
|
||||
# Checking for directory exist
|
||||
if not os.path.isdir(csar_path):
|
||||
return
|
||||
ext = ['.yaml', '.meta']
|
||||
for _, _, files in os.walk(csar_path):
|
||||
for file in files:
|
||||
if file.endswith(tuple(ext)):
|
||||
files_list.append(file)
|
||||
|
||||
return files_list
|
||||
|
||||
|
||||
def create_fake_csar_dir(vnf_package_id, single_yaml_csar=False):
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
csar_file = ('sample_vnfpkg_no_meta_single_vnfd.zip' if single_yaml_csar
|
||||
else 'sample_vnf_package_csar.zip')
|
||||
sample_vnf_package_zip = os.path.join(base_path, "../../etc/samples",
|
||||
csar_file)
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
fake_csar = os.path.join('/tmp/', vnf_package_id)
|
||||
os.rename(tmpdir, fake_csar)
|
||||
|
||||
with zipfile.ZipFile(sample_vnf_package_zip, 'r') as zf:
|
||||
zf.extractall(fake_csar)
|
||||
cfg.CONF.set_override('vnf_package_csar_path', '/tmp',
|
||||
group='vnf_package')
|
||||
return fake_csar
|
||||
|
||||
|
||||
def get_expected_vnfd_data():
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
sample_vnf_package_zip = os.path.join(
|
||||
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
|
||||
|
||||
csar_temp_dir = tempfile.mkdtemp()
|
||||
|
||||
with zipfile.ZipFile(sample_vnf_package_zip, 'r') as zf:
|
||||
zf.extractall(csar_temp_dir)
|
||||
|
||||
file_names = ['TOSCA-Metadata/TOSCA.meta',
|
||||
'Definitions/etsi_nfv_sol001_vnfd_types.yaml',
|
||||
'Definitions/helloworld3_types.yaml',
|
||||
'Definitions/helloworld3_df_simple.yaml',
|
||||
'Definitions/helloworld3_top.vnfd.yaml',
|
||||
'Definitions/etsi_nfv_sol001_common_types.yaml']
|
||||
file_path_and_data = {}
|
||||
for file_name in file_names:
|
||||
file_path_and_data.update({file_name: yaml.dump(yaml.safe_load(
|
||||
io.open(os.path.join(csar_temp_dir, file_name))))})
|
||||
|
||||
shutil.rmtree(csar_temp_dir)
|
||||
return file_path_and_data
|
||||
|
@ -13,13 +13,19 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
import os
|
||||
from oslo_config import cfg
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from glance_store import exceptions as store_exceptions
|
||||
import mock
|
||||
import yaml
|
||||
|
||||
from tacker.common import csar_utils
|
||||
from tacker.common import exceptions
|
||||
from tacker.conductor import conductor_server
|
||||
import tacker.conf
|
||||
from tacker import context
|
||||
from tacker.glance_store import store as glance_store
|
||||
from tacker import objects
|
||||
@ -28,6 +34,8 @@ from tacker.tests.unit.conductor import fakes
|
||||
from tacker.tests.unit.db.base import SqlTestCase
|
||||
from tacker.tests import uuidsentinel
|
||||
|
||||
CONF = tacker.conf.CONF
|
||||
|
||||
|
||||
class TestConductor(SqlTestCase):
|
||||
|
||||
@ -92,6 +100,59 @@ class TestConductor(SqlTestCase):
|
||||
self.conductor.delete_vnf_package(self.context, self.vnf_package)
|
||||
mock_delete_csar.assert_called()
|
||||
|
||||
def test_get_vnf_package_vnfd_with_tosca_meta_file_in_csar(self):
|
||||
fake_csar = fakes.create_fake_csar_dir(self.vnf_package.id)
|
||||
expected_data = fakes.get_expected_vnfd_data()
|
||||
result = self.conductor.get_vnf_package_vnfd(self.context,
|
||||
self.vnf_package)
|
||||
self.assertEqual(expected_data, result)
|
||||
shutil.rmtree(fake_csar)
|
||||
|
||||
def test_get_vnf_package_vnfd_with_single_yaml_csar(self):
|
||||
fake_csar = fakes.create_fake_csar_dir(self.vnf_package.id,
|
||||
single_yaml_csar=True)
|
||||
result = self.conductor.get_vnf_package_vnfd(self.context,
|
||||
self.vnf_package)
|
||||
# only one key present in the result shows that it contains only one
|
||||
# yaml file
|
||||
self.assertEqual(1, len(result.keys()))
|
||||
shutil.rmtree(fake_csar)
|
||||
|
||||
@mock.patch.object(glance_store, 'load_csar')
|
||||
def test_get_vnf_package_vnfd_download_from_glance_store(self,
|
||||
mock_load_csar):
|
||||
fake_csar = os.path.join('/tmp/', self.vnf_package.id)
|
||||
cfg.CONF.set_override('vnf_package_csar_path', '/tmp',
|
||||
group='vnf_package')
|
||||
# Scenario in which csar path is not present in the local storage.
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
sample_vnf_package = os.path.join(
|
||||
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
|
||||
mock_load_csar.return_value = sample_vnf_package
|
||||
expected_data = fakes.get_expected_vnfd_data()
|
||||
result = self.conductor.get_vnf_package_vnfd(self.context,
|
||||
self.vnf_package)
|
||||
self.assertEqual(expected_data, result)
|
||||
shutil.rmtree(fake_csar)
|
||||
|
||||
@mock.patch.object(glance_store, 'load_csar')
|
||||
def test_get_vnf_package_vnfd_exception_from_glance_store(self,
|
||||
mock_load_csar):
|
||||
mock_load_csar.side_effect = store_exceptions.NotFound
|
||||
self.assertRaises(exceptions.FailedToGetVnfdData,
|
||||
self.conductor.get_vnf_package_vnfd, self.context,
|
||||
self.vnf_package)
|
||||
|
||||
@mock.patch.object(conductor_server.Conductor, '_read_vnfd_files')
|
||||
def test_get_vnf_package_vnfd_exception_from_read_vnfd_files(
|
||||
self, mock_read_vnfd_files):
|
||||
fake_csar = fakes.create_fake_csar_dir(self.vnf_package.id)
|
||||
mock_read_vnfd_files.side_effect = yaml.YAMLError
|
||||
self.assertRaises(exceptions.FailedToGetVnfdData,
|
||||
self.conductor.get_vnf_package_vnfd, self.context,
|
||||
self.vnf_package)
|
||||
shutil.rmtree(fake_csar)
|
||||
|
||||
@mock.patch.object(os, 'remove')
|
||||
@mock.patch.object(shutil, 'rmtree')
|
||||
@mock.patch.object(os.path, 'exists')
|
||||
|
@ -15,8 +15,14 @@
|
||||
|
||||
|
||||
import datetime
|
||||
import io
|
||||
import iso8601
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import webob
|
||||
import yaml
|
||||
import zipfile
|
||||
|
||||
from tacker.api.vnfpkgm.v1.router import VnfpkgmAPIRouter
|
||||
from tacker import context
|
||||
@ -117,7 +123,7 @@ def return_vnf_package_user_data(**updates):
|
||||
return model_obj
|
||||
|
||||
|
||||
def return_vnf_package(**updates):
|
||||
def return_vnf_package(onboarded=False, **updates):
|
||||
model_obj = models.VnfPackage()
|
||||
if 'user_data' in updates:
|
||||
metadata = []
|
||||
@ -126,14 +132,27 @@ def return_vnf_package(**updates):
|
||||
**{'key': key, 'value': value})
|
||||
metadata.extend([vnf_package_user_data])
|
||||
model_obj._metadata = metadata
|
||||
model_obj.update(fake_vnf_package(**updates))
|
||||
|
||||
if onboarded:
|
||||
updates = {'onboarding_state': 'ONBOARDED',
|
||||
'operational_state': 'ENABLED',
|
||||
'algorithm': 'test',
|
||||
'hash': 'test',
|
||||
'location_glance_store': 'file:test/path/pkg-uuid',
|
||||
'updated_at': datetime.datetime(
|
||||
1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)}
|
||||
model_obj.update(fake_vnf_package(**updates))
|
||||
else:
|
||||
model_obj.update(fake_vnf_package(**updates))
|
||||
|
||||
return model_obj
|
||||
|
||||
|
||||
def return_vnfpkg_obj(**updates):
|
||||
def return_vnfpkg_obj(onboarded=False, **updates):
|
||||
vnf_package = vnf_package_obj.VnfPackage._from_db_object(
|
||||
context, vnf_package_obj.VnfPackage(),
|
||||
return_vnf_package(**updates), expected_attrs=None)
|
||||
return_vnf_package(onboarded=onboarded, **updates),
|
||||
expected_attrs=None)
|
||||
return vnf_package
|
||||
|
||||
|
||||
@ -151,3 +170,29 @@ def wsgi_app_v1(fake_auth_context=None):
|
||||
uuidsentinel.project_id, is_admin=True)
|
||||
api_v1 = InjectContext(ctxt, inner_app_v1)
|
||||
return api_v1
|
||||
|
||||
|
||||
def return_vnfd_data(multiple_yaml_files=True):
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
sample_vnf_package_zip = os.path.join(
|
||||
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
|
||||
|
||||
csar_temp_dir = tempfile.mkdtemp()
|
||||
|
||||
with zipfile.ZipFile(sample_vnf_package_zip, 'r') as zf:
|
||||
zf.extractall(csar_temp_dir)
|
||||
|
||||
file_names = ['Definitions/etsi_nfv_sol001_vnfd_types.yaml']
|
||||
if multiple_yaml_files:
|
||||
file_names.extend(['TOSCA-Metadata/TOSCA.meta',
|
||||
'Definitions/helloworld3_types.yaml',
|
||||
'Definitions/helloworld3_df_simple.yaml',
|
||||
'Definitions/helloworld3_top.vnfd.yaml',
|
||||
'Definitions/etsi_nfv_sol001_common_types.yaml'])
|
||||
file_path_and_data = {}
|
||||
for file_name in file_names:
|
||||
file_path_and_data.update({file_name: yaml.dump(yaml.safe_load(
|
||||
io.open(os.path.join(csar_temp_dir, file_name))))})
|
||||
|
||||
shutil.rmtree(csar_temp_dir)
|
||||
return file_path_and_data
|
||||
|
@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_serialization import jsonutils
|
||||
from six.moves import http_client
|
||||
@ -20,6 +21,7 @@ from six.moves import urllib
|
||||
from webob import exc
|
||||
|
||||
from tacker.api.vnfpkgm.v1 import controller
|
||||
from tacker.common import exceptions
|
||||
from tacker.conductor.conductorrpc.vnf_pkgm_rpc import VNFPackageRPCAPI
|
||||
from tacker.glance_store import store as glance_store
|
||||
from tacker import objects
|
||||
@ -32,6 +34,7 @@ from tacker.tests.unit import fake_request
|
||||
from tacker.tests.unit.vnfpkgm import fakes
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestController(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -413,3 +416,121 @@ class TestController(base.TestCase):
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.patch,
|
||||
req, constants.UUID, body=body)
|
||||
|
||||
@mock.patch.object(VNFPackageRPCAPI, "get_vnf_package_vnfd")
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
@ddt.data('application/zip', 'text/plain,application/zip',
|
||||
'application/zip,text/plain')
|
||||
def test_get_vnf_package_vnfd_with_valid_accept_headers(
|
||||
self, accept_headers, mock_vnf_by_id, mock_get_vnf_package_vnfd):
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
|
||||
mock_get_vnf_package_vnfd.return_value = fakes.return_vnfd_data()
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/vnfd'
|
||||
% constants.UUID)
|
||||
req.headers['Accept'] = accept_headers
|
||||
req.method = 'GET'
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(http_client.OK, resp.status_code)
|
||||
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_get_vnf_package_vnfd_with_invalid_accept_header(
|
||||
self, mock_vnf_by_id):
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/vnfd'
|
||||
% constants.UUID)
|
||||
req.headers['Accept'] = 'test-invalid-header'
|
||||
req.method = 'GET'
|
||||
self.assertRaises(exc.HTTPNotAcceptable,
|
||||
self.controller.get_vnf_package_vnfd,
|
||||
req, constants.UUID)
|
||||
|
||||
@mock.patch.object(VNFPackageRPCAPI, "get_vnf_package_vnfd")
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_get_vnf_package_vnfd_failed_with_bad_request(
|
||||
self, mock_vnf_by_id, mock_get_vnf_package_vnfd):
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
|
||||
mock_get_vnf_package_vnfd.return_value = fakes.return_vnfd_data()
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/vnfd'
|
||||
% constants.UUID)
|
||||
req.headers['Accept'] = 'text/plain'
|
||||
req.method = 'GET'
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.get_vnf_package_vnfd,
|
||||
req, constants.UUID)
|
||||
|
||||
@mock.patch.object(VNFPackageRPCAPI, "get_vnf_package_vnfd")
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_get_vnf_package_vnfd_for_content_type_text_plain(self,
|
||||
mock_vnf_by_id,
|
||||
mock_get_vnf_package_vnfd):
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
|
||||
fake_vnfd_data = fakes.return_vnfd_data(multiple_yaml_files=False)
|
||||
mock_get_vnf_package_vnfd.return_value = fake_vnfd_data
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/vnfd'
|
||||
% constants.UUID)
|
||||
req.headers['Accept'] = 'text/plain'
|
||||
req.method = 'GET'
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(http_client.OK, resp.status_code)
|
||||
self.assertEqual('text/plain', resp.content_type)
|
||||
self.assertEqual(fake_vnfd_data[list(fake_vnfd_data.keys())[0]],
|
||||
resp.text)
|
||||
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_get_vnf_package_vnfd_failed_with_invalid_status(
|
||||
self, mock_vnf_by_id):
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=False)
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/vnfd'
|
||||
% constants.UUID)
|
||||
req.headers['Accept'] = 'application/zip'
|
||||
req.method = 'GET'
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(http_client.CONFLICT, resp.status_code)
|
||||
|
||||
def test_get_vnf_package_vnfd_with_invalid_uuid(self):
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/vnfd'
|
||||
% constants.INVALID_UUID)
|
||||
req.headers['Accept'] = 'application/zip'
|
||||
req.method = 'GET'
|
||||
exception = self.assertRaises(exc.HTTPNotFound,
|
||||
self.controller.get_vnf_package_vnfd,
|
||||
req, constants.INVALID_UUID)
|
||||
self.assertEqual(
|
||||
"Can not find requested vnf package: %s" % constants.INVALID_UUID,
|
||||
exception.explanation)
|
||||
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_get_vnf_package_vnfd_with_non_existing_vnf_packagee(
|
||||
self, mock_vnf_by_id):
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/vnfd'
|
||||
% constants.UUID)
|
||||
req.headers['Accept'] = 'application/zip'
|
||||
req.method = 'GET'
|
||||
mock_vnf_by_id.side_effect = exceptions.VnfPackageNotFound
|
||||
self.assertRaises(exc.HTTPNotFound,
|
||||
self.controller.get_vnf_package_vnfd, req,
|
||||
constants.UUID)
|
||||
|
||||
@mock.patch.object(VNFPackageRPCAPI, "get_vnf_package_vnfd")
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_get_vnf_package_vnfd_failed_with_internal_server_error(
|
||||
self, mock_vnf_by_id, mock_get_vnf_package_vnfd):
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
|
||||
mock_get_vnf_package_vnfd.side_effect = exceptions.FailedToGetVnfdData
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/vnfd'
|
||||
% constants.UUID)
|
||||
req.headers['Accept'] = 'application/zip'
|
||||
req.method = 'GET'
|
||||
resp = req.get_response(self.app)
|
||||
self.assertRaises(exc.HTTPInternalServerError,
|
||||
self.controller.get_vnf_package_vnfd,
|
||||
req, constants.UUID)
|
||||
self.assertEqual(http_client.INTERNAL_SERVER_ERROR, resp.status_code)
|
||||
|
@ -396,7 +396,7 @@ class Request(webob.Request):
|
||||
type_from_header = self.get_content_type()
|
||||
if type_from_header:
|
||||
return type_from_header
|
||||
ctypes = ['application/json']
|
||||
ctypes = ['application/json', 'text/plain', 'application/zip']
|
||||
|
||||
# Finally search in Accept-* headers
|
||||
bm = self.accept.best_match(ctypes)
|
||||
@ -935,9 +935,11 @@ class ResponseObject(object):
|
||||
"""
|
||||
|
||||
serializer = self.serializer
|
||||
|
||||
body = None
|
||||
if self.obj is not None:
|
||||
if self.obj is None:
|
||||
body = None
|
||||
elif content_type == 'text/plain':
|
||||
body = self.obj
|
||||
else:
|
||||
body = serializer.serialize(self.obj)
|
||||
response = webob.Response(body=body)
|
||||
response.status_int = self.code
|
||||
@ -1032,7 +1034,8 @@ class Resource(Application):
|
||||
|
||||
if not response:
|
||||
resp_obj = None
|
||||
if type(action_result) is dict or action_result is None:
|
||||
if isinstance(action_result, (dict, str)) \
|
||||
or action_result is None:
|
||||
resp_obj = ResponseObject(action_result)
|
||||
elif isinstance(action_result, ResponseObject):
|
||||
resp_obj = action_result
|
||||
|
Loading…
Reference in New Issue
Block a user