]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
HDS Cinder Driver. Rev #1
authorLakhinder Walia <lakhinder.walia@hds.com>
Tue, 7 May 2013 23:05:46 +0000 (16:05 -0700)
committerLakhinder Walia <lakhinder.walia@hds.com>
Sat, 1 Jun 2013 03:16:45 +0000 (20:16 -0700)
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

cinder/exception.py
cinder/tests/test_hds.py [new file with mode: 0644]
cinder/volume/drivers/hds/__init__.py [new file with mode: 0644]
cinder/volume/drivers/hds/hds.py [new file with mode: 0644]
cinder/volume/drivers/hds/hus_backend.py [new file with mode: 0644]
etc/cinder/cinder.conf.sample
etc/cinder/rootwrap.conf
etc/cinder/rootwrap.d/volume.filters

index 4c840ecb7da57481b3b6fc6141ef1f5592900fd1..b3ae8ccb7011456d6b16c055da9ed80d93abf528 100644 (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")
 
diff --git a/cinder/tests/test_hds.py b/cinder/tests/test_hds.py
new file mode 100644 (file)
index 0000000..d9cea26
--- /dev/null
@@ -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)
diff --git a/cinder/volume/drivers/hds/__init__.py b/cinder/volume/drivers/hds/__init__.py
new file mode 100644 (file)
index 0000000..8998fc9
--- /dev/null
@@ -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.
+#
diff --git a/cinder/volume/drivers/hds/hds.py b/cinder/volume/drivers/hds/hds.py
new file mode 100644 (file)
index 0000000..45c944b
--- /dev/null
@@ -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
diff --git a/cinder/volume/drivers/hds/hus_backend.py b/cinder/volume/drivers/hds/hus_backend.py
new file mode 100644 (file)
index 0000000..52d6882
--- /dev/null
@@ -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
index 055d698f72aceb177d68c03257da97385e3cd10d..4e8b127d6d9e80f0d8d3c4f5cebb9fbbbca088ac 100644 (file)
 #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
 #
 # Driver to use for volume creation (string value)
 #volume_driver=cinder.volume.drivers.lvm.LVMISCSIDriver
 
-
-# Total option count: 299
+# Total option count: 300
index dfa8a99ccddc760f21ec19624e8dd9b39d46373e..ff58e1b35b51524e081e369c2798b7c9ba663345 100644 (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
index 9aca2742bfb981a632219ee72099b9aac203ff33..11224cedaba3989a42dd8edaf676c4b1c27db92e 100644 (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