# 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:
# 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:
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,
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():
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())
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)
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,
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)
# 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
# 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,
# 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)
# 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>
"""
-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
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
import string
import time
+from cinder import context
from cinder import exception
from cinder import flags
from cinder.openstack.common import cfg
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',
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))
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']
'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:
'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')
'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)
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'],
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
'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)