]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Update Storwize/SVC driver for Grizzly.
authorAvishay Traeger <avishay@il.ibm.com>
Thu, 7 Feb 2013 19:50:31 +0000 (21:50 +0200)
committerAvishay Traeger <avishay@il.ibm.com>
Tue, 19 Feb 2013 05:59:18 +0000 (07:59 +0200)
Includes FC support, create_cloned_volume, volume type support,
get_volume_stats, and minor bug fixes.

Change-Id: I13e3d7921c5127e6b4e0cbb4e91761e0249ec295

cinder/tests/test_storwize_svc.py
cinder/volume/driver.py
cinder/volume/drivers/storwize_svc.py

index 5f7c471d619051b5fcdee3d6c3e8f41c316add13..5f9d7b7827c22abf3471892bd80e50cfba586bb4 100644 (file)
@@ -1,7 +1,7 @@
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
 
-# Copyright (c) 2012 IBM, Inc.
-# Copyright (c) 2012 OpenStack LLC.
+# Copyright 2012, 2013 IBM Corp
+# Copyright 2012 OpenStack LLC.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #   Avishay Traeger <avishay@il.ibm.com>
 
 """
-Tests for the IBM Storwize V7000 and SVC volume driver.
+Tests for the IBM Storwize family and SVC volume driver.
 """
 
 import mox
 import random
+import re
 import socket
 
+from cinder import context
 from cinder import exception
 from cinder import flags
 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 configuration as conf
 from cinder.volume.drivers import storwize_svc
+from cinder.volume import volume_types
 
 FLAGS = flags.FLAGS
 
 LOG = logging.getLogger(__name__)
 
 
+class StorwizeSVCFakeDB:
+    def __init__(self):
+        self.volume = None
+
+    def volume_get(self, context, vol_id):
+        return self.volume
+
+    def volume_set(self, vol):
+        self.volume = vol
+
+
 class StorwizeSVCManagementSimulator:
     def __init__(self, pool_name):
-        self._flags = {"storwize_svc_volpool_name": 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 = {
-            "lsportip": "",
-            "lsnodecanister": "",
-            "mkvdisk": "",
-            "lsvdisk": "",
-            "lsfcmap": "",
-            "prestartfcmap": "",
-            "startfcmap": "",
-            "rmfcmap": "",
+            'lsportip': '',
+            'lsportfc': '',
+            'lsfabric': '',
+            'lsiscsiauth': '',
+            'lsnodecanister': '',
+            'mkvdisk': '',
+            'lsvdisk': '',
+            'lsfcmap': '',
+            'prestartfcmap': '',
+            'startfcmap': '',
+            'rmfcmap': '',
+            'lslicense': '',
         }
         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."),
-            "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."),
-            "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."),
-            "CMMVC7050E": ("", "CMMVC7050E The command failed because at " +
-                               "least one node in the I/O group does not " +
-                               "support compressed VDisks."),
+            '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.'),
+            '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.'),
+            '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.'),
+            'CMMVC7050E': ('', 'CMMVC7050E The command failed because at '
+                               'least one node in the I/O group does not '
+                               'support compressed VDisks.'),
+            # Catch-all for invalid state transitions:
+            'CMMVC5903E': ('', 'CMMVC5903E The FlashCopy mapping was not '
+                               'changed because the mapping or consistency '
+                               'group is another state.'),
         }
+        self._transitions = {'begin': {'make': 'idle_or_copied'},
+                             'idle_or_copied': {'prepare': 'preparing',
+                                                'delete': 'end',
+                                                'delete_force': 'end'},
+                             'preparing': {'flush_failed': 'stopped',
+                                           'wait': 'prepared'},
+                             'end': None,
+                             'stopped': {'prepare': 'preparing',
+                                         'delete_force': 'end'},
+                             'prepared': {'stop': 'stopped',
+                                          'start': 'copying'},
+                             'copying': {'wait': 'idle_or_copied',
+                                         'stop': 'stopping'},
+                             # Assume the worst case where stopping->stopped
+                             # rather than stopping idle_or_copied
+                             'stopping': {'wait': 'stopped'},
+                             }
+
+    def _state_transition(self, function, fcmap):
+        if (function == 'wait' and
+            'wait' not in self._transitions[fcmap['status']]):
+            return ('', '')
+
+        if fcmap['status'] == 'copying' and function == 'wait':
+            if fcmap['copyrate'] != '0':
+                if fcmap['progress'] == '0':
+                    fcmap['progress'] = '50'
+                else:
+                    fcmap['progress'] = '100'
+                    fcmap['status'] = 'idle_or_copied'
+            return ('', '')
+        else:
+            try:
+                curr_state = fcmap['status']
+                fcmap['status'] = self._transitions[curr_state][function]
+                return ('', '')
+            except Exception:
+                return self._errors['CMMVC5903E']
 
     # Find an unused ID
     def _find_unused_id(self, d):
         ids = []
         for k, v in d.iteritems():
-            ids.append(int(v["id"]))
+            ids.append(int(v['id']))
         ids.sort()
         for index, n in enumerate(ids):
             if n > index:
@@ -116,60 +177,58 @@ class StorwizeSVCManagementSimulator:
 
     # 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
+        if re.match("^[a-zA-Z_][\w ._-]*$", name):
+            return False
+        return True
 
     # Convert argument string to dictionary
     def _cmd_to_dict(self, cmd):
         arg_list = cmd.split()
         no_param_args = [
-            "autodelete",
-            "autoexpand",
-            "bytes",
-            "compressed",
-            "force",
-            "nohdr",
+            'autodelete',
+            'autoexpand',
+            'bytes',
+            'compressed',
+            'force',
+            'nohdr',
         ]
         one_param_args = [
-            "chapsecret",
-            "cleanrate",
-            "delim",
-            "filtervalue",
-            "grainsize",
-            "host",
-            "iogrp",
-            "iscsiname",
-            "mdiskgrp",
-            "name",
-            "rsize",
-            "scsi",
-            "size",
-            "source",
-            "target",
-            "unit",
-            "easytier",
-            "warning",
+            'chapsecret',
+            'cleanrate',
+            'copyrate',
+            'delim',
+            'filtervalue',
+            'grainsize',
+            'hbawwpn',
+            'host',
+            'iogrp',
+            'iscsiname',
+            'mdiskgrp',
+            'name',
+            'rsize',
+            'scsi',
+            'size',
+            'source',
+            'target',
+            'unit',
+            'easytier',
+            'warning',
         ]
 
         # Handle the special case of lsnode which is a two-word command
         # Use the one word version of the command internally
-        if arg_list[0] == "svcinfo" and arg_list[1] == "lsnode":
-            ret = {"cmd": "lsnodecanister"}
+        if arg_list[0] == 'svcinfo' and arg_list[1] == 'lsnode':
+            ret = {'cmd': 'lsnodecanister'}
             arg_list.pop(0)
         else:
-            ret = {"cmd": arg_list[0]}
+            ret = {'cmd': arg_list[0]}
 
         skip = False
         for i in range(1, len(arg_list)):
             if skip:
                 skip = False
                 continue
-            if arg_list[i][0] == "-":
+            if arg_list[i][0] == '-':
                 if arg_list[i][1:] in no_param_args:
                     ret[arg_list[i][1:]] = True
                 elif arg_list[i][1:] in one_param_args:
@@ -179,740 +238,958 @@ class StorwizeSVCManagementSimulator:
                     raise exception.InvalidInput(
                         reason=_('unrecognized argument %s') % arg_list[i])
             else:
-                ret["obj"] = arg_list[i]
+                ret['obj'] = arg_list[i]
         return ret
 
-    # Generic function for printing information
-    def _print_info_cmd(self, rows, delim=" ", nohdr=False, **kwargs):
+    def _print_info_cmd(self, rows, delim=' ', nohdr=False, **kwargs):
+        """Generic function for printing information."""
         if nohdr:
                 del rows[0]
 
         for index in range(len(rows)):
             rows[index] = delim.join(rows[index])
-        return ("%s" % "\n".join(rows), "")
+        return ('%s' % '\n'.join(rows), '')
+
+    def _print_info_obj_cmd(self, header, row, delim=' ', nohdr=False):
+        """Generic function for printing information for a specific object."""
+        objrows = []
+        for idx, val in enumerate(header):
+            objrows.append([val, row[idx]])
+
+        if nohdr:
+            for index in range(len(objrows)):
+                objrows[index] = ' '.join(objrows[index][1:])
+        for index in range(len(objrows)):
+            objrows[index] = delim.join(objrows[index])
+        return ('%s' % '\n'.join(objrows), '')
+
+    def _convert_bytes_units(self, bytestr):
+        num = int(bytestr)
+        unit_array = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
+        unit_index = 0
+
+        while num > 1024:
+            num = num / 1024
+            unit_index += 1
+
+        return '%d%s' % (num, unit_array[unit_index])
+
+    def _convert_units_bytes(self, num, unit):
+        unit_array = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
+        unit_index = 0
+
+        while unit.lower() != unit_array[unit_index].lower():
+            num = num * 1024
+            unit_index += 1
+
+        return str(num)
+
+    def _cmd_lslicense(self, **kwargs):
+        rows = [None] * 3
+        rows[0] = ['used_compression_capacity', '0.08']
+        rows[1] = ['license_compression_capacity', '0']
+        if self._next_cmd_error['lslicense'] == 'no_compression':
+            self._next_cmd_error['lslicense'] = ''
+            rows[2] = ['license_compression_enclosures', '0']
+        else:
+            rows[2] = ['license_compression_enclosures', '1']
+        return self._print_info_cmd(rows=rows, **kwargs)
 
     # Print mostly made-up stuff in the correct syntax
+    def _cmd_lssystem(self, **kwargs):
+        rows = [None] * 2
+        rows[0] = ['id', '0123456789ABCDEF']
+        rows[1] = ['name', 'storwize-svc-sim']
+        return self._print_info_cmd(rows=rows, **kwargs)
+
+    # Print mostly made-up stuff in the correct syntax, assume -bytes passed
     def _cmd_lsmdiskgrp(self, **kwargs):
         rows = [None] * 3
-        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] = ["1", 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"]
-        rows[2] = ["2", "volpool2", "online",
-                   "1", "0", "3.25TB", "256",
-                   "3.21TB", "1.54TB", "264.97MB", "35.58GB", "47", "80",
-                   "auto", "inactive"]
-        return self._print_info_cmd(rows=rows, **kwargs)
+        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] = ['1', self._flags['storwize_svc_volpool_name'], 'online',
+                   '1', str(len(self._volumes_list)), '3573412790272',
+                   '256', '3529926246400', '1693247906775', '277841182',
+                   '38203734097', '47', '80', 'auto', 'inactive']
+        rows[2] = ['2', 'volpool2', 'online',
+                   '1', '0', '3573412790272', '256',
+                   '3529432325160', '1693247906775', '277841182',
+                   '38203734097', '47', '80', 'auto', 'inactive']
+        if 'obj' not in kwargs:
+            return self._print_info_cmd(rows=rows, **kwargs)
+        else:
+            if kwargs['obj'] == self._flags['storwize_svc_volpool_name']:
+                row = rows[1]
+            elif kwargs['obj'] == 'volpool2':
+                row = rows[2]
+            else:
+                return self._errors['CMMVC5754E']
+
+            objrows = []
+            for idx, val in enumerate(rows[0]):
+                objrows.append([val, row[idx]])
+
+            if 'nohdr' in kwargs:
+                for index in range(len(objrows)):
+                    objrows[index] = ' '.join(objrows[index][1:])
+
+            if 'delim' in kwargs:
+                for index in range(len(objrows)):
+                    objrows[index] = kwargs['delim'].join(objrows[index])
+
+            return ('%s' % '\n'.join(objrows), '')
 
     # Print mostly made-up stuff in the correct syntax
     def _cmd_lsnodecanister(self, **kwargs):
         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"]
-
-        if self._next_cmd_error["lsnodecanister"] == "header_mismatch":
+        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] = ['1', 'node1', '', '123456789ABCDEF0', 'online', '0',
+                   'io_grp0',
+                   'yes', '123456789ABCDEF0', '100',
+                   'iqn.1982-01.com.ibm:1234.sim.node1', '', '01-1', '1', '1',
+                   '0123ABC']
+        rows[2] = ['2', 'node2', '', '123456789ABCDEF1', 'online', '0',
+                   'io_grp0',
+                   'no', '123456789ABCDEF1', '100',
+                   'iqn.1982-01.com.ibm:1234.sim.node2', '', '01-2', '1', '2',
+                   '0123ABC']
+
+        if self._next_cmd_error['lsnodecanister'] == 'header_mismatch':
             rows[0].pop(2)
-            self._next_cmd_error["lsnodecanister"] = ""
-        if self._next_cmd_error["lsnodecanister"] == "remove_field":
+            self._next_cmd_error['lsnodecanister'] = ''
+        if self._next_cmd_error['lsnodecanister'] == 'remove_field':
             for row in rows:
                 row.pop(0)
-            self._next_cmd_error["lsnodecanister"] = ""
+            self._next_cmd_error['lsnodecanister'] = ''
 
         return self._print_info_cmd(rows=rows, **kwargs)
 
     # Print mostly made-up stuff in the correct syntax
     def _cmd_lsportip(self, **kwargs):
-        if self._next_cmd_error["lsportip"] == "ip_no_config":
-            self._next_cmd_error["lsportip"] = ""
-            ip_addr1 = ""
-            ip_addr2 = ""
-            gw = ""
+        if self._next_cmd_error['lsportip'] == 'ip_no_config':
+            self._next_cmd_error['lsportip'] = ''
+            ip_addr1 = ''
+            ip_addr2 = ''
+            gw = ''
         else:
-            ip_addr1 = "1.234.56.78"
-            ip_addr2 = "1.234.56.79"
-            gw = "1.234.56.1"
+            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"]
-
-        if self._next_cmd_error["lsportip"] == "header_mismatch":
+        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', '1', 'node1', ip_addr1, '255.255.255.0',
+                   gw, '', '', '', '01:23:45:67:89:00', 'Full',
+                   'online', '1Gb/s', 'no']
+        rows[2] = ['1', '1', 'node1', '', '', '', '', '', '',
+                   '01:23:45:67:89:00', 'Full', 'online', '1Gb/s', 'yes']
+        rows[3] = ['2', '1', 'node1', '', '', '', '', '', '',
+                   '01:23:45:67:89:01', 'Full', 'unconfigured', '1Gb/s', 'no']
+        rows[4] = ['2', '1', 'node1', '', '', '', '', '', '',
+                   '01:23:45:67:89:01', 'Full', 'unconfigured', '1Gb/s', 'yes']
+        rows[5] = ['3', '1', 'node1', '', '', '', '', '', '', '', '',
+                   'unconfigured', '', 'no']
+        rows[6] = ['3', '1', 'node1', '', '', '', '', '', '', '', '',
+                   'unconfigured', '', 'yes']
+        rows[7] = ['4', '1', 'node1', '', '', '', '', '', '', '', '',
+                   'unconfigured', '', 'no']
+        rows[8] = ['4', '1', 'node1', '', '', '', '', '', '', '', '',
+                   'unconfigured', '', 'yes']
+        rows[9] = ['1', '2', 'node2', ip_addr2, '255.255.255.0',
+                   gw, '', '', '', '01:23:45:67:89:02', 'Full',
+                   'online', '1Gb/s', 'no']
+        rows[10] = ['1', '2', 'node2', '', '', '', '', '', '',
+                    '01:23:45:67:89:02', 'Full', 'online', '1Gb/s', 'yes']
+        rows[11] = ['2', '2', 'node2', '', '', '', '', '', '',
+                    '01:23:45:67:89:03', 'Full', 'unconfigured', '1Gb/s', 'no']
+        rows[12] = ['2', '2', 'node2', '', '', '', '', '', '',
+                    '01:23:45:67:89:03', 'Full', 'unconfigured', '1Gb/s',
+                    'yes']
+        rows[13] = ['3', '2', 'node2', '', '', '', '', '', '', '', '',
+                    'unconfigured', '', 'no']
+        rows[14] = ['3', '2', 'node2', '', '', '', '', '', '', '', '',
+                    'unconfigured', '', 'yes']
+        rows[15] = ['4', '2', 'node2', '', '', '', '', '', '', '', '',
+                    'unconfigured', '', 'no']
+        rows[16] = ['4', '2', 'node2', '', '', '', '', '', '', '', '',
+                    'unconfigured', '', 'yes']
+
+        if self._next_cmd_error['lsportip'] == 'header_mismatch':
             rows[0].pop(2)
-            self._next_cmd_error["lsportip"] = ""
-        if self._next_cmd_error["lsportip"] == "remove_field":
+            self._next_cmd_error['lsportip'] = ''
+        if self._next_cmd_error['lsportip'] == 'remove_field':
             for row in rows:
                 row.pop(1)
-            self._next_cmd_error["lsportip"] = ""
+            self._next_cmd_error['lsportip'] = ''
+
+        return self._print_info_cmd(rows=rows, **kwargs)
+
+    def _cmd_lsportfc(self, **kwargs):
+        if self._next_cmd_error['lsportfc'] == 'fc_no_config':
+            self._next_cmd_error['lsportfc'] = ''
+            wwpn1 = ''
+            wwpn2 = ''
+        else:
+            wwpn1 = '123456789ABCDEF0'
+            wwpn2 = '123456789ABCDEF1'
+
+        rows = [None] * 9
+        rows[0] = ['id', 'fc_io_port_id', 'port_id', 'type', 'port_speed',
+                   'node_id', 'node_name', 'WWPN', 'nportid', 'status']
+        rows[1] = ['0', '1', '1', 'fc', '4Gb', '1', 'node1',
+                   wwpn1, '012ABC', 'active']
+        rows[2] = ['1', '2', '2', 'fc', '4Gb', '1', 'node1',
+                   wwpn1, '012ABC', 'active']
+        rows[3] = ['2', '3', '3', 'fc', 'N/A', '1', 'node1',
+                   wwpn1, '000000', 'inactive_unconfigured']
+        rows[4] = ['3', '4', '4', 'fc', '4Gb', '1', 'node1',
+                   wwpn1, 'ABCDEF', 'active']
+        rows[5] = ['6', '1', '1', 'fc', '4Gb', '2', 'node2',
+                   wwpn2, '012ABC', 'active']
+        rows[6] = ['7', '2', '2', 'fc', '4Gb', '2', 'node2',
+                   wwpn2, '012ABC', 'active']
+        rows[7] = ['8', '3', '3', 'fc', '4Gb', '2', 'node2',
+                   wwpn2, 'ABC123', 'active']
+        rows[8] = ['9', '4', '4', 'fc', '4Gb', '2', 'node2',
+                   wwpn2, '012ABC', 'active']
+
+        if self._next_cmd_error['lsportfc'] == 'header_mismatch':
+            rows[0].pop(2)
+            self._next_cmd_error['lsportfc'] = ''
+        if self._next_cmd_error['lsportfc'] == 'remove_field':
+            for row in rows:
+                row.pop(7)
+            self._next_cmd_error['lsportfc'] = ''
 
         return self._print_info_cmd(rows=rows, **kwargs)
 
+    def _cmd_lsfabric(self, **kwargs):
+        host_name = kwargs['host'] if 'host' in kwargs else None
+        host_infos = []
+
+        for hk, hv in self._hosts_list.iteritems():
+            if not host_name or hv['host_name'] == host_name:
+                for mk, mv in self._mappings_list.iteritems():
+                    if mv['host'] == hv['host_name']:
+                        host_infos.append(hv)
+                        break
+
+        if not len(host_infos):
+            return ('', '')
+
+        rows = []
+        rows.append(['remote_wwpn', 'remote_nportid', 'id', 'node_name',
+                     'local_wwpn', 'local_port', 'local_nportid', 'state',
+                     'name', 'cluster_name', 'type'])
+        for host_info in host_infos:
+            for wwpn in host_info['wwpns']:
+                rows.append([wwpn, '123456', host_info['id'], 'nodeN',
+                            'AABBCCDDEEFF0011', '1', '0123ABC', 'active',
+                            host_info['host_name'], '', 'host'])
+
+        if self._next_cmd_error['lsfabric'] == 'header_mismatch':
+            rows[0].pop(2)
+            self._next_cmd_error['lsfabric'] = ''
+        if self._next_cmd_error['lsfabric'] == 'remove_field':
+            for row in rows:
+                row.pop(4)
+            self._next_cmd_error['lsfabric'] = ''
+        return self._print_info_cmd(rows=rows, **kwargs)
+
     # Create a vdisk
     def _cmd_mkvdisk(self, **kwargs):
         # 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"]
