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:
parent
de6d54108b
commit
2bdc086783
File diff suppressed because it is too large
Load Diff
@ -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:
|
||||||
|
165
cinder/volume/drivers/toyou/acs5000/acs5000_fc.py
Normal file
165
cinder/volume/drivers/toyou/acs5000/acs5000_fc.py
Normal 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
|
@ -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': {}}
|
||||||
|
@ -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
|
|
@ -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
|
@ -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
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
New FC cinder volume driver for TOYOU NetStor Storage.
|
Loading…
Reference in New Issue
Block a user