# Copyright (c) 2015 Quobyte Inc.
# 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.

"""
Quobyte driver.

Manila shares are directly mapped to Quobyte volumes. The access to the
shares is provided by the Quobyte NFS proxy (a Ganesha NFS server).
"""

import math

from oslo_config import cfg
from oslo_log import log
from oslo_utils import units

import manila.common.constants
from manila import exception
from manila.i18n import _
from manila.i18n import _LE
from manila.i18n import _LI
from manila.i18n import _LW
from manila.share import driver
from manila.share.drivers.quobyte import jsonrpc

LOG = log.getLogger(__name__)

quobyte_manila_share_opts = [
    cfg.StrOpt('quobyte_api_url',
               help='URL of the Quobyte API server (http or https)'),
    cfg.StrOpt('quobyte_api_ca',
               default=None,
               help='The X.509 CA file to verify the server cert.'),
    cfg.BoolOpt('quobyte_delete_shares',
                default=False,
                help='Actually deletes shares (vs. unexport)'),
    cfg.StrOpt('quobyte_api_username',
               default='admin',
               help='Username for Quobyte API server.'),
    cfg.StrOpt('quobyte_api_password',
               default='quobyte',
               secret=True,
               help='Password for Quobyte API server'),
    cfg.StrOpt('quobyte_volume_configuration',
               default='BASE',
               help='Name of volume configuration used for new shares.'),
    cfg.StrOpt('quobyte_default_volume_user',
               default='root',
               help='Default owning user for new volumes.'),
    cfg.StrOpt('quobyte_default_volume_group',
               default='root',
               help='Default owning group for new volumes.'),
]

CONF = cfg.CONF
CONF.register_opts(quobyte_manila_share_opts)


class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,):
    """Map share commands to Quobyte volumes."""

    DRIVER_VERSION = '1.0'

    def __init__(self, *args, **kwargs):
        super(QuobyteShareDriver, self).__init__(False, *args, **kwargs)
        self.configuration.append_config_values(quobyte_manila_share_opts)
        self.backend_name = (self.configuration.safe_get('share_backend_name')
                             or CONF.share_backend_name or 'Quobyte')

    def do_setup(self, context):
        """Prepares the backend."""
        self.rpc = jsonrpc.JsonRpc(
            url=self.configuration.quobyte_api_url,
            ca_file=self.configuration.quobyte_api_ca,
            user_credentials=(
                self.configuration.quobyte_api_username,
                self.configuration.quobyte_api_password))

        try:
            self.rpc.call('getInformation', {})
        except Exception as exc:
            LOG.error(_LE("Could not connect to API: %s"), exc)
            raise exception.QBException(
                _('Could not connect to API: %s') % exc)

    def _update_share_stats(self):
        total_gb, free_gb = self._get_capacities()

        data = dict(
            storage_protocol='NFS',
            vendor_name='Quobyte',
            share_backend_name=self.backend_name,
            driver_version=self.DRIVER_VERSION,
            total_capacity_gb=total_gb,
            free_capacity_gb=free_gb,
            reserved_percentage=self.configuration.reserved_share_percentage)
        super(QuobyteShareDriver, self)._update_share_stats(data)

    def _get_capacities(self):
        result = self.rpc.call('getSystemStatistics', {})

        total = float(result['total_logical_capacity'])
        used = float(result['total_logical_usage'])
        LOG.info(_LI('Read capacity of %(cap)s bytes and '
                     'usage of %(use)s bytes from backend. '),
                 {'cap': total, 'use': used})
        free = total - used
        # floor numbers to nine digits (bytes)
        total = math.floor((total / units.Gi) * units.G) / units.G
        free = math.floor((free / units.Gi) * units.G) / units.G

        return total, free

    def check_for_setup_error(self):
        pass

    def get_network_allocations_number(self):
        return 0

    def _get_project_name(self, context, project_id):
        """Retrieve the project name.

        TODO (kaisers): retrieve the project name in order
        to store and use in the backend for better usability.
        """
        return project_id

    def _resolve_volume_name(self, volume_name, tenant_domain):
        """Resolve a volume name to the global volume uuid."""
        result = self.rpc.call('resolveVolumeName', dict(
            volume_name=volume_name,
            tenant_domain=tenant_domain))
        if result:
            return result['volume_uuid']
        return None  # not found

    def create_share(self, context, share, share_server=None):
        """Create or export a volume that is usable as a Manila share."""
        if share['share_proto'] != 'NFS':
            raise exception.QBException(
                _('Quobyte driver only supports NFS shares'))

        volume_uuid = self._resolve_volume_name(
            share['name'],
            self._get_project_name(context, share['project_id']))

        if not volume_uuid:
            result = self.rpc.call('createVolume', dict(
                name=share['name'],
                tenant_domain=share['project_id'],
                root_user_id=self.configuration.quobyte_default_volume_user,
                root_group_id=self.configuration.quobyte_default_volume_group,
                configuration_name=(self.configuration.
                                    quobyte_volume_configuration)))
            volume_uuid = result['volume_uuid']

        result = self.rpc.call('exportVolume', dict(
            volume_uuid=volume_uuid,
            protocol='NFS'))

        return '%(nfs_server_ip)s:%(nfs_export_path)s' % result

    def delete_share(self, context, share, share_server=None):
        """Delete the corresponding Quobyte volume."""
        volume_uuid = self._resolve_volume_name(
            share['name'],
            self._get_project_name(context, share['project_id']))
        if not volume_uuid:
            LOG.warning(_LW("No volume found for "
                            "share %(project_id)s/%(name)s")
                        % {"project_id": share['project_id'],
                           "name": share['name']})
            return

        if self.configuration.quobyte_delete_shares:
            self.rpc.call('deleteVolume', {'volume_uuid': volume_uuid})

        self.rpc.call('exportVolume', dict(
            volume_uuid=volume_uuid,
            remove_export=True))

    def create_snapshot(self, context, snapshot, share_server=None):
        """Is called to create snapshot."""
        raise NotImplementedError()

    def create_share_from_snapshot(self, context, share, snapshot,
                                   share_server=None):
        """Is called to create share from snapshot."""
        raise NotImplementedError()

    def delete_snapshot(self, context, snapshot, share_server=None):
        """TBD: Is called to remove snapshot."""
        raise NotImplementedError()

    def ensure_share(self, context, share, share_server=None):
        """Invoked to ensure that share is exported."""

    def allow_access(self, context, share, access, share_server=None):
        """Allow access to a share."""
        if access['access_type'] != 'ip':
            raise exception.InvalidShareAccess(
                _('Quobyte driver only supports ip access control'))

        volume_uuid = self._resolve_volume_name(
            share['name'],
            self._get_project_name(context, share['project_id']))
        self.rpc.call('exportVolume', dict(
            volume_uuid=volume_uuid,
            read_only=access['access_level'] == (manila.common.constants.
                                                 ACCESS_LEVEL_RO),
            add_allow_ip=access['access_to']))

    def deny_access(self, context, share, access, share_server=None):
        """Remove white-list ip from a share."""
        if access['access_type'] != 'ip':
            LOG.debug('Quobyte driver only supports ip access control. '
                      'Ignoring deny access call for %s , %s',
                      share['name'],
                      self._get_project_name(context, share['project_id']))
            return

        volume_uuid = self._resolve_volume_name(
            share['name'],
            self._get_project_name(context, share['project_id']))
        self.rpc.call('exportVolume', dict(
            volume_uuid=volume_uuid,
            remove_allow_ip=access['access_to']))