+        volume_info['id'] = self._find_unused_id(self._volumes_list)
+        volume_info['uid'] = ('ABCDEF' * 3) + ('0' * 14) + volume_info['id']
 
-        if "name" in kwargs:
-            volume_info["name"] = kwargs["name"].strip('\'\"')
+        if 'name' in kwargs:
+            volume_info['name'] = kwargs['name'].strip('\'\'')
         else:
-            volume_info["name"] = "vdisk" + volume_info["id"]
+            volume_info['name'] = 'vdisk' + volume_info['id']
 
         # Assume size and unit are given, store it in bytes
-        capacity = int(kwargs["size"])
-        unit = kwargs["unit"]
-
-        if unit == "b":
-            cap_bytes = capacity
-        elif unit == "kb":
-            cap_bytes = capacity * pow(1024, 1)
-        elif unit == "mb":
-            cap_bytes = capacity * pow(1024, 2)
-        elif unit == "gb":
-            cap_bytes = capacity * pow(1024, 3)
-        elif unit == "tb":
-            cap_bytes = capacity * pow(1024, 4)
-        elif unit == "pb":
-            cap_bytes = capacity * pow(1024, 5)
-        volume_info["cap_bytes"] = str(cap_bytes)
-        volume_info["capacity"] = str(capacity) + unit.upper()
-
-        if "easytier" in kwargs:
-            if kwargs["easytier"] == "on":
-                volume_info["easy_tier"] = "on"
+        capacity = int(kwargs['size'])
+        unit = kwargs['unit']
+        volume_info['capacity'] = self._convert_units_bytes(capacity, unit)
+
+        if 'easytier' in kwargs:
+            if kwargs['easytier'] == 'on':
+                volume_info['easy_tier'] = 'on'
             else:
-                volume_info["easy_tier"] = "off"
+                volume_info['easy_tier'] = 'off'
 
-        if "rsize" in kwargs:
+        if 'rsize' in kwargs:
             # Fake numbers
-            volume_info["used_capacity"] = "0.75MB"
-            volume_info["real_capacity"] = "36.98MB"
-            volume_info["free_capacity"] = "36.23MB"
-            volume_info["used_capacity_bytes"] = "786432"
-            volume_info["real_capacity_bytes"] = "38776340"
-            volume_info["free_capacity_bytes"] = "37989908"
-            if "warning" in kwargs:
-                volume_info["warning"] = kwargs["warning"].rstrip('%')
+            volume_info['used_capacity'] = '786432'
+            volume_info['real_capacity'] = '21474816'
+            volume_info['free_capacity'] = '38219264'
+            if 'warning' in kwargs:
+                volume_info['warning'] = kwargs['warning'].rstrip('%')
             else:
-                volume_info["warning"] = "80"
-            if "autoexpand" in kwargs:
-                volume_info["autoexpand"] = "on"
+                volume_info['warning'] = '80'
+            if 'autoexpand' in kwargs:
+                volume_info['autoexpand'] = 'on'
             else:
-                volume_info["autoexpand"] = "off"
-            if "grainsize" in kwargs:
-                volume_info["grainsize"] = kwargs["grainsize"]
+                volume_info['autoexpand'] = 'off'
+            if 'grainsize' in kwargs:
+                volume_info['grainsize'] = kwargs['grainsize']
             else:
-                volume_info["grainsize"] = "32"
-            if "compressed" in kwargs:
-                if self._next_cmd_error["mkvdisk"] == "no_compression":
-                    self._next_cmd_error["mkvdisk"] = ""
-                    return self._errors["CMMVC7050E"]
-                volume_info["compressed_copy"] = "yes"
+                volume_info['grainsize'] = '32'
+            if 'compressed' in kwargs:
+                volume_info['compressed_copy'] = 'yes'
             else:
-                volume_info["compressed_copy"] = "no"
+                volume_info['compressed_copy'] = 'no'
         else:
-            volume_info["used_capacity"] = volume_info["capacity"]
-            volume_info["real_capacity"] = volume_info["capacity"]
-            volume_info["free_capacity"] = "0.00MB"
-            volume_info["used_capacity_bytes"] = volume_info["cap_bytes"]
-            volume_info["real_capacity_bytes"] = volume_info["cap_bytes"]
-            volume_info["free_capacity_bytes"] = "0"
-            volume_info["warning"] = ""
-            volume_info["autoexpand"] = ""
-            volume_info["grainsize"] = ""
-            volume_info["compressed_copy"] = "no"
-
-        if volume_info["name"] in self._volumes_list:
-            return self._errors["CMMVC6035E"]
+            volume_info['used_capacity'] = volume_info['capacity']
+            volume_info['real_capacity'] = volume_info['capacity']
+            volume_info['free_capacity'] = '0'
+            volume_info['warning'] = ''
+            volume_info['autoexpand'] = ''
+            volume_info['grainsize'] = ''
+            volume_info['compressed_copy'] = 'no'
+
+        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"]), "")
+            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, **kwargs):
-        force = 0
-        if "force" in kwargs:
-            force = 1
+        force = True if 'force' in kwargs else False
 
-        if "obj" not in kwargs:
-            return self._errors["CMMVC5701E"]
-        vol_name = kwargs["obj"].strip('\'\"')
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5701E']
+        vol_name = kwargs['obj'].strip('\'\'')
 
-        if vol_name not in self._volumes_list:
-            return self._errors["CMMVC5753E"]
+        if not vol_name in self._volumes_list:
+            return self._errors['CMMVC5753E']
 
-        if force == 0:
+        if not force:
             for k, mapping in self._mappings_list.iteritems():
-                if mapping["vol"] == vol_name:
-                    return self._errors["CMMVC5840E"]
+                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"]
+                if ((fcmap['source'] == vol_name) or
+                        (fcmap['target'] == vol_name)):
+                    return self._errors['CMMVC5840E']
 
         del self._volumes_list[vol_name]
-        return ("", "")
+        return ('', '')
 
     def _get_fcmap_info(self, vol_name):
         ret_vals = {
-            "fc_id": "",
-            "fc_name": "",
-            "fc_map_count": "0",
+            '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"
+            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, **kwargs):
-        if "obj" not in kwargs:
-            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 (("filtervalue" not in kwargs) or
-                        (kwargs["filtervalue"] == "name=" + vol["name"])):
-                    fcmap_info = self._get_fcmap_info(vol["name"])
-
-                    if "bytes" in kwargs:
-                        cap = vol["cap_bytes"]
-                    else:
-                        cap = vol["capacity"]
-                    rows.append([str(vol["id"]), vol["name"], "0", "io_grp0",
-                                "online", "0",
-                                self._flags["storwize_svc_volpool_name"],
-                                cap, "striped",
-                                fcmap_info["fc_id"], fcmap_info["fc_name"],
-                                "", "", vol["uid"],
-                                fcmap_info["fc_map_count"], "1", "empty",
-                                "1", "no"])
-
+        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 (('filtervalue' not in kwargs) or
+                    (kwargs['filtervalue'] == 'name=' + vol['name'])):
+                fcmap_info = self._get_fcmap_info(vol['name'])
+
+                if 'bytes' in kwargs:
+                    cap = self._convert_bytes_units(vol['capacity'])
+                else:
+                    cap = vol['capacity']
+                rows.append([str(vol['id']), vol['name'], '0', 'io_grp0',
+                            'online', '0',
+                            self._flags['storwize_svc_volpool_name'],
+                            cap, 'striped',
+                            fcmap_info['fc_id'], fcmap_info['fc_name'],
+                            '', '', vol['uid'],
+                            fcmap_info['fc_map_count'], '1', 'empty',
+                            '1', 'no'])
+
+        if 'obj' not in kwargs:
             return self._print_info_cmd(rows=rows, **kwargs)
-
         else:
-            if kwargs["obj"] not in self._volumes_list:
-                return self._errors["CMMVC5754E"]
-            vol = self._volumes_list[kwargs["obj"]]
-            fcmap_info = self._get_fcmap_info(vol["name"])
-            if "bytes" in kwargs:
-                cap = vol["cap_bytes"]
-                cap_u = vol["used_capacity_bytes"]
-                cap_r = vol["real_capacity_bytes"]
-                cap_f = vol["free_capacity_bytes"]
-            else:
-                cap = vol["capacity"]
-                cap_u = vol["used_capacity"]
-                cap_r = vol["real_capacity"]
-                cap_f = vol["free_capacity"]
+            if kwargs['obj'] not in self._volumes_list:
+                return self._errors['CMMVC5754E']
+            vol = self._volumes_list[kwargs['obj']]
+            fcmap_info = self._get_fcmap_info(vol['name'])
+            cap = vol['capacity']
+            cap_u = vol['used_capacity']
+            cap_r = vol['real_capacity']
+            cap_f = vol['free_capacity']
+            if 'bytes' not in kwargs:
+                for item in [cap, cap_u, cap_r, cap_f]:
+                    item = self._convert_bytes_units(item)
             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(['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", cap])
-            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"])
-
-            if self._next_cmd_error["lsvdisk"] == "blank_pref_node":
-                rows.append(["preferred_node_id", ""])
-                self._next_cmd_error["lsvdisk"] = ""
-            elif self._next_cmd_error["lsvdisk"] == "no_pref_node":
-                self._next_cmd_error["lsvdisk"] = ""
+                'mdisk_grp_name',
+                self._flags['storwize_svc_volpool_name']])
+            rows.append(['capacity', cap])
+            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'])
+
+            if self._next_cmd_error['lsvdisk'] == 'blank_pref_node':
+                rows.append(['preferred_node_id', ''])
+                self._next_cmd_error['lsvdisk'] = ''
+            elif self._next_cmd_error['lsvdisk'] == 'no_pref_node':
+                self._next_cmd_error['lsvdisk'] = ''
             else:
-                rows.append(["preferred_node_id", "6"])
-            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"])
-            rows.append(["used_capacity", cap_u])
-            rows.append(["real_capacity", cap_r])
-            rows.append(["free_capacity", cap_f])
-            rows.append(["autoexpand", vol["autoexpand"]])
-            rows.append(["warning", vol["warning"]])
-            rows.append(["grainsize", vol["grainsize"]])
-            rows.append(["easy_tier", vol["easy_tier"]])
-            rows.append(["compressed_copy", vol["compressed_copy"]])
-
-            if "nohdr" in kwargs:
+                rows.append(['preferred_node_id', '1'])
+            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'])
+            rows.append(['used_capacity', cap_u])
+            rows.append(['real_capacity', cap_r])
+            rows.append(['free_capacity', cap_f])
+            rows.append(['autoexpand', vol['autoexpand']])
+            rows.append(['warning', vol['warning']])
+            rows.append(['grainsize', vol['grainsize']])
+            rows.append(['easy_tier', vol['easy_tier']])
+            rows.append(['compressed_copy', vol['compressed_copy']])
+
+            if 'nohdr' in kwargs:
                 for index in range(len(rows)):
-                    rows[index] = " ".join(rows[index][1:])
+                    rows[index] = ' '.join(rows[index][1:])
 
-            if "delim" in kwargs:
+            if 'delim' in kwargs:
                 for index in range(len(rows)):
-                    rows[index] = kwargs["delim"].join(rows[index])
+                    rows[index] = kwargs['delim'].join(rows[index])
+
+            return ('%s' % '\n'.join(rows), '')
+
+    def _add_port_to_host(self, host_info, **kwargs):
+        if 'iscsiname' in kwargs:
+            added_key = 'iscsi_names'
+            added_val = kwargs['iscsiname'].strip('\'\"')
+        elif 'hbawwpn' in kwargs:
+            added_key = 'wwpns'
+            added_val = kwargs['hbawwpn'].strip('\'\"')
+        else:
+            return self._errors['CMMVC5707E']
 
-            return ("%s" % "\n".join(rows), "")
+        host_info[added_key].append(added_val)
+
+        for k, v in self._hosts_list.iteritems():
+            if v['id'] == host_info['id']:
+                continue
+            for port in v[added_key]:
+                if port == added_val:
+                    return self._errors['CMMVC6581E']
+        return ('', '')
 
     # Make a host
     def _cmd_mkhost(self, **kwargs):
         host_info = {}
-        host_info["id"] = self._find_unused_id(self._hosts_list)
+        host_info['id'] = self._find_unused_id(self._hosts_list)
 
-        if "name" in kwargs:
-            host_name = kwargs["name"].strip('\'\"')
+        if 'name' in kwargs:
+            host_name = kwargs['name'].strip('\'\"')
         else:
-            host_name = "host" + str(host_info["id"])
-        host_info["host_name"] = host_name
-
-        if "iscsiname" not in kwargs:
-            return self._errors["CMMVC5707E"]
-        host_info["iscsi_name"] = kwargs["iscsiname"].strip('\'\"')
+            host_name = 'host' + str(host_info['id'])
 
         if self._is_invalid_name(host_name):
-            return self._errors["CMMVC6527E"]
+            return self._errors['CMMVC6527E']
 
         if host_name in self._hosts_list:
-            return self._errors["CMMVC6035E"]
+            return self._errors['CMMVC6035E']
 
-        for k, v in self._hosts_list.iteritems():
-            if v["iscsi_name"] == host_info["iscsi_name"]:
-                return self._errors["CMMVC6581E"]
+        host_info['host_name'] = host_name
+        host_info['iscsi_names'] = []
+        host_info['wwpns'] = []
+
+        out, err = self._add_port_to_host(host_info, **kwargs)
+        if not len(err):
+            self._hosts_list[host_name] = host_info
+            return ('Host, id [%s], successfully created' %
+                    (host_info['id']), '')
+        else:
+            return (out, err)
 
-        self._hosts_list[host_name] = host_info
-        return ("Host, id [%s], successfully created" %
-                (host_info["id"]), "")
+    # Add ports to an existing host
+    def _cmd_addhostport(self, **kwargs):
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5701E']
+        host_name = kwargs['obj'].strip('\'\'')
+
+        if host_name not in self._hosts_list:
+            return self._errors['CMMVC5753E']
+
+        host_info = self._hosts_list[host_name]
+        return self._add_port_to_host(host_info, **kwargs)
 
     # Change host properties
     def _cmd_chhost(self, **kwargs):
-        if "chapsecret" not in kwargs:
-            return self._errors["CMMVC5707E"]
-        secret = kwargs["obj"].strip('\'\"')
+        if 'chapsecret' not in kwargs:
+            return self._errors['CMMVC5707E']
+        secret = kwargs['obj'].strip('\'\'')
 
-        if "obj" not in kwargs:
-            return self._errors["CMMVC5701E"]
-        host_name = kwargs["obj"].strip('\'\"')
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5701E']
+        host_name = kwargs['obj'].strip('\'\'')
 
         if host_name not in self._hosts_list:
-            return self._errors["CMMVC5753E"]
+            return self._errors['CMMVC5753E']
 
-        self._hosts_list[host_name]["chapsecret"] = secret
-        return ("", "")
+        self._hosts_list[host_name]['chapsecret'] = secret
+        return ('', '')
 
     # Remove a host
     def _cmd_rmhost(self, **kwargs):
-        if "obj" not in kwargs:
-            return self._errors["CMMVC5701E"]
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5701E']
 
-        host_name = kwargs["obj"].strip('\'\"')
+        host_name = kwargs['obj'].strip('\'\'')
         if host_name not in self._hosts_list:
-            return self._errors["CMMVC5753E"]
+            return self._errors['CMMVC5753E']
 
         for k, v in self._mappings_list.iteritems():
-            if (v["host"] == host_name):
-                return self._errors["CMMVC5871E"]
+            if (v['host'] == host_name):
+                return self._errors['CMMVC5871E']
 
         del self._hosts_list[host_name]
-        return ("", "")
+        return ('', '')
 
     # List information about hosts
     def _cmd_lshost(self, **kwargs):
-        if "obj" not in kwargs:
+        if 'obj' not in kwargs:
             rows = []
-            rows.append(["id", "name", "port_count", "iogrp_count", "status"])
+            rows.append(['id', 'name', 'port_count', 'iogrp_count', 'status'])
 
             found = False
             for k, host in self._hosts_list.iteritems():
-                filterstr = "name=" + host["host_name"]
-                if (("filtervalue" not in kwargs) or
-                        (kwargs["filtervalue"] == filterstr)):
-                    rows.append([host["id"], host["host_name"], "1", "4",
-                                "offline"])
+                filterstr = 'name=' + host['host_name']
+                if (('filtervalue' not in kwargs) or
+                        (kwargs['filtervalue'] == filterstr)):
+                    rows.append([host['id'], host['host_name'], '1', '4',
+                                'offline'])
                     found = True
             if found:
                 return self._print_info_cmd(rows=rows, **kwargs)
             else:
-                return ("", "")
+                return ('', '')
         else:
-            if kwargs["obj"] not in self._hosts_list:
-                return self._errors["CMMVC5754E"]
-            host = self._hosts_list[kwargs["obj"]]
+            if kwargs['obj'] not in self._hosts_list:
+                return self._errors['CMMVC5754E']
+            host = self._hosts_list[kwargs['obj']]
             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 "nohdr" in kwargs:
+            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', 'online'])
+            for port in host['iscsi_names']:
+                rows.append(['iscsi_name', port])
+                rows.append(['node_logged_in_count', '0'])
+                rows.append(['state', 'offline'])
+            for port in host['wwpns']:
+                rows.append(['WWPN', port])
+                rows.append(['node_logged_in_count', '0'])
+                rows.append(['state', 'active'])
+
+            if 'nohdr' in kwargs:
                 for index in range(len(rows)):
-                    rows[index] = " ".join(rows[index][1:])
+                    rows[index] = ' '.join(rows[index][1:])
 
-            if "delim" in kwargs:
+            if 'delim' in kwargs:
                 for index in range(len(rows)):
-                    rows[index] = kwargs["delim"].join(rows[index])
+                    rows[index] = kwargs['delim'].join(rows[index])
 
-            return ("%s" % "\n".join(rows), "")
+            return ('%s' % '\n'.join(rows), '')
 
     # List iSCSI authorization information about hosts
     def _cmd_lsiscsiauth(self, **kwargs):
+        if self._next_cmd_error['lsiscsiauth'] == 'no_info':
+            self._next_cmd_error['lsiscsiauth'] = ''
+            return ('', '')
         rows = []
-        rows.append(["type", "id", "name", "iscsi_auth_method",
-                     "iscsi_chap_secret"])
+        rows.append(['type', 'id', 'name', 'iscsi_auth_method',
+                     'iscsi_chap_secret'])
 
         for k, host in self._hosts_list.iteritems():
