# Copyright (c) 2012 - 2014 EMC Corporation, 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.
"""
VNX CLI on iSCSI.
"""

import os
import time

from oslo.config import cfg

from cinder import exception
from cinder.openstack.common import log as logging
from cinder.openstack.common import loopingcall
from cinder.openstack.common import processutils
from cinder import utils
from cinder.volume.drivers.san import san
from cinder.volume import volume_types

LOG = logging.getLogger(__name__)

CONF = cfg.CONF
VERSION = '02.00.00'

loc_opts = [
    cfg.StrOpt('naviseccli_path',
               default='',
               help='Naviseccli Path'),
    cfg.StrOpt('storage_vnx_pool_name',
               default=None,
               help='ISCSI pool name'),
    cfg.IntOpt('default_timeout',
               default=20,
               help='Default Time Out For CLI operations in minutes'),
    cfg.IntOpt('max_luns_per_storage_group',
               default=256,
               help='Default max number of LUNs in a storage group'), ]

CONF.register_opts(loc_opts)


class EMCVnxCli(object):
    """This class defines the functions to use the native CLI functionality."""

    stats = {'driver_version': VERSION,
             'free_capacity_gb': 'unknown',
             'reserved_percentage': 0,
             'storage_protocol': None,
             'total_capacity_gb': 'unknown',
             'vendor_name': 'EMC',
             'volume_backend_name': None}

    def __init__(self, prtcl, configuration=None):

        self.protocol = prtcl
        self.configuration = configuration
        self.configuration.append_config_values(loc_opts)
        self.configuration.append_config_values(san.san_opts)
        self.storage_ip = self.configuration.san_ip
        self.storage_username = self.configuration.san_login
        self.storage_password = self.configuration.san_password

        self.pool_name = self.configuration.storage_vnx_pool_name
        if not self.pool_name:
            msg = (_('Pool name is not specified.'))
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        self.timeout = self.configuration.default_timeout
        self.max_luns = self.configuration.max_luns_per_storage_group
        self.hlu_set = set(xrange(1, self.max_luns + 1))
        self.navisecclipath = self.configuration.naviseccli_path
        self.cli_prefix = (self.navisecclipath, '-address', self.storage_ip)
        self.cli_credentials = ()
        self.wait_interval = 3

        # if there is a username/password provided, use those in the cmd line
        if self.storage_username is not None and \
                self.storage_password is not None:
            self.cli_credentials += ('-user', self.storage_username,
                                     '-password', self.storage_password,
                                     '-scope', '0')

        # Checking for existence of naviseccli tool
        if not os.path.exists(self.navisecclipath):
            msg = (_('Could not find NAVISECCLI tool.'))
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        # Testing the naviseccli setup
        query_list = ("storagepool", "-list",
                      "-name", self.pool_name, "-state")
        out, rc = self._cli_execute(*query_list)
        if rc != 0:
            LOG.error(_("Failed to find pool %s"), self.pool_name)
            raise exception.VolumeBackendAPIException(data=out)

    def _cli_execute(self, *cmd, **kwargv):
        if "check_exit_code" not in kwargv:
            kwargv["check_exit_code"] = True
        rc = 0
        try:
            out, _err = utils.execute(*(self.cli_prefix +
                                      self.cli_credentials + cmd), **kwargv)
        except processutils.ProcessExecutionError as pe:
            rc = pe.exit_code
            out = pe.stdout + pe.stderr
        return out, rc

    def create_volume(self, volume):
        """Creates a EMC volume."""

        LOG.debug('Entering create_volume.')
        volumesize = volume['size']
        volumename = volume['name']

        LOG.info(_('Create Volume: %(volume)s  Size: %(size)s')
                 % {'volume': volumename,
                    'size': volumesize})

        # defining CLI command
        thinness = self._get_provisioning_by_volume(volume)

        # executing CLI command to create volume
        LOG.debug('Create Volume: %(volumename)s'
                  % {'volumename': volumename})

        lun_create = ('lun', '-create',
                      '-type', thinness,
                      '-capacity', volumesize,
                      '-sq', 'gb',
                      '-poolName', self.pool_name,
                      '-name', volumename)
        out, rc = self._cli_execute(*lun_create)
        LOG.debug('Create Volume: %(volumename)s  Return code: %(rc)s'
                  % {'volumename': volumename,
                     'rc': rc})
        if rc == 4:
            LOG.warn(_('Volume %s already exists'), volumename)
        elif rc != 0:
            msg = (_('Failed to create %(volumename)s: %(out)s') %
                   {'volumename': volumename, 'out': out})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        # wait for up to a minute to verify that the LUN has progressed
        # to Ready state
        def _wait_for_lun_ready(volumename, start_time):
            # executing cli command to check volume
            command_to_verify = ('lun', '-list', '-name', volumename)
            out, rc = self._cli_execute(*command_to_verify)
            if rc == 0 and out.find("Ready") > -1:
                raise loopingcall.LoopingCallDone()
            if int(time.time()) - start_time > self.timeout * 60:
                msg = (_('LUN %s failed to become Ready'), volumename)
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

        timer = loopingcall.FixedIntervalLoopingCall(
            _wait_for_lun_ready, volumename, int(time.time()))
        timer.start(interval=self.wait_interval).wait()

    def delete_volume(self, volume):
        """Deletes an EMC volume."""

        LOG.debug('Entering delete_volume.')
        volumename = volume['name']
        # defining CLI command
        lun_destroy = ('lun', '-destroy',
                       '-name', volumename,
                       '-forceDetach', '-o')

        # executing CLI command to delete volume
        out, rc = self._cli_execute(*lun_destroy)
        LOG.debug('Delete Volume: %(volumename)s  Output: %(out)s'
                  % {'volumename': volumename, 'out': out})
        if rc not in (0, 9):
            msg = (_('Failed to destroy %s'), volumename)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def extend_volume(self, volume, new_size):
        """Extends an EMC volume."""

        LOG.debug('Entering extend_volume.')
        volumename = volume['name']

        # defining CLI command
        lun_expand = ('lun', '-expand',
                      '-name', volumename,
                      '-capacity', new_size,
                      '-sq', 'gb',
                      '-o', '-ignoreThresholds')

        # executing CLI command to extend volume
        out, rc = self._cli_execute(*lun_expand)

        LOG.debug('Extend Volume: %(volumename)s  Output: %(out)s'
                  % {'volumename': volumename,
                     'out': out})
        if rc == 97:
            msg = (_('The LUN cannot be expanded or shrunk because '
                     'it has snapshots. Command to extend the specified '
                     'volume failed.'))
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        if rc != 0:
            msg = (_('Failed to expand %s'), volumename)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def update_volume_status(self):
        """Retrieve status info."""
        LOG.debug("Updating volume status")

        poolname = self.pool_name
        pool_list = ('storagepool', '-list',
                     '-name', poolname,
                     '-userCap', '-availableCap')
        out, rc = self._cli_execute(*pool_list)
        if rc == 0:
            pool_details = out.split('\n')
            self.stats['total_capacity_gb'] = float(
                pool_details[3].split(':')[1].strip())
            self.stats['free_capacity_gb'] = float(
                pool_details[5].split(':')[1].strip())
        else:
            msg = (_('Failed to list %s'), poolname)
            LOG.error(msg)

        return self.stats

    def create_export(self, context, volume):
        """Driver entry point to get the export info for a new volume."""
        volumename = volume['name']

        device_id = self._find_lun_id(volumename)

        LOG.debug('create_export: Volume: %(volume)s  Device ID: '
                  '%(device_id)s'
                  % {'volume': volumename,
                     'device_id': device_id})

        return {'provider_location': device_id}

    def _find_lun_id(self, volumename):
        """Returns the LUN of a volume."""

        lun_list = ('lun', '-list', '-name', volumename)

        out, rc = self._cli_execute(*lun_list)
        if rc == 0:
            vol_details = out.split('\n')
            lun = vol_details[0].split(' ')[3]
        else:
            msg = (_('Failed to list %s'), volumename)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        return lun

    def create_snapshot(self, snapshot):
        """Creates a snapshot."""
        LOG.debug('Entering create_snapshot.')
        snapshotname = snapshot['name']
        volumename = snapshot['volume_name']
        LOG.info(_('Create snapshot: %(snapshot)s: volume: %(volume)s')
                 % {'snapshot': snapshotname,
                    'volume': volumename})

        volume_lun = self._find_lun_id(volumename)

        # defining CLI command
        snap_create = ('snap', '-create',
                       '-res', volume_lun,
                       '-name', snapshotname,
                       '-allowReadWrite', 'yes')
        # executing CLI command to create snapshot
        out, rc = self._cli_execute(*snap_create)

        LOG.debug('Create Snapshot: %(snapshotname)s  Unity: %(out)s'
                  % {'snapshotname': snapshotname,
                     'out': out})
        if rc != 0:
            msg = (_('Failed to create snap %s'), snapshotname)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def delete_snapshot(self, snapshot):
        """Deletes a snapshot."""
        LOG.debug('Entering delete_snapshot.')

        snapshotname = snapshot['name']
        volumename = snapshot['volume_name']
        LOG.info(_('Delete Snapshot: %(snapshot)s: volume: %(volume)s')
                 % {'snapshot': snapshotname,
                    'volume': volumename})

        def _wait_for_snap_delete(snapshot, start_time):
            # defining CLI command
            snapshotname = snapshot['name']
            volumename = snapshot['volume_name']
            snap_destroy = ('snap', '-destroy', '-id', snapshotname, '-o')
            # executing CLI command
            out, rc = self._cli_execute(*snap_destroy)

            LOG.debug('Delete Snapshot: Volume: %(volumename)s  Snapshot: '
                      '%(snapshotname)s  Output: %(out)s'
                      % {'volumename': volumename,
                         'snapshotname': snapshotname,
                         'out': out})

            if rc not in [0, 9, 5]:
                if rc == 13:
                    if int(time.time()) - start_time < \
                            self.timeout * 60:
                        LOG.info(_('Snapshot %s is in use'), snapshotname)
                    else:
                        msg = (_('Failed to destroy %s '
                               ' because snapshot is in use.'), snapshotname)
                        LOG.error(msg)
                        raise exception.SnapshotIsBusy(data=msg)
                else:
                    msg = (_('Failed to destroy %s'), snapshotname)
                    LOG.error(msg)
                    raise exception.VolumeBackendAPIException(data=msg)
            else:
                raise loopingcall.LoopingCallDone()

        timer = loopingcall.FixedIntervalLoopingCall(
            _wait_for_snap_delete, snapshot, int(time.time()))
        timer.start(interval=self.wait_interval).wait()

    def create_volume_from_snapshot(self, volume, snapshot):
        """Creates a volume from a snapshot."""

        LOG.debug('Entering create_volume_from_snapshot.')

        snapshotname = snapshot['name']
        source_volume_name = snapshot['volume_name']
        volumename = volume['name']
        volumesize = snapshot['volume_size']

        destvolumename = volumename + 'dest'

        # Create a mount point, migrate data from source (snapshot) to
        # destination volume.  The destination volume is the only new volume
        # to be created here.
        LOG.info(_('Creating Destination Volume : %s ') % (destvolumename))

        poolname = self.pool_name
        thinness = self._get_provisioning_by_volume(volume)
        # defining CLI command
        lun_create = ('lun', '-create', '-type', thinness,
                      '-capacity', volumesize, '-sq', 'gb',
                      '-poolName', poolname,
                      '-name', destvolumename)
        # executing CLI command
        out, rc = self._cli_execute(*lun_create)

        LOG.debug('Create temporary Volume: %(volumename)s  '
                  'Output : %(out)s'
                  % {'volumename': destvolumename, 'out': out})

        if rc != 0:
            msg = (_('Command to create the destination volume failed'))
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        # defining CLI command
        smp_create = ('lun', '-create', '-type', 'Snap',
                      '-primaryLunName', source_volume_name,
                      '-name', volumename)

        # executing CLI command
        out, rc = self._cli_execute(*smp_create)
        LOG.debug('Create mount point : Volume: %(volumename)s  '
                  'Source Volume: %(sourcevolumename)s  Output: %(out)s'
                  % {'volumename': volumename,
                     'sourcevolumename': source_volume_name,
                     'out': out})

        if rc != 0:
            msg = (_('Failed to create SMP %s'), volumename)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        # defining CLI command
        lun_attach = ('lun', '-attach',
                      '-name', volumename,
                      '-snapName', snapshotname)

        # executing CLI command
        out, rc = self._cli_execute(*lun_attach)
        LOG.debug('Attaching mount point Volume: %(volumename)s  '
                  'with  Snapshot: %(snapshotname)s  Output: %(out)s'
                  % {'volumename': volumename,
                     'snapshotname': snapshotname,
                     'out': out})

        if rc != 0:
            msg = (_('Failed to attach snapshotname %s'), snapshotname)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        source_vol_lun = self._find_lun_id(volumename)
        dest_vol_lun = self._find_lun_id(destvolumename)

        LOG.info(_('Migrating Mount Point Volume: %s ') % (volumename))

        # defining CLI command
        migrate_start = ('migrate', '-start',
                         '-source', source_vol_lun,
                         '-dest', dest_vol_lun,
                         '-rate', 'ASAP', '-o')

        # executing CLI command
        out, rc = self._cli_execute(*migrate_start)

        LOG.debug('Migrate Mount Point  Volume: %(volumename)s  '
                  'Output : %(out)s'
                  % {'volumename': volumename,
                     'out': out})

        if rc != 0:
            msg = (_('Failed to start migrating SMP %s'), volumename)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

        def _wait_for_sync_status(volumename, start_time):
            lun_list = ('lun', '-list', '-name', volumename,
                        '-attachedSnapshot')
            out, rc = self._cli_execute(*lun_list)
            if rc == 0:
                vol_details = out.split('\n')
                snapshotname = vol_details[2].split(':')[1].strip()
            if (snapshotname == 'N/A'):
                raise loopingcall.LoopingCallDone()
            else:
                LOG.info(_('Waiting for the update on Sync status of %s'),
                         volumename)
                if int(time.time()) - start_time >= self.timeout * 60:
                    msg = (_('Failed to really migrate %s'), volumename)
                    LOG.error(msg)
                    raise exception.VolumeBackendAPIException(data=msg)

        timer = loopingcall.FixedIntervalLoopingCall(
            _wait_for_sync_status, volumename, int(time.time()))
        timer.start(interval=self.wait_interval).wait()

    def create_cloned_volume(self, volume, src_vref):
        """Creates a clone of the specified volume."""

        source_volume_name = src_vref['name']
        volumesize = src_vref['size']
        snapshotname = source_volume_name + '-temp-snapshot'

        snapshot = {
            'name': snapshotname,
            'volume_name': source_volume_name,
            'volume_size': volumesize,
        }

        # Create temp Snapshot
        self.create_snapshot(snapshot)

        try:
            # Create volume
            self.create_volume_from_snapshot(volume, snapshot)
        except Exception:
            msg = (_('Failed to create cloned volume %s'), volume['name'])
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        finally:
            # Delete temp Snapshot
            self.delete_snapshot(snapshot)

    def get_storage_group(self, hostname):
        """Returns the storage group for the host node."""

        storage_groupname = hostname

        sg_list = ('storagegroup', '-list', '-gname', storage_groupname)

        out, rc = self._cli_execute(*sg_list)

        if rc != 0:
            LOG.debug('creating new storage group %s', storage_groupname)

            sg_create = ('storagegroup', '-create',
                         '-gname', storage_groupname)
            out, rc = self._cli_execute(*sg_create)
            LOG.debug('Create new storage group : %(storage_groupname)s, '
                      'Output: %(out)s'
                      % {'storage_groupname': storage_groupname,
                         'out': out})

            if rc != 0:
                msg = (_('Failed to create SG %s'), storage_groupname)
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

            # connecting the new storagegroup to the host
            connect_host = ('storagegroup', '-connecthost',
                            '-host', hostname,
                            '-gname', storage_groupname,
                            '-o')

            out, rc = self._cli_execute(*connect_host)
            LOG.debug('Connect storage group : %(storage_groupname)s ,'
                      'To Host : %(hostname)s, Output : %(out)s'
                      % {'storage_groupname': storage_groupname,
                         'hostname': hostname,
                         'out': out})

            if rc != 0:
                msg = (_('Failed to connect %s'), hostname)
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

        return hostname

    def find_device_details(self, volume, storage_group):
        """Returns the Host Device number for the volume."""

        allocated_lun_id = self._find_lun_id(volume["name"])
        host_lun_id = -1
        owner_sp = ""
        lun_map = {}

        sg_list = ('storagegroup', '-list', '-gname', storage_group)
        out, rc = self._cli_execute(*sg_list)
        if out.find('HLU/ALU Pairs') == -1:
            LOG.info(_('NO LUNs in the storagegroup : %s ')
                     % (storage_group))
        else:
            sg_details = out.split('HLU/ALU Pairs:')[1]
            sg_lun_details = sg_details.split('Shareable')[0]
            lun_details = sg_lun_details.split('\n')

            for data in lun_details:
                if data not in ['', '  HLU Number     ALU Number',
                                '  ----------     ----------']:
                    data = data.strip()
                    items = data.split(' ')
                    lun_map[int(items[len(items) - 1])] = int(items[0])
            for lun in lun_map.iterkeys():
                if lun == int(allocated_lun_id):
                    host_lun_id = lun_map[lun]
                    LOG.debug('Host Lun Id : %s' % (host_lun_id))
                    break

        # finding the owner SP for the LUN
        lun_list = ('lun', '-list', '-l', allocated_lun_id, '-owner')
        out, rc = self._cli_execute(*lun_list)
        if rc == 0:
            output = out.split('\n')
            owner_sp = output[2].split('Current Owner:  SP ')[1]
            LOG.debug('Owner SP : %s' % (owner_sp))

        device = {
            'hostlunid': host_lun_id,
            'ownersp': owner_sp,
            'lunmap': lun_map,
        }
        return device

    def _get_host_lun_id(self, host_lun_id_list):
        # Returns the host lun id for the LUN to be added
        # in the storage group.

        used_hlu_set = set(host_lun_id_list)
        for hlu in self.hlu_set - used_hlu_set:
            return hlu
        return None

    def _add_lun_to_storagegroup(self, volume, storage_group):

        storage_groupname = storage_group
        volumename = volume['name']
        allocated_lun_id = self._find_lun_id(volumename)
        count = 0
        while(count < 5):
            device_info = self.find_device_details(volume, storage_group)
            device_number = device_info['hostlunid']
            if device_number < 0:
                lun_map = device_info['lunmap']
                if lun_map:
                    host_lun_id_list = lun_map.values()

                    if len(host_lun_id_list) >= self.max_luns:
                        msg = (_('The storage group has reached the '
                                 'maximum capacity of LUNs. '
                                 'Command to add LUN for volume - %s '
                                 'in storagegroup failed') % (volumename))
                        LOG.error(msg)
                        raise exception.VolumeBackendAPIException(data=msg)

                    host_lun_id = self._get_host_lun_id(host_lun_id_list)

                    if host_lun_id is None:
                        msg = (_('Unable to get new host lun id. Please '
                                 'check if the storage group can accommodate '
                                 'new LUN. '
                                 'Command to add LUN for volume - %s '
                                 'in storagegroup failed') % (volumename))
                        LOG.error(msg)
                        raise exception.VolumeBackendAPIException(data=msg)
                else:
                    host_lun_id = 1

                addhlu = ('storagegroup', '-addhlu', '-o',
                          '-gname', storage_groupname,
                          '-hlu', host_lun_id,
                          '-alu', allocated_lun_id)
                out, rc = self._cli_execute(*addhlu)
                LOG.debug('Add ALU %(alu)s to SG %(sg)s as %(hlu)s. '
                          'Output: %(out)s'
                          % {'alu': allocated_lun_id,
                             'sg': storage_groupname,
                             'hlu': host_lun_id,
                             'out': out})
                if rc == 0:
                    return host_lun_id
                if rc == 66:
                    LOG.warn(_('Requested Host LUN Number already in use'))
                count += 1
            else:
                LOG.warn(_('LUN was already added in the storage group'))
                return device_number

        if count == 5:
            msg = (_('Failed to add %s into SG') % (volumename))
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def _remove_lun_from_storagegroup(self, device_number, storage_group):

        storage_groupname = storage_group
        removehlu = ('storagegroup', '-removehlu',
                     '-gname', storage_groupname,
                     '-hlu', device_number,
                     '-o')

        out, rc = self._cli_execute(*removehlu)

        LOG.debug('Remove %(hlu)s from SG %(sg)s. Output: %(out)s'
                  % {'hlu': device_number,
                     'sg': storage_groupname,
                     'out': out})
        if rc != 0:
            msg = (_('Failed to remove %(hlu)s from %(sg)s')
                   % {'hlu': device_number, 'sg': storage_groupname})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def initialize_connection(self, volume, connector):
        """Initializes the connection and returns connection info."""

        hostname = connector['host']
        storage_group = self.get_storage_group(hostname)

        device_number = self._add_lun_to_storagegroup(volume, storage_group)
        return device_number

    def terminate_connection(self, volume, connector):
        """Disallow connection from connector."""
        hostname = connector['host']
        storage_group = self.get_storage_group(hostname)
        device_info = self.find_device_details(volume, storage_group)
        device_number = device_info['hostlunid']
        if device_number < 0:
            LOG.error(_('Could not locate the attached volume.'))
        else:
            self._remove_lun_from_storagegroup(device_number, storage_group)

    def _find_iscsi_protocol_endpoints(self, device_sp):
        """Returns the iSCSI initiators for a SP."""

        initiator_address = []

        connection_getport = ('connection', '-getport', '-sp', device_sp)
        out, _rc = self._cli_execute(*connection_getport)
        output = out.split('SP:  ')

        for port in output:
            port_info = port.split('\n')
            if port_info[0] == device_sp:
                port_wwn = port_info[2].split('Port WWN:')[1].strip()
                initiator_address.append(port_wwn)

        LOG.debug('WWNs found for SP %(devicesp)s '
                  'are: %(initiator_address)s'
                  % {'devicesp': device_sp,
                     'initiator_address': initiator_address})

        return initiator_address

    def _get_volumetype_extraspecs(self, volume):
        specs = {}

        type_id = volume['volume_type_id']
        if type_id is not None:
            specs = volume_types.get_volume_type_extra_specs(type_id)

        return specs

    def _get_provisioning_by_volume(self, volume):
        # By default, the user can not create thin LUN without thin
        # provisioning enabler.
        thinness = 'NonThin'
        spec_id = 'storagetype:provisioning'

        specs = self._get_volumetype_extraspecs(volume)
        if specs and spec_id in specs:
            provisioning = specs[spec_id].lower()
            if 'thin' == provisioning:
                thinness = 'Thin'
            elif 'thick' != provisioning:
                LOG.warning(_('Invalid value of extra spec '
                            '\'storagetype:provisioning\': %(provisioning)s')
                            % {'provisioning': specs[spec_id]})
        else:
            LOG.info(_('No extra spec \'storagetype:provisioning\' exist'))

        return thinness
