Support Cinder FC driver for TOYOU NetStor

* Supported Protocol
 - iSCSI
 - FC

* Supported Feature
 - Volume Attach/Detach(FC)
 - Extend Attached Volume
 - Volume Manage/Unmanage
 - Revert to Snapshot
 - Multi-attach
 - Thin Provisioning

ThirdPartySystems: TOYOU ACS5000 CI
Change-Id: Id9bd2f880ea92e9f74ba286a1cb25aea174328c5
This commit is contained in:
yangheng 2021-11-03 17:41:29 +08:00 committed by caiqilong
parent de6d54108b
commit 2bdc086783
8 changed files with 1266 additions and 329 deletions

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ It will be called by iSCSI driver
""" """
import json import json
import math
import random import random
from eventlet import greenthread from eventlet import greenthread
@ -53,7 +54,12 @@ acs5000c_opts = [
min=3, min=3,
max=100, max=100,
help='When volume copy task is going on,refresh volume ' help='When volume copy task is going on,refresh volume '
'status interval') 'status interval'),
cfg.BoolOpt(
'acs5000_multiattach',
default=False,
help='Enable to allow volumes attaching to multiple '
'hosts with no limit.'),
] ]
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(acs5000c_opts) CONF.register_opts(acs5000c_opts)
@ -78,6 +84,7 @@ class Command(object):
def run_ssh_info(self, ssh_cmd, key=False): def run_ssh_info(self, ssh_cmd, key=False):
"""Run an SSH command and return parsed output.""" """Run an SSH command and return parsed output."""
ssh_cmd.insert(0, 'cinder')
out, err = self._run_ssh(ssh_cmd) out, err = self._run_ssh(ssh_cmd)
if len(err): if len(err):
msg = (_('Execute command %(cmd)s failed, ' msg = (_('Execute command %(cmd)s failed, '
@ -134,35 +141,43 @@ class Command(object):
return info['arr'] return info['arr']
def get_system(self): def get_system(self):
ssh_cmd = ['cinder', 'Storage', 'sshGetSystem'] ssh_cmd = ['get_system']
return self.run_ssh_info(ssh_cmd) return self.run_ssh_info(ssh_cmd)
def get_ip_connect(self): def ls_iscsi(self):
ssh_cmd = ['cinder', ssh_cmd = ['ls_iscsi']
'Storage', ports = self.run_ssh_info(ssh_cmd)
'sshGetIpConnect'] up_ports = []
return self.run_ssh_info(ssh_cmd) for port in ports:
if 'link' in port and port['link'] != 'Down':
up_ports.append(up_ports)
return up_ports
def get_pool_info(self, pool): def ls_fc(self):
ssh_cmd = ['cinder', ssh_cmd = ['ls_fc']
'Storage', ports = self.run_ssh_info(ssh_cmd)
'sshGetPoolInfo', up_ports = []
'--poolName', for port in ports:
if 'link' in port and port['link'] == 'Up':
up_ports.append(port)
return up_ports
def get_pool(self, pool):
ssh_cmd = ['get_pool',
'--pool',
pool] pool]
return self.run_ssh_info(ssh_cmd) return self.run_ssh_info(ssh_cmd)
def get_volume(self, volume): def get_volume(self, volume):
ssh_cmd = ['cinder', ssh_cmd = ['get_volume']
'Storage',
'sshGetVolume']
if not volume: if not volume:
return [] return []
elif isinstance(volume, str): elif isinstance(volume, str):
ssh_cmd.append('--name') ssh_cmd.append('--volume')
ssh_cmd.append(volume) ssh_cmd.append(volume)
elif isinstance(volume, list): elif isinstance(volume, list):
for vol in volume: for vol in volume:
ssh_cmd.append('--name') ssh_cmd.append('--volume')
ssh_cmd.append(vol) ssh_cmd.append(vol)
result = self.run_ssh_info(ssh_cmd) result = self.run_ssh_info(ssh_cmd)
if not result: if not result:
@ -170,83 +185,63 @@ class Command(object):
else: else:
return result return result
def ls_ctr_info(self): def ls_controller(self):
ssh_cmd = ['cinder', 'Storage', 'sshGetCtrInfo'] ssh_cmd = ['ls_controller']
ctrs = self.run_ssh_info(ssh_cmd) ctrs = self.run_ssh_info(ssh_cmd)
nodes = {} nodes = {}
for node_data in ctrs: for node_data in ctrs:
nodes[node_data['id']] = { nodes[node_data['id']] = {
'id': node_data['id'], 'id': int(node_data['id']),
'name': node_data['name'], 'name': node_data['name'],
'iscsi_name': node_data['iscsi_name'], 'status': node_data['status']
'WWNN': node_data['WWNN'],
'WWPN': [],
'status': node_data['status'],
'ipv4': [],
'ipv6': [],
'enabled_protocols': []
} }
return nodes return nodes
def create_volume(self, name, size, pool_name, type='0'): def create_volume(self, name, size, pool_name, type='0'):
ssh_cmd = ['cinder', ssh_cmd = ['create_volume',
'Storage', '--size',
'sshCreateVolume',
'--volumesize',
size, size,
'--volumename', '--volume',
name, name,
'--cinderPool', '--pool',
pool_name, pool_name,
'--type', '--type',
type] type]
return self.run_ssh_info(ssh_cmd, key=True) return self.run_ssh_info(ssh_cmd, key=True)
def delete_volume(self, volume): def delete_volume(self, volume):
ssh_cmd = ['cinder', ssh_cmd = ['delete_volume',
'Storage', '--volume',
'sshDeleteVolume',
'--cinderVolume',
volume] volume]
return self.run_ssh_info(ssh_cmd) return self.run_ssh_info(ssh_cmd)
def extend_volume(self, volume, size): def extend_volume(self, volume, size):
ssh_cmd = ['cinder', ssh_cmd = ['extend_volume',
'Storage', '--volume',
'sshCinderExtendVolume',
'--cinderVolume',
volume, volume,
'--extendunit', '--size',
'gb',
'--extendsize',
str(size)] str(size)]
return self.run_ssh_info(ssh_cmd, key=True) return self.run_ssh_info(ssh_cmd, key=True)
def create_clone(self, volume_name, clone_name): def create_clone(self, volume_name, clone_name):
ssh_cmd = ['cinder', ssh_cmd = ['create_clone',
'Storage', '--volume',
'sshMkLocalClone',
'--cinderVolume',
volume_name, volume_name,
'--cloneVolume', '--clone',
clone_name] clone_name]
return self.run_ssh_info(ssh_cmd, key=True) return self.run_ssh_info(ssh_cmd, key=True)
def start_clone(self, volume_name, snapshot=''): def start_clone(self, volume_name, snapshot=''):
ssh_cmd = ['cinder', ssh_cmd = ['start_clone',
'Storage', '--volume',
'sshMkStartLocalClone',
'--cinderVolume',
volume_name, volume_name,
'--snapshot', '--snapshot',
snapshot] snapshot]
return self.run_ssh_info(ssh_cmd, key=True) return self.run_ssh_info(ssh_cmd, key=True)
def delete_clone(self, volume_name, snapshot=''): def delete_clone(self, volume_name, snapshot=''):
ssh_cmd = ['cinder', ssh_cmd = ['delete_clone',
'Storage', '--volume',
'sshRemoveLocalClone',
'--name',
volume_name, volume_name,
'--snapshot', '--snapshot',
snapshot] snapshot]
@ -255,10 +250,8 @@ class Command(object):
def create_lun_map(self, volume_name, protocol, host): def create_lun_map(self, volume_name, protocol, host):
"""Map volume to host.""" """Map volume to host."""
LOG.debug('enter: create_lun_map volume %s.', volume_name) LOG.debug('enter: create_lun_map volume %s.', volume_name)
ssh_cmd = ['cinder', ssh_cmd = ['create_lun_map',
'Storage', '--volume',
'sshMapVoltoHost',
'--cinderVolume',
volume_name, volume_name,
'--protocol', '--protocol',
protocol] protocol]
@ -272,26 +265,22 @@ class Command(object):
return self.run_ssh_info(ssh_cmd, key=True) return self.run_ssh_info(ssh_cmd, key=True)
def delete_lun_map(self, volume_name, protocol, host): def delete_lun_map(self, volume_name, protocol, host):
ssh_cmd = ['cinder', ssh_cmd = ['delete_lun_map',
'Storage', '--volume',
'sshDeleteLunMap',
'--cinderVolume',
volume_name, volume_name,
'--protocol', '--protocol',
protocol] protocol]
if isinstance(host, list): if isinstance(host, list):
for ht in host: for ht in host:
ssh_cmd.append('--cinderHost') ssh_cmd.append('--host')
ssh_cmd.append(ht) ssh_cmd.append(ht)
else: else:
ssh_cmd.append('--cinderHost') ssh_cmd.append('--host')
ssh_cmd.append(str(host)) ssh_cmd.append(str(host))
return self.run_ssh_info(ssh_cmd, key=True) return self.run_ssh_info(ssh_cmd, key=True)
def create_snapshot(self, volume_name, snapshot_name): def create_snapshot(self, volume_name, snapshot_name):
ssh_cmd = ['cinder', ssh_cmd = ['create_snapshot',
'Storage',
'sshCreateSnapshot',
'--volume', '--volume',
volume_name, volume_name,
'--snapshot', '--snapshot',
@ -299,19 +288,23 @@ class Command(object):
return self.run_ssh_info(ssh_cmd, key=True) return self.run_ssh_info(ssh_cmd, key=True)
def delete_snapshot(self, volume_name, snapshot_name): def delete_snapshot(self, volume_name, snapshot_name):
ssh_cmd = ['cinder', ssh_cmd = ['delete_snapshot',
'Storage',
'sshDeleteSnapshot',
'--volume', '--volume',
volume_name, volume_name,
'--snapshot', '--snapshot',
snapshot_name] snapshot_name]
return self.run_ssh_info(ssh_cmd, key=True) return self.run_ssh_info(ssh_cmd, key=True)
def rollback_snapshot(self, snapshot_name, volume_name=''):
ssh_cmd = ['rollback_snapshot',
'--snapshot',
snapshot_name,
'--volume',
volume_name]
return self.run_ssh_info(ssh_cmd, key=True)
def set_volume_property(self, name, setting): def set_volume_property(self, name, setting):
ssh_cmd = ['cinder', ssh_cmd = ['set_volume',
'Storage',
'sshSetVolumeProperty',
'--volume', '--volume',
name] name]
for key, value in setting.items(): for key, value in setting.items():
@ -340,7 +333,7 @@ class Acs5000CommonDriver(san.SanDriver,
self.pools = self.configuration.acs5000_volpool_name self.pools = self.configuration.acs5000_volpool_name
self._cmd = Command(self._run_ssh) self._cmd = Command(self._run_ssh)
self.protocol = None self.protocol = None
self._state = {'storage_nodes': {}, self._state = {'controller': {},
'enabled_protocols': set(), 'enabled_protocols': set(),
'system_name': None, 'system_name': None,
'system_id': None, 'system_id': None,
@ -361,27 +354,53 @@ class Acs5000CommonDriver(san.SanDriver,
self._state.update(self._cmd.get_system()) self._state.update(self._cmd.get_system())
self._state['storage_nodes'] = self._cmd.ls_ctr_info() self._state['controller'] = self._cmd.ls_controller()
ports = self._cmd.get_ip_connect() if self.protocol == 'FC':
ports = self._cmd.ls_fc()
else:
ports = self._cmd.ls_iscsi()
if len(ports) > 0: if len(ports) > 0:
self._state['enabled_protocols'].add('iSCSI') self._state['enabled_protocols'].add(self.protocol)
for node in self._state['storage_nodes'].values():
if node['id'] in ports.keys():
node['enabled_protocols'].append('iSCSI')
for port in ports[node['id']]:
node['ipv4'].append(port['ip'])
return
def _validate_pools_exist(self): def _validate_pools_exist(self):
LOG.debug('_validate_pools_exist. ' LOG.debug('_validate_pools_exist. '
'pools: %s', ' '.join(self.pools)) 'pools: %s', ' '.join(self.pools))
for pool in self.pools: for pool in self.pools:
pool_data = self._cmd.get_pool_info(pool) pool_data = self._cmd.get_pool(pool)
if not pool_data: if not pool_data:
msg = _('Failed getting details for pool %s.') % pool msg = _('Failed getting details for pool %s.') % pool
raise exception.InvalidInput(reason=msg) raise exception.InvalidInput(reason=msg)
return True return True
@staticmethod
def _convert_name(name):
if len(name) >= 12:
suffix = name[-12:]
elif len(name) > 0:
suffix = str(name).zfill(12)
else:
suffix = str(random.randint(0, 999999)).zfill(12)
return VOLUME_PREFIX + suffix
@staticmethod
def _check_multi_attached(volume, connector):
# In the case of multi-attach, these VMs belong to the same host.
# The mapping action only happens once.
# If the only mapping relationship is cancelled,
# volume on other VMs cannot be read or written.
if not connector or 'uuid' not in connector:
return 0
attached_count = 0
uuid = connector['uuid']
for ref in volume.volume_attachment:
ref_connector = {}
if 'connector' in ref and ref.connector:
# ref.connector may be None
ref_connector = ref.connector
if 'uuid' in ref_connector and uuid == ref_connector['uuid']:
attached_count += 1
return attached_count
@volume_utils.trace_method @volume_utils.trace_method
def check_for_setup_error(self): def check_for_setup_error(self):
"""Ensure that the params are set properly.""" """Ensure that the params are set properly."""
@ -391,8 +410,8 @@ class Acs5000CommonDriver(san.SanDriver,
if self._state['system_id'] is None: if self._state['system_id'] is None:
exception_msg = _('Unable to determine system id.') exception_msg = _('Unable to determine system id.')
raise exception.VolumeBackendAPIException(data=exception_msg) raise exception.VolumeBackendAPIException(data=exception_msg)
if len(self._state['storage_nodes']) != 2: if len(self._state['controller']) != 2:
msg = _('do_setup: No configured nodes.') msg = _('do_setup: The dual controller status is incorrect.')
LOG.error(msg) LOG.error(msg)
raise exception.VolumeDriverException(message=msg) raise exception.VolumeDriverException(message=msg)
if self.protocol not in self._state['enabled_protocols']: if self.protocol not in self._state['enabled_protocols']:
@ -468,7 +487,7 @@ class Acs5000CommonDriver(san.SanDriver,
def create_volume(self, volume): def create_volume(self, volume):
LOG.debug('create_volume, volume %s.', volume['id']) LOG.debug('create_volume, volume %s.', volume['id'])
volume_name = VOLUME_PREFIX + volume['id'][-12:] volume_name = self._convert_name(volume.name)
pool_name = volume_utils.extract_host(volume['host'], 'pool') pool_name = volume_utils.extract_host(volume['host'], 'pool')
ret = self._cmd.create_volume( ret = self._cmd.create_volume(
volume_name, volume_name,
@ -492,16 +511,22 @@ class Acs5000CommonDriver(san.SanDriver,
elif ret['key'] == 308: elif ret['key'] == 308:
raise exception.VolumeLimitExceeded(allowed=4096, raise exception.VolumeLimitExceeded(allowed=4096,
name=volume_name) name=volume_name)
model_update = None elif ret['key'] != 0:
return model_update msg = (_('Failed to create_volume %(vol)s on pool %(pool)s, '
'code=%(ret)s, error=%(msg)s.') % {'vol': volume_name,
'pool': pool_name,
'ret': ret['key'],
'msg': ret['msg']})
raise exception.VolumeBackendAPIException(data=msg)
return None
def delete_volume(self, volume): def delete_volume(self, volume):
volume_name = VOLUME_PREFIX + volume['id'][-12:] volume_name = self._convert_name(volume.name)
self._cmd.delete_volume(volume_name) self._cmd.delete_volume(volume_name)
def create_snapshot(self, snapshot): def create_snapshot(self, snapshot):
volume_name = VOLUME_PREFIX + snapshot['volume_name'][-12:] volume_name = self._convert_name(snapshot.volume_name)
snapshot_name = VOLUME_PREFIX + snapshot['name'][-12:] snapshot_name = self._convert_name(snapshot.name)
ret = self._cmd.create_snapshot(volume_name, snapshot_name) ret = self._cmd.create_snapshot(volume_name, snapshot_name)
if ret['key'] == 303: if ret['key'] == 303:
raise exception.VolumeNotFound(volume_id=volume_name) raise exception.VolumeNotFound(volume_id=volume_name)
@ -509,18 +534,32 @@ class Acs5000CommonDriver(san.SanDriver,
raise exception.SnapshotLimitExceeded(allowed=4096) raise exception.SnapshotLimitExceeded(allowed=4096)
elif ret['key'] == 504: elif ret['key'] == 504:
raise exception.SnapshotLimitExceeded(allowed=64) raise exception.SnapshotLimitExceeded(allowed=64)
elif ret['key'] != 0:
msg = (_('Failed to create_snapshot %(snap)s on volume %(vol)s '
'code=%(ret)s, error=%(msg)s.') % {'vol': volume_name,
'snap': snapshot_name,
'ret': ret['key'],
'msg': ret['msg']})
raise exception.VolumeBackendAPIException(data=msg)
def delete_snapshot(self, snapshot): def delete_snapshot(self, snapshot):
volume_name = VOLUME_PREFIX + snapshot['volume_name'][-12:] volume_name = self._convert_name(snapshot.volume_name)
snapshot_name = VOLUME_PREFIX + snapshot['name'][-12:] snapshot_name = self._convert_name(snapshot.name)
ret = self._cmd.delete_snapshot(volume_name, snapshot_name) ret = self._cmd.delete_snapshot(volume_name, snapshot_name)
if ret['key'] == 505: if ret['key'] == 505:
raise exception.SnapshotNotFound(snapshot_id=snapshot['id']) raise exception.SnapshotNotFound(snapshot_id=snapshot['id'])
elif ret['key'] != 0:
msg = (_('Failed to delete_snapshot %(snap)s on volume %(vol)s '
'code=%(ret)s, error=%(msg)s.') % {'vol': volume_name,
'snap': snapshot_name,
'ret': ret['key'],
'msg': ret['msg']})
raise exception.VolumeBackendAPIException(data=msg)
def create_volume_from_snapshot(self, volume, snapshot): def create_volume_from_snapshot(self, volume, snapshot):
snapshot_name = VOLUME_PREFIX + snapshot['name'][-12:] snapshot_name = self._convert_name(snapshot.name)
volume_name = VOLUME_PREFIX + volume['id'][-12:] volume_name = self._convert_name(volume.name)
source_volume = VOLUME_PREFIX + snapshot['volume_name'][-12:] source_volume = self._convert_name(snapshot.volume_name)
pool = volume_utils.extract_host(volume['host'], 'pool') pool = volume_utils.extract_host(volume['host'], 'pool')
self._cmd.create_volume(volume_name, self._cmd.create_volume(volume_name,
str(volume['size']), str(volume['size']),
@ -530,9 +569,32 @@ class Acs5000CommonDriver(san.SanDriver,
'create_volume_from_snapshot', 'create_volume_from_snapshot',
snapshot_name) snapshot_name)
def snapshot_revert_use_temp_snapshot(self):
return False
@volume_utils.trace
def revert_to_snapshot(self, context, volume, snapshot):
volume_name = self._convert_name(volume.name)
snapshot_name = self._convert_name(snapshot.name)
ret = self._cmd.rollback_snapshot(snapshot_name, volume_name)
if ret['key'] == 303:
raise exception.VolumeNotFound(volume_id=volume_name)
elif ret['key'] == 505:
raise exception.SnapshotNotFound(snapshot_id=snapshot_name)
elif ret['key'] == 506:
msg = (_('Snapshot %s is not the latest one.') % snapshot_name)
raise exception.InvalidSnapshot(reason=msg)
elif ret['key'] != 0:
msg = (_('Failed to revert volume %(vol)s to snapshot %(snap)s, '
'code=%(ret)s, error=%(msg)s.') % {'vol': volume_name,
'snap': snapshot_name,
'ret': ret['key'],
'msg': ret['msg']})
raise exception.VolumeBackendAPIException(data=msg)
def create_cloned_volume(self, tgt_volume, src_volume): def create_cloned_volume(self, tgt_volume, src_volume):
clone_name = VOLUME_PREFIX + tgt_volume['id'][-12:] clone_name = self._convert_name(tgt_volume.name)
volume_name = VOLUME_PREFIX + src_volume['id'][-12:] volume_name = self._convert_name(src_volume.name)
tgt_pool = volume_utils.extract_host(tgt_volume['host'], 'pool') tgt_pool = volume_utils.extract_host(tgt_volume['host'], 'pool')
try: try:
self._cmd.create_volume(clone_name, str( self._cmd.create_volume(clone_name, str(
@ -545,7 +607,7 @@ class Acs5000CommonDriver(san.SanDriver,
data='create_cloned_volume failed.') data='create_cloned_volume failed.')
def extend_volume(self, volume, new_size): def extend_volume(self, volume, new_size):
volume_name = VOLUME_PREFIX + volume['id'][-12:] volume_name = self._convert_name(volume.name)
ret = self._cmd.extend_volume(volume_name, int(new_size)) ret = self._cmd.extend_volume(volume_name, int(new_size))
if ret['key'] == 303: if ret['key'] == 303:
raise exception.VolumeNotFound(volume_id=volume_name) raise exception.VolumeNotFound(volume_id=volume_name)
@ -562,6 +624,33 @@ class Acs5000CommonDriver(san.SanDriver,
break break
raise exception.VolumeSizeExceedsLimit(size=int(new_size), raise exception.VolumeSizeExceedsLimit(size=int(new_size),
limit=allow_size) limit=allow_size)
elif ret['key'] != 0:
msg = (_('Failed to extend_volume %(vol)s to size %(size)s, '
'code=%(ret)s, error=%(msg)s.') % {'vol': volume_name,
'size': new_size,
'ret': ret['key'],
'msg': ret['msg']})
raise exception.VolumeBackendAPIException(data=msg)
def update_migrated_volume(self, ctxt, volume, new_volume,
original_volume_status):
"""Only for host copy."""
existing_name = self._convert_name(new_volume.name)
wanted_name = self._convert_name(volume.name)
LOG.debug('enter: update_migrated_volume: rename of %(new)s '
'to original name %(wanted)s.', {'new': existing_name,
'wanted': wanted_name})
is_existed = self._cmd.get_volume(wanted_name)
if len(is_existed) == 1:
LOG.warn('volume name %(wanted)s is existed, The two volumes '
'%(wanted)s and %(new)s may be on the same system.',
{'new': existing_name,
'wanted': wanted_name})
return {'_name_id': new_volume['_name_id'] or new_volume['id']}
else:
self._cmd.set_volume_property(existing_name,
{'new_name': wanted_name})
return {'_name_id': None}
def migrate_volume(self, ctxt, volume, host): def migrate_volume(self, ctxt, volume, host):
LOG.debug('enter: migrate_volume id %(id)s, host %(host)s', LOG.debug('enter: migrate_volume id %(id)s, host %(host)s',
@ -581,7 +670,7 @@ class Acs5000CommonDriver(san.SanDriver,
LOG.info('The target host belongs to the same storage system ' LOG.info('The target host belongs to the same storage system '
'as the current but to a different pool. ' 'as the current but to a different pool. '
'The same storage system will clone volume into the new pool') 'The same storage system will clone volume into the new pool')
volume_name = VOLUME_PREFIX + volume['id'][-12:] volume_name = self._convert_name(volume.name)
tmp_name = VOLUME_PREFIX + 'tmp' tmp_name = VOLUME_PREFIX + 'tmp'
tmp_name += str(random.randint(0, 999999)).zfill(8) tmp_name += str(random.randint(0, 999999)).zfill(8)
self._cmd.create_volume(tmp_name, self._cmd.create_volume(tmp_name,
@ -596,6 +685,58 @@ class Acs5000CommonDriver(san.SanDriver,
'new_name': volume_name}) 'new_name': volume_name})
return (True, None) return (True, None)
def _manage_get_volume(self, ref, pool_name=None):
if 'source-name' in ref:
manage_source = ref['source-name']
volumes = self._cmd.get_volume(manage_source)
else:
reason = _('Reference must contain source-name element '
'and only support source-name.')
raise exception.ManageExistingInvalidReference(existing_ref=ref,
reason=reason)
if not volumes:
reason = (_('No volume by ref %s.')
% manage_source)
raise exception.ManageExistingInvalidReference(existing_ref=ref,
reason=reason)
volume = volumes[0]
if pool_name and pool_name != volume['poolname']:
reason = (_('Volume %(volume)s does not belong to pool name '
'%(pool)s.') % {'volume': manage_source,
'pool': pool_name})
raise exception.ManageExistingInvalidReference(existing_ref=ref,
reason=reason)
return volume
@volume_utils.trace_method
def manage_existing(self, volume, ref):
"""Manages an existing volume."""
volume_name = ref.get('source-name')
if not volume_name:
reason = _('Reference must contain source-name element '
'and only support source-name.')
raise exception.ManageExistingInvalidReference(existing_ref=ref,
reason=reason)
new_name = self._convert_name(volume.name)
self._cmd.set_volume_property(volume_name, {'type': '2',
'new_name': new_name})
@volume_utils.trace_method
def manage_existing_get_size(self, volume, ref):
"""Return size of an existing volume for manage_existing."""
pool_name = volume_utils.extract_host(volume['host'], 'pool')
vol_backend = self._manage_get_volume(ref, pool_name)
size = int(vol_backend.get('size_mb', 0))
size_gb = int(math.ceil(size / 1024))
if (size_gb * 1024) > size:
LOG.warn('Volume %(vol)s capacity is %(mb)s MB, '
'extend to %(gb)s GB.', {'vol': ref['source-name'],
'mb': size,
'gb': size_gb})
self._cmd.extend_volume(ref['source-name'], size_gb)
return size_gb
def get_volume_stats(self, refresh=False): def get_volume_stats(self, refresh=False):
"""Get volume stats. """Get volume stats.
@ -628,15 +769,20 @@ class Acs5000CommonDriver(san.SanDriver,
"""Build pool status""" """Build pool status"""
pool_stats = {} pool_stats = {}
try: try:
pool_data = self._cmd.get_pool_info(pool) pool_data = self._cmd.get_pool(pool)
if pool_data: if pool_data:
total_capacity_gb = float(pool_data['capacity']) / units.Gi total_capacity_gb = float(
free_capacity_gb = float(pool_data['free_capacity']) / units.Gi pool_data['capacity']) / units.Gi
free_capacity_gb = float(
pool_data['free_capacity']) / units.Gi
allocated_capacity_gb = float( allocated_capacity_gb = float(
pool_data['used_capacity']) / units.Gi pool_data['used_capacity']) / units.Gi
total_volumes = None total_volumes = None
if 'total_volumes' in pool_data.keys(): if 'total_volumes' in pool_data.keys():
total_volumes = int(pool_data['total_volumes']) total_volumes = int(pool_data['total_volumes'])
thin_provisioning = False
if 'thin' in pool_data and pool_data['thin'] == 'Enabled':
thin_provisioning = True
pool_stats = { pool_stats = {
'pool_name': pool_data['name'], 'pool_name': pool_data['name'],
'total_capacity_gb': total_capacity_gb, 'total_capacity_gb': total_capacity_gb,
@ -647,8 +793,8 @@ class Acs5000CommonDriver(san.SanDriver,
self.configuration.reserved_percentage, self.configuration.reserved_percentage,
'QoS_support': False, 'QoS_support': False,
'consistencygroup_support': False, 'consistencygroup_support': False,
'multiattach': False, 'multiattach': self.configuration.acs5000_multiattach,
'easytier_support': False, 'thin_provisioning_support': thin_provisioning,
'total_volumes': total_volumes, 'total_volumes': total_volumes,
'system_id': self._state['system_id']} 'system_id': self._state['system_id']}
else: else:

View File

@ -0,0 +1,165 @@
# Copyright 2021 toyou Corp.
# 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.
"""
acs5000 FC driver
"""
from oslo_log import log as logging
from cinder import exception
from cinder.i18n import _
from cinder import interface
from cinder import utils
from cinder.volume.drivers.toyou.acs5000 import acs5000_common
from cinder.zonemanager import utils as zone_utils
LOG = logging.getLogger(__name__)
@interface.volumedriver
class Acs5000FCDriver(acs5000_common.Acs5000CommonDriver):
"""TOYOU ACS5000 storage FC volume driver.
.. code-block:: none
Version history:
1.0.0 - Initial driver
"""
VENDOR = 'TOYOU'
VERSION = '1.0.0'
PROTOCOL = 'FC'
# ThirdPartySystems wiki page
CI_WIKI_NAME = 'TOYOU_ACS5000_CI'
def __init__(self, *args, **kwargs):
super(Acs5000FCDriver, self).__init__(*args, **kwargs)
self.protocol = self.PROTOCOL
@staticmethod
def get_driver_options():
return acs5000_common.Acs5000CommonDriver.get_driver_options()
def _get_connected_wwpns(self):
fc_ports = self._cmd.ls_fc()
connected_wwpns = []
for port in fc_ports:
if 'wwpn' in port:
connected_wwpns.append(port['wwpn'])
elif 'WWPN' in port:
connected_wwpns.append(port['WWPN'])
return connected_wwpns
def validate_connector(self, connector):
"""Check connector for at least one enabled FC protocol."""
if 'wwpns' not in connector:
LOG.error('The connector does not '
'contain the required information.')
raise exception.InvalidConnectorException(
missing='wwpns')
@utils.synchronized('acs5000A-host', external=True)
def initialize_connection(self, volume, connector):
LOG.debug('enter: initialize_connection: volume '
'%(vol)s with connector %(conn)s',
{'vol': volume.id, 'conn': connector})
volume_name = self._convert_name(volume.name)
ret = self._cmd.create_lun_map(volume_name,
self.protocol,
connector['wwpns'])
if ret['key'] == 0:
if 'lun' in ret['arr']:
lun_id = int(ret['arr']['lun'])
else:
msg = (_('_create_fc_lun: Lun id did not find '
'when volume %s create lun map.') % volume['id'])
raise exception.VolumeBackendAPIException(data=msg)
target_wwpns = self._get_connected_wwpns()
if len(target_wwpns) == 0:
if self._check_multi_attached(volume, connector) < 1:
self._cmd.delete_lun_map(volume_name,
self.protocol,
connector['wwpns'])
msg = (_('_create_fc_lun: Did not find '
'available fc wwpns when volume %s '
'create lun map.') % volume['id'])
raise exception.VolumeBackendAPIException(data=msg)
initiator_target = {}
for initiator_wwpn in connector['wwpns']:
initiator_target[str(initiator_wwpn)] = target_wwpns
properties = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': target_wwpns,
'target_discovered': False,
'target_lun': lun_id,
'volume_id': volume['id']}}
properties['data']['initiator_target_map'] = initiator_target
elif ret['key'] == 303:
raise exception.VolumeNotFound(volume_id=volume_name)
else:
msg = (_('failed to map the volume %(vol)s to '
'connector %(conn)s.') %
{'vol': volume['id'], 'conn': connector})
raise exception.VolumeBackendAPIException(data=msg)
zone_utils.add_fc_zone(properties)
LOG.debug('leave: initialize_connection: volume '
'%(vol)s with connector %(conn)s',
{'vol': volume.id, 'conn': connector})
return properties
@utils.synchronized('acs5000A-host', external=True)
def terminate_connection(self, volume, connector, **kwargs):
LOG.debug('enter: terminate_connection: volume '
'%(vol)s with connector %(conn)s',
{'vol': volume.id, 'conn': connector})
volume_name = self._convert_name(volume.name)
properties = {'driver_volume_type': 'fibre_channel',
'data': {}}
initiator_wwpns = []
target_wwpns = []
if connector and 'wwpns' in connector:
initiator_wwpns = connector['wwpns']
target_wwpns = self._get_connected_wwpns()
if len(target_wwpns) == 0:
target_wwpns = []
LOG.warn('terminate_connection: Did not find '
'available fc wwpns when volume %s '
'delete lun map.', volume.id)
initiator_target = {}
for i_wwpn in initiator_wwpns:
initiator_target[str(i_wwpn)] = target_wwpns
properties['data'] = {'initiator_target_map': initiator_target}
if self._check_multi_attached(volume, connector) < 2:
if not initiator_wwpns:
# -1 means all lun maps of this volume
initiator_wwpns = -1
self._cmd.delete_lun_map(volume_name,
self.protocol,
initiator_wwpns)
else:
LOG.warn('volume %s has been mapped to multi VMs, and these VMs '
'belong to the same host. The mapping cancellation '
'request is aborted.', volume.id)
zone_utils.remove_fc_zone(properties)
LOG.debug('leave: terminate_connection: volume '
'%(vol)s with connector %(conn)s',
{'vol': volume.id, 'conn': connector})
return properties

View File

@ -20,6 +20,7 @@ acs5000 iSCSI driver
from oslo_log import log as logging from oslo_log import log as logging
from cinder import exception from cinder import exception
from cinder.i18n import _
from cinder import interface from cinder import interface
from cinder import utils from cinder import utils
from cinder.volume.drivers.toyou.acs5000 import acs5000_common from cinder.volume.drivers.toyou.acs5000 import acs5000_common
@ -61,48 +62,63 @@ class Acs5000ISCSIDriver(acs5000_common.Acs5000CommonDriver):
raise exception.InvalidConnectorException( raise exception.InvalidConnectorException(
missing='initiator') missing='initiator')
@utils.synchronized('Acs5000A-host', external=True) @utils.synchronized('acs5000A-host', external=True)
def initialize_connection(self, volume, connector): def initialize_connection(self, volume, connector):
LOG.debug('initialize_connection: volume %(vol)s with connector ' LOG.debug('enter: initialize_connection: volume '
'%(conn)s', {'vol': volume['id'], 'conn': connector}) '%(vol)s with connector %(conn)s',
volume_name = acs5000_common.VOLUME_PREFIX + volume['name'][-12:] {'vol': volume.id, 'conn': connector})
volume_name = self._convert_name(volume.name)
ret = self._cmd.create_lun_map(volume_name, ret = self._cmd.create_lun_map(volume_name,
'WITH_ISCSI', self.protocol,
connector['initiator']) connector['initiator'])
if ret['key'] == 0:
lun_required = ['iscsi_name', 'portal', 'lun']
lun_info = ret['arr']
for param in lun_required:
if param not in lun_info:
msg = (_('initialize_connection: Param %(param)s '
'was not returned correctly when volume '
'%(vol)s mapping.') % {'param': param,
'vol': volume.id})
raise exception.VolumeBackendAPIException(data=msg)
data = {'target_discovered': False,
'target_iqns': lun_info['iscsi_name'],
'target_portals': lun_info['portal'],
'target_luns': lun_info['lun'],
'volume_id': volume.id}
LOG.debug('leave: initialize_connection: volume '
'%(vol)s with connector %(conn)s',
{'vol': volume.id, 'conn': connector})
return {'driver_volume_type': 'iscsi', 'data': data}
if ret['key'] == 303: if ret['key'] == 303:
raise exception.VolumeNotFound(volume_id=volume_name) raise exception.VolumeNotFound(volume_id=volume_name)
elif ret['key'] == 402: elif ret['key'] == 402:
raise exception.ISCSITargetAttachFailed(volume_id=volume_name) raise exception.ISCSITargetAttachFailed(volume_id=volume_name)
else: else:
lun_info = ret['arr'] msg = (_('failed to map the volume %(vol)s to '
properties = {} 'connector %(conn)s.') %
properties['target_discovered'] = False {'vol': volume['id'], 'conn': connector})
properties['target_iqns'] = lun_info['iscsi_name'] raise exception.VolumeBackendAPIException(data=msg)
properties['target_portals'] = lun_info['portal']
properties['target_luns'] = lun_info['lun']
properties['volume_id'] = volume['id']
properties['auth_method'] = ''
properties['auth_username'] = ''
properties['auth_password'] = ''
properties['discovery_auth_method'] = ''
properties['discovery_auth_username'] = ''
properties['discovery_auth_password'] = ''
return {'driver_volume_type': 'iscsi', 'data': properties}
@utils.synchronized('Acs5000A-host', external=True) @utils.synchronized('acs5000A-host', external=True)
def terminate_connection(self, volume, connector, **kwargs): def terminate_connection(self, volume, connector, **kwargs):
LOG.debug('terminate_connection: volume %(vol)s with connector ' LOG.debug('enter: terminate_connection: volume '
'%(conn)s', {'vol': volume['id'], 'conn': connector}) '%(vol)s with connector %(conn)s',
info = {'driver_volume_type': 'iscsi', 'data': {}} {'vol': volume.id, 'conn': connector})
name = acs5000_common.VOLUME_PREFIX + volume['name'][-12:] name = self._convert_name(volume.name)
# -1 means all lun maps # -1 means all lun maps
initiator = '-1' initiator = '-1'
if connector and connector['initiator']: if connector and connector['initiator']:
initiator = connector['initiator'] initiator = connector['initiator']
self._cmd.delete_lun_map(name, if self._check_multi_attached(volume, connector) < 2:
'WITH_ISCSI', self._cmd.delete_lun_map(name,
initiator) self.protocol,
LOG.debug('leave: terminate_connection: volume %(vol)s with ' initiator)
'connector %(conn)s', {'vol': volume['id'], else:
'conn': connector}) LOG.warn('volume %s has been mapped to multi VMs, and these VMs '
return info 'belong to the same host. The mapping cancellation '
'request is aborted.', volume.id)
LOG.debug('leave: terminate_connection: volume '
'%(vol)s with connector %(conn)s',
{'vol': volume.id, 'conn': connector})
return {'driver_volume_type': 'iscsi', 'data': {}}

View File

@ -1,72 +0,0 @@
==========================
TOYOU ACS5000 iSCSI driver
==========================
TOYOU ACS5000 series volume driver provides OpenStack Compute instances
with access to TOYOU ACS5000 series storage systems.
TOYOU ACS5000 storage can be used with iSCSI connection.
This documentation explains how to configure and connect the block storage
nodes to TOYOU ACS5000 series storage.
Driver options
~~~~~~~~~~~~~~
The following table contains the configuration options supported by the
TOYOU ACS5000 iSCSI driver.
.. config-table::
:config-target: TOYOU ACS5000
cinder.volume.drivers.toyou.acs5000.acs5000_iscsi
cinder.volume.drivers.toyou.acs5000.acs5000_common
Supported operations
~~~~~~~~~~~~~~~~~~~~
- Create, list, delete, attach (map), and detach (unmap) volumes.
- Create, list and delete volume snapshots.
- Create a volume from a snapshot.
- Copy an image to a volume.
- Copy a volume to an image.
- Clone a volume.
- Extend a volume.
- Migrate a volume.
Configure TOYOU ACS5000 iSCSI backend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section details the steps required to configure the TOYOU ACS5000
storage cinder driver.
#. In the ``cinder.conf`` configuration file under the ``[DEFAULT]``
section, set the enabled_backends parameter.
.. code-block:: ini
[DEFAULT]
enabled_backends = ACS5000-1
#. Add a backend group section for the backend group specified
in the enabled_backends parameter.
#. In the newly created backend group section, set the
following configuration options:
.. code-block:: ini
[ACS5000-1]
# The driver path
volume_driver = cinder.volume.drivers.toyou.acs5000.acs5000_iscsi.Acs5000ISCSIDriver
# Management IP of TOYOU ACS5000 storage array
san_ip = 10.0.0.10
# Management username of TOYOU ACS5000 storage array
san_login = cliuser
# Management password of TOYOU ACS5000 storage array
san_password = clipassword
# The Pool used to allocated volumes
acs5000_volpool_name = pool01
# Backend name
volume_backend_name = ACS5000

View File

@ -0,0 +1,106 @@
===========================
TOYOU NetStor Cinder driver
===========================
TOYOU NetStor series volume driver provides OpenStack Compute instances
with access to TOYOU NetStor series storage systems.
TOYOU NetStor storage can be used with iSCSI or FC connection.
This documentation explains how to configure and connect the block storage
nodes to TOYOU NetStor series storage.
Driver options
~~~~~~~~~~~~~~
The following table contains the configuration options supported by the
TOYOU NetStor iSCSI/FC driver.
.. config-table::
:config-target: TOYOU NetStor
cinder.volume.drivers.toyou.acs5000.acs5000_common
Supported operations
~~~~~~~~~~~~~~~~~~~~
- Create, list, delete, attach (map), and detach (unmap) volumes.
- Create, list and delete volume snapshots.
- Create a volume from a snapshot.
- Copy an image to a volume.
- Copy a volume to an image.
- Clone a volume.
- Extend a volume.
- Migrate a volume.
- Manage/Unmanage volume.
- Revert to Snapshot.
- Multi-attach.
- Thin Provisioning.
- Extend Attached Volume.
Configure TOYOU NetStor iSCSI/FC backend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section details the steps required to configure the TOYOU NetStor
storage cinder driver.
#. In the ``cinder.conf`` configuration file under the ``[DEFAULT]``
section, set the enabled_backends parameter
with the iSCSI or FC back-end group.
- For Fibre Channel:
.. code-block:: ini
[DEFAULT]
enabled_backends = toyou-fc-1
- For iSCSI:
.. code-block:: ini
[DEFAULT]
enabled_backends = toyou-iscsi-1
#. Add a backend group section for the backend group specified
in the enabled_backends parameter.
#. In the newly created backend group section, set the
following configuration options:
- For Fibre Channel:
.. code-block:: ini
[toyou-fc-1]
# The TOYOU NetStor driver path
volume_driver = cinder.volume.drivers.toyou.acs5000.acs5000_fc.Acs5000FCDriver
# Management IP of TOYOU NetStor storage array
san_ip = 10.0.0.10
# Management username of TOYOU NetStor storage array
san_login = cliuser
# Management password of TOYOU NetStor storage array
san_password = clipassword
# The Pool used to allocated volumes
acs5000_volpool_name = pool01
# Backend name
volume_backend_name = toyou-fc
- For iSCSI:
.. code-block:: ini
[toyou-iscsi-1]
# The TOYOU NetStor driver path
volume_driver = cinder.volume.drivers.toyou.acs5000.acs5000_iscsi.Acs5000ISCSIDriver
# Management IP of TOYOU NetStor storage array
san_ip = 10.0.0.10
# Management username of TOYOU NetStor storage array
san_login = cliuser
# Management password of TOYOU NetStor storage array
san_password = clipassword
# The Pool used to allocated volumes
acs5000_volpool_name = pool01
# Backend name
volume_backend_name = toyou-iscsi

View File

@ -183,8 +183,8 @@ title=StorPool Storage Driver (storpool)
[driver.synology] [driver.synology]
title=Synology Storage Driver (iSCSI) title=Synology Storage Driver (iSCSI)
[driver.toyou] [driver.toyou_netstor]
title=TOYOU ACS5000 Storage Driver (iSCSI) title=TOYOU NetStor Storage Driver (iSCSI, FC)
[driver.vrtsaccess] [driver.vrtsaccess]
title=Veritas Access iSCSI Driver (iSCSI) title=Veritas Access iSCSI Driver (iSCSI)
@ -273,7 +273,7 @@ driver.sandstone=complete
driver.seagate=complete driver.seagate=complete
driver.storpool=complete driver.storpool=complete
driver.synology=complete driver.synology=complete
driver.toyou=complete driver.toyou_netstor=complete
driver.vrtsaccess=missing driver.vrtsaccess=missing
driver.vrtscnfs=missing driver.vrtscnfs=missing
driver.vzstorage=missing driver.vzstorage=missing
@ -343,7 +343,7 @@ driver.sandstone=complete
driver.seagate=complete driver.seagate=complete
driver.storpool=complete driver.storpool=complete
driver.synology=complete driver.synology=complete
driver.toyou=missing driver.toyou_netstor=complete
driver.vrtsaccess=complete driver.vrtsaccess=complete
driver.vrtscnfs=complete driver.vrtscnfs=complete
driver.vzstorage=complete driver.vzstorage=complete
@ -416,7 +416,7 @@ driver.sandstone=complete
driver.seagate=missing driver.seagate=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
driver.toyou=missing driver.toyou_netstor=missing
driver.vrtsaccess=missing driver.vrtsaccess=missing
driver.vrtscnfs=missing driver.vrtscnfs=missing
driver.vzstorage=missing driver.vzstorage=missing
@ -488,7 +488,7 @@ driver.sandstone=complete
driver.seagate=missing driver.seagate=missing
driver.storpool=complete driver.storpool=complete
driver.synology=missing driver.synology=missing
driver.toyou=missing driver.toyou_netstor=missing
driver.vrtsaccess=missing driver.vrtsaccess=missing
driver.vrtscnfs=missing driver.vrtscnfs=missing
driver.vzstorage=missing driver.vzstorage=missing
@ -561,7 +561,7 @@ driver.sandstone=missing
driver.seagate=missing driver.seagate=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
driver.toyou=missing driver.toyou_netstor=missing
driver.vrtsaccess=missing driver.vrtsaccess=missing
driver.vrtscnfs=missing driver.vrtscnfs=missing
driver.vzstorage=missing driver.vzstorage=missing
@ -633,7 +633,7 @@ driver.sandstone=complete
driver.seagate=missing driver.seagate=missing
driver.storpool=complete driver.storpool=complete
driver.synology=missing driver.synology=missing
driver.toyou=missing driver.toyou_netstor=complete
driver.vrtsaccess=missing driver.vrtsaccess=missing
driver.vrtscnfs=missing driver.vrtscnfs=missing
driver.vzstorage=missing driver.vzstorage=missing
@ -706,7 +706,7 @@ driver.sandstone=missing
driver.seagate=missing driver.seagate=missing
driver.storpool=complete driver.storpool=complete
driver.synology=missing driver.synology=missing
driver.toyou=complete driver.toyou_netstor=complete
driver.vrtsaccess=missing driver.vrtsaccess=missing
driver.vrtscnfs=missing driver.vrtscnfs=missing
driver.vzstorage=missing driver.vzstorage=missing
@ -779,7 +779,7 @@ driver.sandstone=complete
driver.seagate=complete driver.seagate=complete
driver.storpool=complete driver.storpool=complete
driver.synology=missing driver.synology=missing
driver.toyou=missing driver.toyou_netstor=complete
driver.vrtsaccess=missing driver.vrtsaccess=missing
driver.vrtscnfs=missing driver.vrtscnfs=missing
driver.vzstorage=missing driver.vzstorage=missing
@ -849,7 +849,7 @@ driver.sandstone=complete
driver.seagate=missing driver.seagate=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
driver.toyou=missing driver.toyou_netstor=complete
driver.vrtsaccess=missing driver.vrtsaccess=missing
driver.vrtscnfs=missing driver.vrtscnfs=missing
driver.vzstorage=missing driver.vzstorage=missing
@ -923,7 +923,7 @@ driver.sandstone=complete
driver.seagate=missing driver.seagate=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
driver.toyou=missing driver.toyou_netstor=missing
driver.vrtsaccess=missing driver.vrtsaccess=missing
driver.vrtscnfs=missing driver.vrtscnfs=missing
driver.vzstorage=missing driver.vzstorage=missing

View File

@ -0,0 +1,4 @@
---
features:
- |
New FC cinder volume driver for TOYOU NetStor Storage.