# Copyright (c) 2017 Veritas Technologies LLC
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import os

from oslo_log import log as logging
from oslo_utils import excutils

from cinder.common import constants
from cinder import exception
from cinder.i18n import _
from cinder import interface
import cinder.privsep.path
from cinder.volume.drivers import nfs

LOG = logging.getLogger(__name__)


@interface.volumedriver
class VeritasCNFSDriver(nfs.NfsDriver):

    """Veritas Clustered NFS based cinder driver

      .. code-block:: default

        Version History:

          1.0.0 - Initial driver implementations for Kilo.
          1.0.1 - Liberty release driver not implemented.
                  Place holder for Liberty release in case we
                  need to support.
          1.0.2 - cinder.interface.volumedriver decorator.
                  Mitaka/Newton/Okata Release
          1.0.3 - Separate create_cloned_volume() and
                  create_volume_from_snapshot () functionality.
                  Pike Release

    Executes commands relating to Volumes.
    """

    VERSION = "1.0.3"
    # ThirdPartySytems wiki page
    CI_WIKI_NAME = "Veritas_Access_CI"
    DRIVER_VOLUME_TYPE = constants.NFS_VARIANT

    # TODO(jsbryant) Remove driver in the 'V' release if CI is not fixed.
    SUPPORTED = False

    def __init__(self, *args, **kwargs):
        self._execute = None
        self._context = None
        super(VeritasCNFSDriver, self).__init__(*args, **kwargs)

    def do_setup(self, context):
        self._context = context
        super(VeritasCNFSDriver, self).do_setup(context)
        opts = self.configuration.nfs_mount_options
        if not opts or opts.find('vers=3') == -1 or (
           opts.find('nfsvers=3')) == -1:
            msg = _("NFS is not configured to use NFSv3")
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def create_volume_from_snapshot(self, volume, snapshot):
        """Creates a volume from snapshot."""
        LOG.debug('VeritasNFSDriver create_volume_from_snapshot called '
                  'volume_id = %(volume)s and snapshot_id = %(snapshot)s',
                  {'volume': volume.id, 'snapshot': snapshot.id})
        snap_name = snapshot.name
        vol_size = volume.size
        snap_size = snapshot.volume_size
        self._do_clone_volume(snapshot, snap_name, volume)
        volume.provider_location = snapshot.provider_location

        if vol_size != snap_size:
            try:
                self.extend_volume(volume, vol_size)
            except exception.ExtendVolumeError as ex:
                with excutils.save_and_reraise_exception():
                    LOG.error('Failed to extend Volume: %s', ex.msg)
                    path = self.local_path(volume)
                    self._delete_file(path)
        return {'provider_location': volume.provider_location}

    def _get_vol_by_id(self, volid):
        vol = self.db.volume_get(self._context, volid)
        return vol

    def _delete_file(self, path):
        """Deletes file from disk and return result as boolean."""
        try:
            LOG.debug('Deleting file at path %s', path)
            self._execute('rm', '-f', path, run_as_root=True)
        except OSError as ex:
            LOG.warning('Exception during deleting %s', ex.strerror)

    def create_snapshot(self, snapshot):
        """Create a snapshot of the volume."""
        src_vol_id = snapshot.volume_id
        src_vol_name = snapshot.volume_name
        src_vol = self._get_vol_by_id(src_vol_id)
        self._do_clone_volume(src_vol, src_vol_name, snapshot)
        snapshot.provider_location = src_vol.provider_location
        LOG.debug("VeritasNFSDriver create_snapshot %r",
                  snapshot.provider_location)
        return {'provider_location': snapshot.provider_location}

    def delete_snapshot(self, snapshot):
        """Delete a snapshot."""
        if not snapshot.provider_location:
            LOG.warning('Snapshot %s does not have provider_location '
                        'specified, skipping', snapshot.name)
            return
        self._ensure_share_mounted(snapshot.provider_location)
        snap_path = self.local_path(snapshot)
        self._delete_file(snap_path)

    def create_cloned_volume(self, volume, src_vref):
        """Create a clone of the volume."""

        LOG.debug('VeritasNFSDriver create_cloned_volume called '
                  'volume_id = %(volume)s and src_vol_id = %(src_vol_id)s',
                  {'volume': volume.id, 'src_vol_id': src_vref.id})
        src_vol_name = src_vref.name
        vol_size = volume.size
        src_vol_size = src_vref.size
        self._do_clone_volume(src_vref, src_vol_name, volume)
        volume.provider_location = src_vref.provider_location

        if vol_size != src_vol_size:
            try:
                self.extend_volume(volume, vol_size)
            except exception.ExtendVolumeError as ex:
                with excutils.save_and_reraise_exception():
                    LOG.error('Failed to extend Volume: %s', ex.msg)
                    path = self.local_path(volume)
                    self._delete_file(path)
        return {'provider_location': volume.provider_location}

    def _get_local_volume_path(self, provider_loc, vol_name):
        mnt_path = self._get_mount_point_for_share(provider_loc)
        vol_path = os.path.join(mnt_path, vol_name)
        return vol_path

    def _do_clone_volume(self, src_vol, src_vol_name, tgt_vol):
        cnfs_share = src_vol.provider_location
        tgt_vol_name = tgt_vol.name
        tgt_vol_path = self._get_local_volume_path(cnfs_share, tgt_vol_name)
        src_vol_path = self._get_local_volume_path(cnfs_share, src_vol_name)
        tgt_vol_path_spl = tgt_vol_path + "::snap:vxfs:"
        cinder.privsep.path.symlink(src_vol_path, tgt_vol_path_spl)
        LOG.debug("VeritasNFSDriver: do_clone_volume %(src_vol_path)s "
                  "%(tgt_vol_path)s %(tgt_vol_path_spl)s",
                  {'src_vol_path': src_vol_path,
                   'tgt_vol_path_spl': tgt_vol_path_spl,
                   'tgt_vol_path': tgt_vol_path})
        if not os.path.exists(tgt_vol_path):
            self._execute('rm', '-f', tgt_vol_path_spl, run_as_root=True)
            msg = _("Filesnap over NFS is not supported, "
                    "removing the ::snap:vxfs: file")
            LOG.error(msg)
            raise exception.NfsException(msg)

    def extend_volume(self, volume, size):
        """Extend the volume to new size"""
        path = self.local_path(volume)
        self._execute('truncate', '-s', '%sG' % size, path, run_as_root=True)
        LOG.debug("VeritasNFSDriver: extend_volume volume_id = %s", volume.id)

    def _update_volume_stats(self):
        super(VeritasCNFSDriver, self)._update_volume_stats()
        backend_name = self.configuration.safe_get('volume_backend_name')
        res_percentage = self.configuration.safe_get('reserved_percentage')
        self._stats["volume_backend_name"] = backend_name or 'VeritasCNFS'
        self._stats["vendor_name"] = 'Veritas'
        self._stats["reserved_percentage"] = res_percentage or 0
        self._stats["driver_version"] = self.VERSION
        self._stats["storage_protocol"] = self.DRIVER_VOLUME_TYPE
