908 lines
38 KiB
Python
908 lines
38 KiB
Python
#
|
|
# Copyright (c) 2016 NEC Corporation.
|
|
# 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.
|
|
|
|
import errno
|
|
import os
|
|
import re
|
|
import traceback
|
|
|
|
from lxml import etree
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
from oslo_utils import units
|
|
|
|
from cinder import context
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
from cinder.volume import configuration
|
|
from cinder.volume.drivers.nec import cli
|
|
from cinder.volume.drivers.san import san
|
|
from cinder.volume import qos_specs
|
|
from cinder.volume import volume_types
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
FLAGS = cfg.CONF
|
|
|
|
mstorage_opts = [
|
|
cfg.IPOpt('nec_ismcli_fip',
|
|
default=None,
|
|
help='FIP address of M-Series Storage iSMCLI.'),
|
|
cfg.StrOpt('nec_ismcli_user',
|
|
default='',
|
|
help='User name for M-Series Storage iSMCLI.'),
|
|
cfg.StrOpt('nec_ismcli_password',
|
|
secret=True,
|
|
default='',
|
|
help='Password for M-Series Storage iSMCLI.'),
|
|
cfg.StrOpt('nec_ismcli_privkey',
|
|
default='',
|
|
help='Filename of RSA private key for '
|
|
'M-Series Storage iSMCLI.'),
|
|
cfg.StrOpt('nec_ldset',
|
|
default='',
|
|
help='M-Series Storage LD Set name for Compute Node.'),
|
|
cfg.StrOpt('nec_ldname_format',
|
|
default='LX:%s',
|
|
help='M-Series Storage LD name format for volumes.'),
|
|
cfg.StrOpt('nec_backup_ldname_format',
|
|
default='LX:%s',
|
|
help='M-Series Storage LD name format for snapshots.'),
|
|
cfg.StrOpt('nec_diskarray_name',
|
|
default='',
|
|
help='Diskarray name of M-Series Storage.'),
|
|
cfg.StrOpt('nec_ismview_dir',
|
|
default='/tmp/nec/cinder',
|
|
help='Output path of iSMview file.'),
|
|
cfg.IntOpt('nec_ssh_pool_port_number',
|
|
default=22,
|
|
help='Port number of ssh pool.'),
|
|
cfg.IntOpt('nec_unpairthread_timeout',
|
|
default=3600,
|
|
help='Timeout value of Unpairthread.'),
|
|
cfg.IntOpt('nec_backend_max_ld_count',
|
|
default=1024,
|
|
help='Maximum number of managing sessions.'),
|
|
cfg.BoolOpt('nec_actual_free_capacity',
|
|
default=False,
|
|
help='Return actual free capacity.'),
|
|
cfg.BoolOpt('nec_ismview_alloptimize',
|
|
default=False,
|
|
help='Use legacy iSMCLI command with optimization.'),
|
|
cfg.ListOpt('nec_pools',
|
|
default=[],
|
|
help='M-Series Storage pool numbers list to be used.'),
|
|
cfg.ListOpt('nec_backup_pools',
|
|
default=[],
|
|
help='M-Series Storage backup pool number to be used.'),
|
|
cfg.BoolOpt('nec_queryconfig_view',
|
|
default=False,
|
|
help='Use legacy iSMCLI command.'),
|
|
cfg.IntOpt('nec_iscsi_portals_per_cont',
|
|
default=0,
|
|
deprecated_for_removal=True,
|
|
help='Max number of iSCSI portals per controller. '
|
|
'0 => unlimited. '
|
|
'This option is deprecated and may '
|
|
'be removed in the next release.'),
|
|
cfg.BoolOpt('nec_auto_accesscontrol',
|
|
default=True,
|
|
help='Configure access control automatically.'),
|
|
cfg.StrOpt('nec_cv_ldname_format',
|
|
default='LX:__ControlVolume_%xh',
|
|
help='M-Series Storage Control Volume name format.'),
|
|
]
|
|
|
|
FLAGS.register_opts(mstorage_opts, group=configuration.SHARED_CONF_GROUP)
|
|
|
|
|
|
def convert_to_name(uuid):
|
|
alnum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
num = int(uuid.replace(("-"), ""), 16)
|
|
|
|
convertname = ""
|
|
while num != 0:
|
|
convertname = alnum[num % len(alnum)] + convertname
|
|
num = num - num % len(alnum)
|
|
num = num // len(alnum)
|
|
return convertname
|
|
|
|
|
|
def convert_to_id(value62):
|
|
alnum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
length = len(value62)
|
|
|
|
weight = 0
|
|
value = 0
|
|
index = 0
|
|
for i in reversed(range(0, length)):
|
|
num = alnum.find(value62[i])
|
|
if index != 0:
|
|
value += int(weight * (num))
|
|
else:
|
|
value = num
|
|
index += 1
|
|
weight = 62 ** index
|
|
|
|
value = '%032x' % value
|
|
|
|
uuid = value[0:8]
|
|
uuid += '-'
|
|
uuid += value[8:12]
|
|
uuid += '-'
|
|
uuid += value[12:16]
|
|
uuid += '-'
|
|
uuid += value[16:20]
|
|
uuid += '-'
|
|
uuid += value[20:]
|
|
|
|
return uuid
|
|
|
|
|
|
class MStorageVolumeCommon(object):
|
|
"""M-Series Storage volume common class."""
|
|
|
|
def do_setup(self, context):
|
|
self._context = context
|
|
|
|
def check_for_setup_error(self):
|
|
fip = self._configuration.safe_get('nec_ismcli_fip')
|
|
user = self._configuration.safe_get('nec_ismcli_user')
|
|
pw = self._configuration.safe_get('nec_ismcli_password')
|
|
key = self._configuration.safe_get('nec_ismcli_privkey')
|
|
pools = self._configuration.safe_get('nec_pools')
|
|
|
|
if fip is None or fip == '':
|
|
raise exception.ParameterNotFound(param='nec_ismcli_fip')
|
|
if user is None or user == '':
|
|
raise exception.ParameterNotFound(param='nec_ismcli_user')
|
|
if (pw is None or pw == '') and (key is None or key == ''):
|
|
msg = _('nec_ismcli_password nor nec_ismcli_privkey')
|
|
raise exception.ParameterNotFound(param=msg)
|
|
if pools is None or len(pools) == 0:
|
|
raise exception.ParameterNotFound(param='nec_pools')
|
|
|
|
def _set_config(self, configuration, host, driver_name):
|
|
self._configuration = configuration
|
|
self._host = host
|
|
self._driver_name = driver_name
|
|
|
|
self._configuration.append_config_values(mstorage_opts)
|
|
self._configuration.append_config_values(san.san_opts)
|
|
self._config_group = self._configuration.config_group
|
|
|
|
self._properties = self._set_properties()
|
|
self._cli = self._properties['cli']
|
|
|
|
def _create_ismview_dir(self,
|
|
ismview_dir,
|
|
diskarray_name,
|
|
driver_name,
|
|
host):
|
|
"""Create ismview directory."""
|
|
filename = diskarray_name
|
|
if filename == '':
|
|
filename = driver_name + '_' + host
|
|
|
|
ismview_path = os.path.join(ismview_dir, filename)
|
|
LOG.debug('ismview_path=%s.', ismview_path)
|
|
try:
|
|
if os.path.exists(ismview_path):
|
|
os.remove(ismview_path)
|
|
except OSError as e:
|
|
with excutils.save_and_reraise_exception() as ctxt:
|
|
if e.errno == errno.ENOENT:
|
|
ctxt.reraise = False
|
|
|
|
try:
|
|
os.makedirs(ismview_dir)
|
|
except OSError as e:
|
|
with excutils.save_and_reraise_exception() as ctxt:
|
|
if e.errno == errno.EEXIST:
|
|
ctxt.reraise = False
|
|
|
|
return ismview_path
|
|
|
|
def get_conf_properties(self):
|
|
confobj = self._configuration
|
|
|
|
pool_pools = []
|
|
for pool in confobj.safe_get('nec_pools'):
|
|
if pool.endswith('h'):
|
|
pool_pools.append(int(pool[:-1], 16))
|
|
else:
|
|
pool_pools.append(int(pool, 10))
|
|
pool_backup_pools = []
|
|
for pool in confobj.safe_get('nec_backup_pools'):
|
|
if pool.endswith('h'):
|
|
pool_backup_pools.append(int(pool[:-1], 16))
|
|
else:
|
|
pool_backup_pools.append(int(pool, 10))
|
|
|
|
return {
|
|
'cli_fip': confobj.safe_get('nec_ismcli_fip'),
|
|
'cli_user': confobj.safe_get('nec_ismcli_user'),
|
|
'cli_password': confobj.safe_get('nec_ismcli_password'),
|
|
'cli_privkey': confobj.safe_get('nec_ismcli_privkey'),
|
|
'pool_pools': pool_pools,
|
|
'pool_backup_pools': pool_backup_pools,
|
|
'pool_actual_free_capacity':
|
|
confobj.safe_get('nec_actual_free_capacity'),
|
|
'ldset_name': confobj.safe_get('nec_ldset'),
|
|
'ld_name_format': confobj.safe_get('nec_ldname_format'),
|
|
'ld_backupname_format':
|
|
confobj.safe_get('nec_backup_ldname_format'),
|
|
'ld_backend_max_count':
|
|
confobj.safe_get('nec_backend_max_ld_count'),
|
|
'thread_timeout': confobj.safe_get('nec_unpairthread_timeout'),
|
|
'ismview_dir': confobj.safe_get('nec_ismview_dir'),
|
|
'ismview_alloptimize': confobj.safe_get('nec_ismview_alloptimize'),
|
|
'ssh_pool_port_number':
|
|
confobj.safe_get('nec_ssh_pool_port_number'),
|
|
'diskarray_name': confobj.safe_get('nec_diskarray_name'),
|
|
'queryconfig_view': confobj.safe_get('nec_queryconfig_view'),
|
|
'portal_number': confobj.safe_get('nec_iscsi_portals_per_cont'),
|
|
'auto_accesscontrol': confobj.safe_get('nec_auto_accesscontrol'),
|
|
'cv_name_format': confobj.safe_get('nec_cv_ldname_format')
|
|
}
|
|
|
|
def _set_properties(self):
|
|
conf_properties = self.get_conf_properties()
|
|
|
|
ismview_path = self._create_ismview_dir(
|
|
conf_properties['ismview_dir'],
|
|
conf_properties['diskarray_name'],
|
|
self._driver_name,
|
|
self._host)
|
|
|
|
vendor_name, _product_dict = self.get_oem_parameter()
|
|
|
|
backend_name = self._configuration.safe_get('volume_backend_name')
|
|
ssh_timeout = self._configuration.safe_get('ssh_conn_timeout')
|
|
reserved_per = self._configuration.safe_get('reserved_percentage')
|
|
|
|
conf_properties['ssh_conn_timeout'] = ssh_timeout
|
|
conf_properties['reserved_percentage'] = reserved_per
|
|
conf_properties['ismview_path'] = ismview_path
|
|
conf_properties['vendor_name'] = vendor_name
|
|
conf_properties['products'] = _product_dict
|
|
conf_properties['backend_name'] = backend_name
|
|
conf_properties['cli'] = cli.MStorageISMCLI(conf_properties)
|
|
|
|
return conf_properties
|
|
|
|
def get_oem_parameter(self):
|
|
product = os.path.join(os.path.dirname(__file__), 'product.xml')
|
|
try:
|
|
with open(product, 'r') as f:
|
|
xml = f.read()
|
|
root = etree.fromstring(xml)
|
|
vendor_name = root.findall('./VendorName')[0].text
|
|
|
|
product_dict = {}
|
|
product_map = root.findall('./ProductMap/Product')
|
|
for s in product_map:
|
|
product_dict[s.attrib['Name']] = int(s.text, 10)
|
|
|
|
return vendor_name, product_dict
|
|
except OSError as e:
|
|
with excutils.save_and_reraise_exception() as ctxt:
|
|
if e.errno == errno.ENOENT:
|
|
ctxt.reraise = False
|
|
raise exception.NotFound(_('%s not found.') % product)
|
|
|
|
@staticmethod
|
|
def get_ldname(volid, volformat):
|
|
alnum = ('0123456789'
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
|
|
ldname = ""
|
|
num = int(volid.replace(("-"), ""), 16)
|
|
while num != 0:
|
|
ldname = alnum[num % len(alnum)] + ldname
|
|
num = num - num % len(alnum)
|
|
num = num // len(alnum)
|
|
|
|
return volformat % ldname
|
|
|
|
def get_ldset(self, ldsets):
|
|
ldset = None
|
|
if self._properties['ldset_name'] == '':
|
|
nldset = len(ldsets)
|
|
if nldset == 0:
|
|
msg = _('Logical Disk Set could not be found.')
|
|
raise exception.NotFound(msg)
|
|
else:
|
|
ldset = None
|
|
else:
|
|
if self._properties['ldset_name'] not in ldsets:
|
|
msg = (_('Logical Disk Set `%s` could not be found.') %
|
|
self._properties['ldset_name'])
|
|
raise exception.NotFound(msg)
|
|
ldset = ldsets[self._properties['ldset_name']]
|
|
return ldset
|
|
|
|
def get_pool_capacity(self, pools, ldsets):
|
|
pools = [pool for (pn, pool) in pools.items()
|
|
if len(self._properties['pool_pools']) == 0 or
|
|
pn in self._properties['pool_pools']]
|
|
|
|
free_capacity_gb = 0
|
|
total_capacity_gb = 0
|
|
for pool in pools:
|
|
# Convert to GB.
|
|
tmp_total = int(pool['total'] // units.Gi)
|
|
tmp_free = int(pool['free'] // units.Gi)
|
|
|
|
if free_capacity_gb < tmp_free:
|
|
total_capacity_gb = tmp_total
|
|
free_capacity_gb = tmp_free
|
|
|
|
return {'total_capacity_gb': total_capacity_gb,
|
|
'free_capacity_gb': free_capacity_gb}
|
|
|
|
def set_backend_max_ld_count(self, xml, root):
|
|
section = root.findall('./CMD_REQUEST')[0]
|
|
version = section.get('version').replace('Version ', '')[0:3]
|
|
version = float(version)
|
|
if version < 9.1:
|
|
if 512 < self._properties['ld_backend_max_count']:
|
|
self._properties['ld_backend_max_count'] = 512
|
|
else:
|
|
if 1024 < self._properties['ld_backend_max_count']:
|
|
self._properties['ld_backend_max_count'] = 1024
|
|
|
|
def get_diskarray_max_ld_count(self, xml, root):
|
|
max_ld_count = 0
|
|
for section in root.findall(
|
|
'./'
|
|
'CMD_REQUEST/'
|
|
'CHAPTER[@name="Disk Array"]/'
|
|
'OBJECT[@name="Disk Array"]/'
|
|
'SECTION[@name="Disk Array Detail Information"]'):
|
|
unit = section.find('./UNIT[@name="Product ID"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Product ID"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
else:
|
|
product_id = unit.text
|
|
if product_id in self._properties['products']:
|
|
max_ld_count = self._properties['products'][product_id]
|
|
else:
|
|
max_ld_count = 8192
|
|
LOG.debug('UNIT[@name="Product ID"] unknown id. '
|
|
'productId=%s', product_id)
|
|
LOG.debug('UNIT[@name="Product ID"] max_ld_count=%d.',
|
|
max_ld_count)
|
|
return max_ld_count
|
|
|
|
def get_pool_config(self, xml, root):
|
|
pools = {}
|
|
for xmlobj in root.findall('./'
|
|
'CMD_REQUEST/'
|
|
'CHAPTER[@name="Pool"]/'
|
|
'OBJECT[@name="Pool"]'):
|
|
section = xmlobj.find('./SECTION[@name="Pool Detail Information"]')
|
|
if section is None:
|
|
msg = (_('SECTION[@name="Pool Detail Information"] '
|
|
'not found. line=%(line)d out="%(out)s"') %
|
|
{'line': xmlobj.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
unit = section.find('./UNIT[@name="Pool No.(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Pool No.(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
pool_num = int(unit.text, 16)
|
|
unit = section.find('UNIT[@name="Pool Capacity"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Pool Capacity"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
total = int(unit.text, 10)
|
|
unit = section.find('UNIT[@name="Free Pool Capacity"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Free Pool Capacity"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
free = int(unit.text, 10)
|
|
if self._properties['pool_actual_free_capacity']:
|
|
unit = section.find('UNIT[@name="Used Pool Capacity"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Used Pool Capacity"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
used = int(unit.text, 10)
|
|
for section in xmlobj.findall('./SECTION[@name='
|
|
'"Virtual Capacity Pool '
|
|
'Information"]'):
|
|
unit = section.find('UNIT[@name="Actual Capacity"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Actual Capacity"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
total = int(unit.text, 10)
|
|
free = total - used
|
|
pool = {'pool_num': pool_num,
|
|
'total': total,
|
|
'free': free,
|
|
'ld_list': []}
|
|
pools[pool_num] = pool
|
|
return pools
|
|
|
|
def get_ld_config(self, xml, root, pools):
|
|
lds = {}
|
|
used_ldns = []
|
|
for section in root.findall('./'
|
|
'CMD_REQUEST/'
|
|
'CHAPTER[@name="Logical Disk"]/'
|
|
'OBJECT[@name="Logical Disk"]/'
|
|
'SECTION[@name="LD Detail Information"]'):
|
|
unit = section.find('./UNIT[@name="LDN(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LDN(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
ldn = int(unit.text, 16)
|
|
unit = section.find('./UNIT[@name="OS Type"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="OS Type"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
ostype = unit.text if unit.text is not None else ''
|
|
unit = section.find('./UNIT[@name="LD Name"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LD Name"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
ldname = ostype + ':' + unit.text
|
|
unit = section.find('./UNIT[@name="Pool No.(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Pool No.(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
pool_num = int(unit.text, 16)
|
|
|
|
unit = section.find('./UNIT[@name="LD Capacity"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LD Capacity"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# byte capacity transform GB capacity.
|
|
ld_capacity = int(unit.text, 10) // units.Gi
|
|
|
|
unit = section.find('./UNIT[@name="RPL Attribute"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="RPL Attribute"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
rplatr = unit.text
|
|
|
|
unit = section.find('./UNIT[@name="Purpose"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Purpose"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
purpose = unit.text
|
|
|
|
ld = {'ldname': ldname,
|
|
'ldn': ldn,
|
|
'pool_num': pool_num,
|
|
'ld_capacity': ld_capacity,
|
|
'RPL Attribute': rplatr,
|
|
'Purpose': purpose}
|
|
pools[pool_num]['ld_list'].append(ld)
|
|
lds[ldname] = ld
|
|
used_ldns.append(ldn)
|
|
return lds, used_ldns
|
|
|
|
def get_iscsi_ldset_config(self, xml, root):
|
|
ldsets = {}
|
|
for xmlobj in root.findall('./'
|
|
'CMD_REQUEST/'
|
|
'CHAPTER[@name="Access Control"]/'
|
|
'OBJECT[@name="LD Set(iSCSI)"]'):
|
|
ldsetlds = {}
|
|
portals = []
|
|
initiators = []
|
|
for unit in xmlobj.findall('./SECTION[@name="Portal"]/'
|
|
'UNIT[@name="Portal"]'):
|
|
if not unit.text.startswith('0.0.0.0:'):
|
|
portals.append(unit.text)
|
|
|
|
for unit in xmlobj.findall('./SECTION[@name="Initiator List"]/'
|
|
'UNIT[@name="Initiator List"]'):
|
|
initiators.append(unit.text)
|
|
|
|
section = xmlobj.find('./SECTION[@name="LD Set(iSCSI)'
|
|
' Information"]')
|
|
if section is None:
|
|
return ldsets
|
|
unit = section.find('./UNIT[@name="Platform"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Platform"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
platform = unit.text
|
|
unit = section.find('./UNIT[@name="LD Set Name"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LD Set Name"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
ldsetname = platform + ':' + unit.text
|
|
unit = section.find('./UNIT[@name="Target Mode"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Target Mode"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
tmode = unit.text
|
|
if tmode == 'Normal':
|
|
unit = section.find('./UNIT[@name="Target Name"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Target Name"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
iqn = unit.text
|
|
for section in xmlobj.findall('./SECTION[@name='
|
|
'"LUN/LD List"]'):
|
|
unit = section.find('./UNIT[@name="LDN(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LDN(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
ldn = int(unit.text, 16)
|
|
unit = section.find('./UNIT[@name="LUN(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LUN(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
lun = int(unit.text, 16)
|
|
ld = {'ldn': ldn,
|
|
'lun': lun,
|
|
'iqn': iqn}
|
|
ldsetlds[ldn] = ld
|
|
elif tmode == 'Multi-Target':
|
|
for section in xmlobj.findall('./SECTION[@name='
|
|
'"Target Information For '
|
|
'Multi-Target Mode"]'):
|
|
unit = section.find('./UNIT[@name="Target Name"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Target Name"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
iqn = unit.text
|
|
unit = section.find('./UNIT[@name="LDN(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LDN(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
if unit.text.startswith('-'):
|
|
continue
|
|
ldn = int(unit.text, 16)
|
|
unit = section.find('./UNIT[@name="LUN(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LUN(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
if unit.text.startswith('-'):
|
|
continue
|
|
lun = int(unit.text, 16)
|
|
ld = {'ldn': ldn,
|
|
'lun': lun,
|
|
'iqn': iqn}
|
|
ldsetlds[ldn] = ld
|
|
else:
|
|
LOG.debug('`%(mode)s` Unknown Target Mode. '
|
|
'line=%(line)d out="%(out)s"',
|
|
{'mode': tmode, 'line': unit.sourceline, 'out': xml})
|
|
ldset = {'ldsetname': ldsetname,
|
|
'protocol': 'iSCSI',
|
|
'mode': tmode,
|
|
'portal_list': portals,
|
|
'lds': ldsetlds,
|
|
'initiator_list': initiators}
|
|
ldsets[ldsetname] = ldset
|
|
return ldsets
|
|
|
|
def get_fc_ldset_config(self, xml, root):
|
|
ldsets = {}
|
|
for xmlobj in root.findall('./'
|
|
'CMD_REQUEST/'
|
|
'CHAPTER[@name="Access Control"]/'
|
|
'OBJECT[@name="LD Set(FC)"]'):
|
|
ldsetlds = {}
|
|
section = xmlobj.find('./SECTION[@name="LD Set(FC)'
|
|
' Information"]')
|
|
if section is None:
|
|
return ldsets
|
|
unit = section.find('./UNIT[@name="Platform"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Platform"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
platform = unit.text
|
|
unit = section.find('./UNIT[@name="LD Set Name"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LD Set Name"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
ldsetname = platform + ':' + unit.text
|
|
wwpns = []
|
|
ports = []
|
|
for section in xmlobj.findall('./SECTION[@name="Path List"]'):
|
|
unit = section.find('./UNIT[@name="Path"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Path"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
if unit.text.find('(') != -1:
|
|
ports.append(unit.text)
|
|
else:
|
|
wwpns.append(unit.text)
|
|
for section in xmlobj.findall('./SECTION[@name="LUN/LD List"]'):
|
|
unit = section.find('./UNIT[@name="LDN(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LDN(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
ldn = int(unit.text, 16)
|
|
unit = section.find('./UNIT[@name="LUN(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="LUN(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
lun = int(unit.text, 16)
|
|
ld = {'ldn': ldn,
|
|
'lun': lun}
|
|
ldsetlds[ldn] = ld
|
|
ldset = {'ldsetname': ldsetname,
|
|
'lds': ldsetlds,
|
|
'protocol': 'FC',
|
|
'wwpn': wwpns,
|
|
'port': ports}
|
|
ldsets[ldsetname] = ldset
|
|
return ldsets
|
|
|
|
def get_hostport_config(self, xml, root):
|
|
hostports = {}
|
|
for section in root.findall('./'
|
|
'CMD_REQUEST/'
|
|
'CHAPTER[@name="Controller"]/'
|
|
'OBJECT[@name="Host Port"]/'
|
|
'SECTION[@name="Host Director'
|
|
'/Host Port Information"]'):
|
|
unit = section.find('./UNIT[@name="Port No.(h)"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="Port No.(h)"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
units = unit.text.split('-')
|
|
director = int(units[0], 16)
|
|
port = int(units[1], 16)
|
|
unit = section.find('./UNIT[@name="IP Address"]')
|
|
if unit is None:
|
|
unit = section.find('./UNIT[@name="WWPN"]')
|
|
if unit is None:
|
|
msg = (_('UNIT[@name="WWPN"] not found. '
|
|
'line=%(line)d out="%(out)s"') %
|
|
{'line': section.sourceline, 'out': xml})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
wwpn = unit.text
|
|
hostport = {
|
|
'director': director,
|
|
'port': port,
|
|
'wwpn': wwpn,
|
|
'protocol': 'FC',
|
|
}
|
|
else:
|
|
ip = unit.text
|
|
if ip == '0.0.0.0':
|
|
continue
|
|
|
|
hostport = {
|
|
'director': director,
|
|
'port': port,
|
|
'ip': ip,
|
|
'protocol': 'iSCSI',
|
|
}
|
|
if director not in hostports:
|
|
hostports[director] = []
|
|
hostports[director].append(hostport)
|
|
return hostports
|
|
|
|
def configs(self, xml):
|
|
root = etree.fromstring(xml)
|
|
pools = self.get_pool_config(xml, root)
|
|
lds, used_ldns = self.get_ld_config(xml, root, pools)
|
|
iscsi_ldsets = self.get_iscsi_ldset_config(xml, root)
|
|
fc_ldsets = self.get_fc_ldset_config(xml, root)
|
|
hostports = self.get_hostport_config(xml, root)
|
|
diskarray_max_ld_count = self.get_diskarray_max_ld_count(xml, root)
|
|
|
|
self.set_backend_max_ld_count(xml, root)
|
|
|
|
ldsets = {}
|
|
ldsets.update(iscsi_ldsets)
|
|
ldsets.update(fc_ldsets)
|
|
|
|
return pools, lds, ldsets, used_ldns, hostports, diskarray_max_ld_count
|
|
|
|
def get_xml(self):
|
|
ismview_path = self._properties['ismview_path']
|
|
if os.path.exists(ismview_path) and os.path.isfile(ismview_path):
|
|
with open(ismview_path, 'r') as f:
|
|
xml = f.read()
|
|
LOG.debug('loaded from %s.', ismview_path)
|
|
else:
|
|
xml = self._cli.view_all(ismview_path, False, False)
|
|
return xml
|
|
|
|
def parse_xml(self):
|
|
try:
|
|
xml = self.get_xml()
|
|
return self.configs(xml)
|
|
except Exception:
|
|
LOG.debug('parse_xml Unexpected error. exception=%s',
|
|
traceback.format_exc())
|
|
xml = self._cli.view_all(self._properties['ismview_path'], False)
|
|
return self.configs(xml)
|
|
|
|
def get_volume_type_qos_specs(self, volume_type_id):
|
|
specs = {}
|
|
|
|
ctxt = context.get_admin_context()
|
|
if volume_type_id is not None:
|
|
volume_type = volume_types.get_volume_type(ctxt, volume_type_id)
|
|
|
|
qos_specs_id = volume_type.get('qos_specs_id')
|
|
if qos_specs_id is not None:
|
|
specs = qos_specs.get_qos_specs(ctxt, qos_specs_id)['specs']
|
|
|
|
LOG.debug('get_volume_type_qos_specs '
|
|
'volume_type=%(volume_type)s, '
|
|
'qos_specs_id=%(qos_spec_id)s, '
|
|
'specs=%(specs)s',
|
|
{'volume_type': volume_type,
|
|
'qos_spec_id': qos_specs_id,
|
|
'specs': specs})
|
|
return specs
|
|
|
|
def get_qos_parameters(self, specs, reset):
|
|
qos_params = {}
|
|
if 'upperlimit' in specs and specs['upperlimit'] is not None:
|
|
if self.validates_number(specs['upperlimit']) is True:
|
|
upper_limit = int(specs['upperlimit'], 10)
|
|
if ((upper_limit != 0) and
|
|
((upper_limit < 10) or (upper_limit > 1000000))):
|
|
raise exception.InvalidConfigurationValue(
|
|
value=upper_limit, option='upperlimit')
|
|
qos_params['upperlimit'] = upper_limit
|
|
else:
|
|
raise exception.InvalidConfigurationValue(
|
|
value=specs['upperlimit'], option='upperlimit')
|
|
else:
|
|
# 0: Set to no limit.(default)
|
|
# On the QoS function in NEC Storage, 0 means there is no
|
|
# limit.
|
|
# None: Keep current value.
|
|
qos_params['upperlimit'] = 0 if reset else None
|
|
|
|
if 'lowerlimit' in specs and specs['lowerlimit'] is not None:
|
|
if self.validates_number(specs['lowerlimit']) is True:
|
|
lower_limit = int(specs['lowerlimit'], 10)
|
|
if (lower_limit != 0 and (lower_limit < 10 or
|
|
lower_limit > 1000000)):
|
|
raise exception.InvalidConfigurationValue(
|
|
value=lower_limit, option='lowerlimit')
|
|
qos_params['lowerlimit'] = lower_limit
|
|
else:
|
|
raise exception.InvalidConfigurationValue(
|
|
value=specs['lowerlimit'], option='lowerlimit')
|
|
else:
|
|
# 0: Set to no limit.(default)
|
|
# On the QoS function in NEC Storage, 0 means there is no
|
|
# limit.
|
|
# None: Keep current value.
|
|
qos_params['lowerlimit'] = 0 if reset else None
|
|
|
|
if 'upperreport' in specs:
|
|
if specs['upperreport'] not in ['on', 'off']:
|
|
LOG.debug('Illegal arguments. '
|
|
'upperreport is not on or off.'
|
|
'upperreport=%s', specs['upperreport'])
|
|
qos_params['upperreport'] = 'off' if reset else None
|
|
else:
|
|
qos_params['upperreport'] = specs['upperreport']
|
|
else:
|
|
# off: Set to no report.(default)
|
|
# None: Keep current value.
|
|
qos_params['upperreport'] = 'off' if reset else None
|
|
return qos_params
|
|
|
|
def check_accesscontrol(self, ldsets, ld):
|
|
"""Check Logical disk is in-use or not."""
|
|
set_accesscontrol = False
|
|
for ldset in ldsets.values():
|
|
if ld['ldn'] in ldset['lds']:
|
|
set_accesscontrol = True
|
|
break
|
|
return set_accesscontrol
|
|
|
|
def validates_number(self, value):
|
|
return re.match(r'^(?![-+]0+$)[-+]?([1-9][0-9]*)?[0-9](\.[0-9]+)?$',
|
|
'%s' % value) and True or False
|