#  Copyright (c) 2014-2019 LINBIT HA Solutions GmbH
#  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.

"""This driver connects Cinder to an installed LINSTOR instance.

See https://docs.linbit.com/docs/users-guide-9.0/#ch-openstack-linstor
for more details.
"""

import socket
import uuid

from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
from oslo_utils import units

from cinder.common import constants
from cinder import exception
from cinder.i18n import _
from cinder.image import image_utils
from cinder import interface
from cinder.volume import configuration
from cinder.volume import driver
from cinder.volume import volume_utils

try:
    import linstor
    lin_drv = linstor.Linstor
except ImportError:
    linstor = None
    lin_drv = None

# To override these values, update cinder.conf in /etc/cinder/
linstor_opts = [
    cfg.StrOpt('linstor_default_volume_group_name',
               default='drbd-vg',
               help='Default Volume Group name for LINSTOR. '
                    'Not Cinder Volume.'),

    cfg.StrOpt('linstor_default_uri',
               default='linstor://localhost',
               help='Default storage URI for LINSTOR.'),

    cfg.StrOpt('linstor_default_storage_pool_name',
               default='DfltStorPool',
               help='Default Storage Pool name for LINSTOR.'),

    cfg.FloatOpt('linstor_volume_downsize_factor',
                 default=4096,
                 help='Default volume downscale size in KiB = 4 MiB.'),

    cfg.IntOpt('linstor_default_blocksize',
               default=4096,
               help='Default Block size for Image restoration. '
                    'When using iSCSI transport, this option '
                    'specifies the block size.'),

    cfg.IntOpt('linstor_autoplace_count',
               default=0,
               help='Autoplace replication count on volume deployment. '
                    '0 = Full cluster replication without autoplace, '
                    '1 = Single node deployment without replication, '
                    '2 or greater = Replicated deployment with autoplace.'),

    cfg.BoolOpt('linstor_controller_diskless',
                default=True,
                help='True means Cinder node is a diskless LINSTOR node.')
]

LOG = logging.getLogger(__name__)

CONF = cfg.CONF
CONF.register_opts(linstor_opts, group=configuration.SHARED_CONF_GROUP)

CINDER_UNKNOWN = 'unknown'
DM_VN_PREFIX = 'CV_'
DM_SN_PREFIX = 'SN_'
DISKLESS = 'DISKLESS'
LVM = 'LVM'
LVM_THIN = 'LVM_THIN'
ZFS = 'ZFS'
ZFS_THIN = 'ZFS_THIN'


