]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Driver for IBM Storwize and SVC storage.
authorAvishay Traeger <avishay@il.ibm.com>
Sun, 15 Jul 2012 12:53:36 +0000 (15:53 +0300)
committerAvishay Traeger <avishay@il.ibm.com>
Tue, 31 Jul 2012 14:04:46 +0000 (17:04 +0300)
Volume driver for IBM Storwize and SVC storage systems, along with unit
tests and updated sample config file. The unit tests include a
Storwize/SVC management simulator to allow for testing without
controller hardware. Also added a new exception for volume drivers.

Change-Id: Id7e3e79cd6e62fac4b10937b6f1b5f6bcb7908fe

cinder/exception.py
cinder/tests/test_storwize_svc.py [new file with mode: 0644]
cinder/volume/storwize_svc.py [new file with mode: 0644]
etc/cinder/cinder.conf.sample

index 7ee6eb50233b9899f3c23c40d92fe2ebaf14c257..5cf10a15f8ed87f0d79103f646b08e3c4697e26e 100644 (file)
@@ -936,3 +936,8 @@ class InvalidInstanceIDMalformed(Invalid):
 
 class CouldNotFetchImage(CinderException):
     message = _("Could not fetch image %(image)s")
+
+
+class VolumeBackendAPIException(CinderException):
+    message = _("Bad or unexpected response from the storage volume "
+                "backend API: data=%(data)s")
diff --git a/cinder/tests/test_storwize_svc.py b/cinder/tests/test_storwize_svc.py
new file mode 100644 (file)
index 0000000..24fd1d9
--- /dev/null
@@ -0,0 +1,939 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 IBM, Inc.
+# Copyright (c) 2012 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.
+
+"""
+Tests for the IBM Storwize V7000 and SVC volume driver.
+"""
+
+import random
+
+from cinder import exception
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder import test
+from cinder import utils
+from cinder.volume import storwize_svc
+
+LOG = logging.getLogger(__name__)
+
+
+class StorwizeSVCManagementSimulator:
+    def __init__(self, pool_name):
+        self._flags = {"storwize_svc_volpool_name": pool_name}
+        self._volumes_list = {}
+        self._hosts_list = {}
+        self._mappings_list = {}
+        self._fcmappings_list = {}
+        self._next_cmd_error = {}
+        self._errors = {
+            "CMMVC5701E": ("", "CMMVC5701E No object ID was specified."),
+            "CMMVC6035E": ("", "CMMVC6035E The action failed as the " +
+                               "object already exists."),
+            "CMMVC5753E": ("", "CMMVC5753E The specified object does not " +
+                               "exist or is not a suitable candidate."),
+            "CMMVC5707E": ("", "CMMVC5707E Required parameters are missing."),
+            "CMMVC6581E": ("", "CMMVC6581E The command has failed because " +
+                               "the maximum number of allowed iSCSI " +
+                               "qualified names (IQNs) has been reached, " +
+                               "or the IQN is already assigned or is not " +
+                               "valid."),
+            "CMMVC5708E": ("", "CMMVC5708E The [XXX] parameter is missing " +
+                               "its associated arguments."),
+            "CMMVC5754E": ("", "CMMVC5754E The specified object does not " +
+                               "exist, or the name supplied does not meet " +
+                               "the naming rules."),
+            "CMMVC6071E": ("", "CMMVC6071E The VDisk-to-host mapping was " +
+                               "not created because the VDisk is already " +
+                               "mapped to a host."),
+            "CMMVC5879E": ("", "CMMVC5879E The VDisk-to-host mapping was " +
+                               "not created because a VDisk is already " +
+                               "mapped to this host with this SCSI LUN."),
+            "CMMVC5840E": ("", "CMMVC5840E The virtual disk (VDisk) was " +
+                               "not deleted because it is mapped to a " +
+                               "host or because it is part of a FlashCopy " +
+                               "or Remote Copy mapping, or is involved in " +
+                               "an image mode migrate."),
+            "CMMVC6070E": ("", "CMMVC6070E An invalid or duplicated " +
+                               "parameter, unaccompanied argument, or " +
+                               "incorrect argument sequence has been " +
+                               "detected. Ensure that the input is as per " +
+                               "the help."),
+            "CMMVC6527E": ("", "CMMVC6527E The name that you have entered " +
+                               "is not valid. The name can contain letters, " +
+                               "numbers, spaces, periods, dashes, and " +
+                               "underscores. The name must begin with a " +
+                               "letter or an underscore. The name must not " +
+                               "begin or end with a space."),
+            "CMMVC5871E": ("", "CMMVC5871E The action failed because one or " +
+                               "more of the configured port names is in a " +
+                               "mapping."),
+            "CMMVC5924E": ("", "CMMVC5924E The FlashCopy mapping was not " +
+                               "created because the source and target " +
+                               "virtual disks (VDisks) are different sizes."),
+            "CMMVC6303E": ("", "CMMVC6303E The create failed because the " +
+                               "source and target VDisks are the same."),
+        }
+
+    # Find an unused ID
+    def _find_unused_id(self, d):
+        ids = []
+        for k, v in d.iteritems():
+            ids.append(int(v["id"]))
+        ids.sort()
+        for index, n in enumerate(ids):
+            if n > index:
+                return str(index)
+        return str(len(ids))
+
+    # Check if name is valid
+    def _is_invalid_name(self, name):
+        if (name[0] == " ") or (name[-1] == " "):
+            return True
+        for c in name:
+            if ((not c.isalnum()) and (c != " ") and (c != ".")
+                    and (c != "-") and (c != "_")):
+                return True
+        return False
+
+    # Check if name is valid
+    def _strip_quotes(self, str):
+        if ((str[0] == '\"' and str[-1] == '\"') or
+                (str[0] == '\'' and str[-1] == '\'')):
+            return str[1:-1]
+        return str
+
+    # Generic function for printing information
+    def _print_info_cmd(self, arg_list, rows):
+        for arg in arg_list:
+            if arg == "-nohdr":
+                del rows[0]
+
+        delimeter = " "
+        try:
+            arg_index = arg_list.index("-delim")
+            delimeter = arg_list[arg_index + 1]
+        except ValueError:
+            pass
+        except IndexError:
+            return self._errors["CMMVC5707E"]
+
+        for index in range(len(rows)):
+            rows[index] = delimeter.join(rows[index])
+        return ("%s" % "\n".join(rows), "")
+
+    # Print mostly made-up stuff in the correct syntax
+    def _cmd_lsmdiskgrp(self, arg_list):
+        rows = [None] * 2
+        rows[0] = ["id", "name", "status", "mdisk_count",
+                   "vdisk_count capacity", "extent_size", "free_capacity",
+                   "virtual_capacity", "used_capacity", "real_capacity",
+                   "overallocation", "warning", "easy_tier",
+                   "easy_tier_status"]
+        rows[1] = ["0", self._flags["storwize_svc_volpool_name"], "online",
+                   "1", str(len(self._volumes_list)), "3.25TB", "256",
+                   "3.21TB", "1.54TB", "264.97MB", "35.58GB", "47", "80",
+                   "auto", "inactive"]
+        return self._print_info_cmd(arg_list, rows)
+
+    # Print mostly made-up stuff in the correct syntax
+    def _cmd_lsnodecanister(self, arg_list):
+        rows = [None] * 3
+        rows[0] = ["id", "name", "UPS_serial_number", "WWNN", "status",
+                   "IO_group_id", "IO_group_name", "config_node",
+                   "UPS_unique_id", "hardware", "iscsi_name", "iscsi_alias",
+                   "panel_name", "enclosure_id", "canister_id",
+                   "enclosure_serial_number"]
+        rows[1] = ["5", "node1", "", "123456789ABCDEF0", "online", "0",
+                   "io_grp0",
+                   "yes", "123456789ABCDEF0", "100",
+                   "iqn.1982-01.com.ibm:1234.sim.node1", "", "01-1", "1", "1",
+                   "0123ABC"]
+        rows[2] = ["6", "node2", "", "123456789ABCDEF1", "online", "0",
+                   "io_grp0",
+                   "no", "123456789ABCDEF1", "100",
+                   "iqn.1982-01.com.ibm:1234.sim.node2", "", "01-2", "1", "2",
+                   "0123ABC"]
+        return self._print_info_cmd(arg_list, rows)
+
+    # Print mostly made-up stuff in the correct syntax
+    def _cmd_lsportip(self, arg_list):
+        if (("lsportip" in self._next_cmd_error) and
+                (self._next_cmd_error["lsportip"] == "ip_no_config")):
+            self._next_cmd_error["lsportip"] = None
+            ip_addr1 = ""
+            ip_addr2 = ""
+            gw = ""
+        else:
+            ip_addr1 = "1.234.56.78"
+            ip_addr2 = "1.234.56.79"
+            gw = "1.234.56.1"
+
+        rows = [None] * 17
+        rows[0] = ["id", "node_id", "node_name", "IP_address", "mask",
+                   "gateway", "IP_address_6", "prefix_6", "gateway_6", "MAC",
+                   "duplex", "state", "speed", "failover"]
+        rows[1] = ["1", "5", "node1", ip_addr1, "255.255.255.0",
+                   gw, "", "", "", "01:23:45:67:89:00", "Full",
+                   "online", "1Gb/s", "no"]
+        rows[2] = ["1", "5", "node1", "", "", "", "", "", "",
+                   "01:23:45:67:89:00", "Full", "online", "1Gb/s", "yes"]
+        rows[3] = ["2", "5", "node1", "", "", "", "", "", "",
+                   "01:23:45:67:89:01", "Full", "unconfigured", "1Gb/s", "no"]
+        rows[4] = ["2", "5", "node1", "", "", "", "", "", "",
+                   "01:23:45:67:89:01", "Full", "unconfigured", "1Gb/s", "yes"]
+        rows[5] = ["3", "5", "node1", "", "", "", "", "", "", "", "",
+                   "unconfigured", "", "no"]
+        rows[6] = ["3", "5", "node1", "", "", "", "", "", "", "", "",
+                   "unconfigured", "", "yes"]
+        rows[7] = ["4", "5", "node1", "", "", "", "", "", "", "", "",
+                   "unconfigured", "", "no"]
+        rows[8] = ["4", "5", "node1", "", "", "", "", "", "", "", "",
+                   "unconfigured", "", "yes"]
+        rows[9] = ["1", "6", "node2", ip_addr2, "255.255.255.0",
+                   gw, "", "", "", "01:23:45:67:89:02", "Full",
+                   "online", "1Gb/s", "no"]
+        rows[10] = ["1", "6", "node2", "", "", "", "", "", "",
+                    "01:23:45:67:89:02", "Full", "online", "1Gb/s", "yes"]
+        rows[11] = ["2", "6", "node2", "", "", "", "", "", "",
+                    "01:23:45:67:89:03", "Full", "unconfigured", "1Gb/s", "no"]
+        rows[12] = ["2", "6", "node2", "", "", "", "", "", "",
+                    "01:23:45:67:89:03", "Full", "unconfigured", "1Gb/s",
+                    "yes"]
+        rows[13] = ["3", "6", "node2", "", "", "", "", "", "", "", "",
+                    "unconfigured", "", "no"]
+        rows[14] = ["3", "6", "node2", "", "", "", "", "", "", "", "",
+                    "unconfigured", "", "yes"]
+        rows[15] = ["4", "6", "node2", "", "", "", "", "", "", "", "",
+                    "unconfigured", "", "no"]
+        rows[16] = ["4", "6", "node2", "", "", "", "", "", "", "", "",
+                    "unconfigured", "", "yes"]
+
+        return self._print_info_cmd(arg_list, rows)
+
+    # Create a vdisk
+    def _cmd_mkvdisk(self, arg_list):
+        # We only save the id/uid, name, and size - all else will be made up
+        volume_info = {}
+        volume_info["id"] = self._find_unused_id(self._volumes_list)
+        volume_info["uid"] = ("ABCDEF" * 3) + ("0" * 14) + volume_info["id"]
+        try:
+            arg_index = arg_list.index("-name") + 1
+            volume_info["name"] = self._strip_quotes(arg_list[arg_index])
+        except ValueError:
+            volume_info["name"] = "vdisk" + str(len(self._volumes_list))
+        except IndexError:
+            return self._errors["CMMVC5707E"]
+
+        # Assume size and unit are given, store it in bytes
+        capacity = int(arg_list[arg_list.index("-size") + 1])
+        unit = arg_list[arg_list.index("-unit") + 1]
+
+        if unit == "b":
+            volume_info["capacity"] = capacity
+        elif unit == "kb":
+            volume_info["capacity"] = capacity * pow(1024, 1)
+        elif unit == "mb":
+            volume_info["capacity"] = capacity * pow(1024, 2)
+        elif unit == "gb":
+            volume_info["capacity"] = capacity * pow(1024, 3)
+        elif unit == "tb":
+            volume_info["capacity"] = capacity * pow(1024, 4)
+        elif unit == "pb":
+            volume_info["capacity"] = capacity * pow(1024, 5)
+
+        if volume_info["name"] in self._volumes_list:
+            return self._errors["CMMVC6035E"]
+        else:
+            self._volumes_list[volume_info["name"]] = volume_info
+            return ("Virtual Disk, id [%s], successfully created" %
+                    (volume_info["id"]), "")
+
+    # Delete a vdisk
+    def _cmd_rmvdisk(self, arg_list):
+        if len(arg_list) == 1:
+            return self._errors["CMMVC5701E"]
+        elif len(arg_list) == 2:
+            force = 0
+            vol_name = arg_list[1]
+        elif len(arg_list) == 3:
+            if (arg_list[1] == "-force"):
+                force = 1
+            else:
+                return self._errors["CMMVC6070E"]
+            vol_name = arg_list[2]
+        else:
+            return self._errors["CMMVC6070E"]
+
+        vol_name = self._strip_quotes(vol_name)
+
+        if not vol_name in self._volumes_list:
+            return self._errors["CMMVC5753E"]
+
+        if force == 0:
+            for k, mapping in self._mappings_list.iteritems():
+                if mapping["vol"] == vol_name:
+                    return self._errors["CMMVC5840E"]
+            for k, fcmap in self._fcmappings_list.iteritems():
+                if ((fcmap["source"] == vol_name) or
+                        (fcmap["target"] == vol_name)):
+                    return self._errors["CMMVC5840E"]
+
+        del self._volumes_list[vol_name]
+        return ("", "")
+
+    def _generic_parse_ls_args(self, arg_list):
+        index = 1
+        ret_vals = {
+            "no_hdr": 0,
+            "delim": "",
+            "obj_name": "",
+            "filter": "",
+        }
+
+        while index < len(arg_list):
+            try:
+                if arg_list[index] == "-delim":
+                    ret_vals["delim"] = arg_list[index + 1]
+                    index += 2
+                elif arg_list[index] == "-nohdr":
+                    ret_vals["no_hdr"] = 1
+                    index += 1
+                elif arg_list[index] == "-filtervalue":
+                    ret_vals["filter"] = arg_list[index + 1].split("=")[1]
+                    index += 2
+                else:
+                    ret_vals["obj_name"] = arg_list[index]
+                    index += 1
+            except IndexError:
+                return self._errors["CMMVC5708E"]
+
+        return ret_vals
+
+    def _get_fcmap_info(self, vol_name):
+        ret_vals = {
+            "fc_id": "",
+            "fc_name": "",
+            "fc_map_count": "0",
+        }
+        for k, fcmap in self._fcmappings_list.iteritems():
+            if ((fcmap["source"] == vol_name) or
+                    (fcmap["target"] == vol_name)):
+                ret_vals["fc_id"] = fcmap["id"]
+                ret_vals["fc_name"] = fcmap["name"]
+                ret_vals["fc_map_count"] = "1"
+        return ret_vals
+
+    # List information about vdisks
+    def _cmd_lsvdisk(self, arg_list):
+        arg_dict = self._generic_parse_ls_args(arg_list)
+
+        if arg_dict["obj_name"] == "":
+            rows = []
+            rows.append(["id", "name", "IO_group_id", "IO_group_name",
+                         "status", "mdisk_grp_id", "mdisk_grp_name",
+                         "capacity", "type", "FC_id", "FC_name", "RC_id",
+                         "RC_name", "vdisk_UID", "fc_map_count", "copy_count",
+                         "fast_write_state", "se_copy_count", "RC_change"])
+
+            for k, vol in self._volumes_list.iteritems():
+                if ((arg_dict["filter"] == "") or
+                        (arg_dict["filter"] == vol["name"])):
+                    fcmap_info = self._get_fcmap_info(vol["name"])
+
+                    rows.append([str(vol["id"]), vol["name"], "0", "io_grp0",
+                                "online", "0",
+                                self._flags["storwize_svc_volpool_name"],
+                                str(vol["capacity"]), "striped",
+                                fcmap_info["fc_id"], fcmap_info["fc_name"],
+                                "", "", vol["uid"],
+                                fcmap_info["fc_map_count"], "1", "empty",
+                                "1", "no"])
+
+            return self._print_info_cmd(arg_list, rows)
+
+        else:
+            if arg_dict["obj_name"] not in self._volumes_list:
+                return self._errors["CMMVC5754E"]
+            vol = self._volumes_list[arg_dict["obj_name"]]
+            fcmap_info = self._get_fcmap_info(vol["name"])
+            rows = []
+            rows.append(["id", str(vol["id"])])
+            rows.append(["name", vol["name"]])
+            rows.append(["IO_group_id", "0"])
+            rows.append(["IO_group_name", "io_grp0"])
+            rows.append(["status", "online"])
+            rows.append(["mdisk_grp_id", "0"])
+            rows.append(["mdisk_grp_name",
+                    self._flags["storwize_svc_volpool_name"]])
+            rows.append(["capacity", str(vol["capacity"])])
+            rows.append(["type", "striped"])
+            rows.append(["formatted", "no"])
+            rows.append(["mdisk_id", ""])
+            rows.append(["mdisk_name", ""])
+            rows.append(["FC_id", fcmap_info["fc_id"]])
+            rows.append(["FC_name", fcmap_info["fc_name"]])
+            rows.append(["RC_id", ""])
+            rows.append(["RC_name", ""])
+            rows.append(["vdisk_UID", vol["uid"]])
+            rows.append(["throttling", "0"])
+            rows.append(["preferred_node_id", "2"])
+            rows.append(["fast_write_state", "empty"])
+            rows.append(["cache", "readwrite"])
+            rows.append(["udid", ""])
+            rows.append(["fc_map_count", fcmap_info["fc_map_count"]])
+            rows.append(["sync_rate", "50"])
+            rows.append(["copy_count", "1"])
+            rows.append(["se_copy_count", "0"])
+            rows.append(["mirror_write_priority", "latency"])
+            rows.append(["RC_change", "no"])
+
+            if arg_dict["no_hdr"] == 1:
+                for index in range(len(rows)):
+                    rows[index] = " ".join(rows[index][1:])
+
+            if arg_dict["delim"] != "":
+                for index in range(len(rows)):
+                    rows[index] = arg_dict["delim"].join(rows[index])
+
+            return ("%s" % "\n".join(rows), "")
+
+    # Make a host
+    def _cmd_mkhost(self, arg_list):
+        try:
+            arg_index = arg_list.index("-name") + 1
+            host_name = self._strip_quotes(arg_list[arg_index])
+        except ValueError:
+            host_name = "host" + str(self._num_host())
+        except IndexError:
+            return self._errors["CMMVC5707E"]
+
+        try:
+            arg_index = arg_list.index("-iscsiname") + 1
+            iscsi_name = self._strip_quotes(arg_list[arg_index])
+        except ValueError:
+            return self._errors["CMMVC5707E"]
+        except IndexError:
+            return self._errors["CMMVC5708E"].replace("XXX", "-iscsiname")
+
+        if self._is_invalid_name(host_name):
+            return self._errors["CMMVC6527E"]
+
+        if host_name in self._hosts_list:
+            return self._errors["CMMVC6035E"]
+
+        for k, v in self._hosts_list.iteritems():
+            if v["iscsi_name"] == iscsi_name:
+                return self._errors["CMMVC6581E"]
+
+        host_info = {}
+        host_info["host_name"] = host_name
+        host_info["iscsi_name"] = iscsi_name
+        host_info["id"] = self._find_unused_id(self._hosts_list)
+
+        self._hosts_list[host_name] = host_info
+        return ("Host, id [%s], successfully created" %
+                (host_info["id"]), "")
+
+    # Remove a host
+    def _cmd_rmhost(self, arg_list):
+        if len(arg_list) == 1:
+            return self._errors["CMMVC5701E"]
+
+        host_name = self._strip_quotes(arg_list[1])
+        if host_name not in self._hosts_list:
+            return self._errors["CMMVC5753E"]
+
+        for k, v in self._mappings_list.iteritems():
+            if (v["host"] == host_name):
+                return self._errors["CMMVC5871E"]
+
+        del self._hosts_list[host_name]
+        return ("", "")
+
+    # List information about hosts
+    def _cmd_lshost(self, arg_list):
+        arg_dict = self._generic_parse_ls_args(arg_list)
+
+        if arg_dict["obj_name"] == "":
+            rows = []
+            rows.append(["id", "name", "port_count", "iogrp_count", "status"])
+
+            for k, host in self._hosts_list.iteritems():
+                if ((arg_dict["filter"] == "") or
+                        (arg_dict["filter"] == host["host_name"])):
+                    rows.append([host["id"], host["host_name"], "1", "4",
+                                "offline"])
+            return self._print_info_cmd(arg_list, rows)
+        else:
+            if arg_dict["obj_name"] not in self._hosts_list:
+                return self._errors["CMMVC5754E"]
+            host = self._hosts_list[arg_dict["obj_name"]]
+            rows = []
+            rows.append(["id", host["id"]])
+            rows.append(["name", host["host_name"]])
+            rows.append(["port_count", "1"])
+            rows.append(["type", "generic"])
+            rows.append(["mask", "1111"])
+            rows.append(["iogrp_count", "4"])
+            rows.append(["status", "offline"])
+            rows.append(["iscsi_name", host["iscsi_name"]])
+            rows.append(["node_logged_in_count", "0"])
+            rows.append(["state", "offline"])
+
+            if arg_dict["no_hdr"] == 1:
+                for index in range(len(rows)):
+                    rows[index] = " ".join(rows[index][1:])
+
+            if arg_dict["delim"] != "":
+                for index in range(len(rows)):
+                    rows[index] = arg_dict["delim"].join(rows[index])
+
+            return ("%s" % "\n".join(rows), "")
+
+    # Create a vdisk-host mapping
+    def _cmd_mkvdiskhostmap(self, arg_list):
+        mapping_info = {}
+        mapping_info["id"] = self._find_unused_id(self._mappings_list)
+        try:
+            arg_index = arg_list.index("-host") + 1
+            mapping_info["host"] = self._strip_quotes(arg_list[arg_index])
+        except (ValueError, IndexError):
+            return self._errors["CMMVC5707E"]
+
+        try:
+            arg_index = arg_list.index("-scsi") + 1
+            mapping_info["lun"] = self._strip_quotes(arg_list[arg_index])
+        except (ValueError, IndexError):
+            return self._errors["CMMVC5707E"]
+
+        mapping_info["vol"] = self._strip_quotes(arg_list[-1])
+
+        if not mapping_info["vol"] in self._volumes_list:
+            return self._errors["CMMVC5753E"]
+
+        if not mapping_info["host"] in self._hosts_list:
+            return self._errors["CMMVC5754E"]
+
+        if mapping_info["vol"] in self._mappings_list:
+            return self._errors["CMMVC6071E"]
+
+        for k, v in self._mappings_list.iteritems():
+            if ((v["host"] == mapping_info["host"]) and
+                    (v["lun"] == mapping_info["lun"])):
+                return self._errors["CMMVC5879E"]
+
+        self._mappings_list[mapping_info["vol"]] = mapping_info
+        return ("Virtual Disk to Host map, id [%s], successfully created"
+                % (mapping_info["id"]), "")
+
+    # Delete a vdisk-host mapping
+    def _cmd_rmvdiskhostmap(self, arg_list):
+        try:
+            host = self._strip_quotes(arg_list[arg_list.index("-host") + 1])
+        except (ValueError, IndexError):
+            return self._errors["CMMVC5707E"]
+
+        vol = self._strip_quotes(arg_list[-1])
+
+        if not vol in self._mappings_list:
+            return self._errors["CMMVC5753E"]
+
+        if self._mappings_list[vol]["host"] != host:
+            return self._errors["CMMVC5753E"]
+
+        del self._mappings_list[vol]
+        return ("", "")
+
+    # List information about vdisk-host mappings
+    def _cmd_lshostvdiskmap(self, arg_list):
+        index = 1
+        no_hdr = 0
+        delimeter = ""
+        host_name = ""
+        while index < len(arg_list):
+            try:
+                if arg_list[index] == "-delim":
+                    delimeter = arg_list[index + 1]
+                    index += 2
+                elif arg_list[index] == "-nohdr":
+                    no_hdr = 1
+                    index += 1
+                else:
+                    host_name = arg_list[index]
+                    index += 1
+            except IndexError:
+                return self._errors["CMMVC5708E"]
+
+        if host_name not in self._hosts_list:
+            return self._errors["CMMVC5754E"]
+
+        rows = []
+        rows.append(["id", "name", "SCSI_id", "vdisk_id", "vdisk_name",
+                     "vdisk_UID"])
+
+        for k, mapping in self._mappings_list.iteritems():
+            if (host_name == "") or (mapping["host"] == host_name):
+                volume = self._volumes_list[mapping["vol"]]
+                rows.append([mapping["id"], mapping["host"],
+                            mapping["lun"], volume["id"],
+                            volume["name"], volume["uid"]])
+
+        return self._print_info_cmd(arg_list, rows)
+
+    # Create a FlashCopy mapping
+    def _cmd_mkfcmap(self, arg_list):
+        source = ""
+        target = ""
+
+        try:
+            arg_index = arg_list.index("-source") + 1
+            source = self._strip_quotes(arg_list[arg_index])
+        except (ValueError, IndexError):
+            return self._errors["CMMVC5707E"]
+        if not source in self._volumes_list:
+            return self._errors["CMMVC5754E"]
+
+        try:
+            arg_index = arg_list.index("-target") + 1
+            target = self._strip_quotes(arg_list[arg_index])
+        except (ValueError, IndexError):
+            return self._errors["CMMVC5707E"]
+        if not target in self._volumes_list:
+            return self._errors["CMMVC5754E"]
+
+        if source == target:
+            return self._errors["CMMVC6303E"]
+
+        if (self._volumes_list[source]["capacity"] !=
+                self._volumes_list[target]["capacity"]):
+            return ("", "%s != %s" % (self._volumes_list[source]["capacity"],
+                    self._volumes_list[target]["capacity"]))
+
+        fcmap_info = {}
+        fcmap_info["source"] = source
+        fcmap_info["target"] = target
+        fcmap_info["id"] = self._find_unused_id(self._fcmappings_list)
+        fcmap_info["name"] = "fcmap" + fcmap_info["id"]
+        fcmap_info["status"] = "idle_or_copied"
+        fcmap_info["progress"] = "0"
+        self._fcmappings_list[target] = fcmap_info
+
+        return("FlashCopy Mapping, id [" + fcmap_info["id"] +
+               "], successfully created", "")
+
+    # Same function used for both prestartfcmap and startfcmap
+    def _cmd_gen_startfcmap(self, arg_list, mode):
+        if len(arg_list) == 1:
+            return self._errors["CMMVC5701E"]
+        elif len(arg_list) > 2:
+            return self._errors["CMMVC6070E"]
+        id_num = arg_list[1]
+
+        for k, fcmap in self._fcmappings_list.iteritems():
+            if fcmap["id"] == id_num:
+                if mode == "pre":
+                    fcmap["status"] = "preparing"
+                else:
+                    fcmap["status"] = "copying"
+                fcmap["progress"] = "0"
+                return ("", "")
+        return self._errors["CMMVC5753E"]
+
+    def _cmd_lsfcmap(self, arg_list):
+        rows = []
+        rows.append(["id", "name", "source_vdisk_id", "source_vdisk_name",
+                     "target_vdisk_id", "target_vdisk_name", "group_id",
+                     "group_name", "status", "progress", "copy_rate",
+                     "clean_progress", "incremental", "partner_FC_id",
+                     "partner_FC_name", "restoring", "start_time",
+                     "rc_controlled"])
+
+        # Assume we always get a filtervalue argument
+        arg_index = arg_list.index("-filtervalue")
+        filter_key = arg_list[arg_index + 1].split("=")[0]
+        filter_value = arg_list[arg_index + 1].split("=")[1]
+        to_delete = []
+        for k, v in self._fcmappings_list.iteritems():
+            if str(v[filter_key]) == filter_value:
+                source = self._volumes_list[v["source"]]
+                target = self._volumes_list[v["target"]]
+                rows.append([v["id"], v["name"], source["id"],
+                            source["name"], target["id"], target["name"], "",
+                            "", v["status"], v["progress"], "50", "100",
+                            "off", "", "", "no", "", "no"])
+                if v["status"] == "preparing":
+                    v["status"] = "prepared"
+                elif (v["status"] == "copying") and (v["progress"] == "0"):
+                    v["progress"] = "50"
+                elif (v["status"] == "copying") and (v["progress"] == "50"):
+                    to_delete.append(k)
+
+        for d in to_delete:
+            del self._fcmappings_list[k]
+
+        return self._print_info_cmd(arg_list, rows)
+
+    # The main function to run commands on the management simulator
+    def execute_command(self, cmd, check_exit_code=True):
+        arg_list = cmd.split()
+
+        if arg_list[0] == "lsmdiskgrp":
+            out, err = self._cmd_lsmdiskgrp(arg_list)
+        elif arg_list[0] == "lsnodecanister":
+            out, err = self._cmd_lsnodecanister(arg_list)
+        elif arg_list[0] == "lsportip":
+            out, err = self._cmd_lsportip(arg_list)
+        elif arg_list[0] == "mkvdisk":
+            out, err = self._cmd_mkvdisk(arg_list)
+        elif arg_list[0] == "rmvdisk":
+            out, err = self._cmd_rmvdisk(arg_list)
+        elif arg_list[0] == "lsvdisk":
+            out, err = self._cmd_lsvdisk(arg_list)
+        elif arg_list[0] == "mkhost":
+            out, err = self._cmd_mkhost(arg_list)
+        elif arg_list[0] == "rmhost":
+            out, err = self._cmd_rmhost(arg_list)
+        elif arg_list[0] == "lshost":
+            out, err = self._cmd_lshost(arg_list)
+        elif arg_list[0] == "mkvdiskhostmap":
+            out, err = self._cmd_mkvdiskhostmap(arg_list)
+        elif arg_list[0] == "rmvdiskhostmap":
+            out, err = self._cmd_rmvdiskhostmap(arg_list)
+        elif arg_list[0] == "lshostvdiskmap":
+            out, err = self._cmd_lshostvdiskmap(arg_list)
+        elif arg_list[0] == "mkfcmap":
+            out, err = self._cmd_mkfcmap(arg_list)
+        elif arg_list[0] == "prestartfcmap":
+            out, err = self._cmd_gen_startfcmap(arg_list, "pre")
+        elif arg_list[0] == "startfcmap":
+            out, err = self._cmd_gen_startfcmap(arg_list, "start")
+        elif arg_list[0] == "lsfcmap":
+            out, err = self._cmd_lsfcmap(arg_list)
+        else:
+            out, err = ("", "ERROR: Unsupported command")
+
+        if (check_exit_code) and (len(err) != 0):
+            raise exception.ProcessExecutionError(exit_code=1,
+                                                  stdout=out,
+                                                  stderr=err,
+                                                  cmd=' '.join(cmd))
+
+        return (out, err)
+
+    # After calling this function, the next call to the specified command will
+    # result in in the error specified
+    def error_injection(self, cmd, error):
+        self._next_cmd_error[cmd] = error
+
+
+class StorwizeSVCFakeDriver(storwize_svc.StorwizeSVCDriver):
+    def __init__(self, *args, **kwargs):
+        super(StorwizeSVCFakeDriver, self).__init__(*args, **kwargs)
+
+    def set_fake_storage(self, fake):
+        self.fake_storage = fake
+
+    def _run_ssh(self, cmd, check_exit_code=True):
+        try:
+            LOG.debug(_('Run CLI command: %s') % cmd)
+            ret = self.fake_storage.execute_command(cmd, check_exit_code)
+            (stdout, stderr) = ret
+            LOG.debug(_('CLI output:\n stdout: %(out)s\n stderr: %(err)s') %
+                        {'out': stdout, 'err': stderr})
+
+        except exception.ProcessExecutionError as e:
+            with excutils.save_and_reraise_exception():
+                LOG.debug(_('CLI Exception output:\n stdout: %(out)s\n '
+                            'stderr: %(err)s') % {'out': e.stdout,
+                            'err': e.stderr})
+
+        return ret
+
+
+class StorwizeSVCDriverTestCase(test.TestCase):
+    def setUp(self):
+        super(StorwizeSVCDriverTestCase, self).setUp()
+        self.USESIM = 1
+        if self.USESIM == 1:
+            self.sim = StorwizeSVCManagementSimulator("volpool")
+            driver = StorwizeSVCFakeDriver()
+            driver.set_fake_storage(self.sim)
+            storwize_svc.FLAGS.san_ip = "hostname"
+            storwize_svc.FLAGS.san_login = "user"
+            storwize_svc.FLAGS.san_password = "pass"
+            storwize_svc.FLAGS.storwize_svc_volpool_name = "volpool"
+            storwize_svc.FLAGS.storwize_svc_flashcopy_timeout = "20"
+        else:
+            driver = storwize_svc.StorwizeSVCDriver()
+            storwize_svc.FLAGS.san_ip = "-1.-1.-1.-1"
+            storwize_svc.FLAGS.san_login = "user"
+            storwize_svc.FLAGS.san_password = "password"
+            storwize_svc.FLAGS.storwize_svc_volpool_name = "pool"
+
+        self.driver = driver
+        self.driver.do_setup(None)
+        self.driver.check_for_setup_error()
+
+    def test_storwize_svc_volume_non_space_efficient(self):
+        storwize_svc.FLAGS.storwize_svc_vol_rsize = "-1"
+        volume = {}
+        volume["name"] = "test1_volume%s" % random.randint(10000, 99999)
+        volume["size"] = 10
+        volume["id"] = 1
+        self.driver.create_volume(volume)
+        # Make sure that the volume has been created
+        is_volume_defined = self.driver._is_volume_defined(volume["name"])
+        self.assertEqual(is_volume_defined, True)
+
+        self.driver.delete_volume(volume)
+
+    def test_storwize_svc_connectivity(self):
+        # Make sure we detect if the pool doesn't exist
+        orig_pool = getattr(storwize_svc.FLAGS, "storwize_svc_volpool_name")
+        no_exist_pool = "i-dont-exist-%s" % random.randint(10000, 99999)
+        storwize_svc.FLAGS.storwize_svc_volpool_name = no_exist_pool
+        self.assertRaises(exception.InvalidParameterValue,
+                self.driver.check_for_setup_error)
+        storwize_svc.FLAGS.storwize_svc_volpool_name = orig_pool
+
+        # Check the case where the user didn't configure IP addresses
+        if self.USESIM == 1:
+            self.sim.error_injection("lsportip", "ip_no_config")
+            self.assertRaises(exception.VolumeBackendAPIException,
+                    self.driver.check_for_setup_error)
+
+        # Finally, check with good parameters
+        self.driver.check_for_setup_error()
+
+    def test_storwize_svc_flashcopy(self):
+        volume1 = {}
+        volume1["name"] = "test_volume%s" % random.randint(10000, 99999)
+        volume1["size"] = 10
+        volume1["id"] = 10
+        self.driver.create_volume(volume1)
+
+        snapshot = {}
+        snapshot["name"] = "snap_volume%s" % random.randint(10000, 99999)
+        snapshot["volume_name"] = volume1["name"]
+        self.driver.create_snapshot(snapshot)
+
+        is_volume_defined = self.driver._is_volume_defined(snapshot["name"])
+        self.assertEqual(is_volume_defined, True)
+
+        self.driver._delete_snapshot(snapshot, True)
+        self.driver._delete_volume(volume1, True)
+
+    def test_storwize_svc_volumes(self):
+        # Create a first volume
+        volume = {}
+        volume["name"] = "test1_volume%s" % random.randint(10000, 99999)
+        volume["size"] = 10
+        volume["id"] = 1
+        self.driver.create_volume(volume)
+
+        # Make sure that the volume has been created
+        is_volume_defined = self.driver._is_volume_defined(volume["name"])
+        self.assertEqual(is_volume_defined, True)
+
+        # Make sure volume attributes are as they should be
+        attributes = self.driver._get_volume_attributes(volume["name"])
+        attr_size = float(attributes["capacity"]) / 1073741824  # bytes to GB
+        self.assertEqual(attr_size, float(volume["size"]))
+        pool = getattr(storwize_svc.FLAGS, "storwize_svc_volpool_name")
+        self.assertEqual(attributes["mdisk_grp_name"], pool)
+        vtype = getattr(storwize_svc.FLAGS, "storwize_svc_vol_vtype")
+        self.assertEqual(attributes["type"], vtype)
+
+        # Try to create the volume again (should fail)
+        self.assertRaises(exception.ProcessExecutionError,
+                self.driver.create_volume, volume)
+
+        # Try to delete a volume that doesn't exist (should not fail)
+        vol_no_exist = {"name": "i_dont_exist"}
+        self.driver.delete_volume(vol_no_exist)
+
+        # Delete the volume
+        self.driver.delete_volume(volume)
+
+    def test_storwize_svc_host_maps(self):
+        # Create two volumes to be used in mappings
+        volume1 = {}
+        volume1["name"] = "test1_volume%s" % random.randint(10000, 99999)
+        volume1["size"] = 2
+        volume1["id"] = 1
+        self.driver.create_volume(volume1)
+        volume2 = {}
+        volume2["name"] = "test2_volume%s" % random.randint(10000, 99999)
+        volume2["size"] = 2
+        volume2["id"] = 1
+        self.driver.create_volume(volume2)
+
+        # Make sure that the volumes have been created
+        is_volume_defined = self.driver._is_volume_defined(volume1["name"])
+        self.assertEqual(is_volume_defined, True)
+        is_volume_defined = self.driver._is_volume_defined(volume2["name"])
+        self.assertEqual(is_volume_defined, True)
+
+        # Initialize connection from the first volume to a host
+        # Add some characters to the initiator name that should be converted
+        # when used for the host name
+        conn = {}
+        conn["initiator"] = "test:init:%s" % random.randint(10000, 99999)
+        conn["ip"] = "10.10.10.10"  # Bogus ip for testing
+        self.driver.initialize_connection(volume1, conn)
+
+        # Initialize connection from the second volume to the host
+        self.driver.initialize_connection(volume2, conn)
+
+        # Try to delete the 1st volume (should fail because it is mapped)
+        self.assertRaises(exception.ProcessExecutionError,
+                self.driver.delete_volume, volume1)
+
+        # Try to remove connection from host that doesn't exist (should fail)
+        conn_no_exist = {"initiator": "i_dont_exist"}
+        self.assertRaises(exception.VolumeBackendAPIException,
+                self.driver.terminate_connection, volume1, conn_no_exist)
+
+        # Try to remove connection from volume that isn't mapped (should print
+        # message but NOT fail)
+        vol_no_exist = {"name": "i_dont_exist"}
+        self.driver.terminate_connection(vol_no_exist, conn)
+
+        # Remove the mapping from the 1st volume and delete it
+        self.driver.terminate_connection(volume1, conn)
+        self.driver.delete_volume(volume1)
+        vol_def = self.driver._is_volume_defined(volume1["name"])
+        self.assertEqual(vol_def, False)
+
+        # Make sure our host still exists
+        host_name = self.driver._get_host_from_iscsiname(conn["initiator"])
+        host_def = self.driver._is_host_defined(host_name)
+        self.assertEquals(host_def, True)
+
+        # Remove the mapping from the 2nd volume and delete it. The host should
+        # be automatically removed because there are no more mappings.
+        self.driver.terminate_connection(volume2, conn)
+        self.driver.delete_volume(volume2)
+        vol_def = self.driver._is_volume_defined(volume2["name"])
+        self.assertEqual(vol_def, False)
+
+        # Check if our host still exists (it should not)
+        ret = self.driver._get_host_from_iscsiname(conn["initiator"])
+        self.assertEquals(ret, None)
+        ret = self.driver._is_host_defined(host_name)
+        self.assertEquals(ret, False)
diff --git a/cinder/volume/storwize_svc.py b/cinder/volume/storwize_svc.py
new file mode 100644 (file)
index 0000000..c7d34e9
--- /dev/null
@@ -0,0 +1,1200 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 IBM, Inc.
+# Copyright (c) 2012 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.
+
+"""
+Volume driver for IBM Storwize V7000 and SVC storage systems.
+
+Notes:
+1. If you specify both a password and a key file, this driver will use the
+   key file only.
+2. When using a key file for authentication, it is up to the user or
+   system administrator to store the private key in a safe manner.
+3. The defaults for creating volumes are "-vtype striped -rsize 2% -autoexpand
+   -grainsize 256 -warning 0".  These can be changed in the configuration file
+   (recommended only for advanced users).
+
+Limitations:
+1. The driver was not tested with SVC or clustered configurations of Storwize
+   V7000.
+2. The driver expects CLI output in English, error messages may be in a
+   localized format.
+"""
+
+import random
+import re
+import string
+import time
+
+import paramiko
+
+from cinder import exception
+from cinder import flags
+from cinder.openstack.common import cfg
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder import utils
+from cinder.volume import san
+
+LOG = logging.getLogger(__name__)
+
+storwize_svc_opts = [
+    cfg.StrOpt('storwize_svc_volpool_name',
+               default='volpool',
+               help='Storage system storage pool for volumes'),
+    cfg.StrOpt('storwize_svc_vol_vtype',
+               default='striped',
+               help='Storage system volume type for volumes'),
+    cfg.StrOpt('storwize_svc_vol_rsize',
+               default='2%',
+               help='Storage system space-efficiency parameter for volumes'),
+    cfg.StrOpt('storwize_svc_vol_warning',
+               default='0',
+               help='Storage system threshold for volume capacity warnings'),
+    cfg.BoolOpt('storwize_svc_vol_autoexpand',
+               default=True,
+               help='Storage system autoexpand parameter for volumes '
+                    '(True/False)'),
+    cfg.StrOpt('storwize_svc_vol_grainsize',
+               default='256',
+               help='Storage system grain size parameter for volumes '
+                    '(32/64/128/256)'),
+    cfg.StrOpt('storwize_svc_flashcopy_timeout',
+               default='120',
+               help='Maximum number of seconds to wait for FlashCopy to be'
+                    'prepared. Maximum value is 600 seconds (10 minutes).'),
+]
+
+FLAGS = flags.FLAGS
+FLAGS.register_opts(storwize_svc_opts)
+
+
+class StorwizeSVCDriver(san.SanISCSIDriver):
+    """IBM Storwize V7000 and SVC iSCSI volume driver."""
+
+    def __init__(self, *args, **kwargs):
+        super(StorwizeSVCDriver, self).__init__(*args, **kwargs)
+        self.iscsi_ipv4_conf = None
+        self.iscsi_ipv6_conf = None
+
+        invalid_ch_in_host = ''
+        for num in range(0, 128):
+            ch = chr(num)
+            if ((not ch.isalnum()) and (ch != ' ') and (ch != '.')
+                    and (ch != '-') and (ch != '_')):
+                invalid_ch_in_host = invalid_ch_in_host + ch
+        self._string_host_name_filter = string.maketrans(invalid_ch_in_host,
+                                                '-' * len(invalid_ch_in_host))
+
+        self._unicode_host_name_filter = dict((ord(unicode(char)), u'-')
+                                         for char in invalid_ch_in_host)
+
+    def _get_hdr_dic(self, header, row, delim):
+        """Return CLI row data as a dictionary indexed by names from header.
+
+        Create a dictionary object from the data row string using the header
+        string. The strings are converted to columns using the delimiter in
+        delim.
+        """
+
+        attributes = header.split(delim)
+        values = row.split(delim)
+        self._driver_assert(len(values) == len(attributes),
+            _('_get_hdr_dic: attribute headers and values do not match.\n '
+              'Headers: %(header)s\n Values: %(row)s')
+                % {'header': str(header),
+                   'row': str(row)})
+        dic = {}
+        for attribute, value in map(None, attributes, values):
+            dic[attribute] = value
+        return dic
+
+    def _driver_assert(self, assert_condition, exception_message):
+        """Internal assertion mechanism for CLI output."""
+        if not assert_condition:
+            LOG.error(exception_message)
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+    def check_for_setup_error(self):
+        """Check that we have all configuration details from the storage."""
+
+        LOG.debug(_('enter: check_for_setup_error'))
+
+        # Validate that the pool exists
+        ssh_cmd = 'lsmdiskgrp -delim ! -nohdr'
+        out, err = self._run_ssh(ssh_cmd)
+        self._driver_assert(len(out) > 0,
+            _('check_for_setup_error: failed with unexpected CLI output.\n '
+              'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s')
+                % {'cmd': ssh_cmd,
+                   'out': str(out),
+                   'err': str(err)})
+        search_text = '!%s!' % getattr(FLAGS, 'storwize_svc_volpool_name')
+        if search_text not in out:
+            raise exception.InvalidParameterValue(
+                    err=_('pool %s doesn\'t exist')
+                        % getattr(FLAGS, 'storwize_svc_volpool_name'))
+
+        storage_nodes = {}
+        # Get the iSCSI names of the Storwize/SVC nodes
+        ssh_cmd = 'lsnodecanister -delim !'
+        out, err = self._run_ssh(ssh_cmd)
+        self._driver_assert(len(out) > 0,
+            _('check_for_setup_error: failed with unexpected CLI output.\n '
+              'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s')
+                % {'cmd': ssh_cmd,
+                   'out': str(out),
+                   'err': str(err)})
+
+        nodes = out.strip().split('\n')
+        self._driver_assert(len(nodes) > 0,
+            _('check_for_setup_error: failed with unexpected CLI output.\n '
+              'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s')
+                % {'cmd': ssh_cmd,
+                   'out': str(out),
+                   'err': str(err)})
+        header = nodes.pop(0)
+        for node_line in nodes:
+            try:
+                node_data = self._get_hdr_dic(header, node_line, '!')
+            except exception.VolumeBackendAPIException as e:
+                with excutils.save_and_reraise_exception():
+                    LOG.error(_('check_for_setup_error: '
+                                'failed with unexpected CLI output.\n '
+                                'Command: %(cmd)s\n '
+                                'stdout: %(out)s\n stderr: %(err)s\n')
+                              % {'cmd': ssh_cmd,
+                                 'out': str(out),
+                                 'err': str(err)})
+            node = {}
+            try:
+                node['id'] = node_data['id']
+                node['name'] = node_data['name']
+                node['iscsi_name'] = node_data['iscsi_name']
+                node['status'] = node_data['status']
+                node['ipv4'] = []
+                node['ipv6'] = []
+                if node['iscsi_name'] != '':
+                    storage_nodes[node['id']] = node
+            except KeyError as e:
+                LOG.error(_('Did not find expected column name in '
+                            'lsnodecanister: %s') % str(e))
+                exception_message = (
+                    _('check_for_setup_error: Unexpected CLI output.\n '
+                      'Details: %(msg)s\n'
+                      'Command: %(cmd)s\n '
+                      'stdout: %(out)s\n stderr: %(err)s')
+                            % {'msg': str(e),
+                               'cmd': ssh_cmd,
+                               'out': str(out),
+                               'err': str(err)})
+                raise exception.VolumeBackendAPIException(
+                        data=exception_message)
+
+        # Get the iSCSI IP addresses of the Storwize/SVC nodes
+        ssh_cmd = 'lsportip -delim !'
+        out, err = self._run_ssh(ssh_cmd)
+        self._driver_assert(len(out) > 0,
+            _('check_for_setup_error: failed with unexpected CLI output.\n '
+              'Command: %(cmd)s\n '
+              'stdout: %(out)s\n stderr: %(err)s')
+                            % {'cmd': ssh_cmd,
+                               'out': str(out),
+                               'err': str(err)})
+
+        portips = out.strip().split('\n')
+        self._driver_assert(len(portips) > 0,
+            _('check_for_setup_error: failed with unexpected CLI output.\n '
+              'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s')
+                % {'cmd': ssh_cmd,
+                   'out': str(out),
+                   'err': str(err)})
+        header = portips.pop(0)
+        for portip_line in portips:
+            try:
+                port_data = self._get_hdr_dic(header, portip_line, '!')
+            except exception.VolumeBackendAPIException as e:
+                with excutils.save_and_reraise_exception():
+                    LOG.error(_('check_for_setup_error: '
+                                'failed with unexpected CLI output.\n '
+                                'Command: %(cmd)s\n '
+                                'stdout: %(out)s\n stderr: %(err)s\n')
+                              % {'cmd': ssh_cmd,
+                                 'out': str(out),
+                                 'err': str(err)})
+            try:
+                port_node_id = port_data['node_id']
+                port_ipv4 = port_data['IP_address']
+                port_ipv6 = port_data['IP_address_6']
+            except KeyError as e:
+                LOG.error(_('Did not find expected column name in '
+                            'lsportip: %s') % str(e))
+                exception_message = (
+                    _('check_for_setup_error: Unexpected CLI output.\n '
+                      'Details: %(msg)s\n'
+                      'Command: %(cmd)s\n '
+                      'stdout: %(out)s\n stderr: %(err)s')
+                            % {'msg': str(e),
+                               'cmd': ssh_cmd,
+                               'out': str(out),
+                               'err': str(err)})
+                raise exception.VolumeBackendAPIException(
+                        data=exception_message)
+
+            if port_node_id in storage_nodes:
+                node = storage_nodes[port_node_id]
+                if len(port_ipv4) > 0:
+                    node['ipv4'].append(port_ipv4)
+                if len(port_ipv6) > 0:
+                    node['ipv6'].append(port_ipv6)
+            else:
+                raise exception.VolumeBackendAPIException(
+                        data=_('check_for_setup_error: '
+                               'fail to storage configuration: unknown '
+                               'storage node %(node_id)s from CLI output.\n '
+                                'stdout: %(out)s\n stderr: %(err)s\n')
+                              % {'node_id': port_node_id,
+                                 'out': str(out),
+                                 'err': str(err)})
+
+        iscsi_ipv4_conf = []
+        iscsi_ipv6_conf = []
+        for node_key in storage_nodes:
+            node = storage_nodes[node_key]
+            if 'ipv4' in node and len(node['iscsi_name']) > 0:
+                iscsi_ipv4_conf.append({'iscsi_name': node['iscsi_name'],
+                                        'ip': node['ipv4'],
+                                        'node_id': node['id']})
+            if 'ipv6' in node and len(node['iscsi_name']) > 0:
+                iscsi_ipv6_conf.append({'iscsi_name': node['iscsi_name'],
+                                        'ip': node['ipv6'],
+                                        'node_id': node['id']})
+            if (len(node['ipv4']) == 0) and (len(node['ipv6']) == 0):
+                raise exception.VolumeBackendAPIException(
+                        data=_('check_for_setup_error: '
+                                'fail to storage configuration: storage '
+                                'node %s has no IP addresses configured')
+                                % node['id'])
+
+        #Make sure we have at least one IPv4 address with a iSCSI name
+        #TODO(ronenkat) need to expand this to support IPv6
+        self._driver_assert(len(iscsi_ipv4_conf) > 0,
+            _('could not obtain IP address and iSCSI name from the storage. '
+              'Please verify that the storage is configured for iSCSI.\n '
+              'Storage nodes: %(nodes)s\n portips: %(portips)s')
+                % {'nodes': nodes, 'portips': portips})
+
+        self.iscsi_ipv4_conf = iscsi_ipv4_conf
+        self.iscsi_ipv6_conf = iscsi_ipv6_conf
+
+        LOG.debug(_('leave: check_for_setup_error'))
+
+    def _check_num_perc(self, value):
+        """Return True if value is either a number or a percentage."""
+        if value.endswith('%'):
+            value = value[0:-1]
+        return value.isdigit()
+
+    def _check_flags(self):
+        """Ensure that the flags are set properly."""
+
+        required_flags = ['san_ip', 'san_ssh_port', 'san_login',
+                          'storwize_svc_volpool_name']
+        for flag in required_flags:
+            if not getattr(FLAGS, flag, None):
+                raise exception.InvalidParameterValue(
+                        err=_('%s is not set') % flag)
+
+        # Ensure that either password or keyfile were set
+        if not (getattr(FLAGS, 'san_password', None)
+                or getattr(FLAGS, 'san_private_key', None)):
+            raise exception.InvalidParameterValue(
+                err=_('Password or SSH private key is required for '
+                      'authentication: set either san_password or '
+                      'san_private_key option'))
+
+        # vtype should either be 'striped' or 'seq'
+        vtype = getattr(FLAGS, 'storwize_svc_vol_vtype')
+        if vtype not in ['striped', 'seq']:
+            raise exception.InvalidParameterValue(
+                err=_('Illegal value specified for storwize_svc_vol_vtype: '
+                  'set to either \'striped\' or \'seq\''))
+
+        # Check that rsize is a number or percentage
+        rsize = getattr(FLAGS, 'storwize_svc_vol_rsize')
+        if not self._check_num_perc(rsize) and (rsize not in ['auto', '-1']):
+            raise exception.InvalidParameterValue(
+                err=_('Illegal value specified for storwize_svc_vol_rsize: '
+                  'set to either a number or a percentage'))
+
+        # Check that warning is a number or percentage
+        warning = getattr(FLAGS, 'storwize_svc_vol_warning')
+        if not self._check_num_perc(warning):
+            raise exception.InvalidParameterValue(
+                err=_('Illegal value specified for storwize_svc_vol_warning: '
+                  'set to either a number or a percentage'))
+
+        # Check that autoexpand is a boolean
+        autoexpand = getattr(FLAGS, 'storwize_svc_vol_autoexpand')
+        if type(autoexpand) != type(True):
+            raise exception.InvalidParameterValue(
+                err=_('Illegal value specified for '
+                  'storwize_svc_vol_autoexpand: set to either '
+                  'True or False'))
+
+        # Check that grainsize is 32/64/128/256
+        grainsize = getattr(FLAGS, 'storwize_svc_vol_grainsize')
+        if grainsize not in ['32', '64', '128', '256']:
+            raise exception.InvalidParameterValue(
+                err=_('Illegal value specified for '
+                  'storwize_svc_vol_grainsize: set to either '
+                  '\'32\', \'64\', \'128\', or \'256\''))
+
+        # Check that flashcopy_timeout is numeric and 32/64/128/256
+        flashcopy_timeout = getattr(FLAGS, 'storwize_svc_flashcopy_timeout')
+        if not (flashcopy_timeout.isdigit() and int(flashcopy_timeout) > 0 and
+            int(flashcopy_timeout) <= 600):
+            raise exception.InvalidParameterValue(
+                err=_('Illegal value %s specified for '
+                      'storwize_svc_flashcopy_timeout: '
+                      'valid values are between 0 and 600')
+                      % flashcopy_timeout)
+
+    def do_setup(self, context):
+        """Validate the flags."""
+        LOG.debug(_('enter: do_setup'))
+        self._check_flags()
+        LOG.debug(_('leave: do_setup'))
+
+    def create_volume(self, volume):
+        """Create a new volume - uses the internal method."""
+        return self._create_volume(volume, units='gb')
+
+    def _create_volume(self, volume, units='gb'):
+        """Create a new volume."""
+
+        name = volume['name']
+        model_update = None
+
+        LOG.debug(_('enter: create_volume: volume %s ') % name)
+
+        size = int(volume['size'])
+
+        if getattr(FLAGS, 'storwize_svc_vol_autoexpand') == True:
+            autoexpand = '-autoexpand'
+        else:
+            autoexpand = ''
+
+        #Set space-efficient options
+        if getattr(FLAGS, 'storwize_svc_vol_rsize').strip() == '-1':
+            ssh_cmd_se_opt = ''
+        else:
+            ssh_cmd_se_opt = ('-rsize %(rsize)s %(autoexpand)s '
+                        '-grainsize %(grain)s' %
+                        {'rsize': getattr(FLAGS, 'storwize_svc_vol_rsize'),
+                         'autoexpand': autoexpand,
+                         'grain':
+                            getattr(FLAGS, 'storwize_svc_vol_grainsize')})
+
+        ssh_cmd = ('mkvdisk -name %(name)s -mdiskgrp %(mdiskgrp)s '
+                    '-iogrp 0 -vtype %(vtype)s -size %(size)s -unit '
+                    '%(unit)s %(ssh_cmd_se_opt)s'
+                    % {'name': name,
+                    'mdiskgrp': getattr(FLAGS, 'storwize_svc_volpool_name'),
+                    'vtype': getattr(FLAGS, 'storwize_svc_vol_vtype'),
+                    'size': size, 'unit': units,
+                    'ssh_cmd_se_opt': ssh_cmd_se_opt})
+        out, err = self._run_ssh(ssh_cmd)
+        self._driver_assert(len(out.strip()) > 0,
+            _('create volume %(name)s - did not find '
+              'success message in CLI output.\n '
+              'stdout: %(out)s\n stderr: %(err)s')
+                % {'name': name, 'out': str(out), 'err': str(err)})
+
+        # Ensure that the output is as expected
+        match_obj = re.search('Virtual Disk, id \[([0-9]+)\], '
+                                'successfully created', out)
+        # Make sure we got a "successfully created" message with vdisk id
+        self._driver_assert(match_obj is not None,
+            _('create volume %(name)s - did not find '
+              'success message in CLI output.\n '
+              'stdout: %(out)s\n stderr: %(err)s')
+                % {'name': name, 'out': str(out), 'err': str(err)})
+
+        LOG.debug(_('leave: create_volume: volume %(name)s ') % {'name': name})
+
+    def delete_volume(self, volume):
+        self._delete_volume(volume, False)
+
+    def _delete_volume(self, volume, force_opt):
+        """Driver entry point for destroying existing volumes."""
+
+        name = volume['name']
+        LOG.debug(_('enter: delete_volume: volume %(name)s ') % {'name': name})
+
+        if force_opt:
+            force_flag = '-force'
+        else:
+            force_flag = ''
+
+        volume_defined = self._is_volume_defined(name)
+        # Try to delete volume only if found on the storage
+        if volume_defined:
+            out, err = self._run_ssh('rmvdisk %(force)s %(name)s'
+                                    % {'force': force_flag,
+                                       'name': name})
+            # No output should be returned from rmvdisk
+            self._driver_assert(len(out.strip()) == 0,
+                _('delete volume %(name)s - non empty output from CLI.\n '
+                  'stdout: %(out)s\n stderr: %(err)s')
+                                % {'name': name,
+                                   'out': str(out),
+                                   'err': str(err)})
+        else:
+            # Log that volume does not exist
+            LOG.info(_('warning: tried to delete volume %(name)s but '
+                       'it does not exist.') % {'name': name})
+
+        LOG.debug(_('leave: delete_volume: volume %(name)s ') % {'name': name})
+
+    def ensure_export(self, context, volume):
+        """Check that the volume exists on the storage.
+
+        The system does not "export" volumes as a Linux iSCSI target does,
+        and therefore we just check that the volume exists on the storage.
+        """
+        volume_defined = self._is_volume_defined(volume['name'])
+        if not volume_defined:
+            LOG.error(_('ensure_export: volume %s not found on storage')
+                       % volume['name'])
+
+    def create_export(self, context, volume):
+        model_update = None
+        return model_update
+
+    def remove_export(self, context, volume):
+        pass
+
+    def check_for_export(self, context, volume_id):
+        raise NotImplementedError()
+
+    def initialize_connection(self, volume, connector):
+        """Perform the necessary work so that an iSCSI connection can be made.
+
+        To be able to create an iSCSI connection from a given iSCSI name to a
+        volume, we must:
+        1. Translate the given iSCSI name to a host name
+        2. Create new host on the storage system if it does not yet exist
+        2. Map the volume to the host if it is not already done
+        3. Return iSCSI properties, including the IP address of the preferred
+           node for this volume and the LUN number.
+        """
+        LOG.debug(_('enter: initialize_connection: volume %(vol)s with '
+                    'connector %(conn)s') % {'vol': str(volume),
+                    'conn': str(connector)})
+
+        initiator_name = connector['initiator']
+        volume_name = volume['name']
+
+        host_name = self._get_host_from_iscsiname(initiator_name)
+        # Check if a host is defined for the iSCSI initiator name
+        if host_name is None:
+            # Host does not exist - add a new host to Storwize/SVC
+            host_name = self._create_new_host('host%s' % initiator_name,
+                                               initiator_name)
+            # Verify that create_new_host succeeded
+            self._driver_assert(host_name is not None,
+                _('_create_new_host failed to return the host name.'))
+
+        lun_id = self._map_vol_to_host(volume_name, host_name)
+
+        # Get preferred path
+        # Only IPv4 for now because lack of OpenStack support
+        # TODO(ronenkat): Add support for IPv6
+        volume_attributes = self._get_volume_attributes(volume_name)
+        if (volume_attributes is not None and
+            'preferred_node_id' in volume_attributes):
+            preferred_node = volume_attributes['preferred_node_id']
+            preferred_node_entry = None
+            for node in self.iscsi_ipv4_conf:
+                if node['node_id'] == preferred_node:
+                    preferred_node_entry = node
+                    break
+            if preferred_node_entry is None:
+                preferred_node_entry = self.iscsi_ipv4_conf[0]
+                LOG.error(_('initialize_connection: did not find preferred '
+                            'node %(node)s for volume %(vol)s in iSCSI '
+                            'configuration') % {'node': preferred_node,
+                            'vol': volume_name})
+        else:
+            # Get 1st node
+            preferred_node_entry = self.iscsi_ipv4_conf[0]
+            LOG.error(
+                _('initialize_connection: did not find a preferred node '
+                  'for volume %s in iSCSI configuration') % volume_name)
+
+        properties = {}
+        # We didn't use iSCSI discover, as in server-based iSCSI
+        properties['target_discovered'] = False
+        # We take the first IP address for now. Ideally, OpenStack will
+        # support multipath for improved performance.
+        properties['target_portal'] = ('%s:%s' %
+                (preferred_node_entry['ip'][0], '3260'))
+        properties['target_iqn'] = preferred_node_entry['iscsi_name']
+        properties['target_lun'] = lun_id
+        properties['volume_id'] = volume['id']
+
+        LOG.debug(_('leave: initialize_connection:\n volume: %(vol)s\n '
+                    'connector %(conn)s\n properties: %(prop)s')
+                  % {'vol': str(volume),
+                    'conn': str(connector),
+                    'prop': str(properties)})
+
+        return {'driver_volume_type': 'iscsi', 'data': properties, }
+
+    def terminate_connection(self, volume, connector):
+        """Cleanup after an iSCSI connection has been terminated.
+
+        When we clean up a terminated connection between a given iSCSI name
+        and volume, we:
+        1. Translate the given iSCSI name to a host name
+        2. Remove the volume-to-host mapping if it exists
+        3. Delete the host if it has no more mappings (hosts are created
+           automatically by this driver when mappings are created)
+        """
+        LOG.debug(_('enter: terminate_connection: volume %(vol)s with '
+                    'connector %(conn)s') % {'vol': str(volume),
+                    'conn': str(connector)})
+
+        vol_name = volume['name']
+        initiator_name = connector['initiator']
+        host_name = self._get_host_from_iscsiname(initiator_name)
+        # Verify that _get_host_from_iscsiname returned the host.
+        # This should always succeed as we terminate an existing connection.
+        self._driver_assert(host_name is not None,
+            _('_get_host_from_iscsiname failed to return the host name '
+              'for iscsi name %s') % initiator_name)
+
+        # Check if vdisk-host mapping exists, remove if it does
+        mapping_data = self._get_hostvdisk_mappings(host_name)
+        if vol_name in mapping_data:
+            out, err = self._run_ssh('rmvdiskhostmap -host %s %s'
+                                     % (host_name, vol_name))
+            # Verify CLI behaviour - no output is returned from
+            # rmvdiskhostmap
+            self._driver_assert(len(out.strip()) == 0,
+                _('delete mapping of volume %(vol)s to host %(host)s '
+                  '- non empty output from CLI.\n '
+                  'stdout: %(out)s\n stderr: %(err)s')
+                                 % {'vol': vol_name,
+                                    'host': host_name,
+                                    'out': str(out),
+                                    'err': str(err)})
+            del mapping_data[vol_name]
+        else:
+            LOG.error(_('terminate_connection: no mapping of volume '
+                        '%(vol)s to host %(host)s found') %
+                        {'vol': vol_name, 'host': host_name})
+
+        # If this host has no more mappings, delete it
+        if not mapping_data:
+            self._delete_host(host_name)
+
+        LOG.debug(_('leave: terminate_connection: volume %(vol)s with '
+                    'connector %(conn)s') % {'vol': str(volume),
+                    'conn': str(connector)})
+
+    def _run_flashcopy(self, source, target):
+        """Create a FlashCopy mapping from the source to the target."""
+
+        LOG.debug(
+            _('enter: _run_flashcopy: execute FlashCopy from source '
+              '%(source)s to target %(target)s') % {'source': source,
+              'target': target})
+
+        fc_map_cli_cmd = ('mkfcmap -source %s -target %s -autodelete '
+                            '-cleanrate 0' % (source, target))
+        out, err = self._run_ssh(fc_map_cli_cmd)
+        self._driver_assert(len(out.strip()) > 0,
+            _('create FC mapping from %(source)s to %(target)s - '
+              'did not find success message in CLI output.\n'
+              ' stdout: %(out)s\n stderr: %(err)s\n')
+                            % {'source': source,
+                                'target': target,
+                                'out': str(out),
+                                'err': str(err)})
+
+        # Ensure that the output is as expected
+        match_obj = re.search('FlashCopy Mapping, id \[([0-9]+)\], '
+                                'successfully created', out)
+        # Make sure we got a "successfully created" message with vdisk id
+        self._driver_assert(match_obj is not None,
+            _('create FC mapping from %(source)s to %(target)s - '
+              'did not find success message in CLI output.\n'
+              ' stdout: %(out)s\n stderr: %(err)s\n')
+                            % {'source': source,
+                               'target': target,
+                               'out': str(out),
+                               'err': str(err)})
+
+        try:
+            fc_map_id = match_obj.group(1)
+            self._driver_assert(fc_map_id is not None,
+                _('create FC mapping from %(source)s to %(target)s - '
+                  'did not find mapping id in CLI output.\n'
+                  ' stdout: %(out)s\n stderr: %(err)s\n')
+                            % {'source': source,
+                               'target': target,
+                               'out': str(out),
+                               'err': str(err)})
+        except IndexError:
+            self._driver_assert(False,
+                _('create FC mapping from %(source)s to %(target)s - '
+                  'did not find mapping id in CLI output.\n'
+                  ' stdout: %(out)s\n stderr: %(err)s\n')
+                            % {'source': source,
+                               'target': target,
+                               'out': str(out),
+                               'err': str(err)})
+        try:
+            out, err = self._run_ssh('prestartfcmap %s' % fc_map_id)
+        except exception.ProcessExecutionError as e:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_('_run_flashcopy: fail to prepare FlashCopy '
+                            'from %(source)s to %(target)s.\n'
+                            'stdout: %(out)s\n stderr: %(err)s')
+                            % {'source': source,
+                               'target': target,
+                               'out': e.stdout,
+                               'err': e.stderr})
+
+        mapping_ready = False
+        wait_time = 5
+        # Allow waiting of up to timeout (set as parameter)
+        exception_msg = (_('mapping %(id)s prepare failed to complete '
+                            'within the alloted %(to)s seconds timeout. '
+                            'Terminating') % {'id': fc_map_id,
+                            'to': getattr(
+                                FLAGS, 'storwize_svc_flashcopy_timeout')})
+        max_retries = (int(getattr(FLAGS,
+                        'storwize_svc_flashcopy_timeout')) / wait_time) + 1
+        for try_number in range(1, max_retries):
+            mapping_attributes = self._get_flashcopy_mapping_attributes(
+                                                            fc_map_id)
+            if (mapping_attributes is None or
+                    'status' not in mapping_attributes):
+                break
+            if mapping_attributes['status'] == 'prepared':
+                mapping_ready = True
+                break
+            elif mapping_attributes['status'] != 'preparing':
+                # Unexpected mapping status
+                exception_msg = (_('unexecpted mapping status %(status)s '
+                                   'for mapping %(id)s. Attributes: '
+                                   '%(attr)s')
+                                 % {'status': mapping_attributes['status'],
+                                    'id': fc_map_id,
+                                    'attr': mapping_attributes})
+                break
+            # Need to wait for mapping to be prepared, wait a few seconds
+            time.sleep(wait_time)
+
+        if not mapping_ready:
+            LOG.error(_('_run_flashcopy: fail to start FlashCopy '
+                        'from %(source)s to %(target)s with '
+                        'exception %(ex)s')
+                        % {'source': source,
+                           'target': target,
+                           'ex': exception_msg})
+            raise exception.InvalidSnapshot(
+                reason=_('_run_flashcopy: %s') % exception_msg)
+
+        try:
+            out, err = self._run_ssh('startfcmap %s' % fc_map_id)
+        except exception.ProcessExecutionError as e:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_('_run_flashcopy: fail to start FlashCopy '
+                            'from %(source)s to %(target)s.\n'
+                            'stdout: %(out)s\n stderr: %(err)s')
+                            % {'source': source,
+                               'target': target,
+                               'out': e.stdout,
+                               'err': e.stderr})
+
+        LOG.debug(_('leave: _run_flashcopy: FlashCopy started from '
+                    '%(source)s to %(target)s') % {'source': source,
+                    'target': target})
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Create a new snapshot from volume."""
+
+        source_volume = volume['name']
+        tgt_volume = snapshot['name']
+
+        LOG.debug(_('enter: create_volume_from_snapshot: snapshot %(tgt)s '
+                    'from volume %(src)s') % {'tgt': tgt_volume,
+                    'src': source_volume})
+
+        src_volume_attributes = self._get_volume_attributes(source_volume)
+        if src_volume_attributes is None:
+            exception_msg = (_('create_volume_from_snapshot: source volume %s '
+                               'does not exist') % source_volume)
+            LOG.error(exception_msg)
+            raise exception.VolumeNotFound(exception_msg,
+                                           volume_id=source_volume)
+        if 'capacity' not in src_volume_attributes:
+            exception_msg = (
+                _('create_volume_from_snapshot: cannot get source '
+                  'volume %(src)s capacity from volume attributes '
+                  '%(attr)s') % {'src': source_volume,
+                                 'attr': src_volume_attributes})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+        src_volume_size = src_volume_attributes['capacity']
+
+        tgt_volume_attributes = self._get_volume_attributes(tgt_volume)
+        # Does the snapshot target exist?
+        if tgt_volume_attributes is not None:
+            exception_msg = (_('create_volume_from_snapshot: target volume %s '
+                               'already exists, cannot create') % tgt_volume)
+            LOG.error(exception_msg)
+            raise exception.InvalidSnapshot(reason=exception_msg)
+
+        snapshot_volume = {}
+        snapshot_volume['name'] = tgt_volume
+        snapshot_volume['size'] = src_volume_size
+
+        self.create_volume(snapshot_volume)
+
+        self._run_flashcopy(source_volume, tgt_volume)
+
+        LOG.debug(
+            _('leave: create_volume_from_snapshot: %s created successfully')
+            % tgt_volume)
+
+    def create_snapshot(self, snapshot):
+        """Create a new snapshot using FlashCopy."""
+
+        src_volume = snapshot['volume_name']
+        tgt_volume = snapshot['name']
+
+        LOG.debug(_('enter: create_snapshot: snapshot %(tgt)s from '
+                    'volume %(src)s') % {'tgt': tgt_volume,
+                    'src': src_volume})
+
+        src_volume_attributes = self._get_volume_attributes(src_volume)
+        if src_volume_attributes is None:
+            exception_msg = (
+                _('create_snapshot: source volume %s does not exist')
+                % src_volume)
+            LOG.error(exception_msg)
+            raise exception.VolumeNotFound(exception_msg,
+                                           volume_id=src_volume)
+        if 'capacity' not in src_volume_attributes:
+            exception_msg = (
+                _('create_snapshot: cannot get source volume %(src)s '
+                  'capacity from volume attributes %(attr)s')
+                    % {'src': src_volume,
+                       'attr': src_volume_attributes})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+        source_volume_size = src_volume_attributes['capacity']
+
+        tgt_volume_attributes = self._get_volume_attributes(tgt_volume)
+        # Does the snapshot target exist?
+        if tgt_volume_attributes is None:
+            # No, create a new snapshot volume
+            snapshot_volume = {}
+            snapshot_volume['name'] = tgt_volume
+            snapshot_volume['size'] = source_volume_size
+            self._create_volume(snapshot_volume, units='b')
+        else:
+            # Yes, target exists, verify exact same size as source
+            if 'capacity' not in src_volume_attributes:
+                exception_msg = (
+                    _('create_snapshot: cannot get target volume '
+                      '%(tgt)s capacity from volume attributes '
+                      '%(attr)s') % {'tgt': tgt_volume,
+                                     'attr': tgt_volume_attributes})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+            target_volume_size = tgt_volume_attributes['capacity']
+            if target_volume_size != source_volume_size:
+                exception_msg = (
+                    _('create_snapshot: source %(src)s and target '
+                      'volume %(tgt)s have different capacities '
+                      '(source:%(ssize)s target:%(tsize)s)') %
+                        {'src': src_volume,
+                         'tgt': tgt_volume,
+                         'ssize': source_volume_size,
+                         'tsize': target_volume_size})
+                LOG.error(exception_msg)
+                raise exception.InvalidSnapshot(reason=exception_msg)
+
+        self._run_flashcopy(src_volume, tgt_volume)
+
+        LOG.debug(_('leave: create_snapshot: %s created successfully')
+                  % tgt_volume)
+
+    def delete_snapshot(self, snapshot):
+        self._delete_snapshot(snapshot, False)
+
+    def _delete_snapshot(self, snapshot, force_opt):
+        """Delete a snapshot from the storage."""
+        LOG.debug(_('enter: delete_snapshot: snapshot %s') % snapshot)
+
+        snapshot_defined = self._is_volume_defined(snapshot['name'])
+        if snapshot_defined:
+            if force_opt:
+                self._delete_volume(snapshot, force_opt)
+            else:
+                self.delete_volume(snapshot)
+
+        LOG.debug(_('leave: delete_snapshot: snapshot %s') % snapshot)
+
+    def _get_host_from_iscsiname(self, iscsi_name):
+        """List the hosts defined in the storage.
+
+        Return the host name with the given iSCSI name, or None if there is
+        no host name with that iSCSI name.
+        """
+
+        LOG.debug(_('enter: _get_host_from_iscsiname: iSCSI initiator %s')
+                   % iscsi_name)
+
+        # Get list of host in the storage
+        ssh_cmd = 'lshost -delim !'
+        out, err = self._run_ssh(ssh_cmd)
+
+        if (len(out.strip()) == 0):
+            return None
+
+        err_msg = _('_get_host_from_iscsiname: '
+              'failed with unexpected CLI output.\n'
+              ' command: %(cmd)s\n stdout: %(out)s\n '
+              'stderr: %(err)s') % {'cmd': ssh_cmd,
+                                    'out': str(out),
+                                    'err': str(err)}
+        host_lines = out.strip().split('\n')
+        self._driver_assert(len(host_lines) > 0, err_msg)
+        header = host_lines.pop(0).split('!')
+        self._driver_assert('name' in header, err_msg)
+        name_index = header.index('name')
+
+        hosts = map(lambda x: x.split('!')[name_index], host_lines)
+        hostname = None
+
+        # For each host, get its details and check for its iSCSI name
+        for host in hosts:
+            ssh_cmd = 'lshost -delim ! %s' % host
+            out, err = self._run_ssh(ssh_cmd)
+            self._driver_assert(len(out) > 0,
+                    _('_get_host_from_iscsiname: '
+                      'Unexpected response from CLI output. '
+                      'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s')
+                        % {'cmd': ssh_cmd,
+                           'out': str(out),
+                           'err': str(err)})
+            for attrib_line in out.split('\n'):
+                #If '!' not found, will return the string and two empty strings
+                attrib_name, foo, attrib_value = attrib_line.partition('!')
+                if attrib_name == 'iscsi_name':
+                    if iscsi_name == attrib_value:
+                        hostname = host
+                        break
+            if hostname is not None:
+                break
+
+        LOG.debug(_('leave: _get_host_from_iscsiname: iSCSI initiator %s')
+                   % iscsi_name)
+
+        return hostname
+
+    def _create_new_host(self, host_name, initiator_name):
+        """Create a new host on the storage system.
+
+        We modify the given host name, replace any invalid characters and
+        adding a random suffix to avoid conflicts due to the translation. The
+        host is associated with the given iSCSI initiator name.
+        """
+
+        LOG.debug(_('enter: _create_new_host: host %(name)s with iSCSI '
+                    'initiator %(init)s') % {'name': host_name,
+                    'init': initiator_name})
+
+        if isinstance(host_name, unicode):
+            host_name = host_name.translate(self._unicode_host_name_filter)
+        elif isinstance(host_name, str):
+            host_name = host_name.translate(self._string_host_name_filter)
+        else:
+            msg = _('_create_new_host: cannot clean host name. Host name '
+                    'is not unicode or string')
+            LOG.error(msg)
+            raise exception.NoValidHost(reason=msg)
+
+        # Add 5 digit random suffix to the host name to avoid
+        # conflicts in host names after removing invalid characters
+        # for Storwize/SVC names
+        host_name = '%s_%s' % (host_name, random.randint(10000, 99999))
+        out, err = self._run_ssh('mkhost -name "%s" -iscsiname "%s"'
+                                 % (host_name, initiator_name))
+        self._driver_assert(len(out.strip()) > 0 and
+                            'successfully created' in out,
+                _('create host %(name)s with iSCSI initiator %(init)s - '
+                  'did not find success message in CLI output.\n '
+                  'stdout: %(out)s\n stderr: %(err)s\n')
+                  % {'name': host_name,
+                     'init': initiator_name,
+                     'out': str(out),
+                     'err': str(err)})
+
+        LOG.debug(_('leave: _create_new_host: host %(host)s with iSCSI '
+                    'initiator %(init)s') % {'host': host_name,
+                    'init': initiator_name})
+
+        return host_name
+
+    def _delete_host(self, host_name):
+        """Delete a host and associated iSCSI initiator name."""
+
+        LOG.debug(_('enter: _delete_host: host %s ') % host_name)
+
+        # Check if host exists on system, expect to find the host
+        is_defined = self._is_host_defined(host_name)
+        if is_defined:
+            # Delete host
+            out, err = self._run_ssh('rmhost %s ' % host_name)
+        else:
+            LOG.info(_('warning: tried to delete host %(name)s but '
+                       'it does not exist.') % {'name': host_name})
+
+        LOG.debug(_('leave: _delete_host: host %s ') % host_name)
+
+    def _is_volume_defined(self, volume_name):
+        """Check if volume is defined."""
+        LOG.debug(_('enter: _is_volume_defined: volume %s ') % volume_name)
+        volume_attributes = self._get_volume_attributes(volume_name)
+        LOG.debug(_('leave: _is_volume_defined: volume %(vol)s with %(str)s ')
+                   % {'vol': volume_name,
+                   'str': volume_attributes is not None})
+        if volume_attributes is None:
+            return False
+        else:
+            return True
+
+    def _is_host_defined(self, host_name):
+        """Check if a host is defined on the storage."""
+
+        LOG.debug(_('enter: _is_host_defined: host %s ') % host_name)
+
+        # Get list of hosts with the name %host_name%
+        # We expect zero or one line if host does not exist,
+        # two lines if it does exist, otherwise error
+        out, err = self._run_ssh('lshost -filtervalue name=%s -delim !'
+                                % host_name)
+        if len(out.strip()) == 0:
+            return False
+
+        lines = out.strip().split('\n')
+        self._driver_assert(len(lines) <= 2,
+                _('_is_host_defined: Unexpected response from CLI output.\n '
+                  'stdout: %(out)s\n stderr: %(err)s\n')
+                % {'out': str(out),
+                   'err': str(err)})
+
+        if len(lines) == 2:
+            host_info = self._get_hdr_dic(lines[0], lines[1], '!')
+            host_name_from_storage = host_info['name']
+            # Make sure we got the data for the right host
+            self._driver_assert(host_name_from_storage == host_name,
+                    _('Data received for host %(host1)s instead of host '
+                      '%(host2)s.\n '
+                      'stdout: %(out)s\n stderr: %(err)s\n')
+                      % {'host1': host_name_from_storage,
+                         'host2': host_name,
+                         'out': str(out),
+                         'err': str(err)})
+        else:  # 0 or 1 lines
+            host_name_from_storage = None
+
+        LOG.debug(_('leave: _is_host_defined: host %(host)s with %(str)s ') % {
+                   'host': host_name,
+                   'str': host_name_from_storage is not None})
+
+        if host_name_from_storage is None:
+            return False
+        else:
+            return True
+
+    def _get_hostvdisk_mappings(self, host_name):
+        """Return the defined storage mappings for a host."""
+
+        return_data = {}
+        ssh_cmd = 'lshostvdiskmap -delim ! %s' % host_name
+        out, err = self._run_ssh(ssh_cmd)
+        if len(out.strip()) == 0:
+            return return_data
+
+        mappings = out.strip().split('\n')
+        if len(mappings) > 0:
+            header = mappings.pop(0)
+            for mapping_line in mappings:
+                mapping_data = self._get_hdr_dic(header, mapping_line, '!')
+                return_data[mapping_data['vdisk_name']] = mapping_data
+
+        return return_data
+
+    def _map_vol_to_host(self, volume_name, host_name):
+        """Create a mapping between a volume to a host."""
+
+        LOG.debug(_('enter: _map_vol_to_host: volume %(vol)s to '
+                    'host %(host)s') % {'vol': volume_name,
+                    'host': host_name})
+
+        # Check if this volume is already mapped to this host
+        mapping_data = self._get_hostvdisk_mappings(host_name)
+
+        mapped_flag = False
+        result_lun = '-1'
+        if volume_name in mapping_data:
+            mapped_flag = True
+            result_lun = mapping_data[volume_name]['SCSI_id']
+        else:
+            lun_used = []
+            for k, v in mapping_data.iteritems():
+                lun_used.append(int(v['SCSI_id']))
+            lun_used.sort()
+            # Assume all luns are taken to this point, and then try to find
+            # an unused one
+            result_lun = str(len(lun_used))
+            for index, n in enumerate(lun_used):
+                if n > index:
+                    result_lun = str(index)
+
+        # Volume is not mapped to host, create a new LUN
+        if not mapped_flag:
+            out, err = self._run_ssh('mkvdiskhostmap -host %s -scsi %s %s'
+                                    % (host_name, result_lun, volume_name))
+            self._driver_assert(len(out.strip()) > 0 and
+                                'successfully created' in out,
+                    _('_map_vol_to_host: mapping host %(host)s to '
+                      'volume %(vol)s with LUN '
+                      '%(lun)s - did not find success message in CLI output. '
+                      'stdout: %(out)s\n stderr: %(err)s\n')
+                    % {'host': host_name,
+                      'vol': volume_name,
+                      'lun': result_lun,
+                      'out': str(out),
+                      'err': str(err)})
+
+        LOG.debug(_('leave: _map_vol_to_host: LUN %(lun)s, volume %(vol)s, '
+                    'host %(host)s') % {'lun': result_lun, 'vol': volume_name,
+                    'host': host_name})
+
+        return result_lun
+
+    def _get_flashcopy_mapping_attributes(self, fc_map_id):
+        """Return the attributes of a FlashCopy mapping.
+
+        Returns the attributes for the specified FlashCopy mapping, or
+        None if the mapping does not exist.
+        An exception is raised if the information from system can not
+        be parsed or matched to a single FlashCopy mapping (this case
+        should not happen under normal conditions).
+        """
+
+        LOG.debug(_('enter: _get_flashcopy_mapping_attributes: mapping %s')
+                   % fc_map_id)
+        # Get the lunid to be used
+
+        fc_ls_map_cmd = ('lsfcmap -filtervalue id=%s -delim !' % fc_map_id)
+        out, err = self._run_ssh(fc_ls_map_cmd)
+        self._driver_assert(len(out) > 0,
+            _('_get_flashcopy_mapping_attributes: '
+              'Unexpected response from CLI output. '
+              'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s')
+                % {'cmd': fc_ls_map_cmd,
+                   'out': str(out),
+                   'err': str(err)})
+
+        # Get list of FlashCopy mappings
+        # We expect zero or one line if mapping does not exist,
+        # two lines if it does exist, otherwise error
+        lines = out.strip().split('\n')
+        self._driver_assert(len(lines) <= 2,
+                 _('_get_flashcopy_mapping_attributes: '
+                   'Unexpected response from CLI output. '
+                   'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s')
+                            % {'cmd': fc_ls_map_cmd,
+                               'out': str(out),
+                               'err': str(err)})
+
+        if len(lines) == 2:
+            attributes = self._get_hdr_dic(lines[0], lines[1], '!')
+        else:  # 0 or 1 lines
+            attributes = None
+
+        LOG.debug(_('leave: _get_flashcopy_mapping_attributes: mapping '
+                    '%(id)s, attributes %(attr)s') %
+                   {'id': fc_map_id,
+                    'attr': attributes})
+
+        return attributes
+
+    def _get_volume_attributes(self, volume_name):
+        """Return volume attributes, or None if volume does not exist
+
+        Exception is raised if the information from system can not be
+        parsed/matched to a single volume.
+        """
+
+        LOG.debug(_('enter: _get_volume_attributes: volume %s')
+                   % volume_name)
+        # Get the lunid to be used
+
+        try:
+            ssh_cmd = 'lsvdisk -bytes -delim ! %s ' % volume_name
+            out, err = self._run_ssh(ssh_cmd)
+        except exception.ProcessExecutionError as e:
+            #Didn't get details from the storage, return None
+            LOG.error(_('CLI Exception output:\n command: %(cmd)s\n '
+                        'stdout: %(out)s\n stderr: %(err)s') %
+                      {'cmd': ssh_cmd,
+                       'out': e.stdout,
+                       'err': e.stderr})
+            return None
+
+        self._driver_assert(len(out) > 0,
+                    ('_get_volume_attributes: '
+                      'Unexpected response from CLI output. '
+                      'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s')
+                        % {'cmd': ssh_cmd,
+                           'out': str(out),
+                           'err': str(err)})
+        attributes = {}
+        for attrib_line in out.split('\n'):
+            #If '!' not found, will return the string and two empty strings
+            attrib_name, foo, attrib_value = attrib_line.partition('!')
+            if attrib_name is not None and attrib_name.strip() > 0:
+                attributes[attrib_name] = attrib_value
+
+        LOG.debug(_('leave: _get_volume_attributes:\n volume %(vol)s\n '
+                    'attributes: %(attr)s')
+                  % {'vol': volume_name,
+                     'attr': str(attributes)})
+
+        return attributes
index 3dba756d12641ec863a55ce7ae5e7ef893a101a9..4d079bfe51b5ed3cde62fa3c00d04d52ef521a6c 100644 (file)
 ###### (StrOpt) The ZFS path under which to create zvols for volumes.
 # san_zfs_volume_base="rpool/"
 
-# Total option count: 467
+######### defined in cinder.volume.storwize_svc #########
+
+##### (StrOpt) Storage system storage pool for volumes
+# storwize_svc_volpool_name=volpool
+##### (StrOpt) Storage system volume type for volumes
+# storwize_svc_vol_vtype=striped
+##### (StrOpt) Storage system space-efficiency parameter for volumes
+# storwize_svc_vol_rsize=2%
+##### (StrOpt) Storage system threshold for volume capacity warnings
+# storwize_svc_vol_warning=0
+##### (BoolOpt) Storage system autoexpand parameter for volumes (True/False)
+# storwize_svc_vol_autoexpand=True
+##### (StrOpt) Storage system grain size parameter for volumes (32/64/128/256)
+# storwize_svc_vol_grainsize=256
+##### (StrOpt) Maximum number of seconds to wait for FlashCopy to be prepared. Maximum value is 600 seconds (10 minutes).
+# storwize_svc_flashcopy_timeout=120
+
+# Total option count: 474