<commit_before># Copyright (C) 2011-2014 Red Hat, Inc.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
#
# Author: <NAME> <agrover at redhat com>
#         <NAME> <<EMAIL>>

import copy

from lsm import (Pool, Volume, System, Capabilities,
                 IStorageAreaNetwork, INfs, FileSystem, FsSnapshot, NfsExport,
                 LsmError, ErrorNumber, uri_parse, md5, VERSION,
                 common_urllib2_error_handler, search_property,
                 AccessGroup)

import urllib2
import json
import time
import urlparse
import socket
import re

DEFAULT_USER = "admin"
DEFAULT_PORT = 18700
PATH = "/targetrpc"

# Current sector size in liblvm
_LVM_SECTOR_SIZE = 512

def handle_errors(method):
    def target_wrapper(*args, **kwargs):
        try:
            return method(*args, **kwargs)
        except TargetdError as te:
            raise LsmError(ErrorNumber.PLUGIN_BUG,
                           "Got error %d from targetd: %s"
                           % (te.errno, te.reason))
        except LsmError:
            raise
        except Exception as e:
            common_urllib2_error_handler(e)

    return target_wrapper


class TargetdError(Exception):
    VOLUME_MASKED = 303
    INVALID_METHOD = 32601
    INVALID_ARGUMENT = 32602
    NAME_CONFLICT = 50
    EXISTS_INITIATOR = 52

    def __init__(self, errno, reason, *args, **kwargs):
        Exception.__init__(self, *args, **kwargs)
        self.errno = int(errno)
        self.reason = reason