class LinstorBaseDriver(driver.VolumeDriver):
    """Cinder driver that uses LINSTOR for storage.

    Version history:

    .. code-block:: none

        1.0.0 - Initial driver
        1.0.1 - Added support for LINSTOR 0.9.12
    """

    VERSION = '1.0.1'

    # ThirdPartySystems wiki page
    CI_WIKI_NAME = 'LINBIT_LINSTOR_CI'

    def __init__(self, *args, **kwargs):
        super(LinstorBaseDriver, self).__init__(*args, **kwargs)
        LOG.debug('START: Base Init Linstor')

        self.configuration.append_config_values(linstor_opts)
        self.default_pool = self.configuration.safe_get(
            'linstor_default_storage_pool_name')
        self.default_uri = self.configuration.safe_get(
            'linstor_default_uri')
        self.default_downsize_factor = self.configuration.safe_get(
            'linstor_volume_downsize_factor')
        self.default_vg_name = self.configuration.safe_get(
            'linstor_default_volume_group_name')
        self.default_blocksize = self.configuration.safe_get(
            'linstor_default_blocksize')
        self.diskless = self.configuration.safe_get(
            'linstor_controller_diskless')
        self.ap_count = self.configuration.safe_get(
            'linstor_autoplace_count')
        self.default_backend_name = self.configuration.safe_get(
            'volume_backend_name')
        self.host_name = socket.gethostname()

    @staticmethod
    def get_driver_options():
        return linstor_opts

    def _ping(self):
        with lin_drv(self.default_uri) as lin:
            return lin.ping()

    def _clean_uuid(self):
        """Returns a UUID string, WITHOUT braces."""
        # Some uuid library versions put braces around the result.
        # We don't want them, just a plain [0-9a-f-]+ string.
        uuid_str = str(uuid.uuid4())
        uuid_str = uuid_str.replace("{", "")
        uuid_str = uuid_str.replace("}", "")
        return uuid_str

    # LINSTOR works in kiB units; Cinder uses GiB.
    def _vol_size_to_linstor(self, size):
        return int(size * units.Mi - self.default_downsize_factor)

    def _vol_size_to_cinder(self, size):
        return int(size / units.Mi)

    def _is_clean_volume_name(self, name, prefix):
        try:
            if (name.startswith(CONF.volume_name_template % "") and
                    uuid.UUID(name[7:]) is not None):
                return prefix + name[7:]
        except ValueError:
            return None

        try:
            if uuid.UUID(name) is not None:
                return prefix + name
        except ValueError:
            return None

    def _snapshot_name_from_cinder_snapshot(self, snapshot):
        sn_name = self._is_clean_volume_name(snapshot['id'], DM_SN_PREFIX)
        return sn_name

    def _cinder_volume_name_from_drbd_resource(self, rsc_name):
        cinder_volume_name = rsc_name.split(DM_VN_PREFIX)[1]
        return cinder_volume_name

    def _drbd_resource_name_from_cinder_snapshot(self, snapshot):
        drbd_resource_name = '{}{}'.format(DM_VN_PREFIX,
                                           snapshot['volume_id'])
        return drbd_resource_name

    def _drbd_resource_name_from_cinder_volume(self, volume):
        drbd_resource_name = '{}{}'.format(DM_VN_PREFIX, volume['id'])
        return drbd_resource_name

    def _get_api_resource_list(self):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()
            api_reply = lin.resource_list()[0].__dict__['_rest_data']
            return api_reply

    def _get_api_resource_dfn_list(self):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()
            api_reply = lin.resource_dfn_list()[0].__dict__['_rest_data']
            return api_reply

    def _get_api_node_list(self):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()
            api_reply = lin.node_list()[0].__dict__['_rest_data']
            return api_reply

    def _get_api_storage_pool_dfn_list(self):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()
            api_reply = lin.storage_pool_dfn_list()[0].__dict__['_rest_data']
            return api_reply

    def _get_api_storage_pool_list(self):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()
            api_reply = lin.storage_pool_list()[0].__dict__['_rest_data']
            return api_reply

    def _get_api_volume_extend(self, rsc_target_name, new_size):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            vol_reply = lin.volume_dfn_modify(
                rsc_name=rsc_target_name,
                volume_nr=0,
                size=self._vol_size_to_linstor(new_size))
            return vol_reply

    def _api_snapshot_create(self, drbd_rsc_name, snapshot_name):
        lin = linstor.Resource(drbd_rsc_name, uri=self.default_uri)
        snap_reply = lin.snapshot_create(snapshot_name)
        return snap_reply

    def _api_snapshot_delete(self, drbd_rsc_name, snapshot_name):
        lin = linstor.Resource(drbd_rsc_name, uri=self.default_uri)
        snap_reply = lin.snapshot_delete(snapshot_name)
        return snap_reply

    def _api_rsc_dfn_delete(self, drbd_rsc_name):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            snap_reply = lin.resource_dfn_delete(drbd_rsc_name)
            return snap_reply

    def _api_storage_pool_create(self,
                                 node_name,
                                 storage_pool_name,
                                 storage_driver,
                                 driver_pool_name):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            sp_reply = lin.storage_pool_create(
                node_name=node_name,
                storage_pool_name=storage_pool_name,
                storage_driver=storage_driver,
                driver_pool_name=driver_pool_name)
            return sp_reply

    def _api_rsc_dfn_create(self, rsc_name):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            rsc_dfn_reply = lin.resource_dfn_create(rsc_name)
            return rsc_dfn_reply

    def _api_volume_dfn_create(self, rsc_name, size):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            vol_dfn_reply = lin.volume_dfn_create(
                rsc_name=rsc_name,
                storage_pool=self.default_pool,
                size=size)
            return vol_dfn_reply

    def _api_rsc_create(self, rsc_name, node_name, diskless=False):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            if diskless:
                storage_pool = None
            else:
                storage_pool = self.default_pool

            new_rsc = linstor.ResourceData(rsc_name=rsc_name,
                                           node_name=node_name,
                                           storage_pool=storage_pool,
                                           diskless=diskless)

            rsc_reply = lin.resource_create([new_rsc], async_msg=False)
            return rsc_reply

    def _api_rsc_autoplace(self, rsc_name):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            new_rsc = linstor.Resource(name=rsc_name, uri=self.default_uri)
            new_rsc.placement.redundancy = self.ap_count
            new_rsc.placement.storage_pool = self.default_pool
            rsc_reply = new_rsc.autoplace()

            return rsc_reply

    def _api_rsc_delete(self, rsc_name, node_name):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            rsc_reply = lin.resource_delete(node_name=node_name,
                                            rsc_name=rsc_name)
            return rsc_reply

    def _api_rsc_auto_delete(self, rsc_name):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            rsc = linstor.Resource(str(rsc_name), self.default_uri)
            return rsc.delete()

    def _api_rsc_is_diskless(self, rsc_name):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            rsc = linstor.Resource(str(rsc_name))
            return rsc.is_diskless(self.host_name)

    def _api_rsc_size(self, rsc_name):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            rsc = linstor.Resource(str(rsc_name))
            if len(rsc.volumes):
                if "size" in rsc.volumes:
                    return rsc.volumes[0].size
                else:
                    return 0
            else:
                return 0

    def _api_volume_dfn_delete(self, rsc_name, volume_nr):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            rsc_reply = lin.volume_dfn_delete(rsc_name=rsc_name,
                                              volume_nr=volume_nr)
            return rsc_reply

    def _api_snapshot_volume_dfn_restore(self,
                                         src_rsc_name,
                                         src_snap_name,
                                         new_vol_name):
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            vol_reply = lin.snapshot_volume_definition_restore(
                from_resource=src_rsc_name,
                from_snapshot=src_snap_name,
                to_resource=new_vol_name)
            return vol_reply

    def _api_snapshot_resource_restore(self,
                                       src_rsc_name,
                                       src_snap_name,
                                       new_vol_name):

        lin = linstor.Resource(src_rsc_name, uri=self.default_uri)
        new_rsc = lin.restore_from_snapshot(src_snap_name, new_vol_name)

        # Adds an aux/property KV for synchronous return from snapshot restore
        with lin_drv(self.default_uri) as lin:
            if not lin.connected:
                lin.connect()

            aux_prop = {}
            aux_prop["Aux/restore"] = "done"
            lin.volume_dfn_modify(
                rsc_name=new_vol_name,
                volume_nr=0,
                set_properties=aux_prop)

        if new_rsc.name == new_vol_name:
            return True
        return False

    def _get_rsc_path(self, rsc_name):
        rsc_list_reply = self._get_api_resource_list()

        if rsc_list_reply:
            for rsc in rsc_list_reply:
                if (rsc["name"] == rsc_name and
                        rsc["node_name"] == self.host_name):
                    for volume in rsc["volumes"]:
                        if volume["volume_number"] == 0:
                            return volume["device_path"]

    def _get_local_path(self, volume):
        try:
            full_rsc_name = (
                self._drbd_resource_name_from_cinder_volume(volume))

            return self._get_rsc_path(full_rsc_name)

        except Exception:
            message = _('Local Volume not found.')
            raise exception.VolumeBackendAPIException(data=message)

    def _get_spd(self):
        # Storage Pool Definition List
        spd_list_reply = self._get_api_storage_pool_dfn_list()
        spd_list = []

        if spd_list_reply:
            for spd in spd_list_reply:
                spd_list.append(spd["storage_pool_name"])

        return spd_list

    def _get_storage_pool(self):
        # Fetch Storage Pool List
        sp_list_reply = self._get_api_storage_pool_list()

        # Separate the diskless nodes
        sp_diskless_list = []
        sp_list = []

        if sp_list_reply:
            for node in sp_list_reply:
                if node["storage_pool_name"] == self.default_pool:
                    sp_node = {}
                    sp_node["node_name"] = node["node_name"]
                    sp_node["sp_uuid"] = node["uuid"]
                    sp_node["sp_name"] = node["storage_pool_name"]

                    if node["provider_kind"] == DISKLESS:
                        diskless = True
                        sp_node["sp_free"] = -1.0
                        sp_node["sp_cap"] = -1.0
                        sp_node["sp_allocated"] = 0.0
                    else:
                        diskless = False
                        if "free_capacity" in node:
                            temp = float(node["free_capacity"]) / units.Mi
                            sp_node["sp_free"] = round(temp)
                            temp = float(node["total_capacity"]) / units.Mi
                            sp_node["sp_cap"] = round(temp)

                    drivers = [LVM, LVM_THIN, ZFS, ZFS_THIN, DISKLESS]

                    # Driver selection
                    if node["provider_kind"] in drivers:
                        sp_node['driver_name'] = node["provider_kind"]
                    else:
                        sp_node['driver_name'] = str(node["provider_kind"])

                    if diskless:
                        sp_diskless_list.append(sp_node)
                    else:
                        sp_list.append(sp_node)

            # Add the diskless nodes to the end of the list
            if sp_diskless_list:
                sp_list.extend(sp_diskless_list)

        return sp_list

    def _get_volume_stats(self):

        data = {}
        data["volume_backend_name"] = self.default_backend_name
        data["vendor_name"] = "LINBIT"
        data["driver_version"] = self.VERSION
        data["pools"] = []

        sp_data = self._get_storage_pool()
        rd_list = self._get_resource_definitions()

        # Total volumes and capacity
        num_vols = 0
        for rd in rd_list:
            num_vols += 1

        # allocated_sizes_gb = []
        free_gb = []
        total_gb = []
        thin_enabled = False

        # Total & Free capacity for Local Node
        single_pool = {}
        for sp in sp_data:
            if "Diskless" not in sp["driver_name"]:
                thin_backends = [LVM_THIN, ZFS_THIN]
                if sp["driver_name"] in thin_backends:
                    thin_enabled = True
                if "sp_cap" in sp:
                    if sp["sp_cap"] >= 0.0:
                        total_gb.append(sp["sp_cap"])
                if "sp_free" in sp:
                    if sp["sp_free"] >= 0.0:
                        free_gb.append(sp["sp_free"])

        # Allocated capacity
        sp_allocated_size_gb = 0.0
        local_resources = []

        reply = self._get_api_resource_list()

        if reply:
            for rsc in reply:
                if rsc["node_name"] == self.host_name:
                    local_resources.append(rsc["name"])

            for rsc_name in local_resources:
                if not self._api_rsc_is_diskless(rsc_name):
                    rsc_size = self._api_rsc_size(rsc_name)
                    sp_allocated_size_gb += round(
                        int(rsc_size) / units.Gi, 2)

        single_pool["pool_name"] = data["volume_backend_name"]
        single_pool["free_capacity_gb"] = min(free_gb) if free_gb else 0
        single_pool["total_capacity_gb"] = min(total_gb) if total_gb else 0
        single_pool["provisioned_capacity_gb"] = sp_allocated_size_gb
        single_pool["reserved_percentage"] = (
            self.configuration.reserved_percentage)
        single_pool["thin_provisioning_support"] = thin_enabled
        single_pool["thick_provisioning_support"] = not thin_enabled
        single_pool["max_over_subscription_ratio"] = (
            self.configuration.max_over_subscription_ratio)
        single_pool["location_info"] = self.default_uri
        single_pool["total_volumes"] = num_vols
        single_pool["filter_function"] = self.get_filter_function()
        single_pool["goodness_function"] = self.get_goodness_function()
        single_pool["QoS_support"] = False
        single_pool["multiattach"] = False
        single_pool["backend_state"] = "up"

        data["pools"].append(single_pool)

        return data

    def _get_resource_definitions(self):

        rd_list_reply = self._get_api_resource_dfn_list()
        rd_list = []

        if rd_list_reply:
            for node in rd_list_reply:
                # Count only Cinder volumes
                if DM_VN_PREFIX in node['name']:
                    rd_node = {}
                    rd_node["rd_uuid"] = node['uuid']
                    rd_node["rd_name"] = node['name']
                    rd_list.append(rd_node)

        return rd_list

    def _get_snapshot_nodes(self, resource):
        """Returns all available resource nodes for snapshot.

        However, it excludes diskless nodes.
        """
        rsc_list_reply = self._get_api_resource_list()
        snap_list = []

        if rsc_list_reply:
            for rsc in rsc_list_reply:
                if rsc["name"] != resource:
                    continue

                # Diskless nodes are not available for snapshots
                diskless = False
                if "flags" in rsc:
                    if 'DISKLESS' in rsc["flags"]:
                        diskless = True
                if not diskless:
                    snap_list.append(rsc["node_name"])

        return snap_list

    def _get_diskless_nodes(self, resource):
        # Returns diskless nodes given a resource
        rsc_list_reply = self._get_api_resource_list()
        diskless_list = []

        if rsc_list_reply:
            for rsc in rsc_list_reply:
                if rsc["name"] != resource:
                    continue

                if "flags" in rsc:
                    if DISKLESS in rsc["flags"]:
                        diskless_list.append(rsc["node_name"])

        return diskless_list

    def _get_linstor_nodes(self):
        # Returns all available LINSTOR nodes
        node_list_reply = self._get_api_node_list()
        node_list = []

        if node_list_reply:
            for node in node_list_reply:
                node_list.append(node["name"])

        return node_list

    def _get_nodes(self):
        # Returns all LINSTOR nodes in a dict list
        node_list_reply = self._get_api_node_list()
        node_list = []

        if node_list_reply:
            for node in node_list_reply:
                node_item = {}
                node_item["node_name"] = node["name"]
                node_item["node_address"] = (
                    node["net_interfaces"][0]["address"])
                node_list.append(node_item)

        return node_list

    def _check_api_reply(self, api_response, noerror_only=False):
        if noerror_only:
            # Checks if none of the replies has an error
            return lin_drv.all_api_responses_no_error(api_response)
        else:
            # Check if all replies are success
            return lin_drv.all_api_responses_success(api_response)

    def _copy_vol_to_image(self, context, image_service, image_meta, rsc_path,
                           volume):

        return volume_utils.upload_volume(context,
                                          image_service,
                                          image_meta,
                                          rsc_path,
                                          volume)

    #
    # Snapshot
    #
    def create_snapshot(self, snapshot):
        snap_name = self._snapshot_name_from_cinder_snapshot(snapshot)
        rsc_name = self._drbd_resource_name_from_cinder_snapshot(snapshot)

        snap_reply = self._api_snapshot_create(drbd_rsc_name=rsc_name,
                                               snapshot_name=snap_name)

        if not snap_reply:
            msg = 'ERROR creating a LINSTOR snapshot {}'.format(snap_name)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(msg)

    def delete_snapshot(self, snapshot):
        snapshot_name = self._snapshot_name_from_cinder_snapshot(snapshot)
        rsc_name = self._drbd_resource_name_from_cinder_snapshot(snapshot)

        snap_reply = self._api_snapshot_delete(rsc_name, snapshot_name)

        if not snap_reply:
            msg = 'ERROR deleting a LINSTOR snapshot {}'.format(snapshot_name)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(msg)

        # Delete RD if no other RSC are found
        if not self._get_snapshot_nodes(rsc_name):
            self._api_rsc_dfn_delete(rsc_name)

    def create_volume_from_snapshot(self, volume, snapshot):
        src_rsc_name = self._drbd_resource_name_from_cinder_snapshot(snapshot)
        src_snap_name = self._snapshot_name_from_cinder_snapshot(snapshot)
        new_vol_name = self._drbd_resource_name_from_cinder_volume(volume)

        # If no autoplace, manually build a cluster list
        if self.ap_count == 0:
            diskless_nodes = []
            nodes = []
            for node in self._get_storage_pool():

                if DISKLESS in node['driver_name']:
                    diskless_nodes.append(node['node_name'])
                    continue

                # Filter out controller node if it is diskless
                if self.diskless and node['node_name'] == self.host_name:
                    continue
                else:
                    nodes.append(node['node_name'])

        reply = self._api_snapshot_resource_restore(src_rsc_name,
                                                    src_snap_name,
                                                    new_vol_name)
        if not reply:
            msg = _('Error on restoring a LINSTOR volume')
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        # Manually add the controller node as a resource if diskless
        if self.diskless:
            reply = self._api_rsc_create(rsc_name=new_vol_name,
                                         node_name=self.host_name,
                                         diskless=self.diskless)

        # Add any other diskless nodes only if not autoplaced
        if self.ap_count == 0 and diskless_nodes:
            for node in diskless_nodes:
                self._api_rsc_create(rsc_name=new_vol_name,
                                     node_name=node,
                                     diskless=True)

        # Upsize if larger volume than original snapshot
        src_rsc_size = int(snapshot['volume_size'])
        new_vol_size = int(volume['size'])

        if new_vol_size > src_rsc_size:

            upsize_target_name = self._is_clean_volume_name(volume['id'],
                                                            DM_VN_PREFIX)
            reply = self._get_api_volume_extend(
                rsc_target_name=upsize_target_name,
                new_size=new_vol_size)

            if not self._check_api_reply(reply, noerror_only=True):
                # Delete failed volume
                failed_volume = {}
                failed_volume['id'] = volume['id']
                self.delete_volume(failed_volume)

                msg = _('Error on extending LINSTOR resource size')
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

    def create_volume(self, volume):

        # Check for Storage Pool List
        sp_data = self._get_storage_pool()
        rsc_size = volume['size']

        # No existing Storage Pools found
        if not sp_data:

            # Check for Nodes
            node_list = self._get_nodes()

            if not node_list:
                msg = _('No LINSTOR resource nodes available / configured')
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

            # Create Storage Pool
            spd_list = self._get_spd()

            if spd_list:
                spd_name = spd_list[0]

            for node in node_list:

                node_driver = None
                for sp in sp_data:
                    if sp['node_name'] == node['node_name']:
                        node_driver = sp['driver_name']

                sp_reply = self._api_storage_pool_create(
                    node_name=node['node_name'],
                    storage_pool_name=spd_name,
                    storage_driver=node_driver,
                    driver_pool_name=self.default_vg_name)

                if not self._check_api_reply(sp_reply, noerror_only=True):
                    msg = _('Could not create a LINSTOR storage pool')
                    LOG.error(msg)
                    raise exception.VolumeBackendAPIException(data=msg)

        # Check for RD
        # If Retyping from another volume, use parent/origin uuid
        # as a name source
        if (volume['migration_status'] is not None and
                str(volume['migration_status']).find('success') == -1):
            src_name = str(volume['migration_status']).split(':')[1]
            rsc_name = self._is_clean_volume_name(str(src_name),
                                                  DM_VN_PREFIX)
        else:
            rsc_name = self._is_clean_volume_name(volume['id'],
                                                  DM_VN_PREFIX)

        # Create a New RD
        rsc_dfn_reply = self._api_rsc_dfn_create(rsc_name)

        if not self._check_api_reply(rsc_dfn_reply, noerror_only=True):
            msg = _("Error creating a LINSTOR resource definition")
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        # Create a New VD
        vd_size = self._vol_size_to_linstor(rsc_size)
        vd_reply = self._api_volume_dfn_create(rsc_name=rsc_name,
                                               size=int(vd_size))

        if not self._check_api_reply(vd_reply, noerror_only=True):
            msg = _("Error creating a LINSTOR volume definition")
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        # Create LINSTOR Resources
        ctrl_in_sp = False
        for node in sp_data:
            # Check if controller is in the pool
            if node['node_name'] == self.host_name:
                ctrl_in_sp = True

        # Use autoplace to deploy if set
        if self.ap_count:
            try:
                self._api_rsc_autoplace(rsc_name=rsc_name)

            except Exception:
                msg = _("Error creating autoplaces LINSTOR resource(s)")
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

        # Otherwise deploy across the entire cluster
        else:
            for node in sp_data:
                # Deploy resource on each node
                if DISKLESS in node['driver_name']:
                    diskless = True
                else:
                    diskless = False

                rsc_reply = self._api_rsc_create(rsc_name=rsc_name,
                                                 node_name=node['node_name'],
                                                 diskless=diskless)

                if not self._check_api_reply(rsc_reply, noerror_only=True):
                    msg = _("Error creating a LINSTOR resource")
                    LOG.error(msg)
                    raise exception.VolumeBackendAPIException(data=msg)

        # If the controller is diskless and not in the pool, create a diskless
        # resource on it
        if not ctrl_in_sp and self.diskless:
            rsc_reply = self._api_rsc_create(rsc_name=rsc_name,
                                             node_name=self.host_name,
                                             diskless=True)

            if not self._check_api_reply(rsc_reply, noerror_only=True):
                msg = _("Error creating a LINSTOR controller resource")
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

        return {}

    def delete_volume(self, volume):
        drbd_rsc_name = self._drbd_resource_name_from_cinder_volume(volume)
        rsc_list_reply = self._get_api_resource_list()
        diskful_nodes = self._get_snapshot_nodes(drbd_rsc_name)
        diskless_nodes = self._get_diskless_nodes(drbd_rsc_name)

        # If autoplace was used, use Resource class
        if self.ap_count:

            rsc_reply = self._api_rsc_auto_delete(drbd_rsc_name)
            if not rsc_reply:
                msg = _("Error deleting an autoplaced LINSTOR resource")
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

        # Delete all resources in a cluster manually if not autoplaced
        else:
            if rsc_list_reply:
                # Remove diskless nodes first
                if diskless_nodes:
                    for node in diskless_nodes:
                        rsc_reply = self._api_rsc_delete(
                            node_name=node,
                            rsc_name=drbd_rsc_name)
                        if not self._check_api_reply(rsc_reply,
                                                     noerror_only=True):
                            msg = _("Error deleting a diskless LINSTOR rsc")
                            LOG.error(msg)
                            raise exception.VolumeBackendAPIException(data=msg)

                # Remove diskful nodes
                if diskful_nodes:
                    for node in diskful_nodes:
                        rsc_reply = self._api_rsc_delete(
                            node_name=node,
                            rsc_name=drbd_rsc_name)
                        if not self._check_api_reply(rsc_reply,
                                                     noerror_only=True):
                            msg = _("Error deleting a LINSTOR resource")
                            LOG.error(msg)
                            raise exception.VolumeBackendAPIException(data=msg)

                # Delete VD
                vd_reply = self._api_volume_dfn_delete(drbd_rsc_name, 0)
                if not vd_reply:
                    if not self._check_api_reply(vd_reply):
                        msg = _("Error deleting a LINSTOR volume definition")
                        LOG.error(msg)
                        raise exception.VolumeBackendAPIException(data=msg)

                # Delete RD
                # Will fail if snapshot exists but expected
                self._api_rsc_dfn_delete(drbd_rsc_name)

        return True

    def extend_volume(self, volume, new_size):
        rsc_target_name = self._is_clean_volume_name(volume['id'],
                                                     DM_VN_PREFIX)

        extend_reply = self._get_api_volume_extend(rsc_target_name, new_size)

        if not self._check_api_reply(extend_reply, noerror_only=True):
            msg = _("ERROR extending a LINSTOR volume")
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def create_cloned_volume(self, volume, src_vref):
        temp_id = self._clean_uuid()
        snapshot = {}
        snapshot['id'] = temp_id
        snapshot['volume_id'] = src_vref['id']
        snapshot['volume_size'] = src_vref['size']

        self.create_snapshot(snapshot)

        self.create_volume_from_snapshot(volume, snapshot)

        self.delete_snapshot(snapshot)

    def copy_image_to_volume(self, context, volume, image_service, image_id,
                             disable_sparse=False):
        # self.create_volume(volume) already called by Cinder, and works
        full_rsc_name = self._drbd_resource_name_from_cinder_volume(volume)

        # This creates a LINSTOR volume from the source image
        image_utils.fetch_to_raw(context,
                                 image_service,
                                 image_id,
                                 str(self._get_rsc_path(full_rsc_name)),
                                 self.default_blocksize,
                                 size=volume['size'],
                                 disable_sparse=disable_sparse)
        return {}

    def copy_volume_to_image(self, context, volume, image_service, image_meta):
        full_rsc_name = self._drbd_resource_name_from_cinder_volume(volume)
        rsc_path = str(self._get_rsc_path(full_rsc_name))
        self._copy_vol_to_image(context,
                                image_service,
                                image_meta,
                                rsc_path,
                                volume)
        return {}

    # Not supported currently
    def migrate_volume(self, ctxt, volume, host, thin=False, mirror_count=0):
        return (False, None)

    def check_for_setup_error(self):
        msg = None
        if linstor is None:
            msg = _('Linstor python package not found')

        if msg is not None:
            LOG.error(msg)
            raise exception.VolumeDriverException(message=msg)

    def create_export(self, context, volume, connector):
        pass

    def ensure_export(self, context, volume):
        pass

    def initialize_connection(self, volume, connector, **kwargs):
        pass

    def remove_export(self, context, volume):
        pass

    def terminate_connection(self, volume, connector, **kwargs):
        pass