-            method = "none"
-            secret = ""
-            if "chapsecret" in host:
-                method = "chap"
-                secret = host["chapsecret"]
-            rows.append(["host", host["id"], host["host_name"], method,
+            method = 'none'
+            secret = ''
+            if 'chapsecret' in host:
+                method = 'chap'
+                secret = host['chapsecret']
+            rows.append(['host', host['id'], host['host_name'], method,
                          secret])
         return self._print_info_cmd(rows=rows, **kwargs)
 
     # Create a vdisk-host mapping
     def _cmd_mkvdiskhostmap(self, **kwargs):
         mapping_info = {}
-        mapping_info["id"] = self._find_unused_id(self._mappings_list)
+        mapping_info['id'] = self._find_unused_id(self._mappings_list)
 
-        if "host" not in kwargs:
-            return self._errors["CMMVC5707E"]
-        mapping_info["host"] = kwargs["host"].strip('\'\"')
+        if 'host' not in kwargs:
+            return self._errors['CMMVC5707E']
+        mapping_info['host'] = kwargs['host'].strip('\'\'')
 
-        if "scsi" not in kwargs:
-            return self._errors["CMMVC5707E"]
-        mapping_info["lun"] = kwargs["scsi"].strip('\'\"')
+        if 'scsi' not in kwargs:
+            return self._errors['CMMVC5707E']
+        mapping_info['lun'] = kwargs['scsi'].strip('\'\'')
 
-        if "obj" not in kwargs:
-            return self._errors["CMMVC5707E"]
-        mapping_info["vol"] = kwargs["obj"].strip('\'\"')
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5707E']
+        mapping_info['vol'] = kwargs['obj'].strip('\'\'')
 
-        if mapping_info["vol"] not in self._volumes_list:
-            return self._errors["CMMVC5753E"]
+        if not mapping_info['vol'] in self._volumes_list:
+            return self._errors['CMMVC5753E']
 
-        if mapping_info["host"] not in self._hosts_list:
-            return self._errors["CMMVC5754E"]
+        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"]
+        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"]
+            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"]), "")
+        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, **kwargs):
-        if "host" not in kwargs:
-            return self._errors["CMMVC5707E"]
-        host = kwargs["host"].strip('\'\"')
+        if 'host' not in kwargs:
+            return self._errors['CMMVC5707E']
+        host = kwargs['host'].strip('\'\'')
 
-        if "obj" not in kwargs:
-            return self._errors["CMMVC5701E"]
-        vol = kwargs["obj"].strip('\'\"')
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5701E']
+        vol = kwargs['obj'].strip('\'\'')
 
-        if vol not in self._mappings_list:
-            return self._errors["CMMVC5753E"]
+        if not vol in self._mappings_list:
+            return self._errors['CMMVC5753E']
 
-        if self._mappings_list[vol]["host"] != host:
-            return self._errors["CMMVC5753E"]
+        if self._mappings_list[vol]['host'] != host:
+            return self._errors['CMMVC5753E']
 
         del self._mappings_list[vol]
-        return ("", "")
+        return ('', '')
 
     # List information about vdisk-host mappings
     def _cmd_lshostvdiskmap(self, **kwargs):
         index = 1
         no_hdr = 0
-        delimeter = ""
-        host_name = kwargs["obj"]
+        delimeter = ''
+        host_name = kwargs['obj']
 
         if host_name not in self._hosts_list:
-            return self._errors["CMMVC5754E"]
+            return self._errors['CMMVC5754E']
 
         rows = []
-        rows.append(["id", "name", "SCSI_id", "vdisk_id", "vdisk_name",
-                     "vdisk_UID"])
+        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"]])
+            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(rows=rows, **kwargs)
 
     # Create a FlashCopy mapping
     def _cmd_mkfcmap(self, **kwargs):
-        source = ""
-        target = ""
-
-        if "source" not in kwargs:
-            return self._errors["CMMVC5707E"]
-        source = kwargs["source"].strip('\'\"')
-        if source not in self._volumes_list:
-            return self._errors["CMMVC5754E"]
-
-        if "target" not in kwargs:
-            return self._errors["CMMVC5707E"]
-        target = kwargs["target"].strip('\'\"')
-        if target not in self._volumes_list:
-            return self._errors["CMMVC5754E"]
+        source = ''
+        target = ''
+        copyrate = kwargs['copyrate'] if 'copyrate' in kwargs else '50'
+
+        if 'source' not in kwargs:
+            return self._errors['CMMVC5707E']
+        source = kwargs['source'].strip('\'\'')
+        if not source in self._volumes_list:
+            return self._errors['CMMVC5754E']
+
+        if 'target' not in kwargs:
+            return self._errors['CMMVC5707E']
+        target = kwargs['target'].strip('\'\'')
+        if not target in self._volumes_list:
+            return self._errors['CMMVC5754E']
 
         if source == target:
-            return self._errors["CMMVC6303E"]
+            return self._errors['CMMVC6303E']
 
-        if (self._volumes_list[source]["cap_bytes"] !=
-                self._volumes_list[target]["cap_bytes"]):
-            return self._errors["CMMVC5924E"]
+        if (self._volumes_list[source]['capacity'] !=
+                self._volumes_list[target]['capacity']):
+            return self._errors['CMMVC5924E']
 
         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, mode, **kwargs):
-        if "obj" not in kwargs:
-            return self._errors["CMMVC5701E"]
-        id_num = kwargs["obj"]
-
-        if mode == "pre":
-            if self._next_cmd_error["prestartfcmap"] == "bad_id":
-                id_num = -1
-                self._next_cmd_error["prestartfcmap"] = ""
-        else:
-            if self._next_cmd_error["startfcmap"] == "bad_id":
-                id_num = -1
-                self._next_cmd_error["startfcmap"] = ""
+        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['copyrate'] = copyrate
+        fcmap_info['progress'] = '0'
+        fcmap_info['autodelete'] = True if 'autodelete' in kwargs else False
+        fcmap_info['status'] = 'idle_or_copied'
+        self._fcmappings_list[fcmap_info['id']] = fcmap_info
+
+        return('FlashCopy Mapping, id [' + fcmap_info['id'] +
+               '], successfully created', '')
+
+    def _cmd_gen_prestartfcmap(self, **kwargs):
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5701E']
+        id_num = kwargs['obj']
+
+        if self._next_cmd_error['prestartfcmap'] == 'bad_id':
+            id_num = -1
+            self._next_cmd_error['prestartfcmap'] = ''
 
-        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"]
-
-    # Same function used for both stopfcmap and rmfcmap
-    # Assumes it is called with "-force <fc_map_id>"
-    def _cmd_stoprmfcmap(self, mode, **kwargs):
-        if "obj" not in kwargs:
-            return self._errors["CMMVC5701E"]
-        id_num = kwargs["obj"]
-
-        if self._next_cmd_error["rmfcmap"] == "bad_id":
+        try:
+            fcmap = self._fcmappings_list[id_num]
+        except KeyError:
+            return self._errors['CMMVC5753E']
+
+        return self._state_transition('prepare', fcmap)
+
+    def _cmd_gen_startfcmap(self, **kwargs):
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5701E']
+        id_num = kwargs['obj']
+
+        if self._next_cmd_error['startfcmap'] == 'bad_id':
             id_num = -1
-            self._next_cmd_error["rmfcmap"] = ""
+            self._next_cmd_error['startfcmap'] = ''
 
-        to_delete = None
-        found = False
-        for k, fcmap in self._fcmappings_list.iteritems():
-            if fcmap["id"] == id_num:
-                found = True
-                if mode == "rm":
-                    to_delete = k
+        try:
+            fcmap = self._fcmappings_list[id_num]
+        except KeyError:
+            return self._errors['CMMVC5753E']
 
-        if to_delete:
-            del self._fcmappings_list[to_delete]
+        return self._state_transition('start', fcmap)
 
-        if found:
-            return ("", "")
-        else:
-            return self._errors["CMMVC5753E"]
+    def _cmd_stopfcmap(self, **kwargs):
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5701E']
+        id_num = kwargs['obj']
+
+        try:
+            fcmap = self._fcmappings_list[id_num]
+        except KeyError:
+            return self._errors['CMMVC5753E']
+
+        return self._state_transition('stop', fcmap)
+
+    def _cmd_rmfcmap(self, **kwargs):
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5701E']
+        id_num = kwargs['obj']
+        force = True if 'force' in kwargs else False
+
+        if self._next_cmd_error['rmfcmap'] == 'bad_id':
+            id_num = -1
+            self._next_cmd_error['rmfcmap'] = ''
+
+        try:
+            fcmap = self._fcmappings_list[id_num]
+        except KeyError:
+            return self._errors['CMMVC5753E']
+
+        function = 'delete_force' if force else 'delete'
+        ret = self._state_transition(function, fcmap)
+        if fcmap['status'] == 'end':
+            del self._fcmappings_list[id_num]
+        return ret
+
+    def _cmd_lsvdiskfcmappings(self, **kwargs):
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5707E']
+        vdisk = kwargs['obj']
+        rows = []
+        rows.append(['id', 'name'])
+        for k, v in self._fcmappings_list.iteritems():
+            if v['source'] == vdisk or v['target'] == vdisk:
+                rows.append([v['id'], v['name']])
+        return self._print_info_cmd(rows=rows, **kwargs)
+
+    def _cmd_chfcmap(self, **kwargs):
+        if 'obj' not in kwargs:
+            return self._errors['CMMVC5707E']
+        id_num = kwargs['obj']
+
+        try:
+            fcmap = self._fcmappings_list[id_num]
+        except KeyError:
+            return self._errors['CMMVC5753E']
+
+        for key in ['name', 'copyrate', 'autodelete']:
+            if key in kwargs:
+                fcmap[key] = kwargs[key]
+        return ('', '')
 
     def _cmd_lsfcmap(self, **kwargs):
         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"])
+        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
-        filter_key = kwargs["filtervalue"].split("=")[0]
-        filter_value = kwargs["filtervalue"].split("=")[1]
+        filter_key = kwargs['filtervalue'].split('=')[0]
+        filter_value = kwargs['filtervalue'].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"]]
-                old_status = v["status"]
-                if old_status == "preparing":
-                    new_status = "prepared"
-                    if self._next_cmd_error["lsfcmap"] == "bogus_prepare":
-                        new_status = "bogus"
-                elif (old_status == "copying") and (v["progress"] == "0"):
-                    new_status = "copying"
-                    v["progress"] = "50"
-                elif (old_status == "copying") and (v["progress"] == "50"):
-                    new_status = "idle_or_copied"
+                source = self._volumes_list[v['source']]
+                target = self._volumes_list[v['target']]
+                self._state_transition('wait', v)
+
+                if self._next_cmd_error['lsfcmap'] == 'speed_up':
+                    self._next_cmd_error['lsfcmap'] = ''
+                    curr_state = v['status']
+                    while self._state_transition('wait', v) == ("", ""):
+                        if curr_state == v['status']:
+                            break
+                        curr_state = v['status']
+
+                if ((v['status'] == 'idle_or_copied' and v['autodelete'] and
+                     v['progress'] == '100') or (v['status'] == 'end')):
                     to_delete.append(k)
                 else:
-                    new_status = old_status
-                v["status"] = new_status
-
-                if ((self._next_cmd_error["lsfcmap"] == "speed_up") or
-                        (self._next_cmd_error["lsfcmap"] == "bogus_prepare")):
-                    print_status = new_status
-                    self._next_cmd_error["lsfcmap"] = ""
-                else:
-                    print_status = old_status
-
-                rows.append([v["id"], v["name"], source["id"],
-                            source["name"], target["id"], target["name"], "",
-                            "", print_status, v["progress"], "50", "100",
-                            "off", "", "", "no", "", "no"])
+                    rows.append([v['id'], v['name'], source['id'],
+                                source['name'], target['id'], target['name'],
+                                '', '', v['status'], v['progress'],
+                                v['copyrate'], '100', 'off', '', '', 'no', '',
+                                'no'])
 
         for d in to_delete:
             del self._fcmappings_list[k]
 
         return self._print_info_cmd(rows=rows, **kwargs)
 
+    # Add host to list
+    def _add_host_to_list(self, connector):
+        host_info = {}
+        host_info['id'] = self._find_unused_id(self._hosts_list)
+        host_info['host_name'] = connector['host']
+        host_info['iscsi_names'] = []
+        host_info['wwpns'] = []
+        if 'initiator' in connector:
+            host_info['iscsi_names'].append(connector['initiator'])
+        if 'wwpns' in connector:
+            host_info['wwpns'] = host_info['wwpns'] + connector['wwpns']
+        self._hosts_list[connector['host']] = host_info
+
     # The main function to run commands on the management simulator
     def execute_command(self, cmd, check_exit_code=True):
         try:
             kwargs = self._cmd_to_dict(cmd)
         except IndexError:
-            return self._errors["CMMVC5707E"]
+            return self._errors['CMMVC5707E']
 
-        command = kwargs["cmd"]
-        del kwargs["cmd"]
+        command = kwargs['cmd']
+        del kwargs['cmd']
         arg_list = cmd.split()
 
-        if command == "lsmdiskgrp":
+        if command == 'lsmdiskgrp':
             out, err = self._cmd_lsmdiskgrp(**kwargs)
-        elif command == "lsnodecanister":
+        elif command == 'lslicense':
+            out, err = self._cmd_lslicense(**kwargs)
+        elif command == 'lssystem':
+            out, err = self._cmd_lssystem(**kwargs)
+        elif command == 'lsnodecanister':
             out, err = self._cmd_lsnodecanister(**kwargs)
-        elif command == "lsportip":
+        elif command == 'lsportip':
             out, err = self._cmd_lsportip(**kwargs)
-        elif command == "mkvdisk":
+        elif command == 'lsportfc':
+            out, err = self._cmd_lsportfc(**kwargs)
+        elif command == 'lsfabric':
+            out, err = self._cmd_lsfabric(**kwargs)
+        elif command == 'mkvdisk':
             out, err = self._cmd_mkvdisk(**kwargs)
-        elif command == "rmvdisk":
+        elif command == 'rmvdisk':
             out, err = self._cmd_rmvdisk(**kwargs)
-        elif command == "lsvdisk":
+        elif command == 'lsvdisk':
             out, err = self._cmd_lsvdisk(**kwargs)
-        elif command == "mkhost":
+        elif command == 'mkhost':
             out, err = self._cmd_mkhost(**kwargs)
-        elif command == "chhost":
+        elif command == 'addhostport':
+            out, err = self._cmd_addhostport(**kwargs)
+        elif command == 'chhost':
             out, err = self._cmd_chhost(**kwargs)
-        elif command == "rmhost":
+        elif command == 'rmhost':
             out, err = self._cmd_rmhost(**kwargs)
-        elif command == "lshost":
+        elif command == 'lshost':
             out, err = self._cmd_lshost(**kwargs)
-        elif command == "lsiscsiauth":
+        elif command == 'lsiscsiauth':
             out, err = self._cmd_lsiscsiauth(**kwargs)
-        elif command == "mkvdiskhostmap":
+        elif command == 'mkvdiskhostmap':
             out, err = self._cmd_mkvdiskhostmap(**kwargs)
-        elif command == "rmvdiskhostmap":
+        elif command == 'rmvdiskhostmap':
             out, err = self._cmd_rmvdiskhostmap(**kwargs)
-        elif command == "lshostvdiskmap":
+        elif command == 'lshostvdiskmap':
             out, err = self._cmd_lshostvdiskmap(**kwargs)
-        elif command == "mkfcmap":
+        elif command == 'mkfcmap':
             out, err = self._cmd_mkfcmap(**kwargs)
-        elif command == "prestartfcmap":
-            out, err = self._cmd_gen_startfcmap(mode="pre", **kwargs)
-        elif command == "startfcmap":
-            out, err = self._cmd_gen_startfcmap(mode="start", **kwargs)
-        elif command == "stopfcmap":
-            out, err = self._cmd_stoprmfcmap(mode="stop", **kwargs)
-        elif command == "rmfcmap":
-            out, err = self._cmd_stoprmfcmap(mode="rm", **kwargs)
-        elif command == "lsfcmap":
+        elif command == 'prestartfcmap':
+            out, err = self._cmd_gen_prestartfcmap(**kwargs)
+        elif command == 'startfcmap':
+            out, err = self._cmd_gen_startfcmap(**kwargs)
+        elif command == 'stopfcmap':
+            out, err = self._cmd_stopfcmap(**kwargs)
+        elif command == 'rmfcmap':
+            out, err = self._cmd_rmfcmap(**kwargs)
+        elif command == 'chfcmap':
+            out, err = self._cmd_chfcmap(**kwargs)
+        elif command == 'lsfcmap':
             out, err = self._cmd_lsfcmap(**kwargs)
+        elif command == 'lsvdiskfcmappings':
+            out, err = self._cmd_lsvdiskfcmappings(**kwargs)
         else:
-            out, err = ("", "ERROR: Unsupported command")
+            out, err = ('', 'ERROR: Unsupported command')
 
         if (check_exit_code) and (len(err) != 0):
             raise exception.ProcessExecutionError(exit_code=1,
@@ -940,8 +1217,8 @@ class StorwizeSVCFakeDriver(storwize_svc.StorwizeSVCDriver):
             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})
+            LOG.debug(_('CLI output:\n stdout: %(stdout)s\n stderr: '
+                        '%(stderr)s') % {'stdout': stdout, 'stderr': stderr})
 
         except exception.ProcessExecutionError as e:
             with excutils.save_and_reraise_exception():
@@ -952,18 +1229,32 @@ class StorwizeSVCFakeDriver(storwize_svc.StorwizeSVCDriver):
         return ret
 
 
+class StorwizeSVCFakeSock:
+    def settimeout(self, time):
+        return
+
+
 class StorwizeSVCDriverTestCase(test.TestCase):
     def setUp(self):
         super(StorwizeSVCDriverTestCase, self).setUp()
-        self.USESIM = 1
-        if self.USESIM == 1:
-            self.flags(
-                san_ip="hostname",
-                san_login="user",
-                san_password="pass",
-                storwize_svc_flashcopy_timeout="20",
-            )
-            self.sim = StorwizeSVCManagementSimulator("volpool")
+        self.USESIM = True
+        if self.USESIM:
+            self._def_flags = {'san_ip': 'hostname',
+                               'san_login': 'user',
+                               'san_password': 'pass',
+                               'storwize_svc_flashcopy_timeout': 20,
+                               'storwize_svc_connection_protocol': 'iscsi',
+                               'storwize_svc_multipath_enabled': False}
+            self._host_name = 'storwize-svc-test'
+            self._host_ip = '1.234.56.78'
+            self._host_wwpns = [
+                    str(random.randint(0, 9999999999999999)).zfill(16),
+                    str(random.randint(0, 9999999999999999)).zfill(16)]
+            self._iscsi_name = ('test.initiator.%s' %
+                                str(random.randint(10000, 99999)))
+            self._reset_flags()
+            self.sim = StorwizeSVCManagementSimulator('volpool')
+
             configuration = mox.MockObject(conf.Configuration)
             configuration.san_is_local = False
             configuration.append_config_values(mox.IgnoreArg())
@@ -971,242 +1262,287 @@ class StorwizeSVCDriverTestCase(test.TestCase):
             self.driver = StorwizeSVCFakeDriver(configuration=configuration)
             self.driver.set_fake_storage(self.sim)
         else:
-            self.flags(
-                san_ip="-1.-1.-1.-1",
-                san_login="user",
-                san_password="password",
-                storwize_svc_volpool_name="pool",
-            )
+            self._def_flags = {'san_ip': '1.111.11.11',
+                               'san_login': 'user',
+                               'san_password': 'password',
+                               'storwize_svc_volpool_name': 'openstack',
+                               'storwize_svc_connection_protocol': 'iscsi',
+                               'storwize_svc_multipath_enabled': False,
+                               'ssh_conn_timeout': 0}
+            self._host_name = socket.gethostname()
+            self._host_ip = socket.gethostbyname(self._host_name)
+
+            self._host_wwpns = []
+            out, err = utils.execute('systool', '-c', 'fc_host', '-v',
+                                     run_as_root=True)
+            lines = out.split('\n')
+            for line in lines:
+                val = line.split('=')
+                if (len(val) == 2 and
+                    val[0].strip().replace(" ", "") == 'port_name'):
+                    self._host_wwpns.append(val[1].strip()[3:-1])
+            self.assertNotEqual(len(self._host_wwpns), 0)
+
+            lines = utils.read_file_as_root('/etc/iscsi/initiatorname.iscsi')
+            for l in lines.split('\n'):
+                if l.startswith('InitiatorName='):
+                    self._iscsi_name = l[l.index('=') + 1:].strip()
+
+            self._reset_flags()
             self.driver = storwize_svc.StorwizeSVCDriver()
 
+        self.driver.db = StorwizeSVCFakeDB()
         self.driver.do_setup(None)
         self.driver.check_for_setup_error()
         self.stubs.Set(storwize_svc.time, 'sleep', lambda s: None)
 
