HDS Cinder Driver. Rev #1

blueprint hds-hus-iscsi-cinder-driver
This is the first rev of Hitachi Data Systems Cinder iSCSI driver.
This driver works with HUS (df850) array.
This driver contains all the base-line features specified for Havana release.
Amended into this submission are changes from code-reviews.

Docimpact: Bug #1180648

Change-Id: Ia27d076443b10da2c653456f9292dd192362b853
This commit is contained in:
Lakhinder Walia 2013-05-07 16:05:46 -07:00
parent 77a77456af
commit da00b6bcca
8 changed files with 888 additions and 6 deletions

View File

@ -426,6 +426,10 @@ class ConfigNotFound(NotFound):
message = _("Could not find config at %(path)s")
class ParameterNotFound(NotFound):
message = _("Could not find parameter %(param)s")
class PasteAppNotFound(NotFound):
message = _("Could not load paste app '%(name)s' from %(path)s")

254
cinder/tests/test_hds.py Normal file
View File

@ -0,0 +1,254 @@
# Copyright (c) 2013 Hitachi Data Systems, Inc.
# Copyright (c) 2013 OpenStack LLC.
# 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.
#
"""
Self test for Hitachi Unified Storage (HUS) platform.
"""
import mox
import os
import tempfile
from cinder import test
from cinder.volume import configuration as conf
from cinder.volume.drivers.hds import hds
CONF = """<?xml version="1.0" encoding="UTF-8" ?>
<config>
<mgmt_ip0>172.17.44.16</mgmt_ip0>
<mgmt_ip1>172.17.44.17</mgmt_ip1>
<username>system</username>
<password>manager</password>
<svc_0>
<volume_type>default</volume_type>
<iscsi_ip>172.17.39.132</iscsi_ip>
<hdp>9</hdp>
</svc_0>
<svc_1>
<volume_type>silver</volume_type>
<iscsi_ip>172.17.39.133</iscsi_ip>
<hdp>9</hdp>
</svc_1>
<svc_2>
<volume_type>gold</volume_type>
<iscsi_ip>172.17.39.134</iscsi_ip>
<hdp>9</hdp>
</svc_2>
<svc_3>
<volume_type>platinum</volume_type>
<iscsi_ip>172.17.39.135</iscsi_ip>
<hdp>9</hdp>
</svc_3>
<snapshot>
<hdp>9</hdp>
</snapshot>
<lun_start>
3300
</lun_start>
</config>
"""
class SimulatedHusBackend:
"""Simulation Back end. Talks to HUS."""
alloc_lun = [] # allocated LUs
connections = [] # iSCSI connections
def __init__(self):
self.start_lun = 0
def get_version(self, cmd, ip0, ip1, user, pw):
out = ("Array_ID: 92210013 (HUS130) version: 0920/B-S LU: 4096"
" RG: 75 RG_LU: 1024 Utility_version: 1.0.0")
return out
def get_iscsi_info(self, cmd, ip0, ip1, user, pw):
out = """CTL: 0 Port: 4 IP: 172.17.39.132 Port: 3260 Link: Up
CTL: 0 Port: 5 IP: 172.17.39.133 Port: 3260 Link: Up
CTL: 1 Port: 4 IP: 172.17.39.134 Port: 3260 Link: Up
CTL: 1 Port: 5 IP: 172.17.39.135 Port: 3260 Link: Up"""
return out
def get_hdp_info(self, cmd, ip0, ip1, user, pw):
out = """HDP: 2 272384 MB 33792 MB 12 % LUs: 70 Normal Normal
HDP: 9 546816 MB 73728 MB 13 % LUs: 194 Normal Normal"""
return out
def create_lu(self, cmd, ip0, ip1, user, pw, id, hdp, start, end, size):
if self.start_lun < int(start): # initialize first time
self.start_lun = int(start)
out = ("LUN: %d HDP: 9 size: %s MB, is successfully created" %
(self.start_lun, size))
self.alloc_lun.append(str(self.start_lun))
self.start_lun += 1
return out
def delete_lu(self, cmd, ip0, ip1, user, pw, id, lun):
out = ""
if lun in self.alloc_lun:
out = "LUN: %s is successfully deleted" % (lun)
self.alloc_lun.remove(lun)
return out
def create_dup(self, cmd, ip0, ip1, user, pw, id, src_lun,
hdp, start, end, size):
out = ("LUN: %s HDP: 9 size: %s MB, is successfully created" %
(self.start_lun, size))
self.alloc_lun.append(str(self.start_lun))
self.start_lun += 1
return out
def add_iscsi_conn(self, cmd, ip0, ip1, user, pw, id, lun, ctl, port, iqn,
tgt_alias, initiator, init_alias):
conn = (initiator, iqn, ctl, port)
out = ("iSCSI Initiator: %s, index: 26, and Target: %s, index 8 is \
successfully paired @ CTL: %s, Port: %s" % conn)
SimulatedHusBackend.connections.append(conn)
return out
def del_iscsi_conn(self, cmd, ip0, ip1, user, pw, id, lun, ctl, port, iqn,
initiator, force):
conn = (initiator, iqn, ctl, port)
out = ("iSCSI Initiator: %s, index: 26, and Target: %s, index 8 is \
successfully un-paired @ CTL: %s, Port: %s" % conn)
if conn in SimulatedHusBackend.connections:
SimulatedHusBackend.connections.remove(conn)
return out
# The following information is passed on to tests, when creating a volume
_VOLUME = {'volume_id': '1234567890', 'size': 128,
'volume_type': None, 'provider_location': None, 'id': 'abcdefg'}
class HUSiSCSIDriverTest(test.TestCase):
"""Test HUS iSCSI volume driver."""
def __init__(self, *args, **kwargs):
super(HUSiSCSIDriverTest, self).__init__(*args, **kwargs)
def setUp(self):
super(HUSiSCSIDriverTest, self).setUp()
(handle, self.config_file) = tempfile.mkstemp('.xml')
os.write(handle, CONF)
os.close(handle)
SimulatedHusBackend.alloc_lun = []
SimulatedHusBackend.connections = []
self.mox = mox.Mox()
self.mox.StubOutWithMock(hds, 'factory_bend')
hds.factory_bend().AndReturn(SimulatedHusBackend())
self.mox.ReplayAll()
self.configuration = mox.MockObject(conf.Configuration)
self.configuration.hds_cinder_config_file = self.config_file
self.driver = hds.HUSDriver(configuration=self.configuration)
def tearDown(self):
os.remove(self.config_file)
self.mox.UnsetStubs()
super(HUSiSCSIDriverTest, self).tearDown()
def test_get_volume_stats(self):
stats = self.driver.get_volume_stats(True)
self.assertEqual(stats["vendor_name"], "HDS")
self.assertEqual(stats["storage_protocol"], "iSCSI")
self.assertTrue(stats["total_capacity_gb"] > 0)
def test_create_volume(self):
loc = self.driver.create_volume(_VOLUME)
self.assertNotEqual(loc, None)
vol = _VOLUME.copy()
vol['provider_location'] = loc['provider_location']
self.assertNotEqual(loc['provider_location'], None)
return vol
def test_delete_volume(self):
"""Delete a volume (test).
Note: this API call should not expect any exception:
This driver will silently accept a delete request, because
the DB can be out of sync, and Cinder manager will keep trying
to delete, even though the volume has been wiped out of the
Array. We don't want to have a dangling volume entry in the
customer dashboard.
"""
vol = self.test_create_volume()
self.assertTrue(SimulatedHusBackend.alloc_lun)
num_luns_before = len(SimulatedHusBackend.alloc_lun)
self.driver.delete_volume(vol)
num_luns_after = len(SimulatedHusBackend.alloc_lun)
self.assertTrue(num_luns_before > num_luns_after)
def test_create_snapshot(self):
vol = self.test_create_volume()
self.mox.StubOutWithMock(self.driver, '_id_to_vol')
self.driver._id_to_vol(vol['volume_id']).AndReturn(vol)
self.mox.ReplayAll()
svol = vol.copy()
svol['volume_size'] = svol['size']
loc = self.driver.create_snapshot(svol)
self.assertNotEqual(loc, None)
svol['provider_location'] = loc['provider_location']
return svol
def test_delete_snapshot(self):
"""Delete a snapshot (test).
Note: this API call should not expect any exception:
This driver will silently accept a delete request, because
the DB can be out of sync, and Cinder manager will keep trying
to delete, even though the snapshot has been wiped out of the
Array. We don't want to have a dangling snapshot entry in the
customer dashboard.
"""
svol = self.test_create_snapshot()
num_luns_before = len(SimulatedHusBackend.alloc_lun)
self.driver.delete_snapshot(svol)
num_luns_after = len(SimulatedHusBackend.alloc_lun)
self.assertTrue(num_luns_before > num_luns_after)
def test_create_volume_from_snapshot(self):
svol = self.test_create_snapshot()
vol = self.driver.create_volume_from_snapshot(_VOLUME, svol)
self.assertNotEqual(vol, None)
return vol
def test_initialize_connection(self):
connector = {}
connector['initiator'] = 'iqn.1993-08.org.debian:01:11f90746eb2'
connector['host'] = 'dut_1.lab.hds.com'
vol = self.test_create_volume()
conn = self.driver.initialize_connection(vol, connector)
self.assertTrue('hitachi' in conn['data']['target_iqn'])
self.assertTrue('3260' in conn['data']['target_portal'])
return (vol, connector)
def test_terminate_connection(self):
"""Terminate a connection (test).
Note: this API call should not expect any exception:
This driver will silently accept a terminate_connection request
because an error/exception return will only jeopardize the
connection tear down at a host.
"""
(vol, conn) = self.test_initialize_connection()
num_conn_before = len(SimulatedHusBackend.connections)
self.driver.terminate_connection(vol, conn)
num_conn_after = len(SimulatedHusBackend.connections)
self.assertTrue(num_conn_before > num_conn_after)

