# 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

from manila.common import constants
from manila import exception
from manila.i18n import _
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',
               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.'),
    cfg.StrOpt('quobyte_export_path',
               default='/quobyte',
               help='Export path for shares of this bacckend. This needs '
                    'to match the quobyte-nfs services "Pseudo" option.'),
]

CONF = cfg.CONF
CONF.register_opts(quobyte_manila_share_opts)


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

    Version history:
        1.0     - Initial driver.
        1.0.1   - Adds ensure_share() implementation.
        1.1     - Adds extend_share() and shrink_share() implementation.
        1.2     - Adds update_access() implementation and related methods
        1.2.1   - Improved capacity calculation
        1.2.2   - Minor optimizations
        1.2.3   - Updated RPC layer for improved stability
        1.2.4   - Fixed handling updated QB API error codes
        1.2.5   - Fixed two quota handling bugs
        1.2.6   - Fixed volume resize and jsonrpc code style bugs
        1.2.7   - Add quobyte_export_path option
    """

    DRIVER_VERSION = '1.2.7'

    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 _fetch_existing_access(self, context, share):
        volume_uuid = self._resolve_volume_name(share['name'],
                                                share['project_id'])
        result = self.rpc.call('getConfiguration', {})
        if result is None:
            raise exception.QBException(
                "Could not retrieve Quobyte configuration data!")
        tenant_configs = result['tenant_configuration']
        qb_access_list = []
        for tc in tenant_configs:
            for va in tc['volume_access']:
                if va['volume_uuid'] == volume_uuid:
                    a_level = constants.ACCESS_LEVEL_RW
                    if va['read_only']:
                        a_level = constants.ACCESS_LEVEL_RO
                    qb_access_list.append({
                        'access_to': va['restrict_to_network'],
                        'access_level': a_level,
                        'access_type': 'ip'
                    })
        return qb_access_list

    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("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,
            reserved_snapshot_percentage=(
                self.configuration.reserved_share_from_snapshot_percentage
                or 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_physical_capacity'])
        used = float(result['total_physical_usage'])
        LOG.info('Read capacity of %(cap)s bytes and '
                 'usage of %(use)s bytes from backend. ',
                 {'cap': total, 'use': used})
        free = total - used
        if free < 0:
            free = 0  # no space available
        free_replicated = free / self._get_qb_replication_factor()
        # floor numbers to nine digits (bytes)
        total = math.floor((total / units.Gi) * units.G) / units.G
        free = math.floor((free_replicated / units.Gi) * units.G) / units.G

        return total, free

    def _get_qb_replication_factor(self):
        result = self.rpc.call('getEffectiveVolumeConfiguration',
                               {'configuration_name': self.
                                configuration.quobyte_volume_configuration})
        return int(result['configuration']['volume_metadata_configuration']
                   ['replication_factor'])

    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 _resize_share(self, share, new_size):
        newsize_bytes = new_size * units.Gi
        self.rpc.call('setQuota', {"quotas": [
            {"consumer":
                [{"type": "VOLUME",
                  "identifier": self._resolve_volume_name(share["name"],
                                                          share['project_id']),
                  "tenant_id": share["project_id"]}],
             "limits": [{"type": "LOGICAL_DISK_SPACE",
                        "value": newsize_bytes}]}