# Class with iSCSI interface methods
@interface.volumedriver
class LinstorIscsiDriver(LinstorBaseDriver):
    """Cinder iSCSI driver that uses LINSTOR for storage."""

    def __init__(self, *args, **kwargs):
        super(LinstorIscsiDriver, self).__init__(*args, **kwargs)

        # iSCSI target_helper
        if 'h_name' in kwargs:
            self.helper_name = kwargs.get('h_name')
            self.helper_driver = self.helper_name
            self.target_driver = None
        else:
            self.helper_name = self.configuration.safe_get('iscsi_helper')
            self.helper_driver = self.target_mapping[self.helper_name]
            self.target_driver = importutils.import_object(
                self.helper_driver,
                configuration=self.configuration,
                executor=self._execute)

        LOG.info('START: LINSTOR DRBD driver %s', self.helper_name)

    def get_volume_stats(self, refresh=False):
        data = self._get_volume_stats()
        data["storage_protocol"] = constants.ISCSI
        data["pools"][0]["location_info"] = (
            'LinstorIscsiDriver:' + data["pools"][0]["location_info"])

        return data

    def ensure_export(self, context, volume):
        volume_path = self._get_local_path(volume)

        return self.target_driver.ensure_export(
            context,
            volume,
            volume_path)

    def create_export(self, context, volume, connector):
        volume_path = self._get_local_path(volume)

        export_info = self.target_driver.create_export(
            context,
            volume,
            volume_path)

        return {'provider_location': export_info['location'],
                'provider_auth': export_info['auth'], }

    def remove_export(self, context, volume):

        return self.target_driver.remove_export(context, volume)

    def initialize_connection(self, volume, connector, **kwargs):

        return self.target_driver.initialize_connection(volume, connector)

    def validate_connector(self, connector):

        return self.target_driver.validate_connector(connector)

    def terminate_connection(self, volume, connector, **kwargs):

        return self.target_driver.terminate_connection(volume,
                                                       connector,
                                                       **kwargs)


