#    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 oslo_concurrency import processutils as putils
from oslo_log import log as logging

from cinder import exception
from cinder.i18n import _
from cinder import utils
from cinder.volume.targets import iscsi
from cinder.volume import utils as vutils

LOG = logging.getLogger(__name__)


class SCSTAdm(iscsi.ISCSITarget):

    def __init__(self, *args, **kwargs):
        super(SCSTAdm, self).__init__(*args, **kwargs)
        self.volumes_dir = self.configuration.safe_get('volumes_dir')
        self.iscsi_target_prefix = self.configuration.safe_get(
            'target_prefix')
        self.target_name = self.configuration.safe_get('scst_target_iqn_name')
        self.target_driver = self.configuration.safe_get('scst_target_driver')
        self.chap_username = self.configuration.safe_get('chap_username')
        self.chap_password = self.configuration.safe_get('chap_password')
        self.initiator_iqn = None
        self.remove_initiator_iqn = None

    def scst_execute(self, *args):
        return utils.execute('scstadmin', *args, run_as_root=True)

    def validate_connector(self, connector):
        # iSCSI drivers require the initiator information
        if 'initiator' not in connector:
            err_msg = _('The volume driver requires the iSCSI initiator '
                        'name in the connector.')
            LOG.error(err_msg)
            raise exception.VolumeBackendAPIException(data=err_msg)
        self.initiator_iqn = connector['initiator']

    def terminate_connection(self, volume, connector, **kwargs):
        self.remove_initiator_iqn = connector['initiator']

    def _get_target(self, iqn):
        (out, _err) = self.scst_execute('-list_target')
        if iqn in out:
            return self._target_attribute(iqn)
        return None

    def _target_attribute(self, iqn):
        (out, _err) = self.scst_execute('-list_tgt_attr', iqn,
                                        '-driver', self.target_driver)
        lines = out.split('\n')
        for line in lines:
            if "rel_tgt_id" in line:
                parsed = line.split()
                return parsed[1]

    def _get_group(self):
        scst_group = "%s%s" % (self.initiator_iqn, self.target_name)
        (out, _err) = self.scst_execute('-list_group')
        if scst_group in out:
            return out
        return None

    def _get_luns_info(self):
        scst_group = "%s%s" % (self.initiator_iqn, self.target_name)
        (out, _err) = self.scst_execute('-list_group', scst_group,
                                        '-driver', self.target_driver,
                                        '-target', self.target_name)

        first = "Assigned LUNs:"
        last = "Assigned Initiators:"
        start = out.index(first) + len(first)
        end = out.index(last, start)
        out = out[start:end]

        luns = []
        for line in out.strip().split("\n")[2:]:
            luns.append(int(line.strip().split(" ")[0]))
        luns = sorted(set(luns))
        return luns

    def _get_target_and_lun(self, context, volume):
        iscsi_target = 0
        if not self.target_name or not self._get_group():
            lun = 1
            return iscsi_target, lun

        luns = self._get_luns_info()
        if (not luns) or (luns[0] != 1):
            lun = 1
            return iscsi_target, lun
        else:
            for lun in luns:
                if (luns[-1] == lun) or (luns[lun - 1] + 1 != luns[lun]):
                    return iscsi_target, (lun + 1)

    def create_iscsi_target(self, name, vol_id, tid, lun, path,
                            chap_auth=None):
        scst_group = "%s%s" % (self.initiator_iqn, self.target_name)
        vol_name = path.split("/")[3]
        try:
            (out, _err) = self.scst_execute('-noprompt',
                                            '-set_drv_attr',
                                            self.target_driver,
                                            '-attributes',
                                            'enabled=1')
            LOG.debug('StdOut from set driver attribute: %s', out)
        except putils.ProcessExecutionError as e:
            LOG.error("Failed to set attribute for enable target driver %s", e)
            raise exception.ISCSITargetHelperCommandFailed(
                error_message="Failed to enable SCST Target driver.")

        if self._get_target(name) is None:
            try:
                (out, _err) = self.scst_execute('-add_target', name,
                                                '-driver', self.target_driver)
                LOG.debug("StdOut from scstadmin create target: %s", out)
            except putils.ProcessExecutionError as e:
                LOG.error("Failed to create iscsi target for volume "
                          "id:%(vol_id)s: %(e)s", {'vol_id': name, 'e': e})
                raise exception.ISCSITargetCreateFailed(volume_id=vol_name)
            try:
                (out, _err) = self.scst_execute('-enable_target', name,
                                                '-driver', self.target_driver)
                LOG.debug("StdOut from scstadmin enable target: %s", out)
            except putils.ProcessExecutionError as e:
                LOG.error("Failed to set 'enable' attribute for "
                          "SCST target %s", e)
                raise exception.ISCSITargetHelperCommandFailed(
                    error_mesage="Failed to enable SCST Target.")
            if chap_auth and self.target_name:
                try:
                    chap_string = self._iscsi_authentication('IncomingUser=',
                                                             *chap_auth)
                    (out, _err) = self.scst_execute('-noprompt',
                                                    '-set_tgt_attr', name,
                                                    '-driver',
                                                    self.target_driver,
                                                    '-attributes',
                                                    chap_string)
                    LOG.debug("StdOut from scstadmin set target attribute:"
                              " %s.", out)
                except putils.ProcessExecutionError:
                    msg = _("Failed to set attribute 'Incoming user' for "
                            "SCST target.")
                    LOG.exception(msg)
                    raise exception.ISCSITargetHelperCommandFailed(
                        error_mesage=msg)

        if self.target_name:
            if self._get_group() is None:
                try:
                    (out, _err) = self.scst_execute('-add_group', scst_group,
                                                    '-driver',
                                                    self.target_driver,
                                                    '-target', name)
                    LOG.debug("StdOut from scstadmin create group: %s", out)
                except putils.ProcessExecutionError as e:
                    LOG.error("Failed to create group to SCST target %s", e)
                    raise exception.ISCSITargetHelperCommandFailed(
                        error_message="Failed to create group to SCST target.")
            try:
                (out, _err) = self.scst_execute('-add_init',
                                                self.initiator_iqn,
                                                '-driver', self.target_driver,
                                                '-target', name,
                                                '-group', scst_group)
                LOG.debug("StdOut from scstadmin add initiator: %s", out)
            except putils.ProcessExecutionError as e:
                LOG.error("Failed to add initiator to group "
                          " for SCST target %s", e)
                raise exception.ISCSITargetHelperCommandFailed(
                    error_message="Failed to add Initiator to group for "
                                  "SCST target.")

        tid = self._get_target(name)
        if self.target_name is None:
            disk_id = "disk%s" % tid
        else:
            disk_id = "%s%s" % (lun, vol_id.split('-')[-1])

        try:
            self.scst_execute('-open_dev', disk_id,
                              '-handler', 'vdisk_fileio',
                              '-attributes', 'filename=%s' % path)
        except putils.ProcessExecutionError as e:
            LOG.error("Failed to add device to handler %s", e)
            raise exception.ISCSITargetHelperCommandFailed(
                error_message="Failed to add device to SCST handler.")

        try:
            if self.target_name:
                self.scst_execute('-add_lun', lun,
                                  '-driver', self.target_driver,
                                  '-target', name,
                                  '-device', disk_id,
                                  '-group', scst_group)
            else:
                self.scst_execute('-add_lun', lun,
                                  '-driver', self.target_driver,
                                  '-target', name,
                                  '-device', disk_id)
        except putils.ProcessExecutionError as e:
            LOG.error("Failed to add lun to SCST target "
                      "id:%(vol_id)s: %(e)s", {'vol_id': name, 'e': e})
            raise exception.ISCSITargetHelperCommandFailed(
                error_message="Failed to add LUN to SCST Target for "
                              "volume " + vol_name)

            # SCST uses /etc/scst.conf as the default configuration when it
            # starts
        try:
            self.scst_execute('-write_config', '/etc/scst.conf')
        except putils.ProcessExecutionError as e:
            LOG.error("Failed to write in /etc/scst.conf.")
            raise exception.ISCSITargetHelperCommandFailed(
                error_message="Failed to write in /etc/scst.conf.")

        return tid

    def _iscsi_location(self, ip, target, iqn, lun=None):
        return "%s:%s,%s %s %s" % (ip, self.configuration.target_port,
                                   target, iqn, lun)

    def _get_iscsi_name(self, volume):
        if self.target_name is None:
            return "%s%s" % (self.configuration.target_prefix,
                             volume['name'])
        else:
            return self.target_name

    def _get_iscsi_target(self, context, vol_id):
        # FIXME(jdg): Need to implement abc method
        pass

    def _get_target_chap_auth(self, context, volume):
        # FIXME(jdg): Need to implement abc method

        iscsi_name = self._get_iscsi_name(volume)

        if self._get_target(iscsi_name) is None:
            return None
        (out, _err) = self.scst_execute('-list_tgt_attr', iscsi_name,
                                        '-driver', self.target_driver)
        first = "KEY"
        last = "Dynamic attributes"
        start = out.index(first) + len(first)
        end = out.index(last, start)
        out = out[start:end]
        out = out.split("\n")[2]
        if "IncomingUser" in out:
            out = out.split(" ")
            out = [a for a in out if a != ""]
            return (out[1], out[2])
        else:
            return None

    def ensure_export(self, context, volume, volume_path):
        iscsi_target, lun = self._get_target_and_lun(context, volume)
        iscsi_name = self._get_iscsi_name(volume)

        if self.chap_username and self.chap_password:
            chap_auth = (self.chap_username, self.chap_password)
        else:
            chap_auth = self._get_target_chap_auth(context, volume)

        self.create_iscsi_target(iscsi_name, volume['id'], iscsi_target,
                                 lun, volume_path, chap_auth)

    def create_export(self, context, volume, volume_path):
        """Creates an export for a logical volume."""
        iscsi_target, lun = self._get_target_and_lun(context, volume)
        iscsi_name = self._get_iscsi_name(volume)

        if self.chap_username and self.chap_password:
            chap_auth = (self.chap_username, self.chap_password)
        else:
            chap_auth = self._get_target_chap_auth(context, volume)
            if not chap_auth:
                chap_auth = (vutils.generate_username(),
                             vutils.generate_password())
        tid = self.create_iscsi_target(iscsi_name, volume['id'], iscsi_target,
                                       lun, volume_path, chap_auth)

        data = {}
        data['location'] = self._iscsi_location(
            self.configuration.target_ip_address, tid, iscsi_name, lun)
        LOG.debug('Set provider_location to: %s', data['location'])
        data['auth'] = self._iscsi_authentication(
            'CHAP', *chap_auth)
        return data

    def remove_export(self, context, volume):
        try:
            location = volume['provider_location'].split(' ')
            iqn = location[1]
            iscsi_target = self._get_target(iqn)
            self.show_target(iscsi_target, iqn)

        except Exception:
            LOG.error("Skipping remove_export. No iscsi_target is"
                      "presently exported for volume: %s", volume['id'])
            return
        vol = self.db.volume_get(context, volume['id'])
        lun = "".join(vol['provider_location'].split(" ")[-1:])

        self.remove_iscsi_target(iscsi_target, lun,
                                 volume['id'], volume['name'])

    def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
        disk_id = "%s%s" % (lun, vol_id.split('-')[-1])
        vol_uuid_file = vol_name
        if self.target_name is None:
            iqn = '%s%s' % (self.iscsi_target_prefix, vol_uuid_file)
        else:
            iqn = self.target_name

        if self.target_name is None:
            try:
                self.scst_execute('-noprompt',
                                  '-rem_target', iqn,
                                  '-driver', 'iscsi')
            except putils.ProcessExecutionError as e:
                LOG.error("Failed to remove iscsi target for volume "
                          "id:%(vol_id)s: %(e)s", {'vol_id': vol_id, 'e': e})
                raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
            try:
                self.scst_execute('-noprompt',
                                  '-close_dev', "disk%s" % tid,
                                  '-handler', 'vdisk_fileio')
            except putils.ProcessExecutionError as e:
                LOG.error("Failed to close disk device %s", e)
                raise exception.ISCSITargetHelperCommandFailed(
                    error_message="Failed to close disk device for "
                                  "SCST handler.")

            if self._get_target(iqn):
                try:
                    self.scst_execute('-noprompt',
                                      '-rem_target', iqn,
                                      '-driver', self.target_driver)
                except putils.ProcessExecutionError as e:
                    LOG.error("Failed to remove iscsi target for "
                              "volume id:%(vol_id)s: %(e)s",
                              {'vol_id': vol_id, 'e': e})
                    raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
        else:
            if not int(lun) in self._get_luns_info():
                raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
            try:
                scst_group = "%s%s" % (self.remove_initiator_iqn,
                                       self.target_name)
                self.scst_execute('-noprompt', '-rem_lun', lun,
                                  '-driver', self.target_driver,
                                  '-target', iqn, '-group',
                                  scst_group)
            except putils.ProcessExecutionError as e:
                LOG.error("Failed to remove LUN %s", e)
                raise exception.ISCSITargetHelperCommandFailed(
                    error_message="Failed to remove LUN for SCST Target.")

            try:
                self.scst_execute('-noprompt',
                                  '-close_dev', disk_id,
                                  '-handler', 'vdisk_fileio')
            except putils.ProcessExecutionError as e:
                LOG.error("Failed to close disk device %s", e)
                raise exception.ISCSITargetHelperCommandFailed(
                    error_message="Failed to close disk device for "
                                  "SCST handler.")

        self.scst_execute('-write_config', '/etc/scst.conf')

    def show_target(self, tid, iqn):
        if iqn is None:
            raise exception.InvalidParameterValue(
                err=_('valid iqn needed for show_target'))

        tid = self._get_target(iqn)
        if tid is None:
            raise exception.ISCSITargetHelperCommandFailed(
                error_message="Target not found")
