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")
--- /dev/null
+# 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)
--- /dev/null
+# 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
###### (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