Cleanup/move code in Storwize Driver
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
This commit is contained in:
committed by
John Griffith
parent
7fb767f2d6
commit
0b1bc1925c
@@ -35,8 +35,7 @@ from cinder.tests.unit import utils as testutils
|
||||
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
|
||||
|
||||
@@ -1956,7 +1955,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
'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):
|
||||
@@ -1993,7 +1992,8 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
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)
|
||||
@@ -2040,7 +2040,8 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
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,
|
||||
@@ -2084,7 +2085,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
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()
|
||||
@@ -2395,7 +2396,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
# 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',
|
||||
@@ -2813,8 +2814,10 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
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)
|
||||
@@ -2936,8 +2939,10 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
'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)
|
||||
@@ -3035,7 +3040,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
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'
|
||||
@@ -3126,7 +3131,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
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',
|
||||
@@ -3160,7 +3165,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
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',
|
||||
@@ -3193,7 +3198,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
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',
|
||||
@@ -3514,7 +3519,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
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)
|
||||
@@ -3785,15 +3790,17 @@ class StorwizeSVCDriverTestCase(test.TestCase):
|
||||
|
||||
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'])
|
||||
@@ -3813,7 +3820,7 @@ age!40
|
||||
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',
|
||||
@@ -3824,7 +3831,7 @@ home address!s4
|
||||
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'])
|
||||
@@ -3840,7 +3847,7 @@ port_id!500507680240C744
|
||||
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'),
|
||||
@@ -3851,7 +3858,7 @@ port_speed!8Gb
|
||||
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 = {}
|
||||
@@ -3862,10 +3869,12 @@ class StorwizeHelpersTestCase(test.TestCase):
|
||||
|
||||
# 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())
|
||||
|
||||
@@ -49,8 +49,10 @@ from cinder import exception
|
||||
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
|
||||
|
||||
@@ -1,470 +0,0 @@
|
||||
# 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_
|
||||
@@ -21,6 +21,7 @@ import unicodedata
|
||||
|
||||
|
||||
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
|
||||
@@ -30,7 +31,6 @@ import six
|
||||
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
|
||||
@@ -40,6 +40,348 @@ DEFAULT_TIMEOUT = 15
|
||||
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
|
||||
@@ -52,7 +394,7 @@ class StorwizeHelpers(object):
|
||||
'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
|
||||
@@ -1091,3 +1433,106 @@ class StorwizeHelpers(object):
|
||||
|
||||
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_
|
||||
Reference in New Issue
Block a user