cinder/cinder/volume/drivers/qnap.py

1572 lines
60 KiB
Python

# Copyright (c) 2016 QNAP Systems, 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.
"""
Volume driver for QNAP Storage.
This driver supports QNAP Storage for iSCSI.
"""
import base64
import eventlet
import functools
import re
import ssl
import time
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import timeutils
from oslo_utils import units
import six
from six.moves import http_client
from six.moves import urllib
from cinder import exception
from cinder.i18n import _
from cinder import interface
from cinder.volume import configuration
from cinder.volume.drivers.san import san
LOG = logging.getLogger(__name__)
qnap_opts = [
cfg.URIOpt('qnap_management_url',
help='The URL to management QNAP Storage'),
cfg.StrOpt('qnap_poolname',
help='The pool name in the QNAP Storage'),
cfg.StrOpt('qnap_storage_protocol',
default='iscsi',
help='Communication protocol to access QNAP storage'),
]
CONF = cfg.CONF
CONF.register_opts(qnap_opts, group=configuration.SHARED_CONF_GROUP)
@interface.volumedriver
class QnapISCSIDriver(san.SanISCSIDriver):
"""OpenStack driver to enable QNAP Storage.
Version history:
1.0.0 - Initial driver (Only iSCSI)
"""
# ThirdPartySystems wiki page
CI_WIKI_NAME = "QNAP_CI"
# TODO(smcginnis) Either remove this if CI requirement are met, or
# remove this driver in the Queens release per normal deprecation
SUPPORTED = False
VERSION = '1.0.0'
TIME_INTERVAL = 3
def __init__(self, *args, **kwargs):
"""Initialize QnapISCSIDriver."""
super(QnapISCSIDriver, self).__init__(*args, **kwargs)
self.api_executor = None
self.group_stats = {}
self.configuration.append_config_values(qnap_opts)
def _check_config(self):
"""Ensure that the flags we care about are set."""
LOG.debug('in _check_config')
required_config = ['qnap_management_url',
'san_login',
'san_password',
'qnap_poolname',
'qnap_storage_protocol']
for attr in required_config:
if not getattr(self.configuration, attr, None):
raise exception.InvalidConfigurationValue(
reason=_('%s is not set.') % attr)
def do_setup(self, context):
"""Setup the QNAP Cinder volume driver."""
self._check_config()
self.ctxt = context
LOG.debug('context: %s', context)
# Setup API Executor
try:
self.api_executor = self.creat_api_executor()
except Exception:
LOG.error('Failed to create HTTP client. '
'Check ip, port, username, password'
' and make sure the array version is compatible')
msg = _('Failed to create HTTP client.')
raise exception.VolumeDriverException(message=msg)
def check_for_setup_error(self):
"""Check the status of setup."""
pass
def creat_api_executor(self):
"""Create api executor by nas model."""
self.api_executor = QnapAPIExecutor(
username=self.configuration.san_login,
password=self.configuration.san_password,
management_url=self.configuration.qnap_management_url)
nas_model_name, internal_model_name, fw_version = (
self.api_executor.get_basic_info(
self.configuration.qnap_management_url))
pattern = re.compile(r"^([A-Z]+)-?[A-Z]{0,2}(\d+)\d{2}(U|[a-z]*)")
matches = pattern.match(nas_model_name)
if not matches:
return None
model_type = matches.group(1)
ts_model_types = [
"TS", "SS", "IS", "TVS", "TDS", "TBS"
]
tes_model_types = [
"TES"
]
es_model_types = [
"ES"
]
if model_type in ts_model_types:
if (fw_version.startswith("4.2") or fw_version.startswith("4.3")):
LOG.debug('Create TS API Executor')
# modify the pool name to pool index
self.configuration.qnap_poolname = (
self._get_ts_model_pool_id(
self.configuration.qnap_poolname))
return (QnapAPIExecutorTS(
username=self.configuration.san_login,
password=self.configuration.san_password,
management_url=self.configuration.qnap_management_url))
elif model_type in tes_model_types:
if 'TS' in internal_model_name:
if (fw_version.startswith("4.2") or
fw_version.startswith("4.3")):
LOG.debug('Create TS API Executor')
# modify the pool name to poole index
self.configuration.qnap_poolname = (
self._get_ts_model_pool_id(
self.configuration.qnap_poolname))
return (QnapAPIExecutorTS(
username=self.configuration.san_login,
password=self.configuration.san_password,
management_url=self.configuration.qnap_management_url))
if (fw_version.startswith("1.1.2") or
fw_version.startswith("1.1.3")):
LOG.debug('Create TES API Executor')
return (QnapAPIExecutorTES(
username=self.configuration.san_login,
password=self.configuration.san_password,
management_url=self.configuration.qnap_management_url))
elif model_type in es_model_types:
if (fw_version.startswith("1.1.2") or
fw_version.startswith("1.1.3")):
LOG.debug('Create ES API Executor')
return (QnapAPIExecutor(
username=self.configuration.san_login,
password=self.configuration.san_password,
management_url=self.configuration.qnap_management_url))
msg = _('Model not support')
raise exception.VolumeDriverException(message=msg)
def _get_ts_model_pool_id(self, pool_name):
"""Modify the pool name to poole index."""
pattern = re.compile(r"^(\d+)+|^Storage Pool (\d+)+")
matches = pattern.match(pool_name)
LOG.debug('matches.group(1): %s', matches.group(1))
LOG.debug('matches.group(2): %s', matches.group(2))
if matches.group(1):
return matches.group(1)
else:
return matches.group(2)
def _gen_random_name(self):
return "cinder-{0}".format(timeutils.
utcnow().
strftime('%Y%m%d%H%M%S%f'))
def _get_volume_metadata(self, volume):
volume_metadata = {}
if 'volume_metadata' in volume:
for metadata in volume['volume_metadata']:
volume_metadata[metadata['key']] = metadata['value']
return volume_metadata
def _gen_lun_name(self):
create_lun_name = ''
while True:
create_lun_name = self._gen_random_name()
# If lunname with the name exists, need to change to
# a different name
created_lun = self.api_executor.get_lun_info(
LUNName=create_lun_name)
if created_lun is None:
break
return create_lun_name
def create_volume(self, volume):
"""Create a new volume."""
start_time = time.time()
LOG.debug('in create_volume')
LOG.debug('volume: %s', volume.__dict__)
reserve = self.configuration.san_thin_provision
# User could create two volume with the same name on horizon.
# Therefore, We should not use displayname to create lun on nas.
create_lun_name = self._gen_lun_name()
create_lun_index = self.api_executor.create_lun(
volume,
self.configuration.qnap_poolname,
create_lun_name,
reserve)
max_wait_sec = 600
try_times = 0
lun_naa = ""
while True:
created_lun = self.api_executor.get_lun_info(
LUNIndex=create_lun_index)
if created_lun.find('LUNNAA') is not None:
lun_naa = created_lun.find('LUNNAA').text
try_times = try_times + 3
eventlet.sleep(self.TIME_INTERVAL)
if(try_times > max_wait_sec or lun_naa is not None):
break
LOG.debug('LUNNAA: %s', lun_naa)
_metadata = self._get_volume_metadata(volume)
_metadata['LUNNAA'] = lun_naa
_metadata['LunName'] = create_lun_name
elapsed_time = time.time() - start_time
LOG.debug('create_volume elapsed_time: %s', elapsed_time)
return {'metadata': _metadata}
def delete_volume(self, volume):
"""Delete the specified volume."""
start_time = time.time()
LOG.debug('volume: %s', volume.__dict__)
lun_naa = self._get_lun_naa_from_volume_metadata(volume)
if lun_naa == '':
LOG.debug('Volume %s does not exist.', volume.id)
return
del_lun = self.api_executor.get_lun_info(LUNNAA=lun_naa)
if del_lun is None:
LOG.debug('Volume %s does not exist.', lun_naa)
return
lun_index = del_lun.find('LUNIndex').text
LOG.debug('LUNIndex: %s', lun_index)
# if lun is mapping at target, the delete action will fail
if del_lun.find('LUNStatus').text == '2':
target_index = (del_lun.find('LUNTargetList')
.find('row').find('targetIndex').text)
LOG.debug('target_index: %s', target_index)
self.api_executor.disable_lun(lun_index, target_index)
self.api_executor.unmap_lun(lun_index, target_index)
is_lun_busy = False
while True:
is_lun_busy = self.api_executor.delete_lun(lun_index)
if not is_lun_busy:
break
elapsed_time = time.time() - start_time
LOG.debug('delete_volume elapsed_time: %s', elapsed_time)
def _get_lun_naa_from_volume_metadata(self, volume):
lun_naa = ''
for metadata in volume['volume_metadata']:
if metadata['key'] == 'LUNNAA':
lun_naa = metadata['value']
break
return lun_naa
def _extend_lun(self, volume, lun_naa):
LOG.debug('volume: %s', volume.__dict__)
if lun_naa == '':
lun_naa = self._get_lun_naa_from_volume_metadata(volume)
LOG.debug('lun_naa: %s', lun_naa)
selected_lun = self.api_executor.get_lun_info(
LUNNAA=lun_naa)
lun_index = selected_lun.find('LUNIndex').text
LOG.debug('LUNIndex: %s', lun_index)
lun_name = selected_lun.find('LUNName').text
LOG.debug('LUNName: %s', lun_name)
lun_thin_allocate = selected_lun.find('LUNThinAllocate').text
LOG.debug('LUNThinAllocate: %s', lun_thin_allocate)
lun_path = ''
if selected_lun.find('LUNPath') is not None:
lun_path = selected_lun.find('LUNPath').text
LOG.debug('LUNPath: %s', lun_path)
lun_status = selected_lun.find('LUNStatus').text
LOG.debug('LUNStatus: %s', lun_status)
lun = {'LUNName': lun_name,
'LUNCapacity': volume['size'],
'LUNIndex': lun_index,
'LUNThinAllocate': lun_thin_allocate,
'LUNPath': lun_path,
'LUNStatus': lun_status}
self.api_executor.edit_lun(lun)
def _create_snapshot_name(self, lun_index):
create_snapshot_name = ''
while True:
# If snapshot with the name exists, need to change to
# a different name
create_snapshot_name = self._gen_random_name()
snapshot = self.api_executor.get_snapshot_info(
lun_index=lun_index, snapshot_name=create_snapshot_name)
if snapshot is None:
break
return create_snapshot_name
def create_cloned_volume(self, volume, src_vref):
"""Create a clone of the specified volume."""
LOG.debug('Entering create_cloned_volume...')
LOG.debug('volume: %s', volume.__dict__)
LOG.debug('src_vref: %s', src_vref.__dict__)
LOG.debug('volume_metadata: %s', volume['volume_metadata'])
src_lun_naa = self._get_lun_naa_from_volume_metadata(src_vref)
# Below is to clone a volume from a snapshot in the snapshot manager
src_lun = self.api_executor.get_lun_info(
LUNNAA=src_lun_naa)
lun_index = src_lun.find('LUNIndex').text
LOG.debug('LUNIndex: %s', lun_index)
# User could create two snapshot with the same name on horizon.
# Therefore, we should not use displayname to create snapshot on nas.
create_snapshot_name = self._create_snapshot_name(lun_index)
self.api_executor.create_snapshot_api(lun_index, create_snapshot_name)
created_snapshot = self.api_executor.get_snapshot_info(
lun_index=lun_index, snapshot_name=create_snapshot_name)
snapshot_id = created_snapshot.find('snapshot_id').text
LOG.debug('snapshot_id: %s', snapshot_id)
# User could create two volume with the same name on horizon.
# Therefore, We should not use displayname to create lun on nas.
while True:
cloned_lun_name = self._gen_random_name()
# If lunname with the name exists, need to change to
# a different name
cloned_lun = self.api_executor.get_lun_info(
LUNName=cloned_lun_name)
if cloned_lun is None:
break
self.api_executor.clone_snapshot(snapshot_id, cloned_lun_name)
max_wait_sec = 600
try_times = 0
lun_naa = ""
while True:
created_lun = self.api_executor.get_lun_info(
LUNName=cloned_lun_name)
if created_lun.find('LUNNAA') is not None:
lun_naa = created_lun.find('LUNNAA').text
try_times = try_times + 3
eventlet.sleep(self.TIME_INTERVAL)
if(try_times > max_wait_sec or lun_naa is not None):
break
LOG.debug('LUNNAA: %s', lun_naa)
if (volume['size'] > src_vref['size']):
self._extend_lun(volume, lun_naa)
_metadata = self._get_volume_metadata(volume)
_metadata['LUNNAA'] = lun_naa
_metadata['LunName'] = cloned_lun_name
return {'metadata': _metadata}
def create_snapshot(self, snapshot):
"""Create a snapshot."""
LOG.debug('snapshot: %s', snapshot.__dict__)
LOG.debug('snapshot id: %s', snapshot['id'])
# Below is to create snapshot in the snapshot manager
LOG.debug('volume_metadata: %s', snapshot.volume['metadata'])
volume_metadata = snapshot.volume['metadata']
LOG.debug('lun_naa: %s', volume_metadata['LUNNAA'])
lun_naa = volume_metadata['LUNNAA']
src_lun = self.api_executor.get_lun_info(LUNNAA=lun_naa)
lun_index = src_lun.find('LUNIndex').text
LOG.debug('LUNIndex: %s', lun_index)
# User could create two snapshot with the same name on horizon.
# Therefore, We should not use displayname to create snapshot on nas.
create_snapshot_name = self._create_snapshot_name(lun_index)
LOG.debug('create_snapshot_name: %s', create_snapshot_name)
self.api_executor.create_snapshot_api(lun_index, create_snapshot_name)
max_wait_sec = 600
try_times = 0
snapshot_id = ""
while True:
created_snapshot = self.api_executor.get_snapshot_info(
lun_index=lun_index, snapshot_name=create_snapshot_name)
if created_snapshot is not None:
snapshot_id = created_snapshot.find('snapshot_id').text
try_times = try_times + 3
eventlet.sleep(self.TIME_INTERVAL)
if(try_times > max_wait_sec or created_snapshot is not None):
break
LOG.debug('created_snapshot: %s', created_snapshot)
LOG.debug('snapshot_id: %s', snapshot_id)
_metadata = snapshot['metadata']
_metadata['snapshot_id'] = snapshot_id
_metadata['SnapshotName'] = create_snapshot_name
return {'metadata': _metadata}
def delete_snapshot(self, snapshot):
"""Delete a snapshot."""
LOG.debug('snapshot: %s', snapshot.__dict__)
# Below is to delete snapshot in the snapshot manager
snap_metadata = snapshot['metadata']
if 'snapshot_id' not in snap_metadata:
return
LOG.debug('snapshot_id: %s', snap_metadata['snapshot_id'])
snapshot_id = snap_metadata['snapshot_id']
self.api_executor.api_delete_snapshot(snapshot_id)
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot."""
LOG.debug('in create_volume_from_snapshot')
LOG.debug('volume: %s', volume.__dict__)
LOG.debug('snapshot: %s', snapshot.__dict__)
# Below is to clone a volume from a snapshot in the snapshot manager
snap_metadata = snapshot['metadata']
if 'snapshot_id' not in snap_metadata:
LOG.debug('Metadata of the snapshot is invalid')
msg = _('Metadata of the snapshot is invalid')
raise exception.VolumeDriverException(message=msg)
LOG.debug('snapshot_id: %s', snap_metadata['snapshot_id'])
snapshot_id = snap_metadata['snapshot_id']
# User could create two volume with the same name on horizon.
# Therefore, We should not use displayname to create lun on nas.
create_lun_name = self._gen_lun_name()
self.api_executor.clone_snapshot(
snapshot_id, create_lun_name)
max_wait_sec = 600
try_times = 0
lun_naa = ""
while True:
created_lun = self.api_executor.get_lun_info(
LUNName=create_lun_name)
if created_lun.find('LUNNAA') is not None:
lun_naa = created_lun.find('LUNNAA').text
try_times = try_times + 3
eventlet.sleep(self.TIME_INTERVAL)
if(try_times > max_wait_sec or lun_naa is not None):
break
if (volume['size'] > snapshot['volume_size']):
self._extend_lun(volume, lun_naa)
_metadata = self._get_volume_metadata(volume)
_metadata['LUNNAA'] = lun_naa
_metadata['LunName'] = create_lun_name
return {'metadata': _metadata}
def get_volume_stats(self, refresh=False):
"""Get volume stats. This is more of getting group stats."""
LOG.debug('in get_volume_stats')
if refresh:
backend_name = (self.configuration.safe_get(
'volume_backend_name') or
self.__class__.__name__)
LOG.debug('backend_name=%(backend_name)s',
{'backend_name': backend_name})
selected_pool = self.api_executor.get_specific_poolinfo(
self.configuration.qnap_poolname)
capacity_bytes = int(selected_pool.find('capacity_bytes').text)
LOG.debug('capacity_bytes: %s GB', capacity_bytes / units.Gi)
freesize_bytes = int(selected_pool.find('freesize_bytes').text)
LOG.debug('freesize_bytes: %s GB', freesize_bytes / units.Gi)
provisioned_bytes = int(selected_pool.find('allocated_bytes').text)
driver_protocol = self.configuration.qnap_storage_protocol
LOG.debug(
'provisioned_bytes: %s GB', provisioned_bytes / units.Gi)
self.group_stats = {'volume_backend_name': backend_name,
'vendor_name': 'QNAP',
'driver_version': self.VERSION,
'storage_protocol': driver_protocol}
# single pool now, need support multiple pools in the future
single_pool = dict(
pool_name=self.configuration.qnap_poolname,
total_capacity_gb=capacity_bytes / units.Gi,
free_capacity_gb=freesize_bytes / units.Gi,
provisioned_capacity_gb=provisioned_bytes / units.Gi,
reserved_percentage=self.configuration.reserved_percentage,
QoS_support=False)
self.group_stats['pools'] = [single_pool]
return self.group_stats
def extend_volume(self, volume, new_size):
"""Extend an existing volume."""
LOG.debug('Entering extend_volume volume=%(vol)s '
'new_size=%(size)s',
{'vol': volume['display_name'], 'size': new_size})
volume['size'] = new_size
self._extend_lun(volume, '')
def initialize_connection(self, volume, connector):
"""Create a target with initiator iqn to attach a volume."""
start_time = time.time()
LOG.debug('in initialize_connection')
LOG.debug('volume: %s', volume.__dict__)
LOG.debug('connector: %s', connector)
lun_status = self.enum('createing', 'unmapped', 'mapped')
ret = self.api_executor.get_iscsi_portal_info()
root = ET.fromstring(ret['data'])
iscsi_port = root.find('iSCSIPortal').find('servicePort').text
LOG.debug('iscsiPort: %s', iscsi_port)
target_iqn_prefix = root.find(
'iSCSIPortal').find('targetIQNPrefix').text
LOG.debug('targetIQNPrefix: %s', target_iqn_prefix)
target_iqn_postfix = (root.find('iSCSIPortal').
find('targetIQNPostfix').text)
LOG.debug('target_iqn_postfix: %s', target_iqn_postfix)
lun_naa = self._get_lun_naa_from_volume_metadata(volume)
if lun_naa == '':
msg = (_("Volume %s does not exist.") % volume.id)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
LOG.debug('volume[name]: %s', volume['name'])
LOG.debug('volume[display_name]: %s', volume['display_name'])
selected_lun = self.api_executor.get_lun_info(LUNNAA=lun_naa)
lun_index = selected_lun.find('LUNIndex').text
LOG.debug('LUNIndex: %s', lun_index)
lun_owner = ''
lun_slot_id = ''
if selected_lun.find('lun_owner') is not None:
lun_owner = selected_lun.find('lun_owner').text
LOG.debug('lun_owner: %s', lun_owner)
lun_slot_id = '0' if (lun_owner == 'SCA') else '1'
LOG.debug('lun_slot_id: %s', lun_slot_id)
ret = self.api_executor.get_all_iscsi_portal_setting()
root = ET.fromstring(ret['data'])
target_index = ''
target_iqn = ''
# find the targets have acl with connector['initiator']
target_with_initiator_list = []
target_acl_tree = root.find('targetACL')
target_acl_list = target_acl_tree.findall('row')
tmp_target_iqn = ''
for targetACL in target_acl_list:
tmp_target_iqn = targetACL.find('targetIQN').text
# If lun and the targetiqn in different controller,
# skip the targetiqn, in case lun in sca map to target of scb
LOG.debug('lun_slot_id: %s', lun_slot_id)
LOG.debug('tmp_target_iqn[-1]: %s', tmp_target_iqn[-1])
if (lun_slot_id != ''):
if (lun_slot_id != tmp_target_iqn[-1]):
LOG.debug('skip the targetiqn')
continue
target_init_info_list = targetACL.findall('targetInitInfo')
for targetInitInfo in target_init_info_list:
if(targetInitInfo.find('initiatorIQN').text ==
connector['initiator']):
target_with_initiator_list.append(
targetACL.find('targetIndex').text)
# find the target in target_with_initiator_list with ready status
target_tree = root.find('iSCSITargetList')
target_list = target_tree.findall('targetInfo')
for target_with_initiator in target_with_initiator_list:
for target in target_list:
if(target_with_initiator == target.find('targetIndex').text):
if int(target.find('targetStatus').text) >= 0:
target_index = target_with_initiator
target_iqn = target.find('targetIQN').text
# create a new target if no target has ACL connector['initiator']
LOG.debug('exist target_index: %s', target_index)
if not target_index:
target_name = self._gen_random_name()
LOG.debug('target_name: %s', target_name)
target_index = self.api_executor.create_target(
target_name, lun_owner)
LOG.debug('targetIndex: %s', target_index)
target_info = self.api_executor.get_target_info(target_index)
target_iqn = target_info.find('targetIQN').text
LOG.debug('target_iqn: %s', target_iqn)
# TS NAS have to remove default ACL
default_acl = target_iqn_prefix[:target_iqn_prefix.find(":") + 1]
default_acl = default_acl + "all:iscsi.default.ffffff"
LOG.debug('default_acl: %s', default_acl)
self.api_executor.remove_target_init(target_iqn, default_acl)
# add ACL
self.api_executor.add_target_init(
target_iqn, connector['initiator'])
LOG.debug('LUNStatus: %s', selected_lun.find('LUNStatus').text)
# lun does not map to any target
if selected_lun.find('LUNStatus').text == str(lun_status.unmapped):
self.api_executor.map_lun(lun_index, target_index)
properties = {}
properties['target_discovered'] = True
properties['target_portal'] = (self.configuration.iscsi_ip_address +
':' + iscsi_port)
properties['target_iqn'] = target_iqn
LOG.debug('properties[target_iqn]: %s', properties['target_iqn'])
lun_naa = self._get_lun_naa_from_volume_metadata(volume)
LOG.debug('LUNNAA: %s', lun_naa)
# LUNNumber of lun will be updated after map lun to target, so here
# get lnu info again
mapped_lun = self.api_executor.get_lun_info(LUNNAA=lun_naa)
target_lun_id = int(mapped_lun.find('LUNTargetList').find(
'row').find('LUNNumber').text)
LOG.debug('target_lun_id: %s', target_lun_id)
properties['target_lun'] = target_lun_id
properties['volume_id'] = volume['id'] # used by xen currently
"""Below are settings for multipath"""
target_iqns = []
eth_list = self.api_executor.get_ethernet_ip(type='data')
target_portals = []
target_portals.append(
self.configuration.iscsi_ip_address + ':' + iscsi_port)
target_iqns.append(target_iqn)
for eth in eth_list:
if eth == self.configuration.iscsi_ip_address:
continue
target_portals.append(eth + ':' + iscsi_port)
target_iqns.append(target_iqn)
properties['target_portals'] = target_portals
properties['target_iqns'] = target_iqns
properties['target_luns'] = [target_lun_id] * len(target_portals)
LOG.debug('properties: %s', properties)
elapsed_time = time.time() - start_time
LOG.debug('initialize_connection elapsed_time: %s', elapsed_time)
return {
'driver_volume_type': 'iscsi',
'data': properties,
}
def enum(self, *sequential, **named):
"""Enum method."""
enums = dict(zip(sequential, range(len(sequential))), **named)
return type('Enum', (), enums)
def terminate_connection(self, volume, connector, **kwargs):
"""Driver entry point to unattach a volume from an instance."""
start_time = time.time()
LOG.debug('in terminate_connection')
LOG.debug('volume: %s', volume.__dict__)
LOG.debug('connector: %s', connector)
# get lun index
lun_naa = self._get_lun_naa_from_volume_metadata(volume)
LOG.debug('lun_naa: %s', lun_naa)
selected_lun = self.api_executor.get_lun_info(
LUNNAA=lun_naa)
lun_index = selected_lun.find('LUNIndex').text
LOG.debug('LUNIndex: %s', lun_index)
lun_status = self.enum('createing', 'unmapped', 'mapped')
LOG.debug('LUNStatus: %s', selected_lun.find('LUNStatus').text)
LOG.debug('lun_status.mapped: %s', six.text_type(lun_status.mapped))
# lun does not map to any target
if (selected_lun.find('LUNStatus').text) != (
six.text_type(lun_status.mapped)):
return
target_index = (selected_lun.find('LUNTargetList')
.find('row').find('targetIndex').text)
LOG.debug('target_index: %s', target_index)
self.api_executor.disable_lun(lun_index, target_index)
self.api_executor.unmap_lun(lun_index, target_index)
elapsed_time = time.time() - start_time
LOG.debug('terminate_connection elapsed_time : %s', elapsed_time)
def update_migrated_volume(
self, context, volume, new_volume, original_volume_status):
"""Return model update for migrated volume."""
LOG.debug('volume: %s', volume.__dict__)
LOG.debug('new_volume: %s', new_volume.__dict__)
LOG.debug('original_volume_status: %s', original_volume_status)
_metadata = self._get_volume_metadata(new_volume)
# metadata will not be swap after migration wiht liberty version
# , and the metadata of new volume is diifferent with the metadata
# of original volume. Therefore, we need to update the migrated volume
if not hasattr(new_volume, '_orig_metadata'):
model_update = {'metadata': _metadata}
return model_update
def _connection_checker(func):
"""Decorator to check session has expired or not."""
@functools.wraps(func)
def inner_connection_checker(self, *args, **kwargs):
LOG.debug('in _connection_checker')
for attempts in range(5):
try:
return func(self, *args, **kwargs)
except exception.VolumeBackendAPIException as e:
pattern = re.compile(
r".*Session id expired$")
matches = pattern.match(six.text_type(e))
if matches:
if attempts < 5:
LOG.debug('Session might have expired.'
' Trying to relogin')
self._login()
continue
LOG.error('Re-throwing Exception %s', e)
raise
return inner_connection_checker
class QnapAPIExecutor(object):
"""Makes QNAP API calls for ES NAS."""
def __init__(self, *args, **kwargs):
"""Init function."""
self.sid = None
self.username = kwargs['username']
self.password = kwargs['password']
self.ip, self.port, self.ssl = (
self._parse_management_url(kwargs['management_url']))
self._login()
def _parse_management_url(self, management_url):
pattern = re.compile(r"(http|https)\:\/\/(\S+)\:(\d+)")
matches = pattern.match(management_url)
if matches.group(1) == 'http':
management_ssl = False
else:
management_ssl = True
management_ip = matches.group(2)
management_port = matches.group(3)
return management_ip, management_port, management_ssl
def get_basic_info(self, management_url):
"""Get the basic information of NAS."""
LOG.debug('in get_basic_info')
management_ip, management_port, management_ssl = (
self._parse_management_url(management_url))
connection = None
if management_ssl:
if hasattr(ssl, '_create_unverified_context'):
context = ssl._create_unverified_context()
connection = http_client.HTTPSConnection(management_ip,
port=management_port,
context=context)
else:
connection = http_client.HTTPSConnection(management_ip,
port=management_port)
else:
connection = (
http_client.HTTPConnection(management_ip, management_port))
connection.request('GET', '/cgi-bin/authLogin.cgi')
response = connection.getresponse()
data = response.read()
LOG.debug('response data: %s', data)
root = ET.fromstring(data)
nas_model_name = root.find('model/displayModelName').text
internal_model_name = root.find('model/internalModelName').text
fw_version = root.find('firmware/version').text
return nas_model_name, internal_model_name, fw_version
def _execute_and_get_response_details(self, nas_ip, url, post_parm=None):
"""Will prepare response after executing an http request."""
LOG.debug('port: %(port)s, ssl: %(ssl)s',
{'port': self.port, 'ssl': self.ssl})
res_details = {}
# Prepare the connection
if self.ssl:
if hasattr(ssl, '_create_unverified_context'):
context = ssl._create_unverified_context()
connection = http_client.HTTPSConnection(nas_ip,
port=self.port,
context=context)
else:
connection = http_client.HTTPSConnection(
nas_ip, port=self.port)
else:
connection = http_client.HTTPConnection(nas_ip, self.port)
# Make the connection
if post_parm is None:
connection.request('GET', url)
else:
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"charset": "utf-8"}
connection.request('POST', url, post_parm, headers)
# Extract the response as the connection was successful
start_time = time.time()
response = connection.getresponse()
elapsed_time = time.time() - start_time
LOG.debug('cgi elapsed_time: %s', elapsed_time)
# Read the response
data = response.read()
LOG.debug('response data: %s', data)
# Extract http error msg if any
error_details = None
res_details['data'] = data
res_details['error'] = error_details
res_details['http_status'] = response.status
connection.close()
return res_details
def execute_login(self):
"""Login and return sid."""
params = {}
params['user'] = self.username
params['pwd'] = base64.b64encode(self.password.encode("utf-8"))
params['serviceKey'] = '1'
sanitized_params = {}
for key in params:
value = params[key]
if value is not None:
sanitized_params[key] = six.text_type(value)
sanitized_params = urllib.parse.urlencode(sanitized_params)
url = ('/cgi-bin/authLogin.cgi?')
res_details = self._execute_and_get_response_details(
self.ip, url, sanitized_params)
root = ET.fromstring(res_details['data'])
session_id = root.find('authSid').text
return session_id
def _login(self):
"""Execute Https Login API."""
self.sid = self.execute_login()
LOG.debug('sid: %s', self.sid)
def _get_res_details(self, url, **kwargs):
sanitized_params = {}
for key, value in kwargs.items():
LOG.debug('%(key)s = %(val)s',
{'key': key, 'val': value})
if value is not None:
sanitized_params[key] = six.text_type(value)
sanitized_params = urllib.parse.urlencode(sanitized_params)
LOG.debug('sanitized_params: %s', sanitized_params)
url = url + sanitized_params
LOG.debug('url: %s', url)
res_details = self._execute_and_get_response_details(self.ip, url)
return res_details
@_connection_checker
def create_lun(self, volume, pool_name, create_lun_name, reserve):
"""Create lun."""
lun_thin_allocate = ''
if reserve:
lun_thin_allocate = '1'
else:
lun_thin_allocate = '0'
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_lun_setting.cgi?',
func='add_lun',
FileIO='no',
LUNThinAllocate=lun_thin_allocate,
LUNName=create_lun_name,
LUNPath=create_lun_name,
poolID=pool_name,
lv_ifssd='no',
LUNCapacity=volume['size'],
lv_threshold='80',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Create volume %s failed') % volume['display_name'])
return root.find('result').text
@_connection_checker
def delete_lun(self, vol_id, *args, **kwargs):
"""Execute delete lun API."""
LOG.debug('Deleting volume id %s', vol_id)
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_lun_setting.cgi?',
func='remove_lun',
run_background='1',
ha_sync='1',
LUNIndex=vol_id,
sid=self.sid)
data_set_is_busy = "-205041"
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
# dataset is busy, retry to delete
if root.find('result').text == data_set_is_busy:
return True
if root.find('result').text < '0':
msg = (_('Volume %s delete failed') % vol_id)
raise exception.VolumeBackendAPIException(data=msg)
return False
@_connection_checker
def get_specific_poolinfo(self, pool_id):
"""Execute deleteInitiatorGrp API."""
res_details = self._get_res_details(
'/cgi-bin/disk/disk_manage.cgi?',
store='poolInfo',
func='extra_get',
poolID=pool_id,
Pool_Info='1',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('get_specific_poolinfo failed'))
pool_list = root.find('Pool_Index')
pool_info_tree = pool_list.findall('row')
for pool in pool_info_tree:
if pool_id == pool.find('poolID').text:
LOG.debug('poolID: %s', pool.find('poolID').text)
return pool
@_connection_checker
def create_target(self, target_name, controller_name):
"""Create target on nas and return target index."""
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_target_setting.cgi?',
func='add_target',
targetName=target_name,
targetAlias=target_name,
bTargetDataDigest='0',
bTargetHeaderDigest='0',
bTargetClusterEnable='1',
controller_name=controller_name,
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Create target failed'))
root = ET.fromstring(res_details['data'])
target_index = root.find('result').text
return target_index
@_connection_checker
def add_target_init(self, target_iqn, init_iqn):
"""Add target acl."""
LOG.debug('targetIqn = %(tgt)s, initIqn = %(init)s',
{'tgt': target_iqn, 'init': init_iqn})
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_target_setting.cgi?',
func='add_init',
targetIQN=target_iqn,
initiatorIQN=init_iqn,
initiatorAlias=init_iqn,
bCHAPEnable='0',
CHAPUserName='',
CHAPPasswd='',
bMutualCHAPEnable='0',
mutualCHAPUserName='',
mutualCHAPPasswd='',
ha_sync='1',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Add target acl failed'))
def remove_target_init(self, target_iqn, init_iqn):
"""Remote target acl."""
pass
@_connection_checker
def map_lun(self, lun_index, target_index):
"""Map lun to sepecific target."""
LOG.debug('LUNIndex: %(lun)s, targetIndex: %(tgt)s',
{'lun': lun_index, 'tgt': target_index})
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_target_setting.cgi?',
func='add_lun',
LUNIndex=lun_index,
targetIndex=target_index,
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(data=_(
"Map lun %(lun_index)s to target %(target_index)s failed") %
{'lun_index': six.text_type(lun_index),
'target_index': six.text_type(target_index)})
@_connection_checker
def disable_lun(self, lun_index, target_index):
"""Disable lun from sepecific target."""
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_target_setting.cgi?',
func='edit_lun',
LUNIndex=lun_index,
targetIndex=target_index,
LUNEnable=0,
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(data=_(
'Disable lun %(lun_index)s from target %(target_index)s failed'
) % {'lun_index': lun_index, 'target_index': target_index})
@_connection_checker
def unmap_lun(self, lun_index, target_index):
"""Unmap lun to sepecific target."""
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_target_setting.cgi?',
func='remove_lun',
LUNIndex=lun_index,
targetIndex=target_index,
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(data=_(
'Unmap lun %(lun_index)s from target %(target_index)s failed')
% {'lun_index': lun_index, 'target_index': target_index})
@_connection_checker
def get_iscsi_portal_info(self):
"""Get iscsi portal info."""
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_portal_setting.cgi?',
func='extra_get',
iSCSI_portal='1',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
else:
return res_details
@_connection_checker
def get_lun_info(self, **kwargs):
"""Execute get_lun_info API."""
for key, value in kwargs.items():
LOG.debug('%(key)s = %(val)s',
{'key': key, 'val': value})
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_portal_setting.cgi?',
func='extra_get',
lunList='1',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if (('LUNIndex' in kwargs) or ('LUNName' in kwargs) or
('LUNNAA' in kwargs)):
lun_list = root.find('iSCSILUNList')
lun_info_tree = lun_list.findall('LUNInfo')
for lun in lun_info_tree:
if ('LUNIndex' in kwargs):
if (kwargs['LUNIndex'] == lun.find('LUNIndex').text):
LOG.debug('LUNIndex:%s',
lun.find('LUNIndex').text)
return lun
elif ('LUNName' in kwargs):
if (kwargs['LUNName'] == lun.find('LUNName').text):
LOG.debug('LUNName:%s', lun.find('LUNName').text)
return lun
elif ('LUNNAA' in kwargs):
if (kwargs['LUNNAA'] == lun.find('LUNNAA').text):
LOG.debug('LUNNAA:%s', lun.find('LUNNAA').text)
return lun
return None
@_connection_checker
def get_snapshot_info(self, **kwargs):
"""Execute get_snapshot_info API."""
for key, value in kwargs.items():
LOG.debug('%(key)s = %(val)s',
{'key': key, 'val': value})
res_details = self._get_res_details(
'/cgi-bin/disk/snapshot.cgi?',
func='extra_get',
LUNIndex=kwargs['lun_index'],
snapshot_list='1',
snap_start='0',
snap_count='100',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Unexpected response from QNAP API'))
snapshot_list = root.find('SnapshotList')
if snapshot_list is None:
return None
snapshot_tree = snapshot_list.findall('row')
for snapshot in snapshot_tree:
if (kwargs['snapshot_name'] ==
snapshot.find('snapshot_name').text):
LOG.debug('snapshot_name:%s', kwargs['snapshot_name'])
return snapshot
return None
@_connection_checker
def create_snapshot_api(self, lun_id, snapshot_name):
"""Execute CGI to create snapshot from source lun NAA."""
res_details = self._get_res_details(
'/cgi-bin/disk/snapshot.cgi?',
func='create_snapshot',
lunID=lun_id,
snapshot_name=snapshot_name,
expire_min='0',
vital='1',
snapshot_type='0',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('create snapshot failed'))
@_connection_checker
def api_delete_snapshot(self, snapshot_id):
"""Execute CGI to delete snapshot from source lun NAA."""
res_details = self._get_res_details(
'/cgi-bin/disk/snapshot.cgi?',
func='del_snapshots',
snapshotID=snapshot_id,
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
# snapshot not exist
if root.find('result').text == '-206021':
return
# lun not exist
if root.find('result').text == '-200005':
return
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('delete snapshot %s failed') % snapshot_id)
@_connection_checker
def clone_snapshot(self, snapshot_id, new_lunname):
"""Execute CGI to clone snapshot as unmap lun."""
res_details = self._get_res_details(
'/cgi-bin/disk/snapshot.cgi?',
func='clone_qsnapshot',
by_lun='1',
snapshotID=snapshot_id,
new_name=new_lunname,
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(data=_(
'Clone lun %(lunname)s from snapshot %(snapshot_id)s failed'
) % {'lunname': new_lunname, 'snapshot_id': snapshot_id})
@_connection_checker
def edit_lun(self, lun):
"""Extend lun."""
LOG.debug(
'LUNName:%(name)s, LUNCapacity:%(cap)s, LUNIndex:%(id)s'), (
{'name': lun['LUNName'],
'cap': lun['LUNCapacity'],
'id': lun['LUNIndex']})
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_lun_setting.cgi?',
func='edit_lun',
LUNName=lun['LUNName'],
LUNCapacity=lun['LUNCapacity'],
LUNIndex=lun['LUNIndex'],
LUNThinAllocate=lun['LUNThinAllocate'],
LUNPath=lun['LUNPath'],
LUNStatus=lun['LUNStatus'],
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Extend lun %s failed') % lun['LUNIndex'])
@_connection_checker
def get_all_iscsi_portal_setting(self):
"""Execute get_all_iscsi_portal_setting API."""
LOG.debug('in get_all_iscsi_portal_setting')
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_portal_setting.cgi?',
func='get_all',
sid=self.sid)
return res_details
@_connection_checker
def get_ethernet_ip(self, **kwargs):
"""Execute get_ethernet_ip API."""
LOG.debug('in get_ethernet_ip')
res_details = self._get_res_details(
'/cgi-bin/sys/sysRequest.cgi?',
subfunc='net_setting',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if ('type' in kwargs):
return_ip = []
ip_list = root.find('func').find('ownContent')
ip_list_tree = ip_list.findall('IPInfo')
for IP in ip_list_tree:
ipv4 = (IP.find('IP').find('IP1').text + '.' +
IP.find('IP').find('IP2').text + '.' +
IP.find('IP').find('IP3').text + '.' +
IP.find('IP').find('IP4').text)
LOG.debug('ipv4 = %s', ipv4)
if ((kwargs['type'] == 'data') and
(IP.find('isManagePort').text != '1') and
(IP.find('status').text == '1')):
return_ip.append(ipv4)
elif ((kwargs['type'] == 'manage') and
(IP.find('isManagePort').text == '1') and
(IP.find('status').text == '1')):
return_ip.append(ipv4)
elif ((kwargs['type'] == 'all') and
(IP.find('status').text == '1')):
return_ip.append(ipv4)
LOG.debug('return_ip = %s', return_ip)
return return_ip
@_connection_checker
def get_target_info(self, target_index):
"""Get target info."""
LOG.debug('target_index: %s', target_index)
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_portal_setting.cgi?',
func='extra_get',
targetInfo=1,
targetIndex=target_index,
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Get target info failed'))
target_list = root.find('targetInfo')
target_tree = target_list.findall('row')
for target in target_tree:
if target_index == target.find('targetIndex').text:
LOG.debug('targetIQN: %s',
target.find('targetIQN').text)
return target
class QnapAPIExecutorTS(QnapAPIExecutor):
"""Makes QNAP API calls for TS NAS."""
@_connection_checker
def remove_target_init(self, target_iqn, init_iqn):
"""Remove target acl."""
LOG.debug('targetIqn = %(tgt)s, initIqn = %(init)s',
{'tgt': target_iqn, 'init': init_iqn})
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_target_setting.cgi?',
func='remove_init',
targetIQN=target_iqn,
initiatorIQN=init_iqn,
ha_sync='1',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Remove target acl failed'))
@_connection_checker
def get_target_info(self, target_index):
"""Get nas target info."""
LOG.debug('targetIndex: %s', target_index)
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_portal_setting.cgi?',
func='extra_get',
targetInfo=1,
targetIndex=target_index,
ha_sync='1',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Get target info failed'))
target_list = root.find('targetInfo')
target_tree = target_list.findall('row')
for target in target_tree:
if target_index == target.find('targetIndex').text:
LOG.debug('targetIQN: %s',
target.find('targetIQN').text)
return target
@_connection_checker
def get_ethernet_ip(self, **kwargs):
"""Execute get_ethernet_ip API."""
LOG.debug('in get_ethernet_ip')
res_details = self._get_res_details(
'/cgi-bin/sys/sysRequest.cgi?',
subfunc='net_setting',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if ('type' in kwargs):
return_ip = []
ip_list = root.find('func').find('ownContent')
ip_list_tree = ip_list.findall('IPInfo')
for IP in ip_list_tree:
ipv4 = (IP.find('IP').find('IP1').text + '.' +
IP.find('IP').find('IP2').text + '.' +
IP.find('IP').find('IP3').text + '.' +
IP.find('IP').find('IP4').text)
LOG.debug('ipv4 = %s', ipv4)
if (IP.find('status').text == '1'):
return_ip.append(ipv4)
LOG.debug('return_ip = %s', return_ip)
return return_ip
@_connection_checker
def get_snapshot_info(self, **kwargs):
"""Execute get_snapshot_info API."""
for key, value in kwargs.items():
LOG.debug('%(key)s = %(val)s',
{'key': key, 'val': value})
LOG.debug('in get_ethernet_ip')
res_details = self._get_res_details(
'/cgi-bin/disk/snapshot.cgi?',
func='extra_get',
LUNIndex=kwargs['lun_index'],
smb_snapshot_list='1',
smb_snapshot='1',
snapshot_list='1',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Unexpected response from QNAP API'))
snapshot_list = root.find('SnapshotList')
if snapshot_list is None:
return None
snapshot_tree = snapshot_list.findall('row')
for snapshot in snapshot_tree:
if (kwargs['snapshot_name'] ==
snapshot.find('snapshot_name').text):
LOG.debug('snapshot_name:%s', kwargs['snapshot_name'])
return snapshot
return None
@_connection_checker
def create_target(self, target_name, controller_name):
"""Create target on nas and return target index."""
res_details = self._get_res_details(
'/cgi-bin/disk/iscsi_target_setting.cgi?',
func='add_target',
targetName=target_name,
targetAlias=target_name,
bTargetDataDigest='0',
bTargetHeaderDigest='0',
bTargetClusterEnable='1',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if root.find('result').text < '0':
raise exception.VolumeBackendAPIException(
data=_('Create target failed'))
root = ET.fromstring(res_details['data'])
target_index = root.find('result').text
return target_index
class QnapAPIExecutorTES(QnapAPIExecutor):
"""Makes QNAP API calls for TES NAS."""
@_connection_checker
def get_ethernet_ip(self, **kwargs):
"""Execute get_ethernet_ip API."""
LOG.debug('in get_ethernet_ip')
res_details = self._get_res_details(
'/cgi-bin/sys/sysRequest.cgi?',
subfunc='net_setting',
sid=self.sid)
root = ET.fromstring(res_details['data'])
if root.find('authPassed').text == '0':
raise exception.VolumeBackendAPIException(
data=_('Session id expired'))
if ('type' in kwargs):
return_ip = []
ip_list = root.find('func').find('ownContent')
ip_list_tree = ip_list.findall('IPInfo')
for IP in ip_list_tree:
ipv4 = (IP.find('IP').find('IP1').text + '.' +
IP.find('IP').find('IP2').text + '.' +
IP.find('IP').find('IP3').text + '.' +
IP.find('IP').find('IP4').text)
LOG.debug('ipv4 = %s', ipv4)
if (IP.find('status').text == '1'):
return_ip.append(ipv4)
LOG.debug('return_ip = %s', return_ip)
return return_ip