# Class with DRBD transport mode
@interface.volumedriver
class LinstorDrbdDriver(LinstorBaseDriver):
    """Cinder DRBD driver that uses LINSTOR for storage."""

    def __init__(self, *args, **kwargs):
        super(LinstorDrbdDriver, self).__init__(*args, **kwargs)

    def _return_drbd_config(self, volume):
        full_rsc_name = self._drbd_resource_name_from_cinder_volume(volume)
        rsc_path = self._get_rsc_path(full_rsc_name)
        return {
            'driver_volume_type': 'local',
            'data': {
                "device_path": str(rsc_path)
            }
        }

    def _node_in_sp(self, node_name):
        for pool in self._get_storage_pool():
            if pool['node_name'] == node_name:
                return True
        return False

    def get_volume_stats(self, refresh=False):
        data = self._get_volume_stats()
        data["storage_protocol"] = constants.DRBD
        data["pools"][0]["location_info"] = 'LinstorDrbdDriver:{}'.format(
            data["pools"][0]["location_info"])

        return data

    def initialize_connection(self, volume, connector, **kwargs):
        node_name = connector['host']
        if not self._node_in_sp(connector['host']):

            full_rsc_name = self._drbd_resource_name_from_cinder_volume(volume)
            rsc_reply = self._api_rsc_create(rsc_name=full_rsc_name,
                                             node_name=node_name,
                                             diskless=True)
            if not self._check_api_reply(rsc_reply, noerror_only=True):
                msg = _('Error on creating LINSTOR Resource')
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

        return self._return_drbd_config(volume)

    def terminate_connection(self, volume, connector, **kwargs):
        if connector:
            node_name = connector['host']
            if not self._node_in_sp(connector['host']):
                rsc_name = self._drbd_resource_name_from_cinder_volume(volume)
                rsc_reply = self._api_rsc_delete(rsc_name=rsc_name,
                                                 node_name=node_name)
                if not self._check_api_reply(rsc_reply, noerror_only=True):
                    msg = _('Error on deleting LINSTOR Resource')
                    LOG.error(msg)
                    raise exception.VolumeBackendAPIException(data=msg)

    def create_export(self, context, volume, connector):

        return self._return_drbd_config(volume)

    def ensure_export(self, context, volume):

        return self._return_drbd_config(volume)

    def remove_export(self, context, volume):
        pass