View File

@ -0,0 +1,16 @@
# Copyright (c) 2013 Hitachi Data Systems, Inc.
# Copyright (c) 2013 OpenStack LLC.
# 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.
#

View File

@ -0,0 +1,454 @@
# Copyright (c) 2013 Hitachi Data Systems, Inc.
# Copyright (c) 2013 OpenStack LLC.
# 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.
#
"""
iSCSI Cinder Volume driver for Hitachi Unified Storage (HUS) platform.
"""
from oslo.config import cfg
from xml.etree import ElementTree as ETree
from cinder import exception
from cinder import flags
from cinder.openstack.common import log as logging
from cinder import utils
from cinder.volume import driver
from cinder.volume.drivers.hds.hus_backend import HusBackend
LOG = logging.getLogger(__name__)
HUS_OPTS = [
cfg.StrOpt('hds_cinder_config_file',
default='/opt/hds/hus/cinder_hus_conf.xml',
help='configuration file for HDS cinder plugin for HUS'), ]
FLAGS = flags.FLAGS
FLAGS.register_opts(HUS_OPTS)
HI_IQN = 'iqn.1994-04.jp.co.hitachi:' # fixed string, for now.
HUS_DEFAULT_CONFIG = {'hus_cmd': 'hus_cmd',
'lun_start': '0',
'lun_end': '8192'}
def factory_bend():
"""Factory over-ride in self-tests."""
return HusBackend()
def _do_lu_range_check(start, end, maxlun):
"""Validate array allocation range."""
LOG.debug(_("Range: start LU: %(start)s, end LU: %(end)s")
% {'start': start,
'end': end})
if int(start) < 0:
msg = 'start LU limit too low: ' + start
raise exception.InvalidInput(reason=msg)
if int(start) >= int(maxlun):
msg = 'start LU limit high: ' + start + ' max: ' + maxlun
raise exception.InvalidInput(reason=msg)
if int(end) <= int(start):
msg = 'LU end limit too low: ' + end
raise exception.InvalidInput(reason=msg)
if int(end) > int(maxlun):
end = maxlun
LOG.debug(_("setting LU uppper (end) limit to %s") % maxlun)
return (start, end)
def _xml_read(root, element, check=None):
"""Read an xml element."""
try:
val = root.findtext(element)
LOG.info(_("%(element)s: %(val)s")
% {'element': element,
'val': val})
if val:
return val.strip()
if check:
raise exception.ParameterNotFound(param=element)
return None
except ETree.ParseError as e:
if check:
LOG.error(_("XML exception reading parameter: %s") % element)
raise e
else:
LOG.info(_("XML exception reading parameter: %s") % element)
return None
def _read_config(xml_config_file):
"""Read hds driver specific xml config file."""
try:
root = ETree.parse(xml_config_file).getroot()
except Exception:
raise exception.NotFound(message='config file not found: '
+ xml_config_file)
config = {}
arg_prereqs = ['mgmt_ip0', 'mgmt_ip1', 'username', 'password']
for req in arg_prereqs:
config[req] = _xml_read(root, req, 'check')
config['hdp'] = {}
config['services'] = {}
for svc in ['svc_0', 'svc_1', 'svc_2', 'svc_3']: # min one needed
if _xml_read(root, svc) is None:
continue
service = {}
service['label'] = svc
for arg in ['volume_type', 'hdp', 'iscsi_ip']: # none optional
service[arg] = _xml_read(root, svc + '/' + arg, 'check')
config['services'][service['volume_type']] = service
config['hdp'][service['hdp']] = service['hdp']
if config['services'].keys() is None: # at least one service required!
raise exception.ParameterNotFound(param="No service found")
config['snapshot_hdp'] = _xml_read(root, 'snapshot/hdp', 'check')
for arg in ['hus_cmd', 'lun_start', 'lun_end']: # optional
config[arg] = _xml_read(root, arg) or HUS_DEFAULT_CONFIG[arg]
return config
class HUSDriver(driver.ISCSIDriver):
"""HDS HUS volume driver."""
def _array_info_get(self):
"""Get array parameters."""
out = self.bend.get_version(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'])
inf = out.split()
return(inf[1], 'hus_' + inf[1], inf[6])
def _get_iscsi_info(self):
"""Validate array iscsi parameters."""
out = self.bend.get_iscsi_info(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'])
lines = out.split('\n')
conf = {} # dict based on iSCSI portal ip addresses
for line in lines:
if 'CTL' in line:
inf = line.split()
(ctl, port, ip, ipp) = (inf[1], inf[3], inf[5], inf[7])
conf[ip] = {}
conf[ip]['ctl'] = ctl
conf[ip]['port'] = port
conf[ip]['iscsi_port'] = ipp # HUS default: 3260
msg = _('portal: %(ip)s:%(ipp)s, CTL: %(ctl)s, port: %(port)s')
LOG.debug(msg
% {'ip': ip,
'ipp': ipp,
'ctl': ctl,
'port': port})
return conf
def _get_service(self, volume):
"""Get the available service parameters for a given volume type."""
label = None
if volume['volume_type']:
label = volume['volume_type']['name']
label = label or 'default'
if label in self.config['services'].keys():
svc = self.config['services'][label]
service = (svc['iscsi_ip'], svc['iscsi_port'], svc['ctl'],
svc['port'], svc['hdp']) # ip, ipp, ctl, port, hdp
else:
LOG.error(_("No configuration found for service: %s") % label)
raise exception.ParameterNotFound(param=label)
return service
def _get_stats(self):
"""Get HDP stats from HUS."""
total_cap = 0
total_used = 0
out = self.bend.get_hdp_info(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'])
for line in out.split('\n'):
if 'HDP' in line:
(hdp, size, _ign, used) = line.split()[1:5] # in MB
if hdp in self.config['hdp'].keys():
total_cap += int(size)
total_used += int(used)
hus_stat = {}
hus_stat['total_capacity_gb'] = int(total_cap / 1024) # in GB
hus_stat['free_capacity_gb'] = int((total_cap - total_used) / 1024)
be_name = self.configuration.safe_get('volume_backend_name')
hus_stat["volume_backend_name"] = be_name or 'HUSDriver'
hus_stat["vendor_name"] = 'HDS'
hus_stat["driver_version"] = '1.0'
hus_stat["storage_protocol"] = 'iSCSI'
hus_stat['QoS_support'] = False
hus_stat['reserved_percentage'] = 0
return hus_stat
def _get_hdp_list(self):
"""Get HDPs from HUS."""
out = self.bend.get_hdp_info(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'])
hdp_list = []
for line in out.split('\n'):
if 'HDP' in line:
hdp_list.extend(line.split()[1:2])
return hdp_list
def _check_hdp_list(self):
"""Verify all HDPs specified in the configuration exist."""
hdpl = self._get_hdp_list()
lst = self.config['hdp'].keys()
lst.extend([self.config['snapshot_hdp'], ])
for hdp in lst:
if hdp not in hdpl:
LOG.error(_("HDP not found: %s") % hdp)
err = "HDP not found: " + hdp
raise exception.ParameterNotFound(param=err)
def _id_to_vol(self, idd):
"""Given the volume id, retrieve the volume object from database."""
vol = self.db.volume_get(self.context, idd)
return vol
def __init__(self, *args, **kwargs):
"""Initialize, read different config parameters."""
super(HUSDriver, self).__init__(*args, **kwargs)
self.driver_stats = {}
self.context = {}
self.bend = factory_bend()
self.configuration.append_config_values(HUS_OPTS)
self.config = _read_config(self.configuration.hds_cinder_config_file)
(self.arid, self.hus_name, self.lumax) = self._array_info_get()
self._check_hdp_list()
start = self.config['lun_start']
end = self.config['lun_end']
maxlun = self.lumax
(self.start, self.end) = _do_lu_range_check(start, end, maxlun)
iscsi_info = self._get_iscsi_info()
for svc in self.config['services'].keys():
svc_ip = self.config['services'][svc]['iscsi_ip']
if svc_ip in iscsi_info.keys():
self.config['services'][svc]['port'] = (
iscsi_info[svc_ip]['port'])
self.config['services'][svc]['ctl'] = iscsi_info[svc_ip]['ctl']
self.config['services'][svc]['iscsi_port'] = (
iscsi_info[svc_ip]['iscsi_port'])
else: # config iscsi address not found on device!
LOG.error(_("iSCSI portal not found for service: %s") % svc_ip)
raise exception.ParameterNotFound(param=svc_ip)
return
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
return
def do_setup(self, context):
"""do_setup.
Setup and verify HDS HUS storage connection. But moved it to
__init__ as (setup/errors) could became an infinite loop.
"""
self.context = context
def ensure_export(self, context, volume):
return
def create_export(self, context, volume):
"""Create an export. Moved to initialize_connection."""
return
@utils.synchronized('hds_hus', external=True)
def create_volume(self, volume):
"""Create a LU on HUS."""
service = self._get_service(volume)
(_ip, _ipp, _ctl, _port, hdp) = service
out = self.bend.create_lu(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'],
self.arid, hdp, self.start, self.end,
'%s' % (int(volume['size']) * 1024))
lun = self.arid + '.' + out.split()[1]
sz = int(out.split()[5])
LOG.debug(_("LUN %(lun)s of size %(sz)s MB is created.")
% {'lun': lun,
'sz': sz})
return {'provider_location': lun}
@utils.synchronized('hds_hus', external=True)
def delete_volume(self, volume):
"""Delete an LU on HUS."""
loc = volume['provider_location']
if loc is None: # to take care of spurious input
return # which could cause exception.
(arid, lun) = loc.split('.')
myid = self.arid
if arid != myid:
LOG.error(_("Array Mismatch %(myid)s vs %(arid)s")
% {'myid': myid,
'arid': arid})
msg = 'Array id mismatch in volume delete'
raise exception.VolumeBackendAPIException(data=msg)
name = self.hus_name
LOG.debug(_("delete lun %(lun)s on %(name)s")
% {'lun': lun,
'name': name})
_out = self.bend.delete_lu(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'],
self.arid, lun)
def remove_export(self, context, volume):
"""Disconnect a volume from an attached instance."""
return
@utils.synchronized('hds_hus', external=True)
def initialize_connection(self, volume, connector):
"""Map the created volume to connector['initiator']."""
service = self._get_service(volume)
(ip, ipp, ctl, port, _hdp) = service
loc = volume['provider_location']
(_array_id, lun) = loc.split('.')
iqn = HI_IQN + loc
tgt_alias = 'cinder.' + loc
init_alias = connector['host'][:(31 - len(loc))] + '.' + loc
_out = self.bend.add_iscsi_conn(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'],
self.arid, lun, ctl, port, iqn,
tgt_alias, connector['initiator'],
init_alias)
hus_portal = ip + ':' + ipp
tgt = hus_portal + ',' + iqn + ',' + loc + ',' + ctl + ',' + port
properties = {}
properties['provider_location'] = tgt
properties['target_discovered'] = False
properties['target_portal'] = hus_portal
properties['target_iqn'] = iqn
properties['target_lun'] = 0 # for now !
properties['volume_id'] = volume['id']
return {'driver_volume_type': 'iscsi', 'data': properties}
@utils.synchronized('hds_hus', external=True)
def terminate_connection(self, volume, connector, **kwargs):
"""Terminate a connection to a volume."""
loc = volume['provider_location']
(_array_id, lun) = loc.split('.')
iqn = HI_IQN + loc
service = self._get_service(volume)
(_ip, _ipp, ctl, port, _hdp) = service
_out = self.bend.del_iscsi_conn(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'],
self.arid, lun, ctl, port, iqn,
connector['initiator'], 1)
return {'provider_location': loc}
@utils.synchronized('hds_hus', external=True)
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot."""
size = int(snapshot['volume_size']) * 1024
(_arid, slun) = snapshot['provider_location'].split('.')
service = self._get_service(volume)
(_ip, _ipp, _ctl, _port, hdp) = service
out = self.bend.create_dup(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'],
self.arid, slun, hdp,
self.start, self.end,
'%s' % (size))
lun = self.arid + '.' + out.split()[1]
sz = int(out.split()[5])
LOG.debug(_("LUN %(lun)s of size %(sz)s MB is created from snapshot.")
% {'lun': lun,
'sz': sz})
return {'provider_location': lun}
@utils.synchronized('hds_hus', external=True)
def create_snapshot(self, snapshot):
"""Create a snapshot."""
source_vol = self._id_to_vol(snapshot['volume_id'])
size = int(snapshot['volume_size']) * 1024
(_arid, slun) = source_vol['provider_location'].split('.')
out = self.bend.create_dup(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'],
self.arid, slun,
self.config['snapshot_hdp'],
self.start, self.end,
'%s' % (size))
lun = self.arid + '.' + out.split()[1]
size = int(out.split()[5])
LOG.debug(_("LUN %(lun)s of size %(size)s MB is created.")
% {'lun': lun,
'size': size})
return {'provider_location': lun}
@utils.synchronized('hds_hus', external=True)
def delete_snapshot(self, snapshot):
"""Delete a snapshot."""
loc = snapshot['provider_location']
if loc is None: # to take care of spurious input
return # which could cause exception.
(arid, lun) = loc.split('.')
myid = self.arid
if arid != myid:
LOG.error(_('Array mismatch %(myid)s vs %(arid)s')
% {'myid': myid,
'arid': arid})
msg = 'Array id mismatch in delete snapshot'
raise exception.VolumeBackendAPIException(data=msg)
_out = self.bend.delete_lu(self.config['hus_cmd'],
self.config['mgmt_ip0'],
self.config['mgmt_ip1'],
self.config['username'],
self.config['password'],
self.arid, lun)
LOG.debug(_("LUN %s is deleted.") % lun)
return
@utils.synchronized('hds_hus', external=True)
def get_volume_stats(self, refresh=False):
"""Get volume stats. If 'refresh', run update the stats first."""
if refresh:
self.driver_stats = self._get_stats()
return self.driver_stats

View File

@ -0,0 +1,148 @@
# Copyright (c) 2013 Hitachi Data Systems, Inc.
# Copyright (c) 2013 OpenStack LLC.
# 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.
#
"""
Hitachi Unified Storage (HUS) platform. Backend operations.
"""
from cinder.openstack.common import log as logging
from cinder import utils
LOG = logging.getLogger("cinder.volume.driver")
class HusBackend:
"""Back end. Talks to HUS."""
def get_version(self, cmd, ip0, ip1, user, pw):
out, err = utils.execute(cmd,
'--ip0', ip0,
'--ip1', ip1,
'--user', user,
'--password', pw,
'--version', '1',
run_as_root=True,
check_exit_code=True)
LOG.debug('get_version: ' + out + ' -- ' + err)
return out
def get_iscsi_info(self, cmd, ip0, ip1, user, pw):
out, err = utils.execute(cmd,
'--ip0', ip0,
'--ip1', ip1,
'--user', user,
'--password', pw,
'--iscsi', '1',
check_exit_code=True)
LOG.debug('get_iscsi_info: ' + out + ' -- ' + err)
return out
def get_hdp_info(self, cmd, ip0, ip1, user, pw):
out, err = utils.execute(cmd,
'--ip0', ip0,
'--ip1', ip1,
'--user', user,
'--password', pw,
'--hdp', '1',
check_exit_code=True)
LOG.debug('get_hdp_info: ' + out + ' -- ' + err)
return out
def create_lu(self, cmd, ip0, ip1, user, pw, id, hdp, start, end, size):
out, err = utils.execute(cmd,
'--ip0', ip0,
'--ip1', ip1,
'--user', user,
'--password', pw,
'--create_lun', '1',
'--array_id', id,
'--hdp', hdp,
'--start', start,
'--end', end,
'--size', size,
check_exit_code=True)
LOG.debug('create_lu: ' + out + ' -- ' + err)
return out
def delete_lu(self, cmd, ip0, ip1, user, pw, id, lun):
out, err = utils.execute(cmd,
'--ip0', ip0,
'--ip1', ip1,
'--user', user,
'--password', pw,
'--delete_lun', '1',
'--array_id', id,
'--lun', lun,
check_exit_code=True)
LOG.debug('delete_lu: ' + out + ' -- ' + err)
return out
def create_dup(self, cmd, ip0, ip1, user, pw, id, src_lun,
hdp, start, end, size):
out, err = utils.execute(cmd,
'--ip0', ip0,
'--ip1', ip1,
'--user', user,
'--password', pw,
'--create_dup', '1',
'--array_id', id,
'--pvol', src_lun,
'--hdp', hdp,
'--start', start,
'--end', end,
'--size', size,
check_exit_code=True)
LOG.debug('create_dup: ' + out + ' -- ' + err)
return out
def add_iscsi_conn(self, cmd, ip0, ip1, user, pw, id, lun, ctl, port, iqn,
tgt_alias, initiator, init_alias):
out, err = utils.execute(cmd,
'--ip0', ip0,
'--ip1', ip1,
'--user', user,
'--password', pw,
'--add_iscsi_connection', '1',
'--array_id', id,
'--lun', lun,
'--ctl', ctl,
'--port', port,
'--target', iqn,
'--target_alias', tgt_alias,
'--initiator', initiator,
'--initiator_alias', init_alias,
check_exit_code=True)
LOG.debug('add_iscsi_conn: ' + out + ' -- ' + err)
return out
def del_iscsi_conn(self, cmd, ip0, ip1, user, pw, id, lun, ctl, port, iqn,
initiator, force):
out, err = utils.execute(cmd,
'--ip0', ip0,
'--ip1', ip1,
'--user', user,
'--password', pw,
'--delete_iscsi_connection', '1',
'--array_id', id,
'--lun', lun,
'--ctl', ctl,
'--port', port,
'--target', iqn,
'--initiator', initiator,
'--force', force,
check_exit_code=True)
LOG.debug('del_iscsi_conn: ' + out + ' -- ' + err)
return out

View File

@ -1314,6 +1314,14 @@
#zadara_vpsa_allow_nonexistent_delete=true
#
# options for cinder.volumes.drivers.hds.hds.HUSDriver
#
# default configuration file location/name is (string) :
# hds_cinder_config_file=/opt/hds/hus/cinder_hds_conf.xml
#
# Options defined in cinder.volume.iscsi
#
@ -1344,5 +1352,4 @@
# Driver to use for volume creation (string value)
#volume_driver=cinder.volume.drivers.lvm.LVMISCSIDriver
# Total option count: 299
# Total option count: 300

View File

@ -10,7 +10,7 @@ filters_path=/etc/cinder/rootwrap.d,/usr/share/cinder/rootwrap
# explicitely specify a full path (separated by ',')
# If not specified, defaults to system PATH environment variable.
# These directories MUST all be only writeable by root !
exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin
exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin
# Enable logging to syslog
# Default value is False

View File

@ -54,6 +54,5 @@ chmod: CommandFilter, chmod, root
rm: CommandFilter, rm, root
lvs: CommandFilter, lvs, root
# cinder/volume/scality.py
mount: CommandFilter, mount, root
dd: CommandFilter, dd, root
# cinder/volumes/drivers/hds/hds.py:
hus_cmd: CommandFilter, hus_cmd, root