-    def test_storwize_svc_volume_tests(self):
-        self.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)
-
-        if self.USESIM == 1:
-            self.flags(storwize_svc_vol_rsize="2%")
-            self.flags(storwize_svc_vol_compression=True)
-            self.driver.create_volume(volume)
-            is_volume_defined = self.driver._is_volume_defined(volume["name"])
-            self.assertEqual(is_volume_defined, True)
-            self.driver.delete_volume(volume)
-            FLAGS.reset()
-
-    def test_storwize_svc_ip_connectivity(self):
-        # Check for missing san_ip
-        self.flags(san_ip=None)
-        self.assertRaises(exception.InvalidInput,
-                          self.driver._check_flags)
-
-        if self.USESIM != 1:
-            # Check for invalid ip
-            self.flags(san_ip="-1.-1.-1.-1")
-            self.assertRaises(socket.gaierror,
-                              self.driver.check_for_setup_error)
+    def _reset_flags(self):
+        FLAGS.reset()
+        self.flags(**self._def_flags)
 
-            # Check for unreachable IP
-            self.flags(san_ip="1.1.1.1")
-            self.assertRaises(socket.error,
-                              self.driver.check_for_setup_error)
+    def _assert_vol_exists(self, name, exists):
+        is_vol_defined = self.driver._is_vdisk_defined(name)
+        self.assertEqual(is_vol_defined, exists)
 
     def test_storwize_svc_connectivity(self):
         # Make sure we detect if the pool doesn't exist
-        no_exist_pool = "i-dont-exist-%s" % random.randint(10000, 99999)
+        no_exist_pool = 'i-dont-exist-%s' % random.randint(10000, 99999)
         self.flags(storwize_svc_volpool_name=no_exist_pool)
         self.assertRaises(exception.InvalidInput,
-                          self.driver.check_for_setup_error)
-        FLAGS.reset()
+                          self.driver.do_setup, None)
+        self._reset_flags()
 
         # Check the case where the user didn't configure IP addresses
         # as well as receiving unexpected results from the storage
-        if self.USESIM == 1:
-            self.sim.error_injection("lsnodecanister", "header_mismatch")
+        if self.USESIM:
+            self.sim.error_injection('lsnodecanister', 'header_mismatch')
             self.assertRaises(exception.VolumeBackendAPIException,
-                              self.driver.check_for_setup_error)
-            self.sim.error_injection("lsnodecanister", "remove_field")
+                              self.driver.do_setup, None)
+            self.sim.error_injection('lsnodecanister', 'remove_field')
             self.assertRaises(exception.VolumeBackendAPIException,
-                              self.driver.check_for_setup_error)
-            self.sim.error_injection("lsportip", "ip_no_config")
+                              self.driver.do_setup, None)
+
+            self.sim.error_injection('lsportip', 'ip_no_config')
+            self.sim.error_injection('lsportfc', 'fc_no_config')
             self.assertRaises(exception.VolumeBackendAPIException,
-                              self.driver.check_for_setup_error)
-            self.sim.error_injection("lsportip", "header_mismatch")
+                              self.driver.do_setup, None)
+            self.sim.error_injection('lsportip', 'header_mismatch')
             self.assertRaises(exception.VolumeBackendAPIException,
-                              self.driver.check_for_setup_error)
-            self.sim.error_injection("lsportip", "remove_field")
+                              self.driver.do_setup, None)
+            self.sim.error_injection('lsportip', 'remove_field')
             self.assertRaises(exception.VolumeBackendAPIException,
-                              self.driver.check_for_setup_error)
+                              self.driver.do_setup, None)
+            self.sim.error_injection('lsportfc', 'header_mismatch')
+            self.assertRaises(exception.VolumeBackendAPIException,
+                              self.driver.do_setup, None)
+            self.sim.error_injection('lsportfc', 'remove_field')
+            self.assertRaises(exception.VolumeBackendAPIException,
+                              self.driver.do_setup, None)
 
         # Check with bad parameters
+        self.flags(san_ip='')
+        self.assertRaises(exception.InvalidInput,
+                          self.driver.check_for_setup_error)
+        self._reset_flags()
+
         self.flags(san_password=None)
         self.flags(san_private_key=None)
         self.assertRaises(exception.InvalidInput,
-                          self.driver._check_flags)
-        FLAGS.reset()
+                          self.driver.check_for_setup_error)
+        self._reset_flags()
 
-        self.flags(storwize_svc_vol_rsize="invalid")
+        self.flags(storwize_svc_vol_rsize=101)
         self.assertRaises(exception.InvalidInput,
-                          self.driver._check_flags)
-        FLAGS.reset()
+                          self.driver.check_for_setup_error)
+        self._reset_flags()
 
-        self.flags(storwize_svc_vol_warning="invalid")
+        self.flags(storwize_svc_vol_warning=101)
         self.assertRaises(exception.InvalidInput,
-                          self.driver._check_flags)
-        FLAGS.reset()
+                          self.driver.check_for_setup_error)
+        self._reset_flags()
 
-        self.flags(storwize_svc_vol_autoexpand="invalid")
+        self.flags(storwize_svc_vol_grainsize=42)
         self.assertRaises(exception.InvalidInput,
-                          self.driver._check_flags)
-        FLAGS.reset()
+                          self.driver.check_for_setup_error)
+        self._reset_flags()
 
-        self.flags(storwize_svc_vol_grainsize=str(42))
+        self.flags(storwize_svc_flashcopy_timeout=601)
         self.assertRaises(exception.InvalidInput,
-                          self.driver._check_flags)
-        FLAGS.reset()
+                          self.driver.check_for_setup_error)
+        self._reset_flags()
+
+        self.flags(storwize_svc_vol_compression=True)
+        self.flags(storwize_svc_vol_rsize=-1)
+        self.assertRaises(exception.InvalidInput,
+                          self.driver.check_for_setup_error)
+        self._reset_flags()
 
-        self.flags(storwize_svc_flashcopy_timeout=str(601))
+        self.flags(storwize_svc_connection_protocol='foo')
         self.assertRaises(exception.InvalidInput,
-                          self.driver._check_flags)
-        FLAGS.reset()
+                          self.driver.check_for_setup_error)
+        self._reset_flags()
 
-        self.flags(storwize_svc_vol_compression=True)
-        self.flags(storwize_svc_vol_rsize="-1")
+        self.flags(storwize_svc_connection_protocol='iscsi')
+        self.flags(storwize_svc_multipath_enabled=True)
         self.assertRaises(exception.InvalidInput,
-                          self.driver._check_flags)
-        FLAGS.reset()
+                          self.driver.check_for_setup_error)
+        self._reset_flags()
+
+        if self.USESIM:
+            self.sim.error_injection('lslicense', 'no_compression')
+            self.flags(storwize_svc_vol_compression=True)
+            self.driver.do_setup(None)
+            self.assertRaises(exception.InvalidInput,
+                              self.driver.check_for_setup_error)
+            self._reset_flags()
 
         # Finally, check with good parameters
-        self.driver.check_for_setup_error()
+        self.driver.do_setup(None)
 
-    def test_storwize_svc_flashcopy(self):
-        volume1 = {}
-        volume1["name"] = "test1_volume%s" % random.randint(10000, 99999)
-        volume1["size"] = 10
-        volume1["id"] = 10
-        self.driver.create_volume(volume1)
+    def _generate_vol_info(self, vol_name, vol_id):
+        rand_id = str(random.randint(10000, 99999))
+        if vol_name:
+            return {'name': 'snap_volume%s' % rand_id,
+                    'volume_name': vol_name,
+                    'id': rand_id,
+                    'volume_id': vol_id,
+                    'volume_size': 10}
+        else:
+            return {'name': 'test_volume%s' % rand_id,
+                    'size': 10,
+                    'id': '%s' % rand_id,
+                    'volume_type_id': None}
+
+    def _create_test_vol(self, opts):
+        ctxt = context.get_admin_context()
+        type_ref = volume_types.create(ctxt, 'testtype', opts)
+        volume = self._generate_vol_info(None, None)
+        volume['volume_type_id'] = type_ref['id']
+        self.driver.create_volume(volume)
 
-        snapshot = {}
-        snapshot["name"] = "snap_volume%s" % random.randint(10000, 99999)
-        snapshot["volume_name"] = volume1["name"]
+        attrs = self.driver._get_vdisk_attributes(volume['name'])
+        self.driver.delete_volume(volume)
+        volume_types.destroy(ctxt, type_ref['id'])
+        return attrs
+
+    def _fail_prepare_fc_map(self, fc_map_id, source, target):
+        raise exception.ProcessExecutionError(exit_code=1,
+                                              stdout='',
+                                              stderr='unit-test-fail',
+                                              cmd='prestartfcmap id')
+
+    def test_storwize_svc_snapshots(self):
+        vol1 = self._generate_vol_info(None, None)
+        self.driver.create_volume(vol1)
+        self.driver.db.volume_set(vol1)
+        snap1 = self._generate_vol_info(vol1['name'], vol1['id'])
 
         # Test timeout and volume cleanup
-        self.flags(storwize_svc_flashcopy_timeout=str(1))
+        self.flags(storwize_svc_flashcopy_timeout=1)
         self.assertRaises(exception.InvalidSnapshot,
-                          self.driver.create_snapshot, snapshot)
-        is_volume_defined = self.driver._is_volume_defined(snapshot["name"])
-        self.assertEqual(is_volume_defined, False)
-        FLAGS.reset()
-
-        # Test bogus statuses
-        if self.USESIM == 1:
-            self.sim.error_injection("lsfcmap", "bogus_prepare")
-            self.assertRaises(exception.VolumeBackendAPIException,
-                              self.driver.create_snapshot, snapshot)
+                          self.driver.create_snapshot, snap1)
+        self._assert_vol_exists(snap1['name'], False)
+        self._reset_flags()
 
         # Test prestartfcmap, startfcmap, and rmfcmap failing
-        if self.USESIM == 1:
-            self.sim.error_injection("prestartfcmap", "bad_id")
-            self.assertRaises(exception.ProcessExecutionError,
-                              self.driver.create_snapshot, snapshot)
-            self.sim.error_injection("lsfcmap", "speed_up")
-            self.sim.error_injection("startfcmap", "bad_id")
+        orig = self.driver._call_prepare_fc_map
+        self.driver._call_prepare_fc_map = self._fail_prepare_fc_map
+        self.assertRaises(exception.ProcessExecutionError,
+                          self.driver.create_snapshot, snap1)
+        self.driver._call_prepare_fc_map = orig
+
+        if self.USESIM:
+            self.sim.error_injection('lsfcmap', 'speed_up')
+            self.sim.error_injection('startfcmap', 'bad_id')
             self.assertRaises(exception.ProcessExecutionError,
-                              self.driver.create_snapshot, snapshot)
-            self.sim.error_injection("prestartfcmap", "bad_id")
-            self.sim.error_injection("rmfcmap", "bad_id")
+                              self.driver.create_snapshot, snap1)
+            self._assert_vol_exists(snap1['name'], False)
+            self.sim.error_injection('prestartfcmap', 'bad_id')
             self.assertRaises(exception.ProcessExecutionError,
-                              self.driver.create_snapshot, snapshot)
+                              self.driver.create_snapshot, snap1)
+            self._assert_vol_exists(snap1['name'], False)
 
         # Test successful snapshot
-        self.driver.create_snapshot(snapshot)
-
-        # Ensure snapshot is defined
-        is_volume_defined = self.driver._is_volume_defined(snapshot["name"])
-        self.assertEqual(is_volume_defined, True)
+        self.driver.create_snapshot(snap1)
+        self._assert_vol_exists(snap1['name'], True)
 
         # Try to create a snapshot from an non-existing volume - should fail
-        snapshot2 = {}
-        snapshot2["name"] = "snap_volume%s" % random.randint(10000, 99999)
-        snapshot2["volume_name"] = "undefined-vol"
+        snap_novol = self._generate_vol_info('undefined-vol', '12345')
         self.assertRaises(exception.VolumeNotFound,
                           self.driver.create_snapshot,
-                          snapshot2)
-
-        # Create volume from snapshot
-        volume2 = {}
-        volume2["name"] = "snap2vol_volume%s" % random.randint(10000, 99999)
-
-        # Create volume from snapshot into an existsing volume
-        self.assertRaises(exception.InvalidSnapshot,
-                          self.driver.create_volume_from_snapshot,
-                          volume1,
-                          snapshot)
+                          snap_novol)
+
+        # We support deleting a volume that has snapshots, so delete the volume
+        # first
+        self.driver.delete_volume(vol1)
+        self.driver.delete_snapshot(snap1)
+
+    def test_storwize_svc_create_volfromsnap_clone(self):
+        vol1 = self._generate_vol_info(None, None)
+        self.driver.create_volume(vol1)
+        self.driver.db.volume_set(vol1)
+        snap1 = self._generate_vol_info(vol1['name'], vol1['id'])
+        self.driver.create_snapshot(snap1)
+        vol2 = self._generate_vol_info(None, None)
+        vol3 = self._generate_vol_info(None, None)
 
         # Try to create a volume from a non-existing snapshot
+        snap_novol = self._generate_vol_info('undefined-vol', '12345')
+        vol_novol = self._generate_vol_info(None, None)
         self.assertRaises(exception.SnapshotNotFound,
                           self.driver.create_volume_from_snapshot,
-                          volume2,
-                          snapshot2)
+                          vol_novol,
+                          snap_novol)
 
         # Fail the snapshot
-        if self.USESIM == 1:
-            self.sim.error_injection("prestartfcmap", "bad_id")
-            self.assertRaises(exception.ProcessExecutionError,
-                              self.driver.create_volume_from_snapshot,
-                              volume2,
-                              snapshot)
+        orig = self.driver._call_prepare_fc_map
+        self.driver._call_prepare_fc_map = self._fail_prepare_fc_map
+        self.assertRaises(exception.ProcessExecutionError,
+                          self.driver.create_volume_from_snapshot,
+                          vol2, snap1)
+        self.driver._call_prepare_fc_map = orig
+        self._assert_vol_exists(vol2['name'], False)
+
+        # Try to create where source size != target size
+        vol2['size'] += 1
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume_from_snapshot,
+                          vol2, snap1)
+        self._assert_vol_exists(vol2['name'], False)
+        vol2['size'] -= 1
 
         # Succeed
-        if self.USESIM == 1:
-            self.sim.error_injection("lsfcmap", "speed_up")
-        self.driver.create_volume_from_snapshot(volume2, snapshot)
-
-        # Ensure volume is defined
-        is_volume_defined = self.driver._is_volume_defined(volume2["name"])
-        self.assertEqual(is_volume_defined, True)
-
-        self.driver._delete_volume(volume2, True)
-        self.driver._delete_snapshot(snapshot, True)
-
-        # Check with target with different size
-        volume3 = {}
-        volume3["name"] = "test3_volume%s" % random.randint(10000, 99999)
-        volume3["size"] = 11
-        volume3["id"] = 11
-        self.driver.create_volume(volume3)
-        snapshot["name"] = volume3["name"]
-        self.assertRaises(exception.InvalidSnapshot,
-                          self.driver.create_snapshot,
-                          snapshot)
-        self.driver._delete_volume(volume1, True)
-        self.driver._delete_volume(volume3, True)
-
-        # Snapshot volume that doesn't exist
-        snapshot = {}
-        snapshot["name"] = "snap_volume%s" % random.randint(10000, 99999)
-        snapshot["volume_name"] = "no_exist"
-        self.assertRaises(exception.VolumeNotFound,
-                          self.driver.create_snapshot,
-                          snapshot)
+        if self.USESIM:
+            self.sim.error_injection('lsfcmap', 'speed_up')
+        self.driver.create_volume_from_snapshot(vol2, snap1)
+        self._assert_vol_exists(vol2['name'], True)
+
+        # Try to clone where source size != target size
+        vol3['size'] += 1
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_cloned_volume,
+                          vol3, vol2)
+        self._assert_vol_exists(vol3['name'], False)
+        vol3['size'] -= 1
+
+        if self.USESIM:
+            self.sim.error_injection('lsfcmap', 'speed_up')
+        self.driver.create_cloned_volume(vol3, vol2)
+        self._assert_vol_exists(vol3['name'], True)
+
+        # Delete in the 'opposite' order to make sure it works
+        self.driver.delete_volume(vol3)
+        self._assert_vol_exists(vol3['name'], False)
+        self.driver.delete_volume(vol2)
+        self._assert_vol_exists(vol2['name'], False)
+        self.driver.delete_snapshot(snap1)
+        self._assert_vol_exists(snap1['name'], False)
+        self.driver.delete_volume(vol1)
+        self._assert_vol_exists(vol1['name'], False)
 
     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
-
+        volume = self._generate_vol_info(None, None)
         self.driver.create_volume(volume)
 
         self.driver.ensure_export(None, volume)
@@ -1216,11 +1552,11 @@ class StorwizeSVCDriverTestCase(test.TestCase):
         self.driver.remove_export(None, volume)
 
         # 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"]))
+        attributes = self.driver._get_vdisk_attributes(volume['name'])
+        attr_size = float(attributes['capacity']) / (1024 ** 3)  # bytes to GB
+        self.assertEqual(attr_size, float(volume['size']))
         pool = storwize_svc.FLAGS.storwize_svc_volpool_name
-        self.assertEqual(attributes["mdisk_grp_name"], pool)
+        self.assertEqual(attributes['mdisk_grp_name'], pool)
 
         # Try to create the volume again (should fail)
         self.assertRaises(exception.ProcessExecutionError,
@@ -1228,7 +1564,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
                           volume)
 
         # Try to delete a volume that doesn't exist (should not fail)
-        vol_no_exist = {"name": "i_dont_exist"}
+        vol_no_exist = {'name': 'i_dont_exist'}
         self.driver.delete_volume(vol_no_exist)
         # Ensure export for volume that doesn't exist (should not fail)
         self.driver.ensure_export(None, vol_no_exist)
@@ -1236,24 +1572,13 @@ class StorwizeSVCDriverTestCase(test.TestCase):
         # Delete the volume
         self.driver.delete_volume(volume)
 
-    def _create_test_vol(self):
-        volume = {}
-        volume["name"] = "testparam_volume%s" % random.randint(10000, 99999)
-        volume["size"] = 1
-        volume["id"] = 1
-        self.driver.create_volume(volume)
-
-        attrs = self.driver._get_volume_attributes(volume["name"])
-        self.driver.delete_volume(volume)
-        return attrs
-
     def test_storwize_svc_volume_params(self):
         # Option test matrix
         # Option        Value   Covered by test #
         # rsize         -1      1
-        # rsize         2%      2,3
+        # rsize         2       2,3
         # warning       0       2
-        # warning       80%     3
+        # warning       80      3
         # autoexpand    True    2
         # autoexpand    False   3
         # grainsize     32      2
@@ -1263,138 +1588,177 @@ class StorwizeSVCDriverTestCase(test.TestCase):
         # easytier      True    1,3
         # easytier      False   2
 
