Cinder volume driver for Inspur AS13000 series

Features that Inspur AS13000 Driver support:
Create, list, delete, attach (map), and detach (unmap) volumes
Create, list, and delete volume snapshots
Copy an image to a volume
Copy a volume to an image
Clone a volume
Extend a volume
Create a volume from a snapshot

ThirdPartySystems: INSPUR CI

Change-Id: Ib18ffb38f87747805a3aaf0c3837d5b8bb71b101
Implements: Blueprint inspur-as13000-driver
This commit is contained in:
jiaohaolin 2018-04-20 16:02:39 +08:00 committed by wang yong
parent 0a19233f79
commit de89f6c370
9 changed files with 2319 additions and 0 deletions

View File

@ -119,6 +119,8 @@ from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_fc as \
from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_iscsi as \
cinder_volume_drivers_ibm_storwize_svc_storwizesvciscsi
from cinder.volume.drivers import infinidat as cinder_volume_drivers_infinidat
from cinder.volume.drivers.inspur.as13000 import as13000_driver as \
cinder_volume_drivers_inspur_as13000_as13000driver
from cinder.volume.drivers.inspur.instorage import instorage_common as \
cinder_volume_drivers_inspur_instorage_instoragecommon
from cinder.volume.drivers.inspur.instorage import instorage_iscsi as \
@ -247,6 +249,8 @@ def list_opts():
cinder_volume_driver.nvmet_opts,
cinder_volume_drivers_datacore_driver.datacore_opts,
cinder_volume_drivers_datacore_iscsi.datacore_iscsi_opts,
cinder_volume_drivers_inspur_as13000_as13000driver.
inspur_as13000_opts,
cinder_volume_drivers_inspur_instorage_instoragecommon.
instorage_mcs_opts,
cinder_volume_drivers_inspur_instorage_instorageiscsi.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,873 @@
# Copyright 2017 Inspur 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.
"""
Volume driver for Inspur AS13000
"""
import ipaddress
import json
import random
import re
import time
import eventlet
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import units
import requests
from cinder import exception
from cinder.i18n import _
from cinder import interface
from cinder import utils
from cinder.volume.drivers.san import san
from cinder.volume import utils as volume_utils
LOG = logging.getLogger(__name__)
inspur_as13000_opts = [
cfg.ListOpt(
'as13000_ipsan_pools',
default=['Pool0'],
help='The Storage Pools Cinder should use, a comma separated list.'),
cfg.IntOpt(
'as13000_token_available_time',
default=3300,
min=600, max=3600,
help='The effective time of token validity in seconds.'),
cfg.StrOpt(
'as13000_meta_pool',
help='The pool which is used as a meta pool when creating a volume, '
'and it should be a replication pool at present. '
'If not set, the driver will choose a replication pool '
'from the value of as13000_ipsan_pools.'),
]
CONF = cfg.CONF
CONF.register_opts(inspur_as13000_opts)
class RestAPIExecutor(object):
def __init__(self, hostname, port, username, password):
self._username = username
self._password = password
self._token = None
self._baseurl = 'http://%s:%s/rest' % (hostname, port)
def login(self):
"""Login the AS13000 and store the token."""
self._token = self._login()
LOG.debug('Login the AS13000.')
def _login(self):
"""Do request to login the AS13000 and get the token."""
method = 'security/token'
params = {'name': self._username, 'password': self._password}
token = self.send_rest_api(method=method, params=params,
request_type='post').get('token')
return token
@utils.retry(exception.VolumeDriverException, interval=1, retries=3)
def send_rest_api(self, method, params=None, request_type='post'):
try:
return self.send_api(method, params, request_type)
except exception.VolumeDriverException:
self.login()
raise
@staticmethod
@utils.trace_method
def do_request(cmd, url, header, data):
"""Send request to the storage and handle the response."""
if cmd in ['post', 'get', 'put', 'delete']:
req = getattr(requests, cmd)(url, data=data, headers=header)
else:
msg = (_('Unsupported cmd: %s.') % cmd)
raise exception.VolumeBackendAPIException(msg)
response = req.json()
code = req.status_code
LOG.debug('CODE: %(code)s, RESPONSE: %(response)s.',
{'code': code, 'response': response})
if code != 200:
msg = (_('Code: %(code)s, URL: %(url)s, Message: %(msg)s.')
% {'code': req.status_code,
'url': req.url,
'msg': req.text})
LOG.error(msg)
raise exception.VolumeDriverException(msg)
return response
@utils.trace
def send_api(self, method, params=None, request_type='post'):
if params:
params = json.dumps(params)
url = '%s/%s' % (self._baseurl, method)
# header is not needed when the driver login the backend
if method == 'security/token':
if request_type == 'delete':
header = {'X-Auth-Token': self._token}
else:
header = None
else:
if not self._token:
self.login()
header = {'X-Auth-Token': self._token}
response = self.do_request(request_type, url, header, params)
try:
code = response.get('code')
if code == 0:
if request_type == 'get':
data = response.get('data')
else:
if method == 'security/token':
data = response.get('data')
else:
data = response.get('message')
data = str(data).lower()
if hasattr(data, 'success'):
return
elif code == 301:
msg = _('Token is expired.')
LOG.error(msg)
raise exception.VolumeDriverException(msg)
else:
message = response.get('message')
msg = (_('Unexpected RestAPI response: %(code)d %(msg)s.') % {
'code': code, 'msg': message})
LOG.error(msg)
raise exception.VolumeBackendAPIException(msg)
except ValueError:
msg = _("Deal with response failed.")
raise exception.VolumeDriverException(msg)
return data
@interface.volumedriver
class AS13000Driver(san.SanISCSIDriver):
"""Driver for Inspur AS13000 storage.
Version history:
.. code-block:: none
1.0.0 - Initial driver
"""
VENDOR = 'INSPUR'
VERSION = '1.0.0'
PROTOCOL = 'iSCSI'
# ThirdPartySystems wiki page
CI_WIKI_NAME = 'INSPUR_CI'
def __init__(self, *args, **kwargs):
super(AS13000Driver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(inspur_as13000_opts)
self.hostname = self.configuration.san_ip
self.port = self.configuration.safe_get('san_api_port') or 8088
self.username = self.configuration.san_login
self.password = self.configuration.san_password
self.token_available_time = (self.configuration.
as13000_token_available_time)
self.pools = self.configuration.as13000_ipsan_pools
self.meta_pool = self.configuration.as13000_meta_pool
self.pools_info = {}
self.nodes = []
self._token_time = 0
# get the RestAPIExecutor
self._rest = RestAPIExecutor(self.hostname,
self.port,
self.username,
self.password)
@utils.trace
def do_setup(self, context):
# get tokens for the driver
self._rest.login()
self._token_time = time.time()
# get available nodes in the backend
for node in self._get_cluster_status():
if node.get('healthStatus') == 1 and node.get('ip'):
self.nodes.append(node)
# collect pools info
meta_pools = [self.meta_pool] if self.meta_pool else []
self.pools_info = self._get_pools_info(self.pools + meta_pools)
# setup the meta pool if it is not setted
if not self.meta_pool:
for pool_info in self.pools_info.values():
if pool_info['type'] in (1, '1'):
self.meta_pool = pool_info['name']
break
self._check_pools()
self._check_meta_pool()
@utils.trace
def check_for_setup_error(self):
"""Do check to make sure service is available."""
# check the required flags in conf
required_flags = ['san_ip', 'san_login', 'san_password',
'as13000_ipsan_pools']
for flag in required_flags:
value = self.configuration.safe_get(flag)
if not value:
msg = (_('Required flag %s is not set.') % flag)
LOG.error(msg)
raise exception.InvalidConfigurationValue(option=flag,
value=value)
# make sure at least one node can
if not self.nodes:
msg = _('No healthy nodes are available!')
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
def _check_pools(self):
"""Check the pool in conf exist in the AS13000."""
if not set(self.pools).issubset(self.pools_info):
pools = set(self.pools) - set(self.pools_info)
msg = _('Pools %s do not exist.') % pools
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
def _check_meta_pool(self):
"""Check whether the meta pool is valid."""
if not self.meta_pool:
msg = _('Meta pool is not set.')
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
if self.meta_pool not in self.pools_info:
msg = _('Meta pool %s does not exist.') % self.meta_pool
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
if self.pools_info[self.meta_pool]['type'] not in (1, '1'):
msg = _('Meta pool %s is not a replication pool.') % self.meta_pool
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
@utils.trace
def create_volume(self, volume):
"""Create volume in the backend."""
pool = volume_utils.extract_host(volume.host, level='pool')
size = volume.size * units.Ki
name = self._trans_name_down(volume.name)
method = 'block/lvm'
request_type = "post"
params = {
"name": name,
"capacity": size,
"dataPool": pool,
"dataPoolType": self.pools_info[pool]['type'],
"metaPool": self.meta_pool
}
self._rest.send_rest_api(method=method, params=params,
request_type=request_type)
@utils.trace
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a new volume base on a specific snapshot."""
if snapshot.volume_size > volume.size:
msg = (_("create_volume_from_snapshot: snapshot %(snapshot_name)s "
"size is %(snapshot_size)dGB and doesn't fit in target "
"volume %(volume_name)s of size %(volume_size)dGB.") %
{'snapshot_name': snapshot.name,
'snapshot_size': snapshot.volume_size,
'volume_name': volume.name,
'volume_size': volume.size})
LOG.error(msg)
raise exception.InvalidInput(message=msg)
src_vol_name = self._trans_name_down(snapshot.volume_name)
source_vol = snapshot.volume
src_pool = volume_utils.extract_host(source_vol['host'],
level='pool')
dest_name = self._trans_name_down(volume.name)
dest_pool = volume_utils.extract_host(volume.host, level='pool')
snap_name = self._trans_name_down(snapshot.name)
# lock the snapshot before clone from it
self._snapshot_lock_op('lock', src_vol_name, snap_name, src_pool)
# do clone from snap to a volume
method = 'snapshot/volume/cloneLvm'
request_type = 'post'
params = {'originalLvm': src_vol_name,
'originalPool': src_pool,
'originalSnap': snap_name,
'name': dest_name,
'pool': dest_pool}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
# do filling the cloned volume
self._filling_volume(dest_name, dest_pool)
# wait until the cloned volume has been filled
while True:
if self._wait_volume_filled(dest_name, dest_pool, 10, 5):
break
# unlock the original snapshot
self._snapshot_lock_op('unlock', src_vol_name, snap_name, src_pool)
if volume.size > snapshot.volume_size:
self.extend_volume(volume, volume.size)
@utils.trace
def create_cloned_volume(self, volume, src_vref):
"""Clone a volume."""
if src_vref.size > volume.size:
msg = (_("create_cloned_volume: source volume %(src_vol)s "
"size is %(src_size)dGB and doesn't fit in target "
"volume %(tgt_vol)s of size %(tgt_size)dGB.") %
{'src_vol': src_vref.name,
'src_size': src_vref.size,
'tgt_vol': volume.name,
'tgt_size': volume.size})
LOG.error(msg)
raise exception.InvalidInput(message=msg)
dest_pool = volume_utils.extract_host(volume.host, level='pool')
dest_vol_name = self._trans_name_down(volume.name)
src_pool = volume_utils.extract_host(src_vref.host, level='pool')
src_vol_name = self._trans_name_down(src_vref.name)
method = 'block/lvm/clone'
request_type = 'post'
params = {'srcVolumeName': src_vol_name,
'srcPoolName': src_pool,
'destVolumeName': dest_vol_name,
'destPoolName': dest_pool}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
if volume.size > src_vref.size:
self.extend_volume(volume, volume.size)
@utils.trace
def extend_volume(self, volume, new_size):
"""Extend volume to new size."""
name = self._trans_name_down(volume.name)
if not self._check_volume(volume):
msg = _('Extend Volume Failed: Volume %s does not exist.') % name
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
size = new_size * units.Ki
pool = volume_utils.extract_host(volume.host, level='pool')
method = 'block/lvm'
request_type = 'put'
params = {'pool': pool,
'name': name,
'newCapacity': size}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@utils.trace
def delete_volume(self, volume):
"""Delete volume from AS13000."""
name = self._trans_name_down(volume.name)
if not self._check_volume(volume):
# if volume is not exist in backend, the driver will do
# nothing but log it
LOG.info('Tried to delete non-existent volume %(name)s.',
{'name': name})
return
pool = volume_utils.extract_host(volume.host, level='pool')
method = 'block/lvm?pool=%s&lvm=%s' % (pool, name)
request_type = 'delete'
self._rest.send_rest_api(method=method, request_type=request_type)
@utils.trace
def create_snapshot(self, snapshot):
"""Create snapshot of volume in backend.
The snapshot type of AS13000 is copy-on-write.
"""
source_volume = snapshot.volume
volume_name = self._trans_name_down(source_volume.name)
if not self._check_volume(source_volume):
msg = (_('create_snapshot: Source_volume %s does not exist.')
% volume_name)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
pool = volume_utils.extract_host(source_volume.host, level='pool')
snapshot_name = self._trans_name_down(snapshot.name)
method = 'snapshot/volume'
request_type = 'post'
params = {'snapName': snapshot_name,
'volumeName': volume_name,
'poolName': pool,
'snapType': 'r'}
self._rest.send_rest_api(method=method, params=params,
request_type=request_type)
@utils.trace
def delete_snapshot(self, snapshot):
"""Delete snapshot of volume."""
source_volume = snapshot.volume
volume_name = self._trans_name_down(source_volume.name)
if self._check_volume(source_volume) is False:
msg = (_('delete_snapshot: Source_volume %s does not exist.')
% volume_name)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
pool = volume_utils.extract_host(source_volume.host, level='pool')
snapshot_name = self._trans_name_down(snapshot.name)
method = ('snapshot/volume?snapName=%s&volumeName=%s&poolName=%s'
% (snapshot_name, volume_name, pool))
request_type = 'delete'
self._rest.send_rest_api(method=method, request_type=request_type)
@utils.trace
def get_volume_stats(self, refresh=False):
"""Get volume stats.
If we haven't gotten stats yet or 'refresh' is True,
run update the stats first.
"""
if not self._stats or refresh:
self._update_volume_stats()
return self._stats
@utils.trace
def _update_volume_stats(self):
"""Update the backend stats including driver info and pools info."""
# As _update_volume_stats runs periodically,
# so we can do a check and refresh the token each time it runs.
time_difference = time.time() - self._token_time
if time_difference > self.token_available_time:
self._rest.login()
self._token_time = time.time()
LOG.debug('Token of the Driver has been refreshed.')
# update the backend stats
data = {}
backend_name = self.configuration.safe_get('volume_backend_name')
data['vendor_name'] = self.VENDOR
data['driver_version'] = self.VERSION
data['storage_protocol'] = self.PROTOCOL
data['volume_backend_name'] = backend_name
data['pools'] = self._get_pools_stats()
self._stats = data
LOG.debug('Update volume stats : %(stats)s.', {'stats': self._stats})
def _build_target_portal(self, ip, port):
"""Build iSCSI portal for both IPV4 and IPV6."""
addr = ipaddress.ip_address(ip)
if addr.version == 4:
ipaddr = ip
else:
ipaddr = '[%s]' % ip
return '%(ip)s:%(port)s' % {'ip': ipaddr, 'port': port}
@utils.trace
def initialize_connection(self, volume, connector, **kwargs):
"""Initialize connection steps:
1. check if the host exist in targets.
2.1 if there is target that has the host, add the volume to the target.
2.2 if not, create an target add host to host add volume to host.
3. return the target info.
"""
host_ip = connector['ip']
multipath = connector.get("multipath", False)
# Check if there host exist in targets
host_exist, target_name, node_of_target = self._get_target_from_conn(
host_ip)
if not host_exist:
# host doesn't exist, need create target and bind the host,
# generate the target name
_TARGET_NAME_PATTERN = 'target.inspur.%(host)s-%(padding)s'
_padding = str(random.randint(0, 99999999)).zfill(8)
target_name = _TARGET_NAME_PATTERN % {'host': connector['host'],
'padding': _padding}
# decide the nodes to be used
if multipath:
node_of_target = [node['name'] for node in self.nodes]
else:
# single node
node_of_target = [self.nodes[0]['name']]
# create the target
nodes = ','.join(node_of_target)
self._create_target(target_node=nodes,
target_name=target_name)
self._add_host_to_target(host_ip=host_ip,
target_name=target_name)
self._add_lun_to_target(target_name=target_name, volume=volume)
if self.configuration.use_chap_auth:
self._add_chap_to_target(target_name,
self.configuration.chap_username,
self.configuration.chap_password)
lun_id = self._get_lun_id(volume, target_name)
connection_data = {
'target_discovered': True,
'volume_id': volume.id,
}
portals = []
for node_name in node_of_target:
for node in self.nodes:
if node['name'] == node_name:
portal = self._build_target_portal(node.get('ip'), '3260')
portals.append(portal)
if multipath:
connection_data.update({
'target_portals': portals,
'target_luns': [int(lun_id)] * len(portals),
'target_iqns': [target_name] * len(portals)
})
else:
# single node
connection_data.update({
'target_portal': portals[0],
'target_lun': int(lun_id),
'target_iqn': target_name
})
if self.configuration.use_chap_auth:
connection_data['auth_method'] = 'CHAP'
connection_data['auth_username'] = self.configuration.chap_username
connection_data['auth_password'] = self.configuration.chap_password
return {'driver_volume_type': 'iscsi', 'data': connection_data}
@utils.trace
def terminate_connection(self, volume, connector, **kwargs):
"""Delete lun from target.
If target has no any lun, driver will delete the target.
"""
volume_name = self._trans_name_down(volume.name)
target_name = None
lun_id = None
host_ip = None
if connector and 'ip' in connector:
host_ip = connector['ip']
target_list = self._get_target_list()
for target in target_list:
if not host_ip or host_ip in target['hostIp']:
for lun in target['lun']:
if volume_name == lun['lvm']:
target_name = target['name']
lun_id = lun['lunID']
break
if lun_id is not None:
break
if lun_id is None:
return
self._delete_lun_from_target(target_name=target_name,
lun_id=lun_id)
luns = self._get_lun_list(target_name)
if not luns:
self._delete_target(target_name)
def _get_pools_info(self, pools):
"""Get the pools info."""
method = 'block/pool?type=2'
requests_type = 'get'
pools_data = self._rest.send_rest_api(method=method,
request_type=requests_type)
pools_info = {}
for pool_data in pools_data:
if pool_data['name'] in pools:
pools_info[pool_data['name']] = pool_data
return pools_info
@utils.trace
def _get_pools_stats(self):
"""Generate the pool stat information."""
pools_info = self._get_pools_info(self.pools)
pools = []
for pool_info in pools_info.values():
total_capacity = pool_info.get('totalCapacity')
total_capacity_gb = self._unit_convert(total_capacity)
used_capacity = pool_info.get('usedCapacity')
used_capacity_gb = self._unit_convert(used_capacity)
free_capacity_gb = total_capacity_gb - used_capacity_gb
pool = {
'pool_name': pool_info.get('name'),
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb,
'thin_provisioning_support': True,
'thick_provisioning_support': False,
}
pools.append(pool)
return pools
@utils.trace
def _get_target_from_conn(self, host_ip):
"""Get target information base on the host ip."""
host_exist = False
target_name = None
node = None
target_list = self._get_target_list()
for target in target_list:
if host_ip in target['hostIp']:
host_exist = True
target_name = target['name']
node = target['node']
break
return host_exist, target_name, node
@utils.trace
def _get_target_list(self):
"""Get a list of all targets in the backend."""
method = 'block/target/detail'
request_type = 'get'
data = self._rest.send_rest_api(method=method,
request_type=request_type)
return data
@utils.trace
def _create_target(self, target_name, target_node):
"""Create a target on the specified node."""
method = 'block/target'
request_type = 'post'
params = {'name': target_name, 'nodeName': target_node}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@utils.trace
def _delete_target(self, target_name):
"""Delete all target of all the node."""
method = 'block/target?name=%s' % target_name
request_type = 'delete'
self._rest.send_rest_api(method=method,
request_type=request_type)
@utils.trace
def _add_chap_to_target(self, target_name, chap_username, chap_password):
"""Add CHAP to target."""
method = 'block/chap/bond'
request_type = 'post'
params = {'target': target_name,
'user': chap_username,
'password': chap_password}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@utils.trace
def _add_host_to_target(self, host_ip, target_name):
"""Add the authority of host to target."""
method = 'block/host'
request_type = 'post'
params = {'name': target_name, 'hostIp': host_ip}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@utils.trace
@utils.retry(exceptions=exception.VolumeDriverException,
interval=1,
retries=3)
def _add_lun_to_target(self, target_name, volume):
"""Add volume to target."""
pool = volume_utils.extract_host(volume.host, level='pool')
volume_name = self._trans_name_down(volume.name)
method = 'block/lun'
request_type = 'post'
params = {'name': target_name,
'pool': pool,
'lvm': volume_name}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@utils.trace
def _delete_lun_from_target(self, target_name, lun_id):
"""Delete lun from target_name."""
method = 'block/lun?name=%s&id=%s&force=1' % (target_name, lun_id)
request_type = 'delete'
self._rest.send_rest_api(method=method, request_type=request_type)
@utils.trace
def _get_lun_list(self, target_name):
"""Get all lun list of the target."""
method = 'block/lun?name=%s' % target_name
request_type = 'get'
return self._rest.send_rest_api(method=method,
request_type=request_type)
@utils.trace
def _snapshot_lock_op(self, op, vol_name, snap_name, pool_name):
"""Lock or unlock a snapshot to protect the snapshot.
op is 'lock' for lock and 'unlock' for unlock
"""
method = 'snapshot/volume/%s' % op
request_type = 'post'
params = {'snapName': snap_name,
'volumeName': vol_name,
'poolName': pool_name}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@utils.trace
def _filling_volume(self, name, pool):
"""Filling a volume so that make it independently."""
method = 'block/lvm/filling'
request_type = 'post'
params = {'pool': pool, 'name': name}
self._rest.send_rest_api(method=method,
params=params,
request_type=request_type)
@utils.trace
def _wait_volume_filled(self, name, pool, attempts, interval):
"""Wait until the volume is filled."""
try_num = 0
while try_num < attempts:
volumes = self._get_volumes(pool)
for vol in volumes:
if name == vol['name']:
if vol['lvmType'] == 1:
return True
else:
break
eventlet.sleep(interval)
try_num += 1
return False
@utils.trace
def _check_volume(self, volume):
"""Check if the volume exists in the backend."""
pool = volume_utils.extract_host(volume.host, 'pool')
volume_name = self._trans_name_down(volume.name)
attempts = 3
while attempts > 0:
volumes = self._get_volumes(pool)
attempts -= 1
for vol in volumes:
if volume_name == vol.get('name'):
return True
eventlet.sleep(1)
return False
@utils.trace
def _get_volumes(self, pool):
"""Get all the volumes in the pool."""
method = 'block/lvm?pool=%s' % pool
request_type = 'get'
return self._rest.send_rest_api(method=method,
request_type=request_type)
@utils.trace
def _get_cluster_status(self):
"""Get all nodes of the backend."""
method = 'cluster/node'
request_type = 'get'
return self._rest.send_rest_api(method=method,
request_type=request_type)
@utils.trace
def _get_lun_id(self, volume, target_name):
"""Get lun id of the voluem in a target."""
pool = volume_utils.extract_host(volume.host, level='pool')
volume_name = self._trans_name_down(volume.name)
lun_id = None
luns = self._get_lun_list(target_name)
for lun in luns:
mappinglvm = lun.get('mappingLvm')
lun_name = mappinglvm.replace(r'%s/' % pool, '')
if lun_name == volume_name:
lun_id = lun.get('id')
return lun_id
def _trans_name_down(self, name):
"""Legitimize the name.
Because AS13000 volume name is only allowed letters, numbers, and '_'.
"""
return name.replace('-', '_')
@utils.trace
def _unit_convert(self, capacity):
"""Convert all units to GB.
The capacity is a string in form like 100GB, 20TB, 100B,
this routine will convert to GB unit.
"""
capacity = capacity.upper()
try:
unit = re.findall(r'[A-Z]+', capacity)[0]
except BaseException:
unit = ''
capacity = float(capacity.replace(unit, ''))
size_gb = 0.0
if unit in ['B', '']:
size_gb = capacity / units.Gi
elif unit in ['K', 'KB']:
size_gb = capacity / units.Mi
elif unit in ['M', 'MB']:
size_gb = capacity / units.Ki
elif unit in ['G', 'GB']:
size_gb = capacity
elif unit in ['T', 'TB']:
size_gb = capacity * units.Ki
elif unit in ['P', 'PB']:
size_gb = capacity * units.Mi
elif unit in ['E', 'EB']:
size_gb = capacity * units.Gi
return float('%.0f' % size_gb)

View File

@ -0,0 +1,78 @@
===================================
Inspur AS13000 series volume driver
===================================
Inspur AS13000 series volume driver provides OpenStack Compute instances
with access to Inspur AS13000 series storage system.
Inspur AS13000 storage can be used with iSCSI connection.
This documentation explains how to configure and connect the block storage
nodes to Inspur AS13000 series storage.
Driver options
~~~~~~~~~~~~~~
The following table contains the configuration options supported by the
Inspur AS13000 iSCSI driver.
.. config-table::
:config-target: Inspur AS13000
cinder.volume.drivers.inspur.as13000.as13000_driver
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.
Configure Inspur AS13000 iSCSI backend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section details the steps required to configure the Inspur AS13000
storage cinder driver.
#. In the ``cinder.conf`` configuration file under the ``[DEFAULT]``
section, set the enabled_backends parameter.
.. code-block:: ini
[DEFAULT]
enabled_backends = AS13000-1
#. Add a backend group section for backend group specified
in the enabled_backends parameter.
#. In the newly created backend group section, set the
following configuration options:
.. code-block:: ini
[AS13000-1]
# The driver path
volume_driver = cinder.volume.drivers.inspur.as13000.as13000_driver.AS13000Driver
# Management IP of Inspur AS13000 storage array
san_ip = 10.0.0.10
# The Rest API port
san_api_port = 8088
# Management username of Inspur AS13000 storage array
san_login = root
# Management password of Inspur AS13000 storage array
san_password = passw0rd
# The Pool used to allocated volumes
as13000_ipsan_pools = Pool0
# The Meta Pool to use, should be a replication Pool
as13000_meta_pool = Pool_Rep
# Backend name
volume_backend_name = AS13000
#. Save the changes to the ``/etc/cinder/cinder.conf`` file and
restart the ``cinder-volume`` service.

View File

@ -51,6 +51,7 @@ Driver Configuration Reference
drivers/ibm-storage-volume-driver
drivers/ibm-storwize-svc-driver
drivers/infinidat-volume-driver
drivers/inspur-as13000-driver
drivers/inspur-instorage-driver
drivers/kaminario-driver
drivers/lenovo-driver

View File

@ -102,6 +102,9 @@ title=Infinidat Storage Driver (iSCSI, FC)
[driver.inspur]
title=Inspur G2 Storage Driver (iSCSI, FC)
[driver.inspur_as13000]
title=Inspur AS13000 Storage Driver (iSCSI)
[driver.kaminario]
title=Kaminario Storage Driver (iSCSI, FC)
@ -228,6 +231,7 @@ driver.ibm_gpfs=complete
driver.ibm_storwize=complete
driver.ibm_xiv=complete
driver.inspur=complete
driver.inspur_as13000=complete
driver.kaminario=complete
driver.lenovo=complete
driver.linbit_drbd=complete
@ -292,6 +296,7 @@ driver.ibm_gpfs=complete
driver.ibm_storwize=complete
driver.ibm_xiv=complete
driver.inspur=complete
driver.inspur_as13000=complete
driver.kaminario=complete
driver.lenovo=complete
driver.linbit_drbd=complete
@ -356,6 +361,7 @@ driver.ibm_gpfs=missing
driver.ibm_storwize=complete
driver.ibm_xiv=missing
driver.inspur=complete
driver.inspur_as13000=missing
driver.kaminario=missing
driver.lenovo=missing
driver.linbit_drbd=missing
@ -421,6 +427,7 @@ driver.ibm_gpfs=missing
driver.ibm_storwize=complete
driver.ibm_xiv=missing
driver.inspur=complete
driver.inspur_as13000=missing
driver.kaminario=missing
driver.lenovo=missing
driver.linbit_drbd=missing
@ -487,6 +494,7 @@ driver.ibm_gpfs=missing
driver.ibm_storwize=complete
driver.ibm_xiv=complete
driver.inspur=complete
driver.inspur_as13000=missing
driver.kaminario=complete
driver.lenovo=missing
driver.linbit_drbd=missing
@ -554,6 +562,7 @@ driver.ibm_gpfs=missing
driver.ibm_storwize=complete
driver.ibm_xiv=complete
driver.inspur=complete
driver.inspur_as13000=missing
driver.kaminario=missing
driver.lenovo=missing
driver.linbit_drbd=missing
@ -620,6 +629,7 @@ driver.ibm_gpfs=missing
driver.ibm_storwize=missing
driver.ibm_xiv=missing
driver.inspur=missing
driver.inspur_as13000=complete
driver.kaminario=complete
driver.lenovo=missing
driver.linbit_drbd=missing
@ -687,6 +697,7 @@ driver.ibm_gpfs=missing
driver.ibm_storwize=missing
driver.ibm_xiv=missing
driver.inspur=missing
driver.inspur_as13000=missing
driver.kaminario=missing
driver.lenovo=missing
driver.linbit_drbd=missing
@ -754,6 +765,7 @@ driver.ibm_gpfs=missing
driver.ibm_storwize=complete
driver.ibm_xiv=complete
driver.inspur=missing
driver.inspur_as13000=complete
driver.kaminario=missing
driver.lenovo=missing
driver.linbit_drbd=missing

View File

@ -0,0 +1,4 @@
---
features:
- |
New Cinder volume driver for Inspur AS13000 series.