# 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 http import client as http_client
from io import BytesIO
import json
import mimetypes
import os
import traceback
import webob
import zipfile
from zipfile import ZipFile

from glance_store import exceptions as store_exceptions
from oslo_config import cfg
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 uuidutils

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 csar_utils
from tacker.common import exceptions
from tacker.common import utils
from tacker.conductor.conductorrpc import vnf_pkgm_rpc
from tacker.db.db_sqlalchemy import models
from tacker.glance_store import store as glance_store
from tacker import objects
from tacker.objects import fields
from tacker.objects import vnf_package as vnf_package_obj
from tacker.objects.vnf_package import VnfPackagesList as vnf_package_list
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()
        self._nextpages = {}

    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()

        headers = {"location": '/vnfpkgm/v1/vnf_packages/%s' % vnf_package.id}
        result = self._view_builder.create(vnf_package)

        return wsgi.ResponseObject(result, headers=headers)

    @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']

        # 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=[
                    models.VnfPackage.vnf_deployment_flavours,
                    models.VnfPackage.vnfd,
                    models.VnfPackage.vnf_artifacts])
            if CONF.oslo_policy.enhanced_tacker_policy:
                context.can(vnf_package_policies.VNFPKGM % 'show',
                            target=self._get_policy_target(vnf_package))
            else:
                context.can(vnf_package_policies.VNFPKGM % 'show',
                            target={'project_id': vnf_package.tenant_id})
        except exceptions.VnfPackageNotFound:
            msg = _("Can not find requested vnf package: %s") % id
            raise webob.exc.HTTPNotFound(explanation=msg)

        return self._view_builder.show(vnf_package)

    def _get_policy_target(self, vnf_package):
        if vnf_package.onboarding_state == \
                fields.PackageOnboardingStateType.ONBOARDED:

            vendor = (vnf_package.vnfd.get('vnf_provider')
                      if vnf_package.vnfd is not None else None)

            return {'vendor': vendor} if vendor else {}

        else:
            return {'vendor': '*'}

    @wsgi.response(http_client.OK)
    @wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN))
    @validation.query_schema(vnf_packages.query_params_v1)
    def index(self, request):
        context = request.environ['tacker.context']
        if not CONF.oslo_policy.enhanced_tacker_policy:
            context.can(vnf_package_policies.VNFPKGM % 'index')

        search_opts = {}
        search_opts.update(request.GET)

        def _key_exists(key, validate_value=True):
            try:
                request.GET[key]
            except KeyError:
                return False

            return True

        all_fields = _key_exists('all_fields')
        exclude_default = _key_exists('exclude_default')
        fields = request.GET.get('fields')
        exclude_fields = request.GET.get('exclude_fields')
        filters = request.GET.get('filter')
        nextpage = request.GET.get('nextpage_opaque_marker')
        allrecords = request.GET.get('all_records')
        if not (all_fields or fields or exclude_fields):
            exclude_default = True

        self._view_builder.validate_attribute_fields(all_fields=all_fields,
            fields=fields, exclude_fields=exclude_fields,
            exclude_default=exclude_default)

        filters = self._view_builder.validate_filter(filters)

        results = []
        limit = None
        marker_obj = None

        if allrecords != 'yes':
            if not CONF.oslo_policy.enhanced_tacker_policy:
                limit = CONF.vnf_package.vnf_package_num

            # get next page marker object from nextpage id
            if nextpage:
                marker_obj = objects.VnfPackage.get_by_id(request.context,
                        nextpage)

        try:
            # get records from DB within maximum record size per page
            # except for getting all records case
            result = vnf_package_list.get_by_marker_filter(request.context,
                    limit, marker_obj, filters=filters, read_deleted='no')
        except Exception as e:
            LOG.exception(traceback.format_exc())
            return self._make_problem_detail(
                str(e), 500, title='Internal Server Error')

        if CONF.oslo_policy.enhanced_tacker_policy:
            result = [vnf_package for vnf_package in result
                      if context.can(
                          vnf_package_policies.VNFPKGM % 'index',
                          target=self._get_policy_target(vnf_package),
                          fatal=False)]
            if allrecords != 'yes':
                limit = CONF.vnf_package.vnf_package_num
                result = result[:limit]

        results = self._view_builder.index(result,
                all_fields=all_fields,
                exclude_fields=exclude_fields,
                fields=fields,
                exclude_default=exclude_default)

        res = webob.Response(content_type='application/json')
        res.status_int = 200

        # if the number of records obtained from DB is equal to maximum record
        # size per page, the id of the last record is used as next page marker
        # and set it to Link header of the response
        if (allrecords != 'yes' and
                len(results) >= limit):
            nextpageid = result[(limit - 1)]['id']
            links = ('Link', '<%s?nextpage_opaque_marker=%s>; rel="next"' % (
                request.path_url, nextpageid))
            res.headerlist.append(links)

        res.body = jsonutils.dump_as_bytes(results, default=str)

        return res

    @wsgi.response(http_client.NO_CONTENT)
    @wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND,
                           http_client.CONFLICT))
    def delete(self, request, id):
        context = request.environ['tacker.context']

        vnf_package = self._get_vnf_package(id, request)
        if CONF.oslo_policy.enhanced_tacker_policy:
            context.can(vnf_package_policies.VNFPKGM % 'delete',
                        target=self._get_policy_target(vnf_package))
        else:
            context.can(vnf_package_policies.VNFPKGM % 'delete',
                        target={'project_id': vnf_package.tenant_id})

        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})

        # Controller deletes vnf_package just from DB here.
        # Related objects in Legacy DB are also deleted.
        # Actual vnf_packages that is in backend storage
        # or each conductors' local filesystems are deleted after.
        if vnf_package.vnfd is not None:
            objects.VnfdAttribute(context).delete(vnf_package.vnfd.vnfd_id)
            objects.Vnfd(context).delete(vnf_package.vnfd.vnfd_id)
        vnf_package.destroy(context)

        if vnf_package.onboarding_state == (
                fields.PackageOnboardingStateType.ONBOARDED):
            # send rpc message for deleting actual vnf_package
            # to conductors
            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,
                           http_client.REQUESTED_RANGE_NOT_SATISFIABLE))
    def fetch_vnf_package_content(self, request, id):
        context = request.environ['tacker.context']

        vnf_package = self._get_vnf_package(id, request)
        if CONF.oslo_policy.enhanced_tacker_policy:
            context.can(vnf_package_policies.VNFPKGM % 'fetch_package_content',
                        target=self._get_policy_target(vnf_package))
        else:
            context.can(vnf_package_policies.VNFPKGM % 'fetch_package_content',
                        target={'project_id': vnf_package.tenant_id})
        if vnf_package.onboarding_state != \
           fields.PackageOnboardingStateType.ONBOARDED:
            msg = _("VNF Package %(id)s onboarding state "
                    "is not %(onboarding)s")
            raise webob.exc.HTTPConflict(explanation=msg % {"id": id,
                    "onboarding": fields.PackageOnboardingStateType.ONBOARDED})

        if vnf_package.size == 0:

            try:
                zip_file_size = glance_store.get_csar_size(id,
                    vnf_package.location_glance_store)
                vnf_package.size = zip_file_size
                vnf_package.save()
            except exceptions.VnfPackageLocationInvalid:
                msg = _("Vnf package not present at location")
                raise webob.exc.HTTPNotFound(explanation=msg)

        else:
            zip_file_size = vnf_package.size

        range_val = self._get_range_from_request(request, zip_file_size)

        return self._download(
            request.response, range_val, id, vnf_package.location_glance_store,
            zip_file_size)

    def _download(self, response, range_val, uuid, location, zip_file_size):
        offset, chunk_size = 0, None
        if range_val:
            if isinstance(range_val, webob.byterange.Range):
                response_end = zip_file_size - 1
                # NOTE(sameert): webob parsing is zero-indexed.
                # i.e.,to download first 5 bytes of a 10 byte image,
                # request should be "bytes=0-4" and the response would be
                # "bytes 0-4/10".
                # Range if validated, will never have 'start' object as None.
                if range_val.start >= 0:
                    offset = range_val.start
                else:
                    # NOTE(sameert): Negative start values needs to be
                    # processed to allow suffix-length for Range request
                    # like "bytes=-2" as per rfc7233.
                    if abs(range_val.start) < zip_file_size:
                        offset = zip_file_size + range_val.start

                if range_val.end is not None and range_val.end < zip_file_size:
                    chunk_size = range_val.end - offset
                    response_end = range_val.end - 1
                else:
                    chunk_size = zip_file_size - offset

            response.status_int = 206

        response.headers['Content-Type'] = 'application/zip'

        response.app_iter = self._get_csar_zip_data(uuid,
            location, offset, chunk_size)
        # NOTE(sameert): In case of a full zip download, when
        # chunk_size was none, reset it to zip.size to set the
        # response header's Content-Length.
        if chunk_size is not None:
            response.headers['Content-Range'] = 'bytes %s-%s/%s'\
                                                % (offset,
                                                    response_end,
                                                    zip_file_size)
        else:
            chunk_size = zip_file_size
        response.headers['Content-Length'] = str(chunk_size)
        return response

    def _get_csar_zip_data(self, uuid, location, offset=0, chunk_size=None):
        try:
            resp, size = glance_store.load_csar_iter(
                uuid, location, offset=offset, chunk_size=chunk_size)
        except exceptions.VnfPackageLocationInvalid:
            msg = _("Vnf package not present at location")
            raise webob.exc.HTTPServerError(explanation=msg)
        return resp

    def _get_range_from_request(self, request, zip_file_size):
        range_str = request._headers.environ.get('HTTP_RANGE')
        if range_str is not None:
            # NOTE(sameert): We do not support multi range requests.
            if ',' in range_str:
                msg = _("Requests with multiple ranges are not supported in "
                       "Tacker. You may make multiple single-range requests "
                       "instead.")
                raise webob.exc.HTTPBadRequest(explanation=msg)

            range_ = webob.byterange.Range.parse(range_str)
            if range_ is None:
                range_err_msg = _("The byte range passed in the 'Range' header"
                 " did not match any available byte range in the VNF package"
                 " file")
                raise webob.exc.HTTPRequestRangeNotSatisfiable(
                    explanation=range_err_msg)
            # NOTE(sameert): Ensure that a range like bytes=4- for an zip
            # size of 3 is invalidated as per rfc7233.
            if range_.start >= zip_file_size:
                msg = _("Invalid start position in Range header. "
                       "Start position MUST be in the inclusive range"
                       "[0, %s].") % (zip_file_size - 1)
                raise webob.exc.HTTPRequestRangeNotSatisfiable(
                    explanation=msg)
            if range_.end is not None:
                if range_.end - 1 >= zip_file_size:
                    msg = _("Invalid end position in Range header. "
                           "End position MUST be in the inclusive range"
                           "[0, %d].") % (zip_file_size - 1)
                    raise webob.exc.HTTPRequestRangeNotSatisfiable(
                        explanation=msg)
            return range_

    @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']

        # 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)
            if not CONF.oslo_policy.enhanced_tacker_policy:
                context.can(
                    vnf_package_policies.VNFPKGM % 'upload_package_content',
                    target={'project_id': vnf_package.tenant_id})
        except exceptions.VnfPackageNotFound:
            msg = _("Can not find requested vnf package: %s") % id
            return self._make_problem_detail('Not Found', msg, 404)
        except exceptions.PolicyNotAuthorized:
            raise
        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")
            return self._make_problem_detail('Conflict', msg % {"id": id,
                    "onboarding": fields.PackageOnboardingStateType.CREATED},
                    409)

        vnf_package.onboarding_state = (
            fields.PackageOnboardingStateType.UPLOADING)

        try:
            vnf_package.save()
        except Exception as e:
            return self._make_problem_detail(
                'Internal Server Error', str(e), 500)

        try:
            (location, size, checksum, multihash,
            loc_meta) = glance_store.store_csar(context, id, body)
            if CONF.oslo_policy.enhanced_tacker_policy:
                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)
                context.can(
                    vnf_package_policies.VNFPKGM % 'upload_package_content',
                    target={'vendor': vnf_data.get('provider')})
        except exceptions.UploadFailedToGlanceStore:
            with excutils.save_and_reraise_exception():
                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)
        except exceptions.Forbidden:
            with excutils.save_and_reraise_exception():
                vnf_package.onboarding_state = (
                    fields.PackageOnboardingStateType.CREATED)
                try:
                    # Delete from glance store
                    glance_store.delete_csar(context, vnf_package.id,
                                             location)
                    csar_utils.delete_csar_data(vnf_package.id)
                    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
        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)

    @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']

        url = body['addressInformation']
        if not utils.is_valid_url(url):
            msg = _("Vnf package url '%s' is invalid") % url
            raise webob.exc.HTTPBadRequest(explanation=msg)

        vnf_package = self._get_vnf_package(id, request)
        if not CONF.oslo_policy.enhanced_tacker_policy:
            context.can(
                vnf_package_policies.VNFPKGM % 'upload_from_uri',
                target={'project_id': vnf_package.tenant_id})

        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']

        old_vnf_package = self._get_vnf_package(id, request)
        if CONF.oslo_policy.enhanced_tacker_policy:
            context.can(vnf_package_policies.VNFPKGM % 'patch',
                        target=self._get_policy_target(old_vnf_package))
        else:
            context.can(
                vnf_package_policies.VNFPKGM % 'patch',
                target={'project_id': old_vnf_package.tenant_id})

        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']

        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 CONF.oslo_policy.enhanced_tacker_policy:
            context.can(vnf_package_policies.VNFPKGM % 'get_vnf_package_vnfd',
                        target=self._get_policy_target(vnf_package))
        else:
            context.can(
                vnf_package_policies.VNFPKGM % 'get_vnf_package_vnfd',
                target={'project_id': vnf_package.tenant_id})
        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=str(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)

    @wsgi.response(http_client.OK)
    @wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
                           http_client.NOT_FOUND, http_client.CONFLICT,
                           http_client.REQUESTED_RANGE_NOT_SATISFIABLE))
    def fetch_vnf_package_artifacts(self, request, id, artifact_path):
        context = request.environ['tacker.context']

        # get vnf_package
        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=[models.VnfPackage.vnf_artifacts])
            if CONF.oslo_policy.enhanced_tacker_policy:
                # get policy
                context.can(vnf_package_policies.VNFPKGM % 'fetch_artifact',
                            target=self._get_policy_target(vnf_package))
            else:
                context.can(
                    vnf_package_policies.VNFPKGM % 'fetch_artifact',
                    target={'project_id': vnf_package.tenant_id})

        except exceptions.VnfPackageNotFound:
            msg = _("Can not find requested vnf package: %s") % id
            raise webob.exc.HTTPNotFound(explanation=msg)

        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})

        offset, chunk_size = 0, None

        # get all artifact's path
        artifact_file_paths = []
        for item in vnf_package.vnf_artifacts:
            artifact_file_paths.append(item.artifact_path)

        if artifact_path in artifact_file_paths:
            # get file's size
            csar_path = self._get_csar_path(vnf_package)
            absolute_artifact_path = os.path.join(csar_path, artifact_path)
            if not os.path.isfile(absolute_artifact_path):
                msg = _(
                    "This type of path(url) '%s' is currently not supported") \
                    % artifact_path
                raise webob.exc.HTTPBadRequest(explanation=msg)
            artifact_size = os.path.getsize(absolute_artifact_path)
            range_val = self._get_range_from_request(request, artifact_size)
            # range_val exists
            if range_val:
                if isinstance(range_val, webob.byterange.Range):
                    # get the position of the last byte in the artifact file
                    response_end = artifact_size - 1
                    if range_val.start >= 0:
                        offset = range_val.start
                    else:
                        if abs(range_val.start) < artifact_size:
                            offset = artifact_size + range_val.start
                    if range_val.end is not None and \
                            range_val.end < artifact_size:
                        chunk_size = range_val.end - offset
                        response_end = range_val.end - 1
                    else:
                        chunk_size = artifact_size - offset
                request.response.status_int = 206
            # range_val does not exist, download the whole content of file
            else:
                offset = 0
                chunk_size = artifact_size

            # get file's mineType;
            mime_type = mimetypes.guess_type(artifact_path.split('/')[-1])[0]
            if mime_type:
                request.response.headers['Content-Type'] = mime_type
            else:
                request.response.headers['Content-Type'] = \
                    'application/octet-stream'
            try:
                artifact_data = self._download_vnf_artifact(
                    absolute_artifact_path, offset, chunk_size)
            except exceptions.FailedToGetVnfArtifact as e:
                LOG.error(e.msg)
                raise webob.exc.HTTPInternalServerError(
                    explanation=e.msg)
            request.response.text = artifact_data.decode('utf-8')
            if request.response.status_int == 206:
                request.response.headers['Content-Range'] = 'bytes %s-%s/%s' \
                                                            % (offset,
                                                               response_end,
                                                               artifact_size)
            else:
                chunk_size = artifact_size

            request.response.headers['Content-Length'] = chunk_size
            return request.response
        else:
            msg = _("Not Found Artifact File.")
            raise webob.exc.HTTPNotFound(explanation=msg)

    def _get_csar_path(self, 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.FailedToGetVnfArtifact(error=msg)
        return csar_path

    def _download_vnf_artifact(self, artifact_file_path, offset=0,
            chunk_size=None):
        try:
            with open(artifact_file_path, 'rb') as f:
                f.seek(offset, 1)
                vnf_artifact_data = f.read(chunk_size)
                return vnf_artifact_data
        except Exception as e:
            exc_msg = encodeutils.exception_to_unicode(e)
            msg = (_("Exception raised while reading artifact file"
                     " Error: %s.") % exc_msg)
            raise exceptions.FailedToGetVnfArtifact(error=msg)

    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 _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 = {
        'application/zip': wsgi.ZipDeserializer()
    }

    deserializer = wsgi.RequestDeserializer(
        body_deserializers=body_deserializers)
    return wsgi.Resource(VnfPkgmController(), deserializer=deserializer)