-        # Test 1
-        self.flags(storwize_svc_vol_rsize="-1")
-        self.flags(storwize_svc_vol_easytier=True)
-        attrs = self._create_test_vol()
-        self.assertEquals(attrs["free_capacity"], "0")
-        self.assertEquals(attrs["easy_tier"], "on")
-        FLAGS.reset()
-
-        # Test 2
-        self.flags(storwize_svc_vol_rsize="2%")
-        self.flags(storwize_svc_vol_compression=False)
-        self.flags(storwize_svc_vol_warning="0")
-        self.flags(storwize_svc_vol_autoexpand=True)
-        self.flags(storwize_svc_vol_grainsize="32")
-        self.flags(storwize_svc_vol_easytier=False)
-        attrs = self._create_test_vol()
-        self.assertNotEqual(attrs["capacity"], attrs["real_capacity"])
-        self.assertEquals(attrs["compressed_copy"], "no")
-        self.assertEquals(attrs["warning"], "0")
-        self.assertEquals(attrs["autoexpand"], "on")
-        self.assertEquals(attrs["grainsize"], "32")
-        self.assertEquals(attrs["easy_tier"], "off")
-        FLAGS.reset()
-
-        # Test 3
-        self.flags(storwize_svc_vol_rsize="2%")
-        self.flags(storwize_svc_vol_compression=False)
-        self.flags(storwize_svc_vol_warning="80%")
-        self.flags(storwize_svc_vol_autoexpand=False)
-        self.flags(storwize_svc_vol_grainsize="256")
-        self.flags(storwize_svc_vol_easytier=True)
-        attrs = self._create_test_vol()
-        self.assertNotEqual(attrs["capacity"], attrs["real_capacity"])
-        self.assertEquals(attrs["compressed_copy"], "no")
-        self.assertEquals(attrs["warning"], "80")
-        self.assertEquals(attrs["autoexpand"], "off")
-        self.assertEquals(attrs["grainsize"], "256")
-        self.assertEquals(attrs["easy_tier"], "on")
-        FLAGS.reset()
-
-        # Test 4
-        self.flags(storwize_svc_vol_rsize="2%")
-        self.flags(storwize_svc_vol_compression=True)
-        try:
-            attrs = self._create_test_vol()
-            self.assertNotEqual(attrs["capacity"], attrs["real_capacity"])
-            self.assertEquals(attrs["compressed_copy"], "yes")
-        except exception.ProcessExecutionError as e:
-            if "CMMVC7050E" not in e.stderr:
-                raise exception.ProcessExecutionError(exit_code=e.exit_code,
-                                                      stdout=e.stdout,
-                                                      stderr=e.stderr,
-                                                      cmd=e.cmd)
-        if self.USESIM == 1:
-            self.sim.error_injection("mkvdisk", "no_compression")
-            self.assertRaises(exception.ProcessExecutionError,
-                              self._create_test_vol)
-        FLAGS.reset()
+        opts_list = []
+        chck_list = []
+        opts_list.append({'rsize': -1, 'easytier': True})
+        chck_list.append({'free_capacity': '0', 'easy_tier': 'on'})
+        opts_list.append({'rsize': 2, 'compression': False, 'warning': 0,
+                          'autoexpand': True, 'grainsize': 32,
+                          'easytier': False})
+        chck_list.append({'-free_capacity': '0', 'compressed_copy': 'no',
+                          'warning': '0', 'autoexpand': 'on',
+                          'grainsize': '32', 'easy_tier': 'off'})
+        opts_list.append({'rsize': 2, 'compression': False, 'warning': 80,
+                          'autoexpand': False, 'grainsize': 256,
+                          'easytier': True})
+        chck_list.append({'-free_capacity': '0', 'compressed_copy': 'no',
+                          'warning': '80', 'autoexpand': 'off',
+                          'grainsize': '256', 'easy_tier': 'on'})
+        opts_list.append({'rsize': 2, 'compression': True})
+        chck_list.append({'-free_capacity': '0',
+                          'compressed_copy': 'yes'})
+
+        for idx in range(len(opts_list)):
+            attrs = self._create_test_vol(opts_list[idx])
+            for k, v in chck_list[idx].iteritems():
+                print k + ' ' + v
+                try:
+                    if k[0] == '-':
+                        k = k[1:]
+                        self.assertNotEqual(attrs[k], v)
+                    else:
+                        self.assertEqual(attrs[k], v)
+                except exception.ProcessExecutionError as e:
+                    if 'CMMVC7050E' not in e.stderr:
+                        raise e
 
     def test_storwize_svc_unicode_host_and_volume_names(self):
-        volume1 = {}
-        volume1["name"] = u"unicode1_volume%s" % random.randint(10000, 99999)
-        volume1["size"] = 2
-        volume1["id"] = 1
+        # We'll check with iSCSI only - nothing protocol-dependednt here
+        self.flags(storwize_svc_connection_protocol='iscsi')
+        self.driver.do_setup(None)
+
+        rand_id = random.randint(10000, 99999)
+        volume1 = {'name': u'unicode1_volume%s' % rand_id,
+                   'size': 2,
+                   'id': 1,
+                   'volume_type_id': None}
         self.driver.create_volume(volume1)
-        # Make sure that the volumes have been created
-        is_volume_defined = self.driver._is_volume_defined(volume1["name"])
-        self.assertEqual(is_volume_defined, True)
-        conn = {}
-        conn["initiator"] = u"unicode:init:%s" % random.randint(10000, 99999)
-        conn["ip"] = "10.10.10.10"  # Bogus ip for testing
+        self._assert_vol_exists(volume1['name'], True)
+
+        self.assertRaises(exception.NoValidHost,
+                          self.driver._connector_to_hostname_prefix,
+                          {'host': 12345})
+
+        # Add a a host first to make life interesting (this host and
+        # conn['host'] should be translated to the same prefix, and the
+        # initiator should differentiate
+        tmpconn1 = {'initiator': u'unicode:initiator1.%s' % rand_id,
+                    'ip': '10.10.10.10',
+                    'host': u'unicode.foo}.bar{.baz-%s' % rand_id}
+        self.driver._create_host(tmpconn1)
+
+        # Add a host with a different prefix
+        tmpconn2 = {'initiator': u'unicode:initiator2.%s' % rand_id,
+                    'ip': '10.10.10.11',
+                    'host': u'unicode.hello.world-%s' % rand_id}
+        self.driver._create_host(tmpconn2)
+
+        conn = {'initiator': u'unicode:initiator3.%s' % rand_id,
+                'ip': '10.10.10.12',
+                'host': u'unicode.foo}.bar}.baz-%s' % rand_id}
         self.driver.initialize_connection(volume1, conn)
+        host_name = self.driver._get_host_from_connector(conn)
+        self.assertNotEqual(host_name, None)
         self.driver.terminate_connection(volume1, conn)
+        host_name = self.driver._get_host_from_connector(conn)
+        self.assertEqual(host_name, None)
         self.driver.delete_volume(volume1)
 
+        # Clean up temporary hosts
+        for tmpconn in [tmpconn1, tmpconn2]:
+            host_name = self.driver._get_host_from_connector(tmpconn)
+            self.assertNotEqual(host_name, None)
+            self.driver._delete_host(host_name)
+
     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
+
+        ctxt = context.get_admin_context()
+        volume1 = self._generate_vol_info(None, None)
         self.driver.create_volume(volume1)
-        volume2 = {}
-        volume2["name"] = "test2_volume%s" % random.randint(10000, 99999)
-        volume2["size"] = 2
-        volume2["id"] = 1
+        volume2 = self._generate_vol_info(None, None)
         self.driver.create_volume(volume2)
 
-        # Check case where no hosts exist
-        if self.USESIM == 1:
-            ret = self.driver._get_host_from_iscsiname("foo")
-            self.assertEquals(ret, None)
-            ret = self.driver._is_host_defined("foo")
-            self.assertEquals(ret, False)
-
-        # 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)
+        # Create volume types that we created
+        types = {}
+        for protocol in ['fc', 'iscsi']:
+            opts = {'protocol': protocol}
+            types[protocol] = volume_types.create(ctxt, protocol, opts)
+
+        conn = {'initiator': self._iscsi_name,
+                'ip': self._host_ip,
+                'host': self._host_name,
+                'wwpns': self._host_wwpns}
+
+        for protocol in ['fc', 'iscsi']:
+            volume1['volume_type_id'] = types[protocol]['id']
+            volume2['volume_type_id'] = types[protocol]['id']
+
+            # Check case where no hosts exist
+            ret = self.driver._get_host_from_connector(conn)
+            self.assertEqual(ret, None)
+
+            # Make sure that the volumes have been created
+            self._assert_vol_exists(volume1['name'], True)
+            self._assert_vol_exists(volume2['name'], True)
+
+            # Check bad output from lsfabric
+            if protocol == 'fc' and self.USESIM:
+                for error in ['remove_field', 'header_mismatch']:
+                    self.sim.error_injection('lsfabric', error)
+                    self.assertRaises(exception.VolumeBackendAPIException,
+                                      self.driver.initialize_connection,
+                                      volume1, conn)
+                    host_name = self.driver._get_host_from_connector(conn)
+                    self.assertEqual(host_name, None)
+
+            # Initialize connection from the first volume to a host
+            self.driver.initialize_connection(volume1, conn)
 
-        # Initialize again, should notice it and do nothing
-        self.driver.initialize_connection(volume1, conn)
+            # Initialize again, should notice it and do nothing
+            self.driver.initialize_connection(volume1, conn)
 
-        # Try to delete the 1st volume (should fail because it is mapped)
-        self.assertRaises(exception.ProcessExecutionError,
-                          self.driver.delete_volume,
-                          volume1)
+            # Try to delete the 1st volume (should fail because it is mapped)
+            self.assertRaises(exception.ProcessExecutionError,
+                              self.driver.delete_volume,
+                              volume1)
+
+            self.driver.terminate_connection(volume1, conn)
+
+        # Check cases with no auth set for host
+        if self.USESIM:
+            for case in ['no_info', 'no_auth_set']:
+                conn_na = {'initiator': 'test:init:%s' %
+                                        random.randint(10000, 99999),
+                           'ip': '11.11.11.11',
+                           'host': 'host-%s' % case}
+                self.sim._add_host_to_list(conn_na)
+                volume1['volume_type_id'] = types['iscsi']['id']
+                if case == 'no_info':
+                    self.sim.error_injection('lsiscsiauth', 'no_info')
+                self.driver.initialize_connection(volume1, conn_na)
+                ret = self.driver._get_chap_secret_for_host(conn_na['host'])
+                self.assertNotEqual(ret, None)
+                self.driver.terminate_connection(volume1, conn_na)
 
         # Test no preferred node
-        self.driver.terminate_connection(volume1, conn)
-        if self.USESIM == 1:
-            self.sim.error_injection("lsvdisk", "no_pref_node")
-            self.driver.initialize_connection(volume1, conn)
+        if self.USESIM:
+            self.sim.error_injection('lsvdisk', 'no_pref_node')
+            self.assertRaises(exception.VolumeBackendAPIException,
+                              self.driver.initialize_connection,
+                              volume1, conn)
 
         # Initialize connection from the second volume to the host with no
         # preferred node set if in simulation mode, otherwise, just
         # another initialize connection.
-        if self.USESIM == 1:
-            self.sim.error_injection("lsvdisk", "blank_pref_node")
+        if self.USESIM:
+            self.sim.error_injection('lsvdisk', 'blank_pref_node')
         self.driver.initialize_connection(volume2, conn)
 
         # Try to remove connection from host that doesn't exist (should fail)
-        conn_no_exist = {"initiator": "i_dont_exist"}
+        conn_no_exist = conn.copy()
+        conn_no_exist['initiator'] = 'i_dont_exist'
+        conn_no_exist['wwpns'] = ['i_dont_exist']
         self.assertRaises(exception.VolumeBackendAPIException,
                           self.driver.terminate_connection,
                           volume1,
@@ -1402,29 +1766,124 @@ class StorwizeSVCDriverTestCase(test.TestCase):
 
         # Try to remove connection from volume that isn't mapped (should print
         # message but NOT fail)
-        vol_no_exist = {"name": "i_dont_exist"}
+        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)
+        self._assert_vol_exists(volume1['name'], 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)
+        host_name = self.driver._get_host_from_connector(conn)
+        self.assertNotEqual(host_name, None)
 
         # 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)
+        self._assert_vol_exists(volume2['name'], False)
+
+        # Delete volume types that we created
+        for protocol in ['fc', 'iscsi']:
+            volume_types.destroy(ctxt, types[protocol]['id'])
 
         # 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)
+        ret = self.driver._get_host_from_connector(conn)
+        self.assertEqual(ret, None)
+
+    def test_storwize_svc_delete_volume_snapshots(self):
+        # Create a volume with two snapshots
+        master = self._generate_vol_info(None, None)
+        self.driver.create_volume(master)
+        self.driver.db.volume_set(master)
+
+        # Fail creating a snapshot - will force delete the snapshot
+        if self.USESIM and False:
+            snap = self._generate_vol_info(master['name'], master['id'])
+            self.sim.error_injection('startfcmap', 'bad_id')
+            self.assertRaises(exception.ProcessExecutionError,
+                              self.driver.create_snapshot, snap)
+            self._assert_vol_exists(snap['name'], False)
+
+        # Delete a snapshot
+        snap = self._generate_vol_info(master['name'], master['id'])
+        self.driver.create_snapshot(snap)
+        self._assert_vol_exists(snap['name'], True)
+        self.driver.delete_snapshot(snap)
+        self._assert_vol_exists(snap['name'], False)
+
+        # Delete a volume with snapshots (regular)
+        snap = self._generate_vol_info(master['name'], master['id'])
+        self.driver.create_snapshot(snap)
+        self._assert_vol_exists(snap['name'], True)
+        self.driver.delete_volume(master)
+        self._assert_vol_exists(master['name'], False)
+
+        # Fail create volume from snapshot - will force delete the volume
+        if self.USESIM:
+            volfs = self._generate_vol_info(None, None)
+            self.sim.error_injection('startfcmap', 'bad_id')
+            self.sim.error_injection('lsfcmap', 'speed_up')
+            self.assertRaises(exception.ProcessExecutionError,
+                              self.driver.create_volume_from_snapshot,
+                              volfs, snap)
+            self._assert_vol_exists(volfs['name'], False)
+
+        # Create volume from snapshot and delete it
+        volfs = self._generate_vol_info(None, None)
+        if self.USESIM:
+            self.sim.error_injection('lsfcmap', 'speed_up')
+        self.driver.create_volume_from_snapshot(volfs, snap)
+        self._assert_vol_exists(volfs['name'], True)
+        self.driver.delete_volume(volfs)
+        self._assert_vol_exists(volfs['name'], False)
+
+        # Create volume from snapshot and delete the snapshot
+        volfs = self._generate_vol_info(None, None)
+        if self.USESIM:
+            self.sim.error_injection('lsfcmap', 'speed_up')
+        self.driver.create_volume_from_snapshot(volfs, snap)
+        self.driver.delete_snapshot(snap)
+        self._assert_vol_exists(snap['name'], False)
+
+        # Fail create clone - will force delete the target volume
+        if self.USESIM:
+            clone = self._generate_vol_info(None, None)
+            self.sim.error_injection('startfcmap', 'bad_id')
+            self.sim.error_injection('lsfcmap', 'speed_up')
+            self.assertRaises(exception.ProcessExecutionError,
+                              self.driver.create_cloned_volume,
+                              clone, volfs)
+            self._assert_vol_exists(clone['name'], False)
+
+        # Create the clone, delete the source and target
+        clone = self._generate_vol_info(None, None)
+        if self.USESIM:
+            self.sim.error_injection('lsfcmap', 'speed_up')
+        self.driver.create_cloned_volume(clone, volfs)
+        self._assert_vol_exists(clone['name'], True)
+        self.driver.delete_volume(volfs)
+        self._assert_vol_exists(volfs['name'], False)
+        self.driver.delete_volume(clone)
+        self._assert_vol_exists(clone['name'], False)
+
+    # Note defined in python 2.6, so define here...
+    def assertLessEqual(self, a, b, msg=None):
+        if not a <= b:
+            self.fail('%s not less than or equal to %s' % (repr(a), repr(b)))
+
+    def test_storwize_svc_get_volume_stats(self):
+        #first call,  no refresh, expect None
+        stats = self.driver.get_volume_stats()
+        self.assertEqual(stats, {})
+
+        #call with refresh
+        stats = self.driver.get_volume_stats(refresh=True)
+        self.assertLessEqual(stats['free_capacity_gb'],
+                             stats['total_capacity_gb'])
+        if self.USESIM:
+            self.assertEqual(stats['volume_backend_name'],
+                             'storwize-svc-sim_volpool')
+            self.assertAlmostEqual(stats['total_capacity_gb'], 3328.0)
+            self.assertAlmostEqual(stats['free_capacity_gb'], 3287.5)
index 96391f0288f2179ba2d2ed3b412b98039db855d4..e4888b504a31c14e39ca9f7edac12401b77c74ec 100644 (file)
@@ -21,6 +21,7 @@ Drivers for volumes.
 """
 
 import os
+import socket
 import time
 
 from cinder import exception
@@ -342,9 +343,8 @@ class ISCSIDriver(VolumeDriver):
     def copy_image_to_volume(self, context, volume, image_service, image_id):
         """Fetch the image from image_service and write it to the volume."""
         LOG.debug(_('copy_image_to_volume %s.') % volume['name'])
-        initiator = self._get_iscsi_initiator()
-        connector = {}
-        connector['initiator'] = initiator
+        connector = {'initiator': self._get_iscsi_initiator(),
+                     'host': socket.gethostname()}
 
         iscsi_properties, volume_path = self._attach_volume(
             context, volume, connector)
@@ -360,9 +360,8 @@ class ISCSIDriver(VolumeDriver):
     def copy_volume_to_image(self, context, volume, image_service, image_meta):
         """Copy the volume to the specified image."""
         LOG.debug(_('copy_volume_to_image %s.') % volume['name'])
-        initiator = self._get_iscsi_initiator()
-        connector = {}
-        connector['initiator'] = initiator
+        connector = {'initiator': self._get_iscsi_initiator(),
+                     'host': socket.gethostname()}
 
         iscsi_properties, volume_path = self._attach_volume(
             context, volume, connector)
index 7afcb5d2e9af0785697aa8c5b2be26834e8eff8b..82f763e5866539288f4f1926c6d5fe7edcfcea7b 100644 (file)
@@ -1,7 +1,7 @@
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
 
-# Copyright (c) 2012 IBM, Inc.
-# Copyright (c) 2012 OpenStack LLC.
+# Copyright 2012, 2013 IBM Corp
+# Copyright 2012 OpenStack LLC.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -21,7 +21,7 @@
 #   Avishay Traeger <avishay@il.ibm.com>
 
 """
-Volume driver for IBM Storwize V7000 and SVC storage systems.
+Volume driver for IBM Storwize family and SVC storage systems.
 
 Notes:
 1. If you specify both a password and a key file, this driver will use the