class TargetdStorage(IStorageAreaNetwork, INfs):
    _FAKE_AG_PREFIX = 'init.'

    def __init__(self):
        self.uri = None
        self.password = None
        self.tmo = 0
        self.rpc_id = 1
        self.host_with_port = None
        self.scheme = None
        self.url = None
        self.headers = None
        self.system = System("targetd", "targetd storage appliance",
                             System.STATUS_UNKNOWN, '')

    @handle_errors
    def plugin_register(self, uri, password, timeout, flags=0):
        self.uri = uri_parse(uri)
        self.password = password
        self.tmo = timeout
        self._flag_ag_support = True

        user = self.uri.get('username', DEFAULT_USER)
        port = self.uri.get('port', DEFAULT_PORT)

        self.host_with_port = "%s:%s" % (self.uri['host'], port)
        if self.uri['scheme'].lower() == 'targetd+ssl':
            self.scheme = 'https'
        else:
            self.scheme = 'http'

        self.url = urlparse.urlunsplit(
            (self.scheme, self.host_with_port, PATH, None, None))

        auth = ('%s:%s' % (user, self.password)).encode('base64')[:-1]
        self.headers = {'Content-Type': 'application/json',
                        'Authorization': 'Basic %s' % (auth,)}

        try:
            self._jsonrequest('access_group_list')
        except TargetdError as te:
            if te.errno == TargetdError.INVALID_METHOD:
                self._flag_ag_support = False
            else:
                raise

    @handle_errors
    def time_out_set(self, ms, flags=0):
        self.tmo = ms

    @handle_errors
    def time_out_get(self, flags=0):
        return self.tmo

    @handle_errors
    def plugin_unregister(self, flags=0):
        pass

    @handle_errors
    def capabilities(self, system, flags=0):
        cap = Capabilities()
        cap.set(Capabilities.VOLUMES)
        cap.set(Capabilities.VOLUME_CREATE)
        cap.set(Capabilities.VOLUME_REPLICATE)
        cap.set(Capabilities.VOLUME_REPLICATE_COPY)
        cap.set(Capabilities.VOLUME_DELETE)
        cap.set(Capabilities.VOLUME_MASK)
        cap.set(Capabilities.VOLUME_UNMASK)
        cap.set(Capabilities.FS)
        cap.set(Capabilities.FS_CREATE)
        cap.set(Capabilities.FS_DELETE)
        cap.set(Capabilities.FS_CLONE)
        cap.set(Capabilities.FS_SNAPSHOT_CREATE)
        cap.set(Capabilities.FS_SNAPSHOT_DELETE)
        cap.set(Capabilities.FS_SNAPSHOTS)
        cap.set(Capabilities.EXPORT_AUTH)
        cap.set(Capabilities.EXPORTS)
        cap.set(Capabilities.EXPORT_FS)
        cap.set(Capabilities.EXPORT_REMOVE)
        cap.set(Capabilities.ACCESS_GROUPS)
        cap.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
        cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
        cap.set(Capabilities.VOLUME_ISCSI_CHAP_AUTHENTICATION)

        if self._flag_ag_support:
            cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
            cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)

        return cap

    @handle_errors
    def plugin_info(self, flags=0):
        return "Linux LIO target support", VERSION

    @handle_errors
    def systems(self, flags=0):
        # verify we're online
        self._jsonrequest("pool_list")

        return [self.system]

    @handle_errors
    def job_status(self, job_id, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    @handle_errors
    def job_free(self, job_id, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    @staticmethod
    def _uuid_to_vpd83(uuid):
        """
        Convert LVM UUID to VPD 83 Device ID.
        LIO kernel module(target_core_mod.ko) does not expose VPD83 via
        ConfigFs.
        Targetd does not expose VPD83 via its API.
        Hence we have to do the convention here base on kernel code.
        """
        # NAA IEEE Registered Extended Identifier/Designator.
        # SPC-4 rev37a 7.8.6.6.5
        vpd83 = '6'
        # Use OpenFabrics IEEE Company ID: 00 14 05
        # https://standards.ieee.org/develop/regauth/oui/oui.txt
        vpd83 += '001405'

        # Take all [a-f0-9] digits from UUID for VENDOR_SPECIFIC_IDENTIFIER
        # and VENDOR_SPECIFIC_IDENTIFIER_EXTENTION
        vpd83 += re.sub('[^a-f0-9]', '', uuid.lower())[:25]

        # Fill up with zero.
        vpd83 += '0' * (32 - len(vpd83))

        return vpd83

    @handle_errors
    def volumes(self, search_key=None, search_value=None, flags=0):
        volumes = []
        for p_name in (p['name'] for p in self._jsonrequest("pool_list") if
                       p['type'] == 'block'):
            for vol in self._jsonrequest("vol_list", dict(pool=p_name)):
                vpd83 = TargetdStorage._uuid_to_vpd83(vol['uuid'])
                volumes.append(
                    Volume(vol['uuid'], vol['name'], vpd83, 512,
                           long(vol['size'] / 512),
                           Volume.ADMIN_STATE_ENABLED,
                           self.system.id, p_name))
        return search_property(volumes, search_key, search_value)

    @handle_errors
    def pools(self, search_key=None, search_value=None, flags=0):
        pools = []
        for pool in self._jsonrequest("pool_list"):
            if pool['name'].startswith('/'):
                et = Pool.ELEMENT_TYPE_FS
            else:
                et = Pool.ELEMENT_TYPE_VOLUME

            pools.append(Pool(pool['name'],
                              pool['name'], et, 0, pool['size'],
                              pool['free_size'], Pool.STATUS_UNKNOWN, '',
                              'targetd'))
        return search_property(pools, search_key, search_value)

    @staticmethod
    def _tgt_ag_to_lsm(tgt_ag, sys_id):
        return AccessGroup(
            tgt_ag['name'], tgt_ag['name'], tgt_ag['init_ids'],
            AccessGroup.INIT_TYPE_ISCSI_IQN, sys_id)

    @staticmethod
    def _tgt_init_to_lsm(tgt_init, sys_id):
        return AccessGroup(
            "%s%s" % (
                TargetdStorage._FAKE_AG_PREFIX, md5(tgt_init['init_id'])),
            'N/A', [tgt_init['init_id']], AccessGroup.INIT_TYPE_ISCSI_IQN,
            sys_id)

    @handle_errors
    def access_groups(self, search_key=None, search_value=None, flags=0):
        rc_lsm_ags = []

        # For backward compatibility
        if self._flag_ag_support is True:
            tgt_inits = self._jsonrequest(
                'initiator_list', {'standalone_only': True})
        else:
            tgt_inits = list(
                {'init_id': x}
                for x in set(
                    i['initiator_wwn']
                    for i in self._jsonrequest("export_list")))

        rc_lsm_ags.extend(
            list(
                TargetdStorage._tgt_init_to_lsm(i, self.system.id)
                for i in tgt_inits))

        if self._flag_ag_support is True:
            for tgt_ag in self._jsonrequest('access_group_list'):
                rc_lsm_ags.append(
                    TargetdStorage._tgt_ag_to_lsm(
                        tgt_ag, self.system.id))

        return search_property(rc_lsm_ags, search_key, search_value)

    def _search_lsm_ag_by_name(self, ag_name, lsm_error_obj=None):
        """
        Raise provided error if defined when not found.
        Return lsm.AccessGroup if found.
        """
        lsm_ags = self.access_groups()
        for lsm_ag in lsm_ags:
            if lsm_ag.name == ag_name:
                return lsm_ag

        if lsm_error_obj:
            raise lsm_error_obj

    @handle_errors
    def access_group_create(self, name, init_id, init_type, system, flags=0):
        if system.id != self.system.id:
            raise LsmError(
                ErrorNumber.NOT_FOUND_SYSTEM,
                "System %s not found" % system.id)
        if self._flag_ag_support is False:
            raise LsmError(
                ErrorNumber.NO_SUPPORT,
                "Please upgrade your targetd package to support "
                "access_group_create()")

        if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
            raise LsmError(ErrorNumber.NO_SUPPORT, "Only iSCSI yet")

        try:
            self._jsonrequest(
                "access_group_create",
                dict(ag_name=name, init_id=init_id, init_type='iscsi'))
        except TargetdError as tgt_error:
            if tgt_error.errno == TargetdError.EXISTS_INITIATOR:
                raise LsmError(
                    ErrorNumber.EXISTS_INITIATOR,
                    "Initiator is already used by other access group")
            elif tgt_error.errno == TargetdError.NAME_CONFLICT:
                raise LsmError(
                    ErrorNumber.NAME_CONFLICT,
                    "Requested access group name is already used by other "
                    "access group")
            elif tgt_error.errno == TargetdError.INVALID_ARGUMENT:
                raise LsmError(
                    ErrorNumber.INVALID_ARGUMENT,
                    str(tgt_error))
            else:
                raise

        return self._search_lsm_ag_by_name(
            name,
            LsmError(
                ErrorNumber.PLUGIN_BUG,
                "access_group_create(): Failed to find the newly created "
                "access group"))

    @handle_errors
    def access_group_initiator_add(self, access_group, init_id, init_type,
                                   flags=0):
        if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
            raise LsmError(
                ErrorNumber.NO_SUPPORT, "Targetd only support iscsi")

        lsm_ag = self._search_lsm_ag_by_name(
            access_group.name,
            LsmError(
                ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))

        # Pre-check for NO_STATE_CHANGE error as targetd silently pass
        # if initiator is already in requested access group.
        if init_id in lsm_ag.init_ids:
            raise LsmError(
                ErrorNumber.NO_STATE_CHANGE,
                "Requested init_id is already in defined access group")

        try:
            self._jsonrequest(
                "access_group_init_add",
                dict(
                    ag_name=access_group.name, init_id=init_id,
                    init_type='iscsi'))
        except TargetdError as tgt_error:
            if tgt_error.errno == TargetdError.EXISTS_INITIATOR:
                raise LsmError(
                    ErrorNumber.EXISTS_INITIATOR,
                    "Initiator is already used by other access group")
            else:
                raise

        return self._search_lsm_ag_by_name(
            access_group.name,
            LsmError(
                ErrorNumber.PLUGIN_BUG,
                "access_group_initiator_add(): "
                "Failed to find the updated access group"))

    def _mask_infos(self):
        """
        Return a list of tgt_mask:
            'vol_id': volume.id
            'ag_id': ag.id
            'lun_id': lun_id
        """
        tgt_masks = []
        tgt_exps = self._jsonrequest("export_list")
        for tgt_exp in tgt_exps:
            tgt_masks.extend([{
                'vol_id': tgt_exp['vol_uuid'],
                'ag_id': md5(tgt_exp['initiator_wwn']),
                'lun_id': tgt_exp['lun'],<commit_msg>Targetd Plugin: Add access_group_initiator_delete() support.<commit_after># Copyright (C) 2011-2014 Red Hat, Inc.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
#
# Author: <NAME> <agrover at redhat com>
#         <NAME> <<EMAIL>>

import copy

from lsm import (Pool, Volume, System, Capabilities,
                 IStorageAreaNetwork, INfs, FileSystem, FsSnapshot, NfsExport,
                 LsmError, ErrorNumber, uri_parse, md5, VERSION,
                 common_urllib2_error_handler, search_property,
                 AccessGroup)

import urllib2
import json
import time
import urlparse
import socket
import re

DEFAULT_USER = "admin"
DEFAULT_PORT = 18700
PATH = "/targetrpc"

# Current sector size in liblvm
_LVM_SECTOR_SIZE = 512

def handle_errors(method):
    def target_wrapper(*args, **kwargs):
        try:
            return method(*args, **kwargs)
        except TargetdError as te:
            raise LsmError(ErrorNumber.PLUGIN_BUG,
                           "Got error %d from targetd: %s"
                           % (te.errno, te.reason))
        except LsmError:
            raise
        except Exception as e:
            common_urllib2_error_handler(e)

    return target_wrapper


class TargetdError(Exception):
    VOLUME_MASKED = 303
    INVALID_METHOD = 32601
    INVALID_ARGUMENT = 32602
    NAME_CONFLICT = 50
    EXISTS_INITIATOR = 52

    def __init__(self, errno, reason, *args, **kwargs):
        Exception.__init__(self, *args, **kwargs)
        self.errno = int(errno)
        self.reason = reason


class TargetdStorage(IStorageAreaNetwork, INfs):
    _FAKE_AG_PREFIX = 'init.'

    def __init__(self):
        self.uri = None
        self.password = <PASSWORD>
        self.tmo = 0
        self.rpc_id = 1
        self.host_with_port = None
        self.scheme = None
        self.url = None
        self.headers = None
        self.system = System("targetd", "targetd storage appliance",
                             System.STATUS_UNKNOWN, '')

    @handle_errors
    def plugin_register(self, uri, password, timeout, flags=0):
        self.uri = uri_parse(uri)
        self.password = password
        self.tmo = timeout
        self._flag_ag_support = True

        user = self.uri.get('username', DEFAULT_USER)
        port = self.uri.get('port', DEFAULT_PORT)

        self.host_with_port = "%s:%s" % (self.uri['host'], port)
        if self.uri['scheme'].lower() == 'targetd+ssl':
            self.scheme = 'https'
        else:
            self.scheme = 'http'

        self.url = urlparse.urlunsplit(
            (self.scheme, self.host_with_port, PATH, None, None))

        auth = ('%s:%s' % (user, self.password)).encode('base64')[:-1]
        self.headers = {'Content-Type': 'application/json',
                        'Authorization': 'Basic %s' % (auth,)}

        try:
            self._jsonrequest('access_group_list')
        except TargetdError as te:
            if te.errno == TargetdError.INVALID_METHOD:
                self._flag_ag_support = False
            else:
                raise

    @handle_errors
    def time_out_set(self, ms, flags=0):
        self.tmo = ms

    @handle_errors
    def time_out_get(self, flags=0):
        return self.tmo

    @handle_errors
    def plugin_unregister(self, flags=0):
        pass

    @handle_errors
    def capabilities(self, system, flags=0):
        cap = Capabilities()
        cap.set(Capabilities.VOLUMES)
        cap.set(Capabilities.VOLUME_CREATE)
        cap.set(Capabilities.VOLUME_REPLICATE)
        cap.set(Capabilities.VOLUME_REPLICATE_COPY)
        cap.set(Capabilities.VOLUME_DELETE)
        cap.set(Capabilities.VOLUME_MASK)
        cap.set(Capabilities.VOLUME_UNMASK)
        cap.set(Capabilities.FS)
        cap.set(Capabilities.FS_CREATE)
        cap.set(Capabilities.FS_DELETE)
        cap.set(Capabilities.FS_CLONE)
        cap.set(Capabilities.FS_SNAPSHOT_CREATE)
        cap.set(Capabilities.FS_SNAPSHOT_DELETE)
        cap.set(Capabilities.FS_SNAPSHOTS)
        cap.set(Capabilities.EXPORT_AUTH)
        cap.set(Capabilities.EXPORTS)
        cap.set(Capabilities.EXPORT_FS)
        cap.set(Capabilities.EXPORT_REMOVE)
        cap.set(Capabilities.ACCESS_GROUPS)
        cap.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
        cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
        cap.set(Capabilities.VOLUME_ISCSI_CHAP_AUTHENTICATION)

        if self._flag_ag_support:
            cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
            cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)
            cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)

        return cap

    @handle_errors
    def plugin_info(self, flags=0):
        return "Linux LIO target support", VERSION

    @handle_errors
    def systems(self, flags=0):
        # verify we're online
        self._jsonrequest("pool_list")

        return [self.system]

    @handle_errors
    def job_status(self, job_id, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    @handle_errors
    def job_free(self, job_id, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    @staticmethod
    def _uuid_to_vpd83(uuid):
        """
        Convert LVM UUID to VPD 83 Device ID.
        LIO kernel module(target_core_mod.ko) does not expose VPD83 via
        ConfigFs.
        Targetd does not expose VPD83 via its API.
        Hence we have to do the convention here base on kernel code.
        """
        # NAA IEEE Registered Extended Identifier/Designator.
        # SPC-4 rev37a 7.8.6.6.5
        vpd83 = '6'
        # Use OpenFabrics IEEE Company ID: 00 14 05
        # https://standards.ieee.org/develop/regauth/oui/oui.txt
        vpd83 += '001405'

        # Take all [a-f0-9] digits from UUID for VENDOR_SPECIFIC_IDENTIFIER
        # and VENDOR_SPECIFIC_IDENTIFIER_EXTENTION
        vpd83 += re.sub('[^a-f0-9]', '', uuid.lower())[:25]

        # Fill up with zero.
        vpd83 += '0' * (32 - len(vpd83))

        return vpd83

    @handle_errors
    def volumes(self, search_key=None, search_value=None, flags=0):
        volumes = []
        for p_name in (p['name'] for p in self._jsonrequest("pool_list") if
                       p['type'] == 'block'):
            for vol in self._jsonrequest("vol_list", dict(pool=p_name)):
                vpd83 = TargetdStorage._uuid_to_vpd83(vol['uuid'])
                volumes.append(
                    Volume(vol['uuid'], vol['name'], vpd83, 512,
                           long(vol['size'] / 512),
                           Volume.ADMIN_STATE_ENABLED,
                           self.system.id, p_name))
        return search_property(volumes, search_key, search_value)

    @handle_errors
    def pools(self, search_key=None, search_value=None, flags=0):
        pools = []
        for pool in self._jsonrequest("pool_list"):
            if pool['name'].startswith('/'):
                et = Pool.ELEMENT_TYPE_FS
            else:
                et = Pool.ELEMENT_TYPE_VOLUME

            pools.append(Pool(pool['name'],
                              pool['name'], et, 0, pool['size'],
                              pool['free_size'], Pool.STATUS_UNKNOWN, '',
                              'targetd'))
        return search_property(pools, search_key, search_value)

    @staticmethod
    def _tgt_ag_to_lsm(tgt_ag, sys_id):
        return AccessGroup(
            tgt_ag['name'], tgt_ag['name'], tgt_ag['init_ids'],
            AccessGroup.INIT_TYPE_ISCSI_IQN, sys_id)

    @staticmethod
    def _tgt_init_to_lsm(tgt_init, sys_id):
        return AccessGroup(
            "%s%s" % (
                TargetdStorage._FAKE_AG_PREFIX, md5(tgt_init['init_id'])),
            'N/A', [tgt_init['init_id']], AccessGroup.INIT_TYPE_ISCSI_IQN,
            sys_id)

    @handle_errors
    def access_groups(self, search_key=None, search_value=None, flags=0):
        rc_lsm_ags = []

        # For backward compatibility
        if self._flag_ag_support is True:
            tgt_inits = self._jsonrequest(
                'initiator_list', {'standalone_only': True})
        else:
            tgt_inits = list(
                {'init_id': x}
                for x in set(
                    i['initiator_wwn']
                    for i in self._jsonrequest("export_list")))

        rc_lsm_ags.extend(
            list(
                TargetdStorage._tgt_init_to_lsm(i, self.system.id)
                for i in tgt_inits))

        if self._flag_ag_support is True:
            for tgt_ag in self._jsonrequest('access_group_list'):
                rc_lsm_ags.append(
                    TargetdStorage._tgt_ag_to_lsm(
                        tgt_ag, self.system.id))

        return search_property(rc_lsm_ags, search_key, search_value)

    def _search_lsm_ag_by_name(self, ag_name, lsm_error_obj=None):
        """
        Raise provided error if defined when not found.
        Return lsm.AccessGroup if found.
        """
        lsm_ags = self.access_groups()
        for lsm_ag in lsm_ags:
            if lsm_ag.name == ag_name:
                return lsm_ag

        if lsm_error_obj:
            raise lsm_error_obj

    @handle_errors
    def access_group_create(self, name, init_id, init_type, system, flags=0):
        if system.id != self.system.id:
            raise LsmError(
                ErrorNumber.NOT_FOUND_SYSTEM,
                "System %s not found" % system.id)
        if self._flag_ag_support is False:
            raise LsmError(
                ErrorNumber.NO_SUPPORT,
                "Please upgrade your targetd package to support "
                "access_group_create()")

        if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
            raise LsmError(ErrorNumber.NO_SUPPORT, "Only iSCSI yet")

        try:
            self._jsonrequest(
                "access_group_create",
                dict(ag_name=name, init_id=init_id, init_type='iscsi'))
        except TargetdError as tgt_error:
            if tgt_error.errno == TargetdError.EXISTS_INITIATOR:
                raise LsmError(
                    ErrorNumber.EXISTS_INITIATOR,
                    "Initiator is already used by other access group")
            elif tgt_error.errno == TargetdError.NAME_CONFLICT:
                raise LsmError(
                    ErrorNumber.NAME_CONFLICT,
                    "Requested access group name is already used by other "
                    "access group")
            elif tgt_error.errno == TargetdError.INVALID_ARGUMENT:
                raise LsmError(
                    ErrorNumber.INVALID_ARGUMENT,
                    str(tgt_error))
            else:
                raise

        return self._search_lsm_ag_by_name(
            name,
            LsmError(
                ErrorNumber.PLUGIN_BUG,
                "access_group_create(): Failed to find the newly created "
                "access group"))

    @handle_errors
    def access_group_initiator_add(self, access_group, init_id, init_type,
                                   flags=0):
        if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
            raise LsmError(
                ErrorNumber.NO_SUPPORT, "Targetd only support iscsi")

        lsm_ag = self._search_lsm_ag_by_name(
            access_group.name,
            LsmError(
                ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))

        # Pre-check for NO_STATE_CHANGE error as targetd silently pass
        # if initiator is already in requested access group.
        if init_id in lsm_ag.init_ids:
            raise LsmError(
                ErrorNumber.NO_STATE_CHANGE,
                "Requested init_id is already in defined access group")

        try:
            self._jsonrequest(
                "access_group_init_add",
                dict(
                    ag_name=access_group.name, init_id=init_id,
                    init_type='iscsi'))
        except TargetdError as tgt_error:
            if tgt_error.errno == TargetdError.EXISTS_INITIATOR:
                raise LsmError(
                    ErrorNumber.EXISTS_INITIATOR,
                    "Initiator is already used by other access group")
            else:
                raise

        return self._search_lsm_ag_by_name(
            access_group.name,
            LsmError(
                ErrorNumber.PLUGIN_BUG,
                "access_group_initiator_add(): "
                "Failed to find the updated access group"))

    @handle_errors
    def access_group_initiator_delete(self, access_group, init_id, init_type,
                                      flags=0):
        if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
            raise LsmError(
                ErrorNumber.NO_SUPPORT,
                "Targetd only support iscsi")

        # Pre-check for NO_STATE_CHANGE as targetd sliently return
        # when init_id not in requested access_group.
        lsm_ag = self._search_lsm_ag_by_name(
            access_group.name,
            LsmError(
                ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))

        if init_id not in lsm_ag.init_ids:
            raise LsmError(
                ErrorNumber.NO_STATE_CHANGE,
                "Requested initiator is not in defined access group")

        if len(lsm_ag.init_ids) == 1:
            raise LsmError(
                ErrorNumber.LAST_INIT_IN_ACCESS_GROUP,
                "Refused to remove the last initiator from access group")

        self._jsonrequest(
            "access_group_init_del",
            dict(
                ag_name=access_group.name,
                init_id=init_id,
                init_type='iscsi'))

        return self._search_lsm_ag_by_name(
            access_group.name,
            LsmError(
                ErrorNumber.PLUGIN_BUG,
                "access_group_initiator_delete(): "
                "Failed to find the updated access group"))

    def _mask_infos(self):
        """
        Return a list of tgt_mask:
            'vol_id': volume.id
            'ag_id': ag.id
            'lun_id': lun_id
        """
        tgt_masks = []
        tgt_exps = self._jsonrequest("export_list")
        for tgt_exp in tgt_exps:
            tgt_masks.extend([{
                'vol_id': tgt_exp['vol_uuid'],
                'ag_id': md5(tgt_exp['initiator_wwn']),
                'lun_id': tgt_exp['lun'],
{'fraction_non_alphanumeric': 0.08731553056921995, 'fraction_numerical': 0.009486999297259312, 'mean_word_length': 1.977715003138732, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 14, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4408089', 'n_tokens_mistral': 8881, 'n_tokens_neox': 8412, 'n_words': 1917}