This patch updates the name of helper.py to storwize_svc_common.py.
In addition, ssh.py has been collapsed into storwize_svc_common.py
to reduce redundant code. Unit tests were also updated to reflect
these updates.
This patch is the first of a series of patches with the aim of
bringing the storwize_svc driver in line with other san based
Cinder drivers.
Co-Authored By: Kendall Nelson <kjnelson@us.ibm.com>
Co-Authored By: Slade Baumann <baumann@us.ibm.com>
Partially implements: blueprint refactor-storwize-driver-for-mitaka
Change-Id: I7725a008105ae54e49f90c74b32aa11713e983c9
from cinder import utils
from cinder.volume import configuration as conf
from cinder.volume.drivers.ibm import storwize_svc
-from cinder.volume.drivers.ibm.storwize_svc import helpers
-from cinder.volume.drivers.ibm.storwize_svc import ssh
+from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_common
from cinder.volume import qos_specs
from cinder.volume import volume_types
'nofmtdisk': False}
return opt
- @mock.patch.object(helpers.StorwizeHelpers, 'add_vdisk_qos')
+ @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'add_vdisk_qos')
@mock.patch.object(storwize_svc.StorwizeSVCDriver, '_get_vdisk_params')
def test_storwize_svc_create_volume_with_qos(self, get_vdisk_params,
add_vdisk_qos):
self._reset_flags()
# Test prestartfcmap failing
- with mock.patch.object(ssh.StorwizeSSH, 'prestartfcmap') as prestart:
+ with mock.patch.object(
+ storwize_svc_common.StorwizeSSH, 'prestartfcmap') as prestart:
prestart.side_effect = exception.VolumeBackendAPIException
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot, snap1)
snap_novol)
# Fail the snapshot
- with mock.patch.object(ssh.StorwizeSSH, 'prestartfcmap') as prestart:
+ with mock.patch.object(
+ storwize_svc_common.StorwizeSSH, 'prestartfcmap') as prestart:
prestart.side_effect = exception.VolumeBackendAPIException
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
self.driver.delete_volume(vol1)
self._assert_vol_exists(vol1['name'], False)
- @mock.patch.object(helpers.StorwizeHelpers, 'add_vdisk_qos')
+ @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'add_vdisk_qos')
def test_storwize_svc_create_volfromsnap_clone_with_qos(self,
add_vdisk_qos):
vol1 = self._create_volume()
# lsfabric can return [] and initilize_connection will still
# complete successfully
- with mock.patch.object(helpers.StorwizeHelpers,
+ with mock.patch.object(storwize_svc_common.StorwizeHelpers,
'get_conn_fc_wwpns') as conn_fc_wwpns:
conn_fc_wwpns.return_value = []
self._set_flag('storwize_svc_npiv_compatibility_mode',
volume_types.destroy(self.ctxt, type_id)
qos_specs.delete(self.ctxt, qos_spec['qos_specs']['id'])
- @mock.patch.object(helpers.StorwizeHelpers, 'disable_vdisk_qos')
- @mock.patch.object(helpers.StorwizeHelpers, 'update_vdisk_qos')
+ @mock.patch.object(storwize_svc_common.StorwizeHelpers,
+ 'disable_vdisk_qos')
+ @mock.patch.object(storwize_svc_common.StorwizeHelpers,
+ 'update_vdisk_qos')
def test_storwize_svc_retype_no_copy(self, update_vdisk_qos,
disable_vdisk_qos):
self.driver.do_setup(None)
'failed')
self.driver.delete_volume(volume)
- @mock.patch.object(helpers.StorwizeHelpers, 'disable_vdisk_qos')
- @mock.patch.object(helpers.StorwizeHelpers, 'update_vdisk_qos')
+ @mock.patch.object(storwize_svc_common.StorwizeHelpers,
+ 'disable_vdisk_qos')
+ @mock.patch.object(storwize_svc_common.StorwizeHelpers,
+ 'update_vdisk_qos')
def test_storwize_svc_retype_need_copy(self, update_vdisk_qos,
disable_vdisk_qos):
self.driver.do_setup(None)
self.assertEqual((7, 2, 0, 0), res['code_level'],
'Get code level error')
- @mock.patch.object(helpers.StorwizeHelpers, 'rename_vdisk')
+ @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'rename_vdisk')
def test_storwize_update_migrated_volume(self, rename_vdisk):
ctxt = testutils.get_test_admin_context()
current_volume_id = 'fake_volume_id'
wwpns = ['ff00000000000000', 'ff00000000000001']
connector = {'host': 'storwize-svc-test', 'wwpns': wwpns}
- with mock.patch.object(helpers.StorwizeHelpers,
+ with mock.patch.object(storwize_svc_common.StorwizeHelpers,
'get_conn_fc_wwpns') as get_mappings:
get_mappings.return_value = ['AABBCCDDEEFF0001',
'AABBCCDDEEFF0002',
wwpns = ['ff00000000000000', 'ff00000000000001']
connector = {'host': 'storwize-svc-test', 'wwpns': wwpns}
- with mock.patch.object(helpers.StorwizeHelpers,
+ with mock.patch.object(storwize_svc_common.StorwizeHelpers,
'get_conn_fc_wwpns') as get_mappings:
get_mappings.return_value = ['AABBCCDDEEFF0001',
'AABBCCDDEEFF0002',
wwpns = ['ff00000000000000', 'ff00000000000001']
connector = {'host': 'storwize-svc-test', 'wwpns': wwpns}
- with mock.patch.object(helpers.StorwizeHelpers,
+ with mock.patch.object(storwize_svc_common.StorwizeHelpers,
'get_conn_fc_wwpns') as get_mappings:
get_mappings.return_value = ['AABBCCDDEEFF0001',
'AABBCCDDEEFF0002',
connector = {'host': 'storwize-svc-test', 'wwpns': wwpns}
# Initialise the connection
- with mock.patch.object(helpers.StorwizeHelpers,
+ with mock.patch.object(storwize_svc_common.StorwizeHelpers,
'get_conn_fc_wwpns') as conn_fc_wwpns:
conn_fc_wwpns.return_value = []
init_ret = self.driver.initialize_connection(volume, connector)
class CLIResponseTestCase(test.TestCase):
def test_empty(self):
- self.assertEqual(0, len(ssh.CLIResponse('')))
- self.assertEqual(0, len(ssh.CLIResponse(('', 'stderr'))))
+ self.assertEqual(0, len(
+ storwize_svc_common.CLIResponse('')))
+ self.assertEqual(0, len(
+ storwize_svc_common.CLIResponse(('', 'stderr'))))
def test_header(self):
raw = r'''id!name
1!node1
2!node2
'''
- resp = ssh.CLIResponse(raw, with_header=True)
+ resp = storwize_svc_common.CLIResponse(raw, with_header=True)
self.assertEqual(2, len(resp))
self.assertEqual('1', resp[0]['id'])
self.assertEqual('2', resp[1]['id'])
home address!s3
home address!s4
'''
- resp = ssh.CLIResponse(raw, with_header=False)
+ resp = storwize_svc_common.CLIResponse(raw, with_header=False)
self.assertEqual([('s1', 'Bill', 's1'), ('s2', 'Bill2', 's2'),
('s3', 'John', 's3'), ('s4', 'John2', 's4')],
list(resp.select('home address', 'name',
1!node1!!500507680200C744!online
2!node2!!500507680200C745!online
'''
- resp = ssh.CLIResponse(raw)
+ resp = storwize_svc_common.CLIResponse(raw)
self.assertEqual(2, len(resp))
self.assertEqual('1', resp[0]['id'])
self.assertEqual('500507680200C744', resp[0]['WWNN'])
port_status!inactive
port_speed!8Gb
'''
- resp = ssh.CLIResponse(raw, with_header=False)
+ resp = storwize_svc_common.CLIResponse(raw, with_header=False)
self.assertEqual(1, len(resp))
self.assertEqual('1', resp[0]['id'])
self.assertEqual([('500507680210C744', 'active'),
class StorwizeHelpersTestCase(test.TestCase):
def setUp(self):
super(StorwizeHelpersTestCase, self).setUp()
- self.helpers = helpers.StorwizeHelpers(None)
+ self.storwize_svc_common = storwize_svc_common.StorwizeHelpers(None)
def test_compression_enabled(self):
fake_license_without_keys = {}
# Check when keys of return licenses do not contain
# 'license_compression_enclosures' and 'license_compression_capacity'
- with mock.patch.object(ssh.StorwizeSSH, 'lslicense') as lslicense:
+ with mock.patch.object(
+ storwize_svc_common.StorwizeSSH, 'lslicense') as lslicense:
lslicense.return_value = fake_license_without_keys
- self.assertFalse(self.helpers.compression_enabled())
+ self.assertFalse(self.storwize_svc_common.compression_enabled())
- with mock.patch.object(ssh.StorwizeSSH, 'lslicense') as lslicense:
+ with mock.patch.object(
+ storwize_svc_common.StorwizeSSH, 'lslicense') as lslicense:
lslicense.return_value = fake_license
- self.assertTrue(self.helpers.compression_enabled())
+ self.assertTrue(self.storwize_svc_common.compression_enabled())
from cinder.i18n import _, _LE, _LI, _LW
from cinder import utils
from cinder.volume import driver
-from cinder.volume.drivers.ibm.storwize_svc import helpers as storwize_helpers
-from cinder.volume.drivers.ibm.storwize_svc import replication as storwize_rep
+from cinder.volume.drivers.ibm.storwize_svc import (
+ replication as storwize_rep)
+from cinder.volume.drivers.ibm.storwize_svc import (
+ storwize_svc_common as storwize_helpers)
from cinder.volume.drivers.san import san
from cinder.volume import volume_types
from cinder.zonemanager import utils as fczm_utils
+++ /dev/null
-# Copyright 2014 IBM Corp.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-#
-
-import re
-
-from oslo_concurrency import processutils
-from oslo_log import log as logging
-import six
-
-from cinder import exception
-from cinder.i18n import _, _LE
-
-LOG = logging.getLogger(__name__)
-
-
-class StorwizeSSH(object):
- """SSH interface to IBM Storwize family and SVC storage systems."""
- def __init__(self, run_ssh):
- self._ssh = run_ssh
-
- def _run_ssh(self, ssh_cmd):
- try:
- return self._ssh(ssh_cmd)
- except processutils.ProcessExecutionError as e:
- msg = (_('CLI Exception output:\n command: %(cmd)s\n '
- 'stdout: %(out)s\n stderr: %(err)s.') %
- {'cmd': ssh_cmd,
- 'out': e.stdout,
- 'err': e.stderr})
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
-
- def run_ssh_info(self, ssh_cmd, delim='!', with_header=False):
- """Run an SSH command and return parsed output."""
- raw = self._run_ssh(ssh_cmd)
- return CLIResponse(raw, ssh_cmd=ssh_cmd, delim=delim,
- with_header=with_header)
-
- def run_ssh_assert_no_output(self, ssh_cmd):
- """Run an SSH command and assert no output returned."""
- out, err = self._run_ssh(ssh_cmd)
- if len(out.strip()) != 0:
- msg = (_('Expected no output from CLI command %(cmd)s, '
- 'got %(out)s.') % {'cmd': ' '.join(ssh_cmd), 'out': out})
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
-
- def run_ssh_check_created(self, ssh_cmd):
- """Run an SSH command and return the ID of the created object."""
- out, err = self._run_ssh(ssh_cmd)
- try:
- match_obj = re.search(r'\[([0-9]+)\],? successfully created', out)
- return match_obj.group(1)
- except (AttributeError, IndexError):
- msg = (_('Failed to parse CLI output:\n command: %(cmd)s\n '
- 'stdout: %(out)s\n stderr: %(err)s.') %
- {'cmd': ssh_cmd,
- 'out': out,
- 'err': err})
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
-
- def lsnode(self, node_id=None):
- with_header = True
- ssh_cmd = ['svcinfo', 'lsnode', '-delim', '!']
- if node_id:
- with_header = False
- ssh_cmd.append(node_id)
- return self.run_ssh_info(ssh_cmd, with_header=with_header)
-
- def lslicense(self):
- ssh_cmd = ['svcinfo', 'lslicense', '-delim', '!']
- return self.run_ssh_info(ssh_cmd)[0]
-
- def lssystem(self):
- ssh_cmd = ['svcinfo', 'lssystem', '-delim', '!']
- return self.run_ssh_info(ssh_cmd)[0]
-
- def lsmdiskgrp(self, pool):
- ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!',
- '"%s"' % pool]
- return self.run_ssh_info(ssh_cmd)[0]
-
- def lsiogrp(self):
- ssh_cmd = ['svcinfo', 'lsiogrp', '-delim', '!']
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
- def lsportip(self):
- ssh_cmd = ['svcinfo', 'lsportip', '-delim', '!']
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
- @staticmethod
- def _create_port_arg(port_type, port_name):
- if port_type == 'initiator':
- port = ['-iscsiname']
- else:
- port = ['-hbawwpn']
- port.append(port_name)
- return port
-
- def mkhost(self, host_name, port_type, port_name):
- port = self._create_port_arg(port_type, port_name)
- ssh_cmd = ['svctask', 'mkhost', '-force'] + port
- ssh_cmd += ['-name', '"%s"' % host_name]
- return self.run_ssh_check_created(ssh_cmd)
-
- def addhostport(self, host, port_type, port_name):
- port = self._create_port_arg(port_type, port_name)
- ssh_cmd = ['svctask', 'addhostport', '-force'] + port + ['"%s"' % host]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def lshost(self, host=None):
- with_header = True
- ssh_cmd = ['svcinfo', 'lshost', '-delim', '!']
- if host:
- with_header = False
- ssh_cmd.append('"%s"' % host)
- return self.run_ssh_info(ssh_cmd, with_header=with_header)
-
- def add_chap_secret(self, secret, host):
- ssh_cmd = ['svctask', 'chhost', '-chapsecret', secret, '"%s"' % host]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def lsiscsiauth(self):
- ssh_cmd = ['svcinfo', 'lsiscsiauth', '-delim', '!']
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
- def lsfabric(self, wwpn=None, host=None):
- ssh_cmd = ['svcinfo', 'lsfabric', '-delim', '!']
- if wwpn:
- ssh_cmd.extend(['-wwpn', wwpn])
- elif host:
- ssh_cmd.extend(['-host', '"%s"' % host])
- else:
- msg = (_('Must pass wwpn or host to lsfabric.'))
- LOG.error(msg)
- raise exception.VolumeDriverException(message=msg)
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
- def mkvdiskhostmap(self, host, vdisk, lun, multihostmap):
- """Map vdisk to host.
-
- If vdisk already mapped and multihostmap is True, use the force flag.
- """
- ssh_cmd = ['svctask', 'mkvdiskhostmap', '-host', '"%s"' % host,
- '-scsi', lun, vdisk]
- out, err = self._ssh(ssh_cmd, check_exit_code=False)
- if 'successfully created' in out:
- return
- if not err:
- msg = (_('Did not find success message nor error for %(fun)s: '
- '%(out)s.') % {'out': out, 'fun': ssh_cmd})
- raise exception.VolumeBackendAPIException(data=msg)
- if err.startswith('CMMVC6071E'):
- if not multihostmap:
- LOG.error(_LE('storwize_svc_multihostmap_enabled is set '
- 'to False, not allowing multi host mapping.'))
- raise exception.VolumeDriverException(
- message=_('CMMVC6071E The VDisk-to-host mapping was not '
- 'created because the VDisk is already mapped '
- 'to a host.\n"'))
-
- ssh_cmd.insert(ssh_cmd.index('mkvdiskhostmap') + 1, '-force')
- return self.run_ssh_check_created(ssh_cmd)
-
- def rmvdiskhostmap(self, host, vdisk):
- ssh_cmd = ['svctask', 'rmvdiskhostmap', '-host', '"%s"' % host, vdisk]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def lsvdiskhostmap(self, vdisk):
- ssh_cmd = ['svcinfo', 'lsvdiskhostmap', '-delim', '!', vdisk]
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
- def lshostvdiskmap(self, host):
- ssh_cmd = ['svcinfo', 'lshostvdiskmap', '-delim', '!', '"%s"' % host]
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
- def rmhost(self, host):
- ssh_cmd = ['svctask', 'rmhost', '"%s"' % host]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def mkvdisk(self, name, size, units, pool, opts, params):
- ssh_cmd = ['svctask', 'mkvdisk', '-name', name, '-mdiskgrp',
- '"%s"' % pool, '-iogrp', str(opts['iogrp']), '-size',
- size, '-unit', units] + params
- return self.run_ssh_check_created(ssh_cmd)
-
- def rmvdisk(self, vdisk, force=True):
- ssh_cmd = ['svctask', 'rmvdisk']
- if force:
- ssh_cmd += ['-force']
- ssh_cmd += [vdisk]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def lsvdisk(self, vdisk):
- """Return vdisk attributes or None if it doesn't exist."""
- ssh_cmd = ['svcinfo', 'lsvdisk', '-bytes', '-delim', '!', vdisk]
- out, err = self._ssh(ssh_cmd, check_exit_code=False)
- if not len(err):
- return CLIResponse((out, err), ssh_cmd=ssh_cmd, delim='!',
- with_header=False)[0]
- if err.startswith('CMMVC5754E'):
- return None
- msg = (_('CLI Exception output:\n command: %(cmd)s\n '
- 'stdout: %(out)s\n stderr: %(err)s.') %
- {'cmd': ssh_cmd,
- 'out': out,
- 'err': err})
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
-
- def lsvdisks_from_filter(self, filter_name, value):
- """Performs an lsvdisk command, filtering the results as specified.
-
- Returns an iterable for all matching vdisks.
- """
- ssh_cmd = ['svcinfo', 'lsvdisk', '-bytes', '-delim', '!',
- '-filtervalue', '%s=%s' % (filter_name, value)]
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
- def chvdisk(self, vdisk, params):
- ssh_cmd = ['svctask', 'chvdisk'] + params + [vdisk]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def movevdisk(self, vdisk, iogrp):
- ssh_cmd = ['svctask', 'movevdisk', '-iogrp', iogrp, vdisk]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def expandvdisksize(self, vdisk, amount):
- ssh_cmd = (['svctask', 'expandvdisksize', '-size', str(amount),
- '-unit', 'gb', vdisk])
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def mkfcmap(self, source, target, full_copy, consistgrp=None):
- ssh_cmd = ['svctask', 'mkfcmap', '-source', source, '-target',
- target, '-autodelete']
- if not full_copy:
- ssh_cmd.extend(['-copyrate', '0'])
- if consistgrp:
- ssh_cmd.extend(['-consistgrp', consistgrp])
- out, err = self._ssh(ssh_cmd, check_exit_code=False)
- if 'successfully created' not in out:
- msg = (_('CLI Exception output:\n command: %(cmd)s\n '
- 'stdout: %(out)s\n stderr: %(err)s.') %
- {'cmd': ssh_cmd,
- 'out': out,
- 'err': err})
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
- try:
- match_obj = re.search(r'FlashCopy Mapping, id \[([0-9]+)\], '
- 'successfully created', out)
- fc_map_id = match_obj.group(1)
- except (AttributeError, IndexError):
- msg = (_('Failed to parse CLI output:\n command: %(cmd)s\n '
- 'stdout: %(out)s\n stderr: %(err)s.') %
- {'cmd': ssh_cmd,
- 'out': out,
- 'err': err})
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
- return fc_map_id
-
- def prestartfcmap(self, fc_map_id):
- ssh_cmd = ['svctask', 'prestartfcmap', fc_map_id]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def startfcmap(self, fc_map_id):
- ssh_cmd = ['svctask', 'startfcmap', fc_map_id]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def prestartfcconsistgrp(self, fc_consist_group):
- ssh_cmd = ['svctask', 'prestartfcconsistgrp', fc_consist_group]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def startfcconsistgrp(self, fc_consist_group):
- ssh_cmd = ['svctask', 'startfcconsistgrp', fc_consist_group]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def stopfcconsistgrp(self, fc_consist_group):
- ssh_cmd = ['svctask', 'stopfcconsistgrp', fc_consist_group]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def chfcmap(self, fc_map_id, copyrate='50', autodel='on'):
- ssh_cmd = ['svctask', 'chfcmap', '-copyrate', copyrate,
- '-autodelete', autodel, fc_map_id]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def stopfcmap(self, fc_map_id):
- ssh_cmd = ['svctask', 'stopfcmap', fc_map_id]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def rmfcmap(self, fc_map_id):
- ssh_cmd = ['svctask', 'rmfcmap', '-force', fc_map_id]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def lsvdiskfcmappings(self, vdisk):
- ssh_cmd = ['svcinfo', 'lsvdiskfcmappings', '-delim', '!', vdisk]
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
- def lsfcmap(self, fc_map_id):
- ssh_cmd = ['svcinfo', 'lsfcmap', '-filtervalue',
- 'id=%s' % fc_map_id, '-delim', '!']
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
- def lsfcconsistgrp(self, fc_consistgrp):
- ssh_cmd = ['svcinfo', 'lsfcconsistgrp', '-delim', '!', fc_consistgrp]
- out, err = self._ssh(ssh_cmd)
- return CLIResponse((out, err), ssh_cmd=ssh_cmd, delim='!',
- with_header=False)
-
- def mkfcconsistgrp(self, fc_consist_group):
- ssh_cmd = ['svctask', 'mkfcconsistgrp', '-name', fc_consist_group]
- return self.run_ssh_check_created(ssh_cmd)
-
- def rmfcconsistgrp(self, fc_consist_group):
- ssh_cmd = ['svctask', 'rmfcconsistgrp', '-force', fc_consist_group]
- return self.run_ssh_assert_no_output(ssh_cmd)
-
- def addvdiskcopy(self, vdisk, dest_pool, params):
- ssh_cmd = (['svctask', 'addvdiskcopy'] + params + ['-mdiskgrp',
- '"%s"' % dest_pool, vdisk])
- return self.run_ssh_check_created(ssh_cmd)
-
- def lsvdiskcopy(self, vdisk, copy_id=None):
- ssh_cmd = ['svcinfo', 'lsvdiskcopy', '-delim', '!']
- with_header = True
- if copy_id:
- ssh_cmd += ['-copy', copy_id]
- with_header = False
- ssh_cmd += [vdisk]
- return self.run_ssh_info(ssh_cmd, with_header=with_header)
-
- def lsvdisksyncprogress(self, vdisk, copy_id):
- ssh_cmd = ['svcinfo', 'lsvdisksyncprogress', '-delim', '!',
- '-copy', copy_id, vdisk]
- return self.run_ssh_info(ssh_cmd, with_header=True)[0]
-
- def rmvdiskcopy(self, vdisk, copy_id):
- ssh_cmd = ['svctask', 'rmvdiskcopy', '-copy', copy_id, vdisk]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def addvdiskaccess(self, vdisk, iogrp):
- ssh_cmd = ['svctask', 'addvdiskaccess', '-iogrp', iogrp, vdisk]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def rmvdiskaccess(self, vdisk, iogrp):
- ssh_cmd = ['svctask', 'rmvdiskaccess', '-iogrp', iogrp, vdisk]
- self.run_ssh_assert_no_output(ssh_cmd)
-
- def lsportfc(self, node_id):
- ssh_cmd = ['svcinfo', 'lsportfc', '-delim', '!',
- '-filtervalue', 'node_id=%s' % node_id]
- return self.run_ssh_info(ssh_cmd, with_header=True)
-
-
-class CLIResponse(object):
- """Parse SVC CLI output and generate iterable."""
-
- def __init__(self, raw, ssh_cmd=None, delim='!', with_header=True):
- super(CLIResponse, self).__init__()
- if ssh_cmd:
- self.ssh_cmd = ' '.join(ssh_cmd)
- else:
- self.ssh_cmd = 'None'
- self.raw = raw
- self.delim = delim
- self.with_header = with_header
- self.result = self._parse()
-
- def select(self, *keys):
- for a in self.result:
- vs = []
- for k in keys:
- v = a.get(k, None)
- if isinstance(v, six.string_types) or v is None:
- v = [v]
- if isinstance(v, list):
- vs.append(v)
- for item in zip(*vs):
- if len(item) == 1:
- yield item[0]
- else:
- yield item
-
- def __getitem__(self, key):
- try:
- return self.result[key]
- except KeyError:
- msg = (_('Did not find the expected key %(key)s in %(fun)s: '
- '%(raw)s.') % {'key': key, 'fun': self.ssh_cmd,
- 'raw': self.raw})
- raise exception.VolumeBackendAPIException(data=msg)
-
- def __iter__(self):
- for a in self.result:
- yield a
-
- def __len__(self):
- return len(self.result)
-
- def _parse(self):
- def get_reader(content, delim):
- for line in content.lstrip().splitlines():
- line = line.strip()
- if line:
- yield line.split(delim)
- else:
- yield []
-
- if isinstance(self.raw, six.string_types):
- stdout, stderr = self.raw, ''
- else:
- stdout, stderr = self.raw
- reader = get_reader(stdout, self.delim)
- result = []
-
- if self.with_header:
- hds = tuple()
- for row in reader:
- hds = row
- break
- for row in reader:
- cur = dict()
- if len(hds) != len(row):
- msg = (_('Unexpected CLI response: header/row mismatch. '
- 'header: %(header)s, row: %(row)s.')
- % {'header': hds,
- 'row': row})
- raise exception.VolumeBackendAPIException(data=msg)
- for k, v in zip(hds, row):
- CLIResponse.append_dict(cur, k, v)
- result.append(cur)
- else:
- cur = dict()
- for row in reader:
- if row:
- CLIResponse.append_dict(cur, row[0], ' '.join(row[1:]))
- elif cur: # start new section
- result.append(cur)
- cur = dict()
- if cur:
- result.append(cur)
- return result
-
- @staticmethod
- def append_dict(dict_, key, value):
- key, value = key.strip(), value.strip()
- obj = dict_.get(key, None)
- if obj is None:
- dict_[key] = value
- elif isinstance(obj, list):
- obj.append(value)
- dict_[key] = obj
- else:
- dict_[key] = [obj, value]
- return dict_
from eventlet import greenthread
+from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_service import loopingcall
from oslo_utils import excutils
from cinder import context
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
-from cinder.volume.drivers.ibm.storwize_svc import ssh as storwize_ssh
from cinder.volume import qos_specs
from cinder.volume import utils
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
+class StorwizeSSH(object):
+ """SSH interface to IBM Storwize family and SVC storage systems."""
+ def __init__(self, run_ssh):
+ self._ssh = run_ssh
+
+ def _run_ssh(self, ssh_cmd):
+ try:
+ return self._ssh(ssh_cmd)
+ except processutils.ProcessExecutionError as e:
+ msg = (_('CLI Exception output:\n command: %(cmd)s\n '
+ 'stdout: %(out)s\n stderr: %(err)s.') %
+ {'cmd': ssh_cmd,
+ 'out': e.stdout,
+ 'err': e.stderr})
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+
+ def run_ssh_info(self, ssh_cmd, delim='!', with_header=False):
+ """Run an SSH command and return parsed output."""
+ raw = self._run_ssh(ssh_cmd)
+ return CLIResponse(raw, ssh_cmd=ssh_cmd, delim=delim,
+ with_header=with_header)
+
+ def run_ssh_assert_no_output(self, ssh_cmd):
+ """Run an SSH command and assert no output returned."""
+ out, err = self._run_ssh(ssh_cmd)
+ if len(out.strip()) != 0:
+ msg = (_('Expected no output from CLI command %(cmd)s, '
+ 'got %(out)s.') % {'cmd': ' '.join(ssh_cmd), 'out': out})
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+
+ def run_ssh_check_created(self, ssh_cmd):
+ """Run an SSH command and return the ID of the created object."""
+ out, err = self._run_ssh(ssh_cmd)
+ try:
+ match_obj = re.search(r'\[([0-9]+)\],? successfully created', out)
+ return match_obj.group(1)
+ except (AttributeError, IndexError):
+ msg = (_('Failed to parse CLI output:\n command: %(cmd)s\n '
+ 'stdout: %(out)s\n stderr: %(err)s.') %
+ {'cmd': ssh_cmd,
+ 'out': out,
+ 'err': err})
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+
+ def lsnode(self, node_id=None):
+ with_header = True
+ ssh_cmd = ['svcinfo', 'lsnode', '-delim', '!']
+ if node_id:
+ with_header = False
+ ssh_cmd.append(node_id)
+ return self.run_ssh_info(ssh_cmd, with_header=with_header)
+
+ def lslicense(self):
+ ssh_cmd = ['svcinfo', 'lslicense', '-delim', '!']
+ return self.run_ssh_info(ssh_cmd)[0]
+
+ def lssystem(self):
+ ssh_cmd = ['svcinfo', 'lssystem', '-delim', '!']
+ return self.run_ssh_info(ssh_cmd)[0]
+
+ def lsmdiskgrp(self, pool):
+ ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!',
+ '"%s"' % pool]
+ return self.run_ssh_info(ssh_cmd)[0]
+
+ def lsiogrp(self):
+ ssh_cmd = ['svcinfo', 'lsiogrp', '-delim', '!']
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+ def lsportip(self):
+ ssh_cmd = ['svcinfo', 'lsportip', '-delim', '!']
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+ @staticmethod
+ def _create_port_arg(port_type, port_name):
+ if port_type == 'initiator':
+ port = ['-iscsiname']
+ else:
+ port = ['-hbawwpn']
+ port.append(port_name)
+ return port
+
+ def mkhost(self, host_name, port_type, port_name):
+ port = self._create_port_arg(port_type, port_name)
+ ssh_cmd = ['svctask', 'mkhost', '-force'] + port
+ ssh_cmd += ['-name', '"%s"' % host_name]
+ return self.run_ssh_check_created(ssh_cmd)
+
+ def addhostport(self, host, port_type, port_name):
+ port = self._create_port_arg(port_type, port_name)
+ ssh_cmd = ['svctask', 'addhostport', '-force'] + port + ['"%s"' % host]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def lshost(self, host=None):
+ with_header = True
+ ssh_cmd = ['svcinfo', 'lshost', '-delim', '!']
+ if host:
+ with_header = False
+ ssh_cmd.append('"%s"' % host)
+ return self.run_ssh_info(ssh_cmd, with_header=with_header)
+
+ def add_chap_secret(self, secret, host):
+ ssh_cmd = ['svctask', 'chhost', '-chapsecret', secret, '"%s"' % host]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def lsiscsiauth(self):
+ ssh_cmd = ['svcinfo', 'lsiscsiauth', '-delim', '!']
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+ def lsfabric(self, wwpn=None, host=None):
+ ssh_cmd = ['svcinfo', 'lsfabric', '-delim', '!']
+ if wwpn:
+ ssh_cmd.extend(['-wwpn', wwpn])
+ elif host:
+ ssh_cmd.extend(['-host', '"%s"' % host])
+ else:
+ msg = (_('Must pass wwpn or host to lsfabric.'))
+ LOG.error(msg)
+ raise exception.VolumeDriverException(message=msg)
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+ def mkvdiskhostmap(self, host, vdisk, lun, multihostmap):
+ """Map vdisk to host.
+
+ If vdisk already mapped and multihostmap is True, use the force flag.
+ """
+ ssh_cmd = ['svctask', 'mkvdiskhostmap', '-host', '"%s"' % host,
+ '-scsi', lun, vdisk]
+ out, err = self._ssh(ssh_cmd, check_exit_code=False)
+ if 'successfully created' in out:
+ return
+ if not err:
+ msg = (_('Did not find success message nor error for %(fun)s: '
+ '%(out)s.') % {'out': out, 'fun': ssh_cmd})
+ raise exception.VolumeBackendAPIException(data=msg)
+ if err.startswith('CMMVC6071E'):
+ if not multihostmap:
+ LOG.error(_LE('storwize_svc_multihostmap_enabled is set '
+ 'to False, not allowing multi host mapping.'))
+ raise exception.VolumeDriverException(
+ message=_('CMMVC6071E The VDisk-to-host mapping was not '
+ 'created because the VDisk is already mapped '
+ 'to a host.\n"'))
+
+ ssh_cmd.insert(ssh_cmd.index('mkvdiskhostmap') + 1, '-force')
+ return self.run_ssh_check_created(ssh_cmd)
+
+ def rmvdiskhostmap(self, host, vdisk):
+ ssh_cmd = ['svctask', 'rmvdiskhostmap', '-host', '"%s"' % host, vdisk]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def lsvdiskhostmap(self, vdisk):
+ ssh_cmd = ['svcinfo', 'lsvdiskhostmap', '-delim', '!', vdisk]
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+ def lshostvdiskmap(self, host):
+ ssh_cmd = ['svcinfo', 'lshostvdiskmap', '-delim', '!', '"%s"' % host]
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+ def rmhost(self, host):
+ ssh_cmd = ['svctask', 'rmhost', '"%s"' % host]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def mkvdisk(self, name, size, units, pool, opts, params):
+ ssh_cmd = ['svctask', 'mkvdisk', '-name', name, '-mdiskgrp',
+ '"%s"' % pool, '-iogrp', six.text_type(opts['iogrp']),
+ '-size', size, '-unit', units] + params
+ return self.run_ssh_check_created(ssh_cmd)
+
+ def rmvdisk(self, vdisk, force=True):
+ ssh_cmd = ['svctask', 'rmvdisk']
+ if force:
+ ssh_cmd += ['-force']
+ ssh_cmd += [vdisk]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def lsvdisk(self, vdisk):
+ """Return vdisk attributes or None if it doesn't exist."""
+ ssh_cmd = ['svcinfo', 'lsvdisk', '-bytes', '-delim', '!', vdisk]
+ out, err = self._ssh(ssh_cmd, check_exit_code=False)
+ if not len(err):
+ return CLIResponse((out, err), ssh_cmd=ssh_cmd, delim='!',
+ with_header=False)[0]
+ if err.startswith('CMMVC5754E'):
+ return None
+ msg = (_('CLI Exception output:\n command: %(cmd)s\n '
+ 'stdout: %(out)s\n stderr: %(err)s.') %
+ {'cmd': ssh_cmd,
+ 'out': out,
+ 'err': err})
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+
+ def lsvdisks_from_filter(self, filter_name, value):
+ """Performs an lsvdisk command, filtering the results as specified.
+
+ Returns an iterable for all matching vdisks.
+ """
+ ssh_cmd = ['svcinfo', 'lsvdisk', '-bytes', '-delim', '!',
+ '-filtervalue', '%s=%s' % (filter_name, value)]
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+ def chvdisk(self, vdisk, params):
+ ssh_cmd = ['svctask', 'chvdisk'] + params + [vdisk]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def movevdisk(self, vdisk, iogrp):
+ ssh_cmd = ['svctask', 'movevdisk', '-iogrp', iogrp, vdisk]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def expandvdisksize(self, vdisk, amount):
+ ssh_cmd = (
+ ['svctask', 'expandvdisksize', '-size', six.text_type(amount),
+ '-unit', 'gb', vdisk])
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def mkfcmap(self, source, target, full_copy, consistgrp=None):
+ ssh_cmd = ['svctask', 'mkfcmap', '-source', source, '-target',
+ target, '-autodelete']
+ if not full_copy:
+ ssh_cmd.extend(['-copyrate', '0'])
+ if consistgrp:
+ ssh_cmd.extend(['-consistgrp', consistgrp])
+ out, err = self._ssh(ssh_cmd, check_exit_code=False)
+ if 'successfully created' not in out:
+ msg = (_('CLI Exception output:\n command: %(cmd)s\n '
+ 'stdout: %(out)s\n stderr: %(err)s.') %
+ {'cmd': ssh_cmd,
+ 'out': out,
+ 'err': err})
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+ try:
+ match_obj = re.search(r'FlashCopy Mapping, id \[([0-9]+)\], '
+ 'successfully created', out)
+ fc_map_id = match_obj.group(1)
+ except (AttributeError, IndexError):
+ msg = (_('Failed to parse CLI output:\n command: %(cmd)s\n '
+ 'stdout: %(out)s\n stderr: %(err)s.') %
+ {'cmd': ssh_cmd,
+ 'out': out,
+ 'err': err})
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+ return fc_map_id
+
+ def prestartfcmap(self, fc_map_id):
+ ssh_cmd = ['svctask', 'prestartfcmap', fc_map_id]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def startfcmap(self, fc_map_id):
+ ssh_cmd = ['svctask', 'startfcmap', fc_map_id]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def prestartfcconsistgrp(self, fc_consist_group):
+ ssh_cmd = ['svctask', 'prestartfcconsistgrp', fc_consist_group]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def startfcconsistgrp(self, fc_consist_group):
+ ssh_cmd = ['svctask', 'startfcconsistgrp', fc_consist_group]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def stopfcconsistgrp(self, fc_consist_group):
+ ssh_cmd = ['svctask', 'stopfcconsistgrp', fc_consist_group]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def chfcmap(self, fc_map_id, copyrate='50', autodel='on'):
+ ssh_cmd = ['svctask', 'chfcmap', '-copyrate', copyrate,
+ '-autodelete', autodel, fc_map_id]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def stopfcmap(self, fc_map_id):
+ ssh_cmd = ['svctask', 'stopfcmap', fc_map_id]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def rmfcmap(self, fc_map_id):
+ ssh_cmd = ['svctask', 'rmfcmap', '-force', fc_map_id]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def lsvdiskfcmappings(self, vdisk):
+ ssh_cmd = ['svcinfo', 'lsvdiskfcmappings', '-delim', '!', vdisk]
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+ def lsfcmap(self, fc_map_id):
+ ssh_cmd = ['svcinfo', 'lsfcmap', '-filtervalue',
+ 'id=%s' % fc_map_id, '-delim', '!']
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+ def lsfcconsistgrp(self, fc_consistgrp):
+ ssh_cmd = ['svcinfo', 'lsfcconsistgrp', '-delim', '!', fc_consistgrp]
+ out, err = self._ssh(ssh_cmd)
+ return CLIResponse((out, err), ssh_cmd=ssh_cmd, delim='!',
+ with_header=False)
+
+ def mkfcconsistgrp(self, fc_consist_group):
+ ssh_cmd = ['svctask', 'mkfcconsistgrp', '-name', fc_consist_group]
+ return self.run_ssh_check_created(ssh_cmd)
+
+ def rmfcconsistgrp(self, fc_consist_group):
+ ssh_cmd = ['svctask', 'rmfcconsistgrp', '-force', fc_consist_group]
+ return self.run_ssh_assert_no_output(ssh_cmd)
+
+ def addvdiskcopy(self, vdisk, dest_pool, params):
+ ssh_cmd = (['svctask', 'addvdiskcopy'] + params + ['-mdiskgrp',
+ '"%s"' % dest_pool, vdisk])
+ return self.run_ssh_check_created(ssh_cmd)
+
+ def lsvdiskcopy(self, vdisk, copy_id=None):
+ ssh_cmd = ['svcinfo', 'lsvdiskcopy', '-delim', '!']
+ with_header = True
+ if copy_id:
+ ssh_cmd += ['-copy', copy_id]
+ with_header = False
+ ssh_cmd += [vdisk]
+ return self.run_ssh_info(ssh_cmd, with_header=with_header)
+
+ def lsvdisksyncprogress(self, vdisk, copy_id):
+ ssh_cmd = ['svcinfo', 'lsvdisksyncprogress', '-delim', '!',
+ '-copy', copy_id, vdisk]
+ return self.run_ssh_info(ssh_cmd, with_header=True)[0]
+
+ def rmvdiskcopy(self, vdisk, copy_id):
+ ssh_cmd = ['svctask', 'rmvdiskcopy', '-copy', copy_id, vdisk]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def addvdiskaccess(self, vdisk, iogrp):
+ ssh_cmd = ['svctask', 'addvdiskaccess', '-iogrp', iogrp, vdisk]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def rmvdiskaccess(self, vdisk, iogrp):
+ ssh_cmd = ['svctask', 'rmvdiskaccess', '-iogrp', iogrp, vdisk]
+ self.run_ssh_assert_no_output(ssh_cmd)
+
+ def lsportfc(self, node_id):
+ ssh_cmd = ['svcinfo', 'lsportfc', '-delim', '!',
+ '-filtervalue', 'node_id=%s' % node_id]
+ return self.run_ssh_info(ssh_cmd, with_header=True)
+
+
class StorwizeHelpers(object):
# All the supported QoS key are saved in this dict. When a new
'type': int}}
def __init__(self, run_ssh):
- self.ssh = storwize_ssh.StorwizeSSH(run_ssh)
+ self.ssh = StorwizeSSH(run_ssh)
self.check_fcmapping_interval = 3
@staticmethod
def change_vdisk_primary_copy(self, vdisk, copy_id):
self.ssh.chvdisk(vdisk, ['-primary', copy_id])
+
+
+class CLIResponse(object):
+ """Parse SVC CLI output and generate iterable."""
+
+ def __init__(self, raw, ssh_cmd=None, delim='!', with_header=True):
+ super(CLIResponse, self).__init__()
+ if ssh_cmd:
+ self.ssh_cmd = ' '.join(ssh_cmd)
+ else:
+ self.ssh_cmd = 'None'
+ self.raw = raw
+ self.delim = delim
+ self.with_header = with_header
+ self.result = self._parse()
+
+ def select(self, *keys):
+ for a in self.result:
+ vs = []
+ for k in keys:
+ v = a.get(k, None)
+ if isinstance(v, six.string_types) or v is None:
+ v = [v]
+ if isinstance(v, list):
+ vs.append(v)
+ for item in zip(*vs):
+ if len(item) == 1:
+ yield item[0]
+ else:
+ yield item
+
+ def __getitem__(self, key):
+ try:
+ return self.result[key]
+ except KeyError:
+ msg = (_('Did not find the expected key %(key)s in %(fun)s: '
+ '%(raw)s.') % {'key': key, 'fun': self.ssh_cmd,
+ 'raw': self.raw})
+ raise exception.VolumeBackendAPIException(data=msg)
+
+ def __iter__(self):
+ for a in self.result:
+ yield a
+
+ def __len__(self):
+ return len(self.result)
+
+ def _parse(self):
+ def get_reader(content, delim):
+ for line in content.lstrip().splitlines():
+ line = line.strip()
+ if line:
+ yield line.split(delim)
+ else:
+ yield []
+
+ if isinstance(self.raw, six.string_types):
+ stdout, stderr = self.raw, ''
+ else:
+ stdout, stderr = self.raw
+ reader = get_reader(stdout, self.delim)
+ result = []
+
+ if self.with_header:
+ hds = tuple()
+ for row in reader:
+ hds = row
+ break
+ for row in reader:
+ cur = dict()
+ if len(hds) != len(row):
+ msg = (_('Unexpected CLI response: header/row mismatch. '
+ 'header: %(header)s, row: %(row)s.')
+ % {'header': hds,
+ 'row': row})
+ raise exception.VolumeBackendAPIException(data=msg)
+ for k, v in zip(hds, row):
+ CLIResponse.append_dict(cur, k, v)
+ result.append(cur)
+ else:
+ cur = dict()
+ for row in reader:
+ if row:
+ CLIResponse.append_dict(cur, row[0], ' '.join(row[1:]))
+ elif cur: # start new section
+ result.append(cur)
+ cur = dict()
+ if cur:
+ result.append(cur)
+ return result
+
+ @staticmethod
+ def append_dict(dict_, key, value):
+ key, value = key.strip(), value.strip()
+ obj = dict_.get(key, None)
+ if obj is None:
+ dict_[key] = value
+ elif isinstance(obj, list):
+ obj.append(value)
+ dict_[key] = obj
+ else:
+ dict_[key] = [obj, value]
+ return dict_