@@ -33,10 +33,11 @@ Notes:
    file or by using volume types(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
+1. The driver expects CLI output in English, error messages may be in a
    localized format.
+2. Clones and creating volumes from snapshots, where the source and target
+   are of different sizes, is not supported.
+
 """
 
 import random
@@ -44,6 +45,7 @@ import re
 import string
 import time
 
+from cinder import context
 from cinder import exception
 from cinder import flags
 from cinder.openstack.common import cfg
@@ -51,25 +53,29 @@ from cinder.openstack.common import excutils
 from cinder.openstack.common import log as logging
 from cinder import utils
 from cinder.volume.drivers.san import san
+from cinder.volume import volume_types
 
+VERSION = 1.1
 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_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.IntOpt('storwize_svc_vol_rsize',
+               default=2,
+               help='Storage system space-efficiency parameter for volumes '
+                    '(percentage)'),
+    cfg.IntOpt('storwize_svc_vol_warning',
+               default=0,
+               help='Storage system threshold for volume capacity warnings '
+                    '(percentage)'),
     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',
+    cfg.IntOpt('storwize_svc_vol_grainsize',
+               default=256,
                help='Storage system grain size parameter for volumes '
                     '(32/64/128/256)'),
     cfg.BoolOpt('storwize_svc_vol_compression',
@@ -78,30 +84,50 @@ storwize_svc_opts = [
     cfg.BoolOpt('storwize_svc_vol_easytier',
                 default=True,
                 help='Enable Easy Tier for volumes'),
-    cfg.StrOpt('storwize_svc_flashcopy_timeout',
-               default='120',
+    cfg.IntOpt('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).'), ]
+                    'prepared. Maximum value is 600 seconds (10 minutes).'),
+    cfg.StrOpt('storwize_svc_connection_protocol',
+               default='iscsi',
+               help='Connection protocol (iscsi/fc)'),
+    cfg.BoolOpt('storwize_svc_multipath_enabled',
+                default=False,
+                help='Connect with multipath (currently FC-only)'),
+]
 
 FLAGS = flags.FLAGS
 FLAGS.register_opts(storwize_svc_opts)
 
 
 class StorwizeSVCDriver(san.SanISCSIDriver):
-    """IBM Storwize V7000 and SVC iSCSI volume driver."""
+    """IBM Storwize V7000 and SVC iSCSI/FC volume driver.
+
+    Version history:
+    1.0 - Initial driver
+    1.1 - FC support, create_cloned_volume, volume type support,
+          get_volume_stats, minor bug fixes
+
+    """
+
+    """====================================================================="""
+    """ SETUP                                                               """
+    """====================================================================="""
 
     def __init__(self, *args, **kwargs):
         super(StorwizeSVCDriver, self).__init__(*args, **kwargs)
-        self.iscsi_ipv4_conf = None
-        self.iscsi_ipv6_conf = None
+        self._storage_nodes = {}
+        self._enabled_protocols = set()
+        self._supported_protocols = ['iscsi', 'fc']
+        self._compression_enabled = False
+        self._context = None
 
-        # Build cleanup transaltion tables for hosts names to follow valid
-        # host names for Storwizew V7000 and SVC storage systems.
+        # Build cleanup translation tables for host names
         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 != '_')):
+            ch = str(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))
@@ -109,222 +135,152 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
         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.
+    def _get_iscsi_ip_addrs(self):
+        generator = self._port_conf_generator('lsportip')
+        header = next(generator, None)
+        if not header:
+            return
 
-        Create a dictionary object from the data row string using the header
-        string. The strings are converted to columns using the delimiter in
-        delim.
-        """
+        for port_data in generator:
+            try:
+                port_node_id = port_data['node_id']
+                port_ipv4 = port_data['IP_address']
+                port_ipv6 = port_data['IP_address_6']
+                state = port_data['state']
+            except KeyError:
+                self._handle_keyerror('lsportip', header)
+
+            if port_node_id in self._storage_nodes and (
+                    state == 'configured' or state == 'online'):
+                node = self._storage_nodes[port_node_id]
+                if len(port_ipv4):
+                    node['ipv4'].append(port_ipv4)
+                if len(port_ipv6):
+                    node['ipv6'].append(port_ipv6)
 
-        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 _get_fc_wwpns(self):
+        generator = self._port_conf_generator('lsportfc')
+        header = next(generator, None)
+        if not header:
+            return
 
-    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)
+        for port_data in generator:
+            try:
+                port_node_id = port_data['node_id']
+                wwpn = port_data['WWPN']
+                status = port_data['status']
+            except KeyError as e:
+                self._handle_keyerror('lsportfc', header)
 
-    def check_for_setup_error(self):
+            if (port_node_id in self._storage_nodes and
+                'unconfigured' not in status):
+                node = self._storage_nodes[port_node_id]
+                if len(wwpn) and wwpn not in node['WWPN']:
+                    node['WWPN'].append(wwpn)
+
+    def do_setup(self, ctxt):
         """Check that we have all configuration details from the storage."""
 
-        LOG.debug(_('enter: check_for_setup_error'))
+        LOG.debug(_('enter: do_setup'))
+        self._context = ctxt
 
         # 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)})
+        self._assert_ssh_return(len(out.strip()), 'do_setup',
+                                ssh_cmd, out, err)
         search_text = '!%s!' % FLAGS.storwize_svc_volpool_name
         if search_text not in out:
             raise exception.InvalidInput(
                 reason=(_('pool %s doesn\'t exist')
                         % FLAGS.storwize_svc_volpool_name))
 
-        storage_nodes = {}
-        # Get the iSCSI names of the Storwize/SVC nodes
+        # Check if compression is supported
+        ssh_cmd = 'lslicense -delim !'
+        out, err = self._run_ssh(ssh_cmd)
+        license_lines = out.strip().split('\n')
+        self._compression_enabled = False
+        for license_line in license_lines:
+            name, foo, value = license_line.partition('!')
+            if ((name == "license_compression_enclosures" and value != '0') or
+                (name == "license_compression_capacity" and value != '0')):
+                self._compression_enabled = True
+
+        # Get the iSCSI and FC names of the Storwize/SVC nodes
         ssh_cmd = 'svcinfo lsnode -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)})
+        self._assert_ssh_return(len(out.strip()), 'do_setup',
+                                ssh_cmd, out, 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)})
+        self._assert_ssh_return(len(nodes),
+                                'do_setup', ssh_cmd, out, 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:
+            except exception.VolumeBackendAPIException:
                 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)})
+                    self._log_cli_output_error('do_setup',
+                                               ssh_cmd, out, err)
             node = {}
             try:
                 node['id'] = node_data['id']
                 node['name'] = node_data['name']
+                node['IO_group'] = node_data['IO_group_id']
                 node['iscsi_name'] = node_data['iscsi_name']
+                node['WWNN'] = node_data['WWNN']
                 node['status'] = node_data['status']
+                node['WWPN'] = []
                 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 '
-                            'svcinfo lsnode: %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
+                node['enabled_protocols'] = []
+                if node['status'] == 'online':
+                    self._storage_nodes[node['id']] = node
+            except KeyError:
+                self._handle_keyerror('lsnode', header)
+
+        # Get the iSCSI IP addresses and WWPNs of the Storwize/SVC nodes
+        self._get_iscsi_ip_addrs()
+        self._get_fc_wwpns()
+
+        # For each node, check what connection modes it supports.  Delete any
+        # nodes that do not support any types (may be partially configured).
+        to_delete = []
+        for k, node in self._storage_nodes.iteritems():
+            if ((len(node['ipv4']) or len(node['ipv6']))
+                    and len(node['iscsi_name'])):
+                node['enabled_protocols'].append('iscsi')
+                self._enabled_protocols.add('iscsi')
+            if len(node['WWPN']):
+                node['enabled_protocols'].append('fc')
+                self._enabled_protocols.add('fc')
+            if not len(node['enabled_protocols']):
+                to_delete.append(k)
+
+        for delkey in to_delete:
+            del self._storage_nodes[delkey]
+
+        # Make sure we have at least one node configured
+        self._driver_assert(len(self._storage_nodes),
+                            _('do_setup: No configured nodes'))
 
-        LOG.debug(_('leave: check_for_setup_error'))
+        LOG.debug(_('leave: do_setup'))
 
-    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 _build_default_opts(self):
+        opts = {'rsize': FLAGS.storwize_svc_vol_rsize,
+                'warning': FLAGS.storwize_svc_vol_warning,
+                'autoexpand': FLAGS.storwize_svc_vol_autoexpand,
+                'grainsize': FLAGS.storwize_svc_vol_grainsize,
+                'compression': FLAGS.storwize_svc_vol_compression,
+                'easytier': FLAGS.storwize_svc_vol_easytier,
+                'protocol': FLAGS.storwize_svc_connection_protocol,
+                'multipath': FLAGS.storwize_svc_multipath_enabled}
+        return opts
 
-    def _check_flags(self):
+    def check_for_setup_error(self):
         """Ensure that the flags are set properly."""
+        LOG.debug(_('enter: check_for_setup_error'))
 
         required_flags = ['san_ip', 'san_ssh_port', 'san_login',
                           'storwize_svc_volpool_name']
@@ -339,302 +295,475 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
                          'authentication: set either san_password or '
                          'san_private_key option'))
 
-        # Check that rsize is a number or percentage
-        rsize = FLAGS.storwize_svc_vol_rsize
-        if not self._check_num_perc(rsize) and (rsize != '-1'):
-            raise exception.InvalidInput(
-                reason=_('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 = FLAGS.storwize_svc_vol_warning
-        if not self._check_num_perc(warning):
-            raise exception.InvalidInput(
-                reason=_('Illegal value specified for '
-                         'storwize_svc_vol_warning: '
-                         'set to either a number or a percentage'))
-
-        # Check that grainsize is 32/64/128/256
-        grainsize = FLAGS.storwize_svc_vol_grainsize
-        if grainsize not in ['32', '64', '128', '256']:
-            raise exception.InvalidInput(
-                reason=_('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
+        # Check that flashcopy_timeout is not more than 10 minutes
         flashcopy_timeout = FLAGS.storwize_svc_flashcopy_timeout
-        if not (flashcopy_timeout.isdigit() and int(flashcopy_timeout) > 0 and
-                int(flashcopy_timeout) <= 600):
+        if not (flashcopy_timeout > 0 and flashcopy_timeout <= 600):
             raise exception.InvalidInput(
-                reason=_('Illegal value %s specified for '
+                reason=_('Illegal value %d specified for '
                          'storwize_svc_flashcopy_timeout: '
                          'valid values are between 0 and 600')
                 % flashcopy_timeout)
 
-        # Check that rsize is set
-        volume_compression = FLAGS.storwize_svc_vol_compression
-        if ((volume_compression is True) and
-                (FLAGS.storwize_svc_vol_rsize == '-1')):
-            raise exception.InvalidInput(
-                reason=_('If compression is set to True, rsize must '
-                         'also be set (not equal to -1)'))
+        opts = self._build_default_opts()
+        self._check_vdisk_opts(opts)
 
-    def do_setup(self, context):
-        """Validate the flags."""
-        LOG.debug(_('enter: do_setup'))
-        self._check_flags()
-        LOG.debug(_('leave: do_setup'))
+        LOG.debug(_('leave: check_for_setup_error'))
 
-    def create_volume(self, volume):
-        """Create a new volume - uses the internal method."""
-        return self._create_volume(volume, units='gb')
+    """====================================================================="""
+    """ INITIALIZE/TERMINATE CONNECTIONS                                    """
+    """====================================================================="""
+
+    def ensure_export(self, ctxt, volume):
+        """Check that the volume exists on the storage.
 
-    def _create_volume(self, volume, units='gb'):
-        """Create a new volume."""
+        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_vdisk_defined(volume['name'])
+        if not volume_defined:
+            LOG.error(_('ensure_export: Volume %s not found on storage')
+                      % volume['name'])
 
-        name = volume['name']
+    def create_export(self, ctxt, volume):
         model_update = None
+        return model_update
 
-        LOG.debug(_('enter: create_volume: volume %s ') % name)
+    def remove_export(self, ctxt, volume):
+        pass
 
-        size = int(volume['size'])
+    def _add_chapsecret_to_host(self, host_name):
+        """Generate and store a randomly-generated CHAP secret for the host."""
 
-        if FLAGS.storwize_svc_vol_autoexpand is True:
-            autoex = '-autoexpand'
-        else:
-            autoex = ''
+        chap_secret = utils.generate_password()
+        ssh_cmd = ('chhost -chapsecret "%(chap_secret)s" %(host_name)s'
+                   % {'chap_secret': chap_secret, 'host_name': host_name})
+        out, err = self._run_ssh(ssh_cmd)
+        # No output should be returned from chhost
+        self._assert_ssh_return(len(out.strip()) == 0,
+                                '_add_chapsecret_to_host', ssh_cmd, out, err)
+        return chap_secret
 
-        if FLAGS.storwize_svc_vol_easytier is True:
-            easytier = '-easytier on'
-        else:
-            easytier = '-easytier off'
+    def _get_chap_secret_for_host(self, host_name):
+        """Return the CHAP secret for the given host."""
 
-        # Set space-efficient options
-        if FLAGS.storwize_svc_vol_rsize.strip() == '-1':
-            ssh_cmd_se_opt = ''
-        else:
-            ssh_cmd_se_opt = (
-                '-rsize %(rsize)s %(autoex)s -warning %(warn)s' %
-                {'rsize': FLAGS.storwize_svc_vol_rsize,
-                 'autoex': autoex,
-                 'warn': FLAGS.storwize_svc_vol_warning})
-            if FLAGS.storwize_svc_vol_compression:
-                ssh_cmd_se_opt = ssh_cmd_se_opt + ' -compressed'
-            else:
-                ssh_cmd_se_opt = ssh_cmd_se_opt + (
-                    ' -grainsize %(grain)s' %
-                    {'grain': FLAGS.storwize_svc_vol_grainsize})
+        LOG.debug(_('enter: _get_chap_secret_for_host: host name %s')
+                  % host_name)
 
-        ssh_cmd = ('mkvdisk -name %(name)s -mdiskgrp %(mdiskgrp)s '
-                   '-iogrp 0 -size %(size)s -unit '
-                   '%(unit)s %(easytier)s %(ssh_cmd_se_opt)s'
-                   % {'name': name,
-                   'mdiskgrp': FLAGS.storwize_svc_volpool_name,
-                   'size': size, 'unit': units, 'easytier': easytier,
-                   'ssh_cmd_se_opt': ssh_cmd_se_opt})
+        ssh_cmd = 'lsiscsiauth -delim !'
         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)})
+        if not len(out.strip()):
+            return None
 
-        LOG.debug(_('leave: create_volume: volume %(name)s ') % {'name': name})
+        host_lines = out.strip().split('\n')
+        self._assert_ssh_return(len(host_lines), '_get_chap_secret_for_host',
+                                ssh_cmd, out, err)
 
-    def delete_volume(self, volume):
-        self._delete_volume(volume, False)
+        header = host_lines.pop(0).split('!')
+        self._assert_ssh_return('name' in header, '_get_chap_secret_for_host',
+                                ssh_cmd, out, err)
+        self._assert_ssh_return('iscsi_auth_method' in header,
+                                '_get_chap_secret_for_host', ssh_cmd, out, err)
+        self._assert_ssh_return('iscsi_chap_secret' in header,
+                                '_get_chap_secret_for_host', ssh_cmd, out, err)
+        name_index = header.index('name')
+        method_index = header.index('iscsi_auth_method')
+        secret_index = header.index('iscsi_chap_secret')
 
-    def _delete_volume(self, volume, force_opt):
-        """Driver entry point for destroying existing volumes."""
+        chap_secret = None
+        host_found = False
+        for line in host_lines:
+            info = line.split('!')
+            if info[name_index] == host_name:
+                host_found = True
+                if info[method_index] == 'chap':
+                    chap_secret = info[secret_index]
 
-        name = volume['name']
-        LOG.debug(_('enter: delete_volume: volume %(name)s ') % {'name': name})
+        self._assert_ssh_return(host_found, '_get_chap_secret_for_host',
+                                ssh_cmd, out, err)
 
-        if force_opt:
-            force_flag = '-force'
-        else:
-            force_flag = ''
+        LOG.debug(_('leave: _get_chap_secret_for_host: host name '
+                    '%(host_name)s with secret %(chap_secret)s')
+                  % {'host_name': host_name, 'chap_secret': chap_secret})
 
-        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})
+        return chap_secret
 
-        LOG.debug(_('leave: delete_volume: volume %(name)s ') % {'name': name})
+    def _connector_to_hostname_prefix(self, connector):
+        """Translate connector info to storage system host name.
 
-    def ensure_export(self, context, volume):
-        """Check that the volume exists on the storage.
+        Translate a host's name and IP to the prefix of its hostname on the
+        storage subsystem.  We create a host name host name from the host and
+        IP address, replacing any invalid characters (at most 55 characters),
+        and adding a random 8-character suffix to avoid collisions. The total
+        length should be at most 63 characters.
 
-        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
+        host_name = connector['host']
+        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_host: Cannot clean host name. Host name '
+                    'is not unicode or string')
+            LOG.error(msg)
+            raise exception.NoValidHost(reason=msg)
 
-    def remove_export(self, context, volume):
-        pass
+        host_name = str(host_name)
+        return host_name[:55]
 
-    def initialize_connection(self, volume, connector):
-        """Perform the necessary work so that an iSCSI connection can be made.
+    def _get_host_from_connector(self, connector):
+        """List the hosts defined in the storage.
+
+        Return the host name with the given connection info, or None if there
+        is no host fitting that information.
 
-        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']
+        prefix = self._connector_to_hostname_prefix(connector)
+        LOG.debug(_('enter: _get_host_from_connector: prefix %s') % prefix)
 
-        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.'))
+        # Get list of host in the storage
+        ssh_cmd = 'lshost -delim !'
+        out, err = self._run_ssh(ssh_cmd)
 
-        chap_secret = self._get_chap_secret_for_host(host_name)
-        if chap_secret is None:
-            chap_secret = self._add_chapsecret_to_host(host_name)
+        if not len(out.strip()):
+            return None
 
-        lun_id = self._map_vol_to_host(volume_name, host_name)
+        host_lines = out.strip().split('\n')
+        self._assert_ssh_return(len(host_lines), '_get_host_from_connector',
+                                ssh_cmd, out, err)
+        header = host_lines.pop(0).split('!')
+        self._assert_ssh_return('name' in header, '_get_host_from_connector',
+                                ssh_cmd, out, err)
+        name_index = header.index('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
+        hosts = map(lambda x: x.split('!')[name_index], host_lines)
+        hostname = None
+
+        # For each host with the prefix, check connection details to verify
+        for host in hosts:
+            if not host.startswith(prefix):
+                continue
+            ssh_cmd = 'lshost -delim ! %s' % host
+            out, err = self._run_ssh(ssh_cmd)
+            self._assert_ssh_return(len(out.strip()),
+                                    '_get_host_from_connector',
+                                    ssh_cmd, out, err)
+            for attr_line in out.split('\n'):
+                # If '!' not found, return the string and two empty strings
+                attr_name, foo, attr_val = attr_line.partition('!')
+                found = False
+                if ('initiator' in connector and
+                    attr_name == 'iscsi_name' and
+                    attr_val == connector['initiator']):
+                        found = True
+                elif ('wwpns' in connector and
+                      attr_name == 'WWPN' and
+                      attr_val.lower() in
+                      map(str.lower, map(str, connector['wwpns']))):
+                        found = True
+
+                if found:
+                    hostname = host
                     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']
-        properties['auth_method'] = 'CHAP'
-        properties['auth_username'] = initiator_name
-        properties['auth_password'] = chap_secret
 
-        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)})
+            if hostname is not None:
+                break
 
-        return {'driver_volume_type': 'iscsi', 'data': properties, }
+        LOG.debug(_('leave: _get_host_from_connector: host %s') % hostname)
 
-    def terminate_connection(self, volume, connector, **kwargs):
-        """Cleanup after an iSCSI connection has been terminated.
+        return hostname
+
+    def _create_host(self, connector):
+        """Create a new host on the storage system.
+
+        We create a host name and associate it with the given connection
+        information.
 
-        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)
+        LOG.debug(_('enter: _create_host: host %s') % connector['host'])
+
+        rand_id = str(random.randint(0, 99999999)).zfill(8)
+        host_name = '%s-%s' % (self._connector_to_hostname_prefix(connector),
+                               rand_id)
+
+        # Get all port information from the connector
+        ports = []
+        if 'initiator' in connector:
+            ports.append('-iscsiname %s' % connector['initiator'])
+        if 'wwpns' in connector:
+            for wwpn in connector['wwpns']:
+                ports.append('-hbawwpn %s' % wwpn)
+
+        # When creating a host, we need one port
+        self._driver_assert(len(ports), _('_create_host: No connector ports'))
+        port1 = ports.pop(0)
+        ssh_cmd = ('mkhost -force %(port1)s -name "%(host_name)s"' %
+                   {'port1': port1, 'host_name': host_name})
+        out, err = self._run_ssh(ssh_cmd)
+        self._assert_ssh_return('successfully created' in out,
+                                '_create_host', ssh_cmd, out, err)
 
-        # 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)})
+        # Add any additional ports to the host
+        for port in ports:
+            ssh_cmd = ('addhostport -force %s %s' % (port, host_name))
+            out, err = self._run_ssh(ssh_cmd)
+
+        LOG.debug(_('leave: _create_host: host %(host)s - %(host_name)s') %
+                  {'host': connector['host'], 'host_name': host_name})
+        return host_name
+
+    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)
+
+        mappings = out.strip().split('\n')
+        if len(mappings):
+            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 %(volume_name)s to '
+                    'host %(host_name)s')
+                  % {'volume_name': volume_name, 'host_name': 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:
+            ssh_cmd = ('mkvdiskhostmap -host %(host_name)s -scsi '
+                       '%(result_lun)s %(volume_name)s' %
+                       {'host_name': host_name,
+                        'result_lun': result_lun,
+                        'volume_name': volume_name})
+            out, err = self._run_ssh(ssh_cmd)
+            self._assert_ssh_return('successfully created' in out,
+                                    '_map_vol_to_host', ssh_cmd, out, err)
+
+        LOG.debug(_('leave: _map_vol_to_host: LUN %(result_lun)s, volume '
+                    '%(volume_name)s, host %(host_name)s') %
+                  {'result_lun': result_lun,
+                   'volume_name': volume_name,
+                   'host_name': host_name})
+        return result_lun
+
+    def _delete_host(self, host_name):
+        """Delete a host on the storage system."""
+
+        LOG.debug(_('enter: _delete_host: host %s ') % host_name)
+
+        ssh_cmd = 'rmhost %s ' % host_name
+        out, err = self._run_ssh(ssh_cmd)
+        # No output should be returned from rmhost
+        self._assert_ssh_return(len(out.strip()) == 0,
+                                '_delete_host', ssh_cmd, out, err)
+
+        LOG.debug(_('leave: _delete_host: host %s ') % host_name)
+
+    def _get_conn_fc_wwpns(self, host_name):
+        wwpns = []
+        cmd = 'lsfabric -host %s' % host_name
+        generator = self._port_conf_generator(cmd)
+        header = next(generator, None)
+        if not header:
+            return wwpns
+
+        for port_data in generator:
+            try:
+                wwpns.append(port_data['local_wwpn'])
+            except KeyError as e:
+                self._handle_keyerror('lsfabric', header)
+
+        return wwpns
+
+    def initialize_connection(self, volume, connector):
+        """Perform the necessary work so that an iSCSI/FC connection can
+        be made.
+
+        To be able to create an iSCSI/FC connection from a given host to a
+        volume, we must:
+        1. Translate the given iSCSI name or WWNN to a host name
+        2. Create new host on the storage system if it does not yet exist
+        3. Map the volume to the host if it is not already done
+        4. Return the connection information for relevant nodes (in the
+           proper I/O group)
+
+        """
+
+        LOG.debug(_('enter: initialize_connection: volume %(vol)s with '
+                    'connector %(conn)s') % {'vol': str(volume),
+                                             'conn': str(connector)})
+
+        vol_opts = self._get_vdisk_params(volume['volume_type_id'])
+        host_name = connector['host']
+        volume_name = volume['name']
+
+        # Check if a host object is defined for this host name
+        host_name = self._get_host_from_connector(connector)
+        if host_name is None:
+            # Host does not exist - add a new host to Storwize/SVC
+            host_name = self._create_host(connector)
+            # Verify that create_new_host succeeded
+            self._driver_assert(
+                host_name is not None,
+                _('_create_host failed to return the host name.'))
+
+        if vol_opts['protocol'] == 'iscsi':
+            chap_secret = self._get_chap_secret_for_host(host_name)
+            if chap_secret is None:
+                chap_secret = self._add_chapsecret_to_host(host_name)
+
+        volume_attributes = self._get_vdisk_attributes(volume_name)
+        lun_id = self._map_vol_to_host(volume_name, host_name)
+
+        self._driver_assert(volume_attributes is not None,
+                            _('initialize_connection: Failed to get attributes'
+                              ' for volume %s') % volume_name)
+
+        try:
+            preferred_node = volume_attributes['preferred_node_id']
+            IO_group = volume_attributes['IO_group_id']
+        except KeyError as e:
+                LOG.error(_('Did not find expected column name in '
+                            'lsvdisk: %s') % str(e))
+                exception_msg = (_('initialize_connection: Missing volume '
+                                   'attribute for volume %s') % volume_name)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+
+        try:
+            # Get preferred node and other nodes in I/O group
+            preferred_node_entry = None
+            io_group_nodes = []
+            for k, node in self._storage_nodes.iteritems():
+                if vol_opts['protocol'] not in node['enabled_protocols']:
+                    continue
+                if node['id'] == preferred_node:
+                    preferred_node_entry = node
+                if node['IO_group'] == IO_group:
+                    io_group_nodes.append(node)
+
+            if not len(io_group_nodes):
+                exception_msg = (_('initialize_connection: No node found in '
+                                   'I/O group %(gid)s for volume %(vol)s') %
+                                 {'gid': IO_group, 'vol': volume_name})
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+
+            if not preferred_node_entry and not vol_opts['multipath']:
+                # Get 1st node in I/O group
+                preferred_node_entry = io_group_nodes[0]
+                LOG.warn(_('initialize_connection: Did not find a preferred '
+                           'node for volume %s') % volume_name)
+
+            properties = {}
+            properties['target_discovered'] = False
+            properties['target_lun'] = lun_id
+            properties['volume_id'] = volume['id']
+            if vol_opts['protocol'] == 'iscsi':
+                type_str = 'iscsi'
+                # We take the first IP address for now. Ideally, OpenStack will
+                # support iSCSI multipath for improved performance.
+                if len(preferred_node_entry['ipv4']):
+                    ipaddr = preferred_node_entry['ipv4'][0]
+                else:
+                    ipaddr = preferred_node_entry['ipv6'][0]
+                properties['target_portal'] = '%s:%s' % (ipaddr, '3260')
+                properties['target_iqn'] = preferred_node_entry['iscsi_name']
+                properties['auth_method'] = 'CHAP'
+                properties['auth_username'] = connector['initiator']
+                properties['auth_password'] = chap_secret
+            else:
+                type_str = 'fibre_channel'
+                conn_wwpns = self._get_conn_fc_wwpns(host_name)
+                if not vol_opts['multipath']:
+                    if preferred_node_entry['WWPN'] in conn_wwpns:
+                        properties['target_wwn'] = preferred_node_entry['WWPN']
+                    else:
+                        properties['target_wwn'] = conn_wwpns[0]
+                else:
+                    properties['target_wwn'] = conn_wwpns
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                self.terminate_connection(volume, connector)
+                LOG.error(_('initialize_connection: Failed to collect return '
+                            'properties for volume %(vol)s and connector '
+                            '%(conn)s.\n') % {'vol': str(volume),
+                                              'conn': str(connector)})
+
+        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': type_str, 'data': properties, }
+
+    def terminate_connection(self, volume, connector, **kwargs):
+        """Cleanup after an iSCSI connection has been terminated.
+
+        When we clean up a terminated connection between a given connector
+        and volume, we:
+        1. Translate the given connector 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']
+        host_name = self._get_host_from_connector(connector)
+        # Verify that _get_host_from_connector returned the host.
+        # This should always succeed as we terminate an existing connection.
+        self._driver_assert(
+            host_name is not None,
+            _('_get_host_from_connector failed to return the host name '
+              'for connector'))
+
+        # Check if vdisk-host mapping exists, remove if it does
+        mapping_data = self._get_hostvdisk_mappings(host_name)
+        if vol_name in mapping_data:
+            ssh_cmd = 'rmvdiskhostmap -host %s %s' % (host_name, vol_name)
+            out, err = self._run_ssh(ssh_cmd)
+            # Verify CLI behaviour - no output is returned from
+            # rmvdiskhostmap
+            self._assert_ssh_return(len(out.strip()) == 0,
+                                    'terminate_connection', ssh_cmd, out, 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})
+            LOG.error(_('terminate_connection: No mapping of volume '
+                        '%(vol_name)s to host %(host_name)s found') %
+                      {'vol_name': vol_name, 'host_name': host_name})
 
         # If this host has no more mappings, delete it
         if not mapping_data:
@@ -644,36 +773,108 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
                     'connector %(conn)s') % {'vol': str(volume),
                                              'conn': str(connector)})
 
-    def _flashcopy_cleanup(self, fc_map_id, source, target):
-        """Clean up a failed FlashCopy operation."""
+    """====================================================================="""
+    """ VOLUMES/SNAPSHOTS                                                   """
+    """====================================================================="""
 
-        try:
-            out, err = self._run_ssh('stopfcmap -force %s' % fc_map_id)
-            out, err = self._run_ssh('rmfcmap -force %s' % fc_map_id)
-        except exception.ProcessExecutionError as e:
-            LOG.error(_('_run_flashcopy: fail to cleanup failed FlashCopy '
-                        'mapping %(fc_map_id)% '
-                        'from %(source)s to %(target)s.\n'
-                        'stdout: %(out)s\n stderr: %(err)s')
-                      % {'fc_map_id': fc_map_id,
-                         'source': source,
-                         'target': target,
-                         'out': e.stdout,
-                         'err': e.stderr})
+    def _get_vdisk_attributes(self, vdisk_name):
+        """Return vdisk attributes, or None if vdisk does not exist
 
-    def _run_flashcopy(self, source, target):
-        """Create a FlashCopy mapping from the source to the target."""
+        Exception is raised if the information from system can not be
+        parsed/matched to a single vdisk.
+        """
+
+        ssh_cmd = 'lsvdisk -bytes -delim ! %s ' % vdisk_name
+        return self._execute_command_and_parse_attributes(ssh_cmd)
+
+    def _get_vdisk_fc_mappings(self, vdisk_name):
+        """Return FlashCopy mappings that this vdisk is associated with."""
+
+        ssh_cmd = 'lsvdiskfcmappings -nohdr %s' % vdisk_name
+        out, err = self._run_ssh(ssh_cmd)
+
+        mapping_ids = []
+        if (len(out.strip())):
+            lines = out.strip().split('\n')
+            for line in lines:
+                mapping_ids.append(line.split()[0])
+        return mapping_ids
+
+    def _get_vdisk_params(self, type_id):
+        opts = self._build_default_opts()
+        if type_id:
+            ctxt = context.get_admin_context()
+            volume_type = volume_types.get_volume_type(ctxt, type_id)
+            specs = volume_type.get('extra_specs')
+            for key, value in specs.iteritems():
+                if key in opts:
+                    this_type = type(opts[key]).__name__
+                    if this_type == 'int':
+                        value = int(value)
+                    elif this_type == 'bool':
+                        value = False if value == "0" else True
+                    opts[key] = value
+        self._check_vdisk_opts(opts)
+        return opts
+
+    def _create_vdisk(self, name, size, units, opts):
+        """Create a new vdisk."""
+
+        LOG.debug(_('enter: _create_vdisk: vdisk %s ') % name)
+
+        model_update = None
+        autoex = '-autoexpand' if opts['autoexpand'] else ''
+        easytier = '-easytier on' if opts['easytier'] else '-easytier off'
+
+        # Set space-efficient options
+        if opts['rsize'] == -1:
+            ssh_cmd_se_opt = ''
+        else:
+            ssh_cmd_se_opt = (
+                '-rsize %(rsize)d%% %(autoex)s -warning %(warn)d%%' %
+                {'rsize': opts['rsize'],
+                 'autoex': autoex,
+                 'warn': opts['warning']})
+            if opts['compression']:
+                ssh_cmd_se_opt = ssh_cmd_se_opt + ' -compressed'
+            else:
+                ssh_cmd_se_opt = ssh_cmd_se_opt + (
+                    ' -grainsize %d' % opts['grainsize'])
+
+        ssh_cmd = ('mkvdisk -name %(name)s -mdiskgrp %(mdiskgrp)s '
+                   '-iogrp 0 -size %(size)s -unit '
+                   '%(unit)s %(easytier)s %(ssh_cmd_se_opt)s'
+                   % {'name': name,
+                   'mdiskgrp': FLAGS.storwize_svc_volpool_name,
+                   'size': size, 'unit': units, 'easytier': easytier,
+                   'ssh_cmd_se_opt': ssh_cmd_se_opt})
+        out, err = self._run_ssh(ssh_cmd)
+        self._assert_ssh_return(len(out.strip()), '_create_vdisk',
+                                ssh_cmd, out, 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_vdisk %(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(
-            _('enter: _run_flashcopy: execute FlashCopy from source '
-              '%(source)s to target %(target)s') % {'source': source,
-                                                    'target': target})
+        LOG.debug(_('leave: _create_vdisk: volume %s ') % name)
 
-        fc_map_cli_cmd = ('mkfcmap -source %s -target %s -autodelete '
-                          '-cleanrate 0' % (source, target))
+    def _make_fc_map(self, source, target, full_copy):
+        copyflag = '' if full_copy else '-copyrate 0'
+        fc_map_cli_cmd = ('mkfcmap -source %(src)s -target %(tgt)s '
+                          '-autodelete %(copyflag)s' %
+                          {'src': source,
+                           'tgt': target,
+                           'copyflag': copyflag})
         out, err = self._run_ssh(fc_map_cli_cmd)
         self._driver_assert(
-            len(out.strip()) > 0,
+            len(out.strip()),
             _('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')
@@ -717,24 +918,27 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
                    'target': target,
                    'out': str(out),
                    'err': str(err)})
+        return fc_map_id
+
+    def _call_prepare_fc_map(self, fc_map_id, source, target):
         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 '
+                LOG.error(_('_prepare_fc_map: Failed 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})
-                self._flashcopy_cleanup(fc_map_id, source, target)
 
+    def _prepare_fc_map(self, fc_map_id, source, target):
+        self._call_prepare_fc_map(fc_map_id, source, target)
         mapping_ready = False
         wait_time = 5
         # Allow waiting of up to timeout (set as parameter)
-        max_retries = (int(FLAGS.storwize_svc_flashcopy_timeout)
-                       / wait_time) + 1
+        max_retries = (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)
@@ -744,9 +948,11 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
             if mapping_attributes['status'] == 'prepared':
                 mapping_ready = True
                 break
+            elif mapping_attributes['status'] == 'stopped':
+                self._call_prepare_fc_map(fc_map_id, source, target)
             elif mapping_attributes['status'] != 'preparing':
                 # Unexpected mapping status
-                exception_msg = (_('unexecpted mapping status %(status)s '
+                exception_msg = (_('Unexecpted mapping status %(status)s '
                                    'for mapping %(id)s. Attributes: '
                                    '%(attr)s')
                                  % {'status': mapping_attributes['status'],
@@ -757,519 +963,418 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
             time.sleep(wait_time)
 
         if not mapping_ready:
-            exception_msg = (_('mapping %(id)s prepare failed to complete '
-                               'within the alloted %(to)s seconds timeout. '
-                               'Terminating')
+            exception_msg = (_('Mapping %(id)s prepare failed to complete '
+                               'within the alloted %(to)d seconds timeout. '
+                               'Terminating.')
                              % {'id': fc_map_id,
                                 'to': FLAGS.storwize_svc_flashcopy_timeout})
-            LOG.error(_('_run_flashcopy: fail to start FlashCopy '
+            LOG.error(_('_prepare_fc_map: Failed to start FlashCopy '
                         'from %(source)s to %(target)s with '
                         'exception %(ex)s')
                       % {'source': source,
                          'target': target,
                          'ex': exception_msg})
-            self._flashcopy_cleanup(fc_map_id, source, target)
             raise exception.InvalidSnapshot(
-                reason=_('_run_flashcopy: %s') % exception_msg)
+                reason=_('_prepare_fc_map: %s') % exception_msg)
 
+    def _start_fc_map(self, fc_map_id, source, target):
         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 '
+                LOG.error(_('_start_fc_map: Failed 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})
-                self._flashcopy_cleanup(fc_map_id, source, target)
-
-        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 = snapshot['name']
-        tgt_volume = volume['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.SnapshotNotFound(exception_msg,
-                                             volume_id=source_volume)
 
-        self._driver_assert(
-            'capacity' in src_volume_attributes,
-            _('create_volume_from_snapshot: cannot get source '
-              'volume %(src)s capacity from volume attributes '
-              '%(attr)s')
-            % {'src': source_volume,
-               'attr': src_volume_attributes})
-        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
+    def _run_flashcopy(self, source, target, full_copy=True):
+        """Create a FlashCopy mapping from the source to the target."""
 
-        self._create_volume(snapshot_volume, units='b')
+        LOG.debug(_('enter: _run_flashcopy: execute FlashCopy from source '
+                    '%(source)s to target %(target)s') %
+                  {'source': source, 'target': target})
 
+        fc_map_id = self._make_fc_map(source, target, full_copy)
         try:
-            self._run_flashcopy(source_volume, tgt_volume)
+            self._prepare_fc_map(fc_map_id, source, target)
+            self._start_fc_map(fc_map_id, source, target)
         except Exception:
             with excutils.save_and_reraise_exception():
-                # Clean up newly-created snapshot if the FlashCopy failed
-                self._delete_volume(snapshot_volume, True)
+                self._delete_vdisk(target, True)
 
-        LOG.debug(
-            _('leave: create_volume_from_snapshot: %s created successfully')
-            % tgt_volume)
+        LOG.debug(_('leave: _run_flashcopy: FlashCopy started from '
+                    '%(source)s to %(target)s') %
+                  {'source': source, 'target': target})
 
-    def create_snapshot(self, snapshot):
+    def _create_copy(self, src_vdisk, tgt_vdisk, full_copy, opts, src_id,
+                     from_vol):
         """Create a new snapshot using FlashCopy."""
 
-        src_volume = snapshot['volume_name']
-        tgt_volume = snapshot['name']
+        LOG.debug(_('enter: _create_copy: snapshot %(tgt_vdisk)s from '
+                    'vdisk %(src_vdisk)s') %
+                  {'tgt_vdisk': tgt_vdisk, 'src_vdisk': src_vdisk})
 
-        # Flag to keep track of created volumes in case FlashCopy
-        tgt_volume_created = False
-
-        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:
+        src_vdisk_attributes = self._get_vdisk_attributes(src_vdisk)
+        if src_vdisk_attributes is None:
             exception_msg = (
-                _('create_snapshot: source volume %s does not exist')
-                % src_volume)
+                _('_create_copy: Source vdisk %s does not exist')
+                % src_vdisk)
             LOG.error(exception_msg)
-            raise exception.VolumeNotFound(exception_msg,
-                                           volume_id=src_volume)
+            if from_vol:
+                raise exception.VolumeNotFound(exception_msg,
+                                               volume_id=src_id)
+            else:
+                raise exception.SnapshotNotFound(exception_msg,
+                                                 snapshot_id=src_id)
 
         self._driver_assert(
-            'capacity' in src_volume_attributes,
-            _('create_volume_from_snapshot: cannot get source '
-              'volume %(src)s capacity from volume attributes '
+            'capacity' in src_vdisk_attributes,
+            _('_create_copy: cannot get source vdisk '
+              '%(src)s capacity from vdisk attributes '
               '%(attr)s')
-            % {'src': src_volume,
-               'attr': src_volume_attributes})
-
-        source_volume_size = src_volume_attributes['capacity']
-
-        tgt_volume_attributes = self._get_volume_attributes(tgt_volume)
-        # Does the snapshot target exist?
-        snapshot_volume = {}
-        if tgt_volume_attributes is None:
-            # No, create a new snapshot volume
-            snapshot_volume['name'] = tgt_volume
-            snapshot_volume['size'] = source_volume_size
-            self._create_volume(snapshot_volume, units='b')
-            tgt_volume_created = True
-        else:
-            # Yes, target exists, verify exact same size as source
-            self._driver_assert(
-                'capacity' in tgt_volume_attributes,
-                _('create_volume_from_snapshot: cannot get source '
-                  'volume %(src)s capacity from volume attributes '
-                  '%(attr)s')
-                % {'src': tgt_volume,
-                   'attr': tgt_volume_attributes})
-            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)
+            % {'src': src_vdisk,
+               'attr': src_vdisk_attributes})
 
-        try:
-            self._run_flashcopy(src_volume, tgt_volume)
-        except exception.InvalidSnapshot:
-            with excutils.save_and_reraise_exception():
-                # Clean up newly-created snapshot if the FlashCopy failed
-                if tgt_volume_created:
-                    self._delete_volume(snapshot_volume, True)
+        src_vdisk_size = src_vdisk_attributes['capacity']
+        self._create_vdisk(tgt_vdisk, src_vdisk_size, 'b', opts)
+        self._run_flashcopy(src_vdisk, tgt_vdisk, full_copy)
 
-        LOG.debug(_('leave: create_snapshot: %s created successfully')
-                  % tgt_volume)
+        LOG.debug(_('leave: _create_copy: snapshot %(tgt_vdisk)s from '
+                    'vdisk %(src_vdisk)s') %
+                  {'tgt_vdisk': tgt_vdisk, 'src_vdisk': src_vdisk})
 
-    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)
+    def _get_flashcopy_mapping_attributes(self, fc_map_id):
+        LOG.debug(_('enter: _get_flashcopy_mapping_attributes: mapping %s')
+                  % fc_map_id)
 
-        if (len(out.strip()) == 0):
+        fc_ls_map_cmd = ('lsfcmap -filtervalue id=%s -delim !' % fc_map_id)
+        out, err = self._run_ssh(fc_ls_map_cmd)
+        if not len(out.strip()):
             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, 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)
+        # 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._assert_ssh_return(len(lines) <= 2,
+                                '_get_flashcopy_mapping_attributes',
+                                fc_ls_map_cmd, out, err)
 
-        return hostname
+        if len(lines) == 2:
+            attributes = self._get_hdr_dic(lines[0], lines[1], '!')
+        else:  # 0 or 1 lines
+            attributes = None
 
-    def _add_chapsecret_to_host(self, host_name):
-        """Generate and store a randomly-generated CHAP secret for the host."""
+        LOG.debug(_('leave: _get_flashcopy_mapping_attributes: mapping '
+                    '%(fc_map_id)s, attributes %(attributes)s') %
+                  {'fc_map_id': fc_map_id, 'attributes': attributes})
 
-        chap_secret = utils.generate_password()
-        out, err = self._run_ssh('chhost -chapsecret "%s" %s'
-                                 % (chap_secret, host_name))
-        # No output should be returned from chhost
-        self._driver_assert(
-            len(out.strip()) == 0,
-            _('change host %(name)s - non empty output from CLI.\n '
-              'stdout: %(out)s\n stderr: %(err)s')
-            % {'name': host_name,
-               'out': str(out),
-               'err': str(err)})
+        return attributes
 
-        return chap_secret
+    def _is_vdisk_defined(self, vdisk_name):
+        """Check if vdisk is defined."""
+        LOG.debug(_('enter: _is_vdisk_defined: vdisk %s ') % vdisk_name)
+        vdisk_attributes = self._get_vdisk_attributes(vdisk_name)
+        LOG.debug(_('leave: _is_vdisk_defined: vdisk %(vol)s with %(str)s ')
+                  % {'vol': vdisk_name,
+                     'str': vdisk_attributes is not None})
+        if vdisk_attributes is None:
+            return False
+        else:
+            return True
 
-    def _create_new_host(self, host_name, initiator_name):
-        """Create a new host on the storage system.
+    def _delete_vdisk(self, name, force):
+        """Deletes existing vdisks.
+
+        It is very important to properly take care of mappings before deleting
+        the disk:
+        1. If no mappings, then it was a vdisk, and can be deleted
+        2. If it is the source of a flashcopy mapping and copy_rate is 0, then
+           it is a vdisk that has a snapshot.  If the force flag is set,
+           delete the mapping and the vdisk, otherwise set the mapping to
+           copy and wait (this will allow users to delete vdisks that have
+           snapshots if/when the upper layers allow it).
+        3. If it is the target of a mapping and copy_rate is 0, it is a
+           snapshot, and we should properly stop the mapping and delete.
+        4. If it is the source/target of a mapping and copy_rate is not 0, it
+           is a clone or vdisk created from a snapshot.  We wait for the copy
+           to complete (the mapping will be autodeleted) and then delete the
+           vdisk.
 
-        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)
+        LOG.debug(_('enter: _delete_vdisk: vdisk %s') % name)
 
-        # 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)})
+        # Try to delete volume only if found on the storage
+        vdisk_defined = self._is_vdisk_defined(name)
+        if not vdisk_defined:
+            LOG.info(_('warning: Tried to delete vdisk %s but it does not '
+                       'exist.') % name)
+            return
+
+        # Ensure vdisk has no FlashCopy mappings
+        mapping_ids = self._get_vdisk_fc_mappings(name)
+        while len(mapping_ids):
+            wait_for_copy = False
+            for map_id in mapping_ids:
+                attrs = self._get_flashcopy_mapping_attributes(map_id)
+                if not attrs:
+                    continue
+                source = attrs['source_vdisk_name']
+                target = attrs['target_vdisk_name']
+                copy_rate = attrs['copy_rate']
+                status = attrs['status']
+
+                if copy_rate == '0':
+                    # Case #2: A vdisk that has snapshots
+                    if source == name:
+                            ssh_cmd = ('chfcmap -copyrate 50 -autodelete '
+                                       'on %s' % map_id)
+                            out, err = self._run_ssh(ssh_cmd)
+                            wait_for_copy = True
+                    # Case #3: A snapshot
+                    else:
+                        msg = (_('Vdisk %(name)s not involved in '
+                                 'mapping %(src)s -> %(tgt)s') %
+                               {'name': name, 'src': source, 'tgt': target})
+                        self._driver_assert(target == name, msg)
+                        if status in ['copying', 'prepared']:
+                            self._run_ssh('stopfcmap %s' % map_id)
+                        elif status == 'stopping':
+                            wait_for_copy = True
+                        else:
+                            self._run_ssh('rmfcmap -force %s' % map_id)
+                # Case 4: Copy in progress - wait and will autodelete
+                else:
+                    if status == 'prepared':
+                        self._run_ssh('stopfcmap %s' % map_id)
+                        self._run_ssh('rmfcmap -force %s' % map_id)
+                    elif status == 'idle_or_copied':
+                        # Prepare failed
+                        self._run_ssh('rmfcmap -force %s' % map_id)
+                    else:
+                        wait_for_copy = True
+            if wait_for_copy:
+                time.sleep(5)
+            mapping_ids = self._get_vdisk_fc_mappings(name)
+
+        forceflag = '-force' if force else ''
+        ssh_cmd = 'rmvdisk %(frc)s %(name)s' % {'frc': forceflag, 'name': name}
+        out, err = self._run_ssh(ssh_cmd)
+        # No output should be returned from rmvdisk
+        self._assert_ssh_return(len(out.strip()) == 0,
+                                ('_delete_vdisk %(name)s')
+                                % {'name': name},
+                                ssh_cmd, out, err)
+        LOG.debug(_('leave: _delete_vdisk: vdisk %s') % name)
 
-        LOG.debug(_('leave: _create_new_host: host %(host)s with iSCSI '
-                    'initiator %(init)s') % {'host': host_name,
-                                             'init': initiator_name})
+    def create_volume(self, volume):
+        opts = self._get_vdisk_params(volume['volume_type_id'])
+        return self._create_vdisk(volume['name'], str(volume['size']), 'gb',
+                                  opts)
 
-        return host_name
+    def delete_volume(self, volume):
+        self._delete_vdisk(volume['name'], False)
 
-    def _delete_host(self, host_name):
-        """Delete a host and associated iSCSI initiator name."""
+    def create_snapshot(self, snapshot):
+        source_vol = self.db.volume_get(self._context, snapshot['volume_id'])
+        opts = self._get_vdisk_params(source_vol['volume_type_id'])
+        self._create_copy(src_vdisk=snapshot['volume_name'],
+                          tgt_vdisk=snapshot['name'],
+                          full_copy=False,
+                          opts=opts,
+                          src_id=snapshot['volume_id'],
+                          from_vol=True)
 
-        LOG.debug(_('enter: _delete_host: host %s ') % host_name)
+    def delete_snapshot(self, snapshot):
+        self._delete_vdisk(snapshot['name'], False)
 
-        # 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)
-            # No output should be returned from rmhost
-            self._driver_assert(
-                len(out.strip()) == 0,
-                _('delete host %(name)s - non empty output from CLI.\n '
-                  'stdout: %(out)s\n stderr: %(err)s')
-                % {'name': host_name,
-                   'out': str(out),
-                   'err': str(err)})
-        else:
-            LOG.info(_('warning: tried to delete host %(name)s but '
-                       'it does not exist.') % {'name': host_name})
+    def create_volume_from_snapshot(self, volume, snapshot):
+        if volume['size'] != snapshot['volume_size']:
+            exception_message = (_('create_volume_from_snapshot: '
+                                   'Source and destination size differ.'))
+            raise exception.VolumeBackendAPIException(data=exception_message)
 
-        LOG.debug(_('leave: _delete_host: host %s ') % host_name)
+        opts = self._get_vdisk_params(volume['volume_type_id'])
+        self._create_copy(src_vdisk=snapshot['name'],
+                          tgt_vdisk=volume['name'],
+                          full_copy=True,
+                          opts=opts,
+                          src_id=snapshot['id'],
+                          from_vol=False)
+
+    def create_cloned_volume(self, tgt_volume, src_volume):
+        if src_volume['size'] != tgt_volume['size']:
+            exception_message = (_('create_cloned_volume: '
+                                   'Source and destination size differ.'))
+            raise exception.VolumeBackendAPIException(data=exception_message)
 
-    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
+        opts = self._get_vdisk_params(tgt_volume['volume_type_id'])
+        self._create_copy(src_vdisk=src_volume['name'],
+                          tgt_vdisk=tgt_volume['name'],
+                          full_copy=True,
+                          opts=opts,
+                          src_id=src_volume['id'],
+                          from_vol=True)
+
+    def copy_image_to_volume(self, context, volume, image_service, image_id):
+        opts = self._get_vdisk_params(volume['volume_type_id'])
+        if opts['protocol'] == 'iscsi':
+            # Implemented in base iSCSI class
+            return super(StorwizeSVCDriver, self).copy_image_to_volume(
+                    context, volume, image_service, image_id)
         else:
-            return True
+            raise NotImplementedError()
+
+    def copy_volume_to_image(self, context, volume, image_service, image_meta):
+        opts = self._get_vdisk_params(volume['volume_type_id'])
+        if opts['protocol'] == 'iscsi':
+            # Implemented in base iSCSI class
+            return super(StorwizeSVCDriver, self).copy_volume_to_image(
+                    context, volume, image_service, image_meta)
+        else:
+            raise NotImplementedError()
 
-    def _is_host_defined(self, host_name):
-        """Check if a host is defined on the storage."""
+    """====================================================================="""
+    """ MISC/HELPERS                                                        """
+    """====================================================================="""
 
-        LOG.debug(_('enter: _is_host_defined: host %s ') % host_name)
+    def get_volume_stats(self, refresh=False):
+        """Get volume status.
 
-        # 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
+        If 'refresh' is True, run update the stats first."""
+        if refresh:
+            self._update_volume_status()
 
-        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)})
+        return self._stats
 
-        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
+    def _update_volume_status(self):
+        """Retrieve status info from volume group."""
 
-        LOG.debug(_('leave: _is_host_defined: host %(host)s with %(str)s ') % {
-            'host': host_name,
-            'str': host_name_from_storage is not None})
+        LOG.debug(_("Updating volume status"))
+        data = {}
 
-        if host_name_from_storage is None:
-            return False
-        else:
-            return True
+        data['volume_backend_name'] = 'IBM_STORWIZE_SVC'  # To be overwritten
+        data['vendor_name'] = 'IBM'
+        data['driver_version'] = '1.1'
+        data['storage_protocol'] = 'iSCSI'
+        data['storage_protocols'] = self._enabled_protocols
 
-    def _get_hostvdisk_mappings(self, host_name):
-        """Return the defined storage mappings for a host."""
+        data['total_capacity_gb'] = 0
+        data['free_capacity_gb'] = 0
+        data['reserved_percentage'] = 0
+        data['QoS_support'] = False
 
-        return_data = {}
-        ssh_cmd = 'lshostvdiskmap -delim ! %s' % host_name
-        out, err = self._run_ssh(ssh_cmd)
-
-        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."""
+        pool = FLAGS.storwize_svc_volpool_name
+        #Get storage system name
+        ssh_cmd = 'lssystem -delim !'
+        attributes = self._execute_command_and_parse_attributes(ssh_cmd)
+        if not attributes or not attributes['name']:
+            exception_message = (_('_update_volume_status: '
+                                   'Could not get system name'))
+            raise exception.VolumeBackendAPIException(data=exception_message)
 
-        LOG.debug(_('enter: _map_vol_to_host: volume %(vol)s to '
-                    'host %(host)s')
-                  % {'vol': volume_name,
-                     'host': host_name})
+        data['volume_backend_name'] = '%s_%s' % (attributes['name'], pool)
 
-        # Check if this volume is already mapped to this host
-        mapping_data = self._get_hostvdisk_mappings(host_name)
+        ssh_cmd = 'lsmdiskgrp -bytes -delim ! %s' % pool
+        attributes = self._execute_command_and_parse_attributes(ssh_cmd)
+        if not attributes:
+            LOG.error(_('Could not get pool data from the storage'))
+            exception_message = (_('_update_volume_status: '
+                                   'Could not get storage pool data'))
+            raise exception.VolumeBackendAPIException(data=exception_message)
 
-        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)
+        data['total_capacity_gb'] = (float(attributes['capacity']) /
+                                    (1024 ** 3))
+        data['free_capacity_gb'] = (float(attributes['free_capacity']) /
+                                    (1024 ** 3))
 
-        # 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)})
+        self._stats = data
 
-        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})
+    def _port_conf_generator(self, cmd):
+        ssh_cmd = '%s -delim !' % cmd
+        out, err = self._run_ssh(ssh_cmd)
 
-        return result_lun
+        if not len(out.strip()):
+            return
+        port_lines = out.strip().split('\n')
+        if not len(port_lines):
+            return
 
-    def _get_flashcopy_mapping_attributes(self, fc_map_id):
-        """Return the attributes of a FlashCopy mapping.
+        header = port_lines.pop(0)
+        yield header
+        for portip_line in port_lines:
+            try:
+                port_data = self._get_hdr_dic(header, portip_line, '!')
+            except exception.VolumeBackendAPIException:
+                with excutils.save_and_reraise_exception():
+                    self._log_cli_output_error('_port_conf_generator',
+                                               ssh_cmd, out, err)
+            yield port_data
 
-        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).
-        """
+    def _check_vdisk_opts(self, opts):
+        # Check that rsize is either -1 or between 0 and 100
+        if not (opts['rsize'] >= -1 and opts['rsize'] <= 100):
+            raise exception.InvalidInput(
+                reason=_('Illegal value specified for storwize_svc_vol_rsize: '
+                         'set to either a percentage (0-100) or -1'))
 
-        LOG.debug(_('enter: _get_flashcopy_mapping_attributes: mapping %s')
-                  % fc_map_id)
-        # Get the lunid to be used
+        # Check that warning is either -1 or between 0 and 100
+        if not (opts['warning'] >= -1 and opts['warning'] <= 100):
+            raise exception.InvalidInput(
+                reason=_('Illegal value specified for '
+                         'storwize_svc_vol_warning: '
+                         'set to a percentage (0-100)'))
 
-        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)})
+        # Check that grainsize is 32/64/128/256
+        if opts['grainsize'] not in [32, 64, 128, 256]:
+            raise exception.InvalidInput(
+                reason=_('Illegal value specified for '
+                         'storwize_svc_vol_grainsize: set to either '
+                         '32, 64, 128, or 256'))
 
-        # 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)})
+        # Check that compression is supported
+        if opts['compression'] and not self._compression_enabled:
+            raise exception.InvalidInput(
+                reason=_('System does not support compression'))
 
-        if len(lines) == 2:
-            attributes = self._get_hdr_dic(lines[0], lines[1], '!')
-        else:  # 0 or 1 lines
-            attributes = None
+        # Check that rsize is set if compression is set
+        if opts['compression'] and opts['rsize'] == -1:
+            raise exception.InvalidInput(
+                reason=_('If compression is set to True, rsize must '
+                         'also be set (not equal to -1)'))
 
-        LOG.debug(_('leave: _get_flashcopy_mapping_attributes: mapping '
-                    '%(id)s, attributes %(attr)s')
-                  % {'id': fc_map_id,
-                     'attr': attributes})
+        # Check that the requested protocol is enabled
+        if not opts['protocol'] in self._enabled_protocols:
+            raise exception.InvalidInput(
+                reason=_('Illegal value %(prot)s specified for '
+                         'storwize_svc_connection_protocol: '
+                         'valid values are %(enabled)s')
+                % {'prot': opts['protocol'],
+                   'enabled': ','.join(self._enabled_protocols)})
+
+        # Check that multipath is only enabled for fc
+        if opts['protocol'] != 'fc' and opts['multipath']:
+            raise exception.InvalidInput(
+                reason=_('Multipath is currently only supported for FC '
+                         'connections and not iSCSI.  (This is a Nova '
+                         'limitation.)'))
 
-        return attributes
+    def _execute_command_and_parse_attributes(self, ssh_cmd):
+        """Execute command on the Storwize/SVC and parse 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 the system
+        can not be obtained.
 
-        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
+        LOG.debug(_('enter: _execute_command_and_parse_attributes: '
+                    ' command %s') % ssh_cmd)
 
         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
@@ -1280,71 +1385,69 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
                        '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)})
+        self._assert_ssh_return(len(out),
+                                '_execute_command_and_parse_attributes',
+                                ssh_cmd, out, err)
         attributes = {}
         for attrib_line in out.split('\n'):
             # If '!' not found, 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:
+            if attrib_name is not None and len(attrib_name.strip()):
                 attributes[attrib_name] = attrib_value
 
-        LOG.debug(_('leave: _get_volume_attributes:\n volume %(vol)s\n '
+        LOG.debug(_('leave: _execute_command_and_parse_attributes:\n'
+                    'command: %(cmd)s\n'
                     'attributes: %(attr)s')
-                  % {'vol': volume_name,
+                  % {'cmd': ssh_cmd,
                      'attr': str(attributes)})
 
         return attributes
 
-    def _get_chap_secret_for_host(self, host_name):
-        """Return the CHAP secret for the given host."""
-
-        LOG.debug(_('enter: _get_chap_secret_for_host: host name %s')
-                  % host_name)
-
-        ssh_cmd = 'lsiscsiauth -delim !'
-        out, err = self._run_ssh(ssh_cmd)
-
-        if (len(out.strip()) == 0):
-            return None
-
-        err_msg = _('_get_chap_secret_for_host: '
-                    '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)
+    def _get_hdr_dic(self, header, row, delim):
+        """Return CLI row data as a dictionary indexed by names from header.
+        string. The strings are converted to columns using the delimiter in
+        delim.
+        """
 
-        header = host_lines.pop(0).split('!')
-        self._driver_assert('name' in header, err_msg)
-        self._driver_assert('iscsi_auth_method' in header, err_msg)
-        self._driver_assert('iscsi_chap_secret' in header, err_msg)
-        name_index = header.index('name')
-        method_index = header.index('iscsi_auth_method')
-        secret_index = header.index('iscsi_chap_secret')
+        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
 
-        chap_secret = None
-        host_found = False
-        for line in host_lines:
-            info = line.split('!')
-            if info[name_index] == host_name:
-                host_found = True
-                if info[method_index] == 'chap':
-                    chap_secret = info[secret_index]
+    def _log_cli_output_error(self, function, cmd, out, err):
+        LOG.error(_('%(fun)s: Failed with unexpected CLI output.\n '
+                    'Command: %(cmd)s\nstdout: %(out)s\nstderr: %(err)s\n')
+                  % {'fun': function, 'cmd': cmd,
+                     'out': str(out), 'err': str(err)})
 
-        self._driver_assert(host_found is not False, err_msg)
+    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)
 
-        LOG.debug(_('leave: _get_chap_secret_for_host: host name %(host)s '
-                    'with secret %(secret)s')
-                  % {'host': host_name, 'secret': chap_secret})
+    def _assert_ssh_return(self, test, fun, ssh_cmd, out, err):
+        self._driver_assert(
+            test,
+            _('%(fun)s: Failed with unexpected CLI output.\n '
+              'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s')
+            % {'fun': fun,
+               'cmd': ssh_cmd,
+               'out': str(out),
+               'err': str(err)})
 
-        return chap_secret
+    def _handle_keyerror(self, function, header):
+        msg = (_('Did not find expected column in %(fun)s: %(hdr)s') %
+               {'fun': function, 'hdr': header})
+        LOG.error(msg)
+        raise exception.VolumeBackendAPIException(
+            data=msg)