From: Avishay Traeger Date: Wed, 10 Apr 2013 08:24:45 +0000 (+0300) Subject: Fix incompatible Storwize/SVC commands. X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=f38f943899ea95fe237be1d096ceafd35b57a843;p=openstack-build%2Fcinder-build.git Fix incompatible Storwize/SVC commands. Inconsistencies in the command line syntax between Storwize/SVC models and firmware versions caused driver failures. This patch makes commands compatible to fix driver crashes on the V3700 and controllers with firmware version 6.3. Change-Id: I51d5b97afa5419cdbf488f9e4e4597d22bb22f20 Fixes: bug 1163754 Fixed: bug 1163964 --- diff --git a/cinder/tests/test_storwize_svc.py b/cinder/tests/test_storwize_svc.py index ed878cab6..cad040a02 100644 --- a/cinder/tests/test_storwize_svc.py +++ b/cinder/tests/test_storwize_svc.py @@ -27,6 +27,7 @@ Tests for the IBM Storwize family and SVC volume driver. import random import re import socket +import unittest from cinder import context from cinder import exception @@ -62,7 +63,6 @@ class StorwizeSVCManagementSimulator: self._fcmappings_list = {} self._next_cmd_error = { 'lsportip': '', - 'lsportfc': '', 'lsfabric': '', 'lsiscsiauth': '', 'lsnodecanister': '', @@ -215,8 +215,14 @@ class StorwizeSVCManagementSimulator: # 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] in ('svcinfo', 'svctask'): + if arg_list[1] == 'lsnode': + if len(arg_list) > 4: # e.g. svcinfo lsnode -delim ! + ret = {'cmd': 'lsnode', 'node_id': arg_list[-1]} + else: + ret = {'cmd': 'lsnodecanister'} + else: + ret = {'cmd': arg_list[1]} arg_list.pop(0) else: ret = {'cmd': arg_list[0]} @@ -242,7 +248,7 @@ class StorwizeSVCManagementSimulator: def _print_info_cmd(self, rows, delim=' ', nohdr=False, **kwargs): """Generic function for printing information.""" if nohdr: - del rows[0] + del rows[0] for index in range(len(rows)): rows[index] = delim.join(rows[index]) @@ -369,6 +375,31 @@ class StorwizeSVCManagementSimulator: return self._print_info_cmd(rows=rows, **kwargs) + # Print information of every single node of SVC + def _cmd_lsnode(self, **kwargs): + node_infos = dict() + node_infos['1'] = r'''id!1 +name!node1 +port_id!500507680210C744 +port_status!active +port_speed!8Gb +port_id!500507680220C744 +port_status!active +port_speed!8Gb +''' + node_infos['2'] = r'''id!2 +name!node2 +port_id!500507680220C745 +port_status!active +port_speed!8Gb +port_id!500507680230C745 +port_status!inactive +port_speed!N/A +''' + node_id = kwargs.get('node_id', None) + stdout = node_infos.get(node_id, '') + return stdout, '' + # Print mostly made-up stuff in the correct syntax def _cmd_lsportip(self, **kwargs): if self._next_cmd_error['lsportip'] == 'ip_no_config': @@ -431,45 +462,6 @@ class StorwizeSVCManagementSimulator: 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 target_wwpn = kwargs['wwpn'] if 'wwpn' in kwargs else None @@ -1142,10 +1134,10 @@ class StorwizeSVCManagementSimulator: out, err = self._cmd_lssystem(**kwargs) elif command == 'lsnodecanister': out, err = self._cmd_lsnodecanister(**kwargs) + elif command == 'lsnode': + out, err = self._cmd_lsnode(**kwargs) elif command == 'lsportip': out, err = self._cmd_lsportip(**kwargs) - elif command == 'lsportfc': - out, err = self._cmd_lsportfc(**kwargs) elif command == 'lsfabric': out, err = self._cmd_lsfabric(**kwargs) elif command == 'mkvdisk': @@ -1322,11 +1314,6 @@ class StorwizeSVCDriverTestCase(test.TestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.do_setup, None) self.sim.error_injection('lsnodecanister', 'remove_field') - self.assertRaises(exception.VolumeBackendAPIException, - 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.do_setup, None) self.sim.error_injection('lsportip', 'header_mismatch') @@ -1335,12 +1322,6 @@ class StorwizeSVCDriverTestCase(test.TestCase): self.sim.error_injection('lsportip', 'remove_field') self.assertRaises(exception.VolumeBackendAPIException, 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._set_flag('san_ip', '') @@ -1886,3 +1867,73 @@ class StorwizeSVCDriverTestCase(test.TestCase): 'storwize-svc-sim_volpool') self.assertAlmostEqual(stats['total_capacity_gb'], 3328.0) self.assertAlmostEqual(stats['free_capacity_gb'], 3287.5) + + +# The test case does not rely on Openstack runtime, +# so it should inherit from unittest.TestCase. +class CLIResponseTestCase(unittest.TestCase): + def test_empty(self): + self.assertEqual(0, len(storwize_svc.CLIResponse(''))) + self.assertEqual(0, len(storwize_svc.CLIResponse(('', 'stderr')))) + + def test_header(self): + raw = r'''id!name +1!node1 +2!node2 +''' + resp = storwize_svc.CLIResponse(raw, with_header=True) + self.assertEqual(2, len(resp)) + self.assertEqual('1', resp[0]['id']) + self.assertEqual('2', resp[1]['id']) + + def test_select(self): + raw = r'''id!123 +name!Bill +name!Bill2 +age!30 +home address!s1 +home address!s2 + +id! 7 +name!John +name!John2 +age!40 +home address!s3 +home address!s4 +''' + resp = storwize_svc.CLIResponse(raw, with_header=False) + self.assertEqual(list(resp.select('home address', 'name', + 'home address')), + [('s1', 'Bill', 's1'), ('s2', 'Bill2', 's2'), + ('s3', 'John', 's3'), ('s4', 'John2', 's4')]) + + def test_lsnode_all(self): + raw = r'''id!name!UPS_serial_number!WWNN!status +1!node1!!500507680200C744!online +2!node2!!500507680200C745!online +''' + resp = storwize_svc.CLIResponse(raw) + self.assertEqual(2, len(resp)) + self.assertEqual('1', resp[0]['id']) + self.assertEqual('500507680200C744', resp[0]['WWNN']) + self.assertEqual('2', resp[1]['id']) + self.assertEqual('500507680200C745', resp[1]['WWNN']) + + def test_lsnode_single(self): + raw = r'''id!1 +port_id!500507680210C744 +port_status!active +port_speed!8Gb +port_id!500507680240C744 +port_status!inactive +port_speed!8Gb +''' + resp = storwize_svc.CLIResponse(raw, with_header=False) + self.assertEqual(1, len(resp)) + self.assertEqual('1', resp[0]['id']) + self.assertEqual(list(resp.select('port_id', 'port_status')), + [('500507680210C744', 'active'), + ('500507680240C744', 'inactive')]) + +if __name__ == '__main__': + unittest.main() diff --git a/cinder/volume/drivers/storwize_svc.py b/cinder/volume/drivers/storwize_svc.py index f798332cb..4da610e02 100644 --- a/cinder/volume/drivers/storwize_svc.py +++ b/cinder/volume/drivers/storwize_svc.py @@ -134,7 +134,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): for char in invalid_ch_in_host) def _get_iscsi_ip_addrs(self): - generator = self._port_conf_generator('lsportip') + generator = self._port_conf_generator('svcinfo lsportip') header = next(generator, None) if not header: return @@ -157,24 +157,18 @@ class StorwizeSVCDriver(san.SanISCSIDriver): node['ipv6'].append(port_ipv6) def _get_fc_wwpns(self): - generator = self._port_conf_generator('lsportfc') - header = next(generator, None) - if not header: - return - - 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) - - 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) + for key in self._storage_nodes: + node = self._storage_nodes[key] + ssh_cmd = 'svcinfo lsnode -delim ! %s' % node['id'] + raw = self._run_ssh(ssh_cmd) + resp = CLIResponse(raw, delim='!', with_header=False) + wwpns = set(node['WWPN']) + for i, s in resp.select('port_id', 'port_status'): + if 'unconfigured' != s: + wwpns.add(i) + node['WWPN'] = list(wwpns) + LOG.info(_('WWPN on node %(node)s: %(wwpn)s') + % {'node': node['id'], 'wwpn': node['WWPN']}) def do_setup(self, ctxt): """Check that we have all configuration details from the storage.""" @@ -183,7 +177,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): self._context = ctxt # Validate that the pool exists - ssh_cmd = 'lsmdiskgrp -delim ! -nohdr' + ssh_cmd = 'svcinfo lsmdiskgrp -delim ! -nohdr' out, err = self._run_ssh(ssh_cmd) self._assert_ssh_return(len(out.strip()), 'do_setup', ssh_cmd, out, err) @@ -194,15 +188,19 @@ class StorwizeSVCDriver(san.SanISCSIDriver): % self.configuration.storwize_svc_volpool_name)) # 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 + try: + ssh_cmd = 'svcinfo lslicense -delim !' + out, err = self._run_ssh(ssh_cmd) + license_lines = out.strip().split('\n') + for license_line in license_lines: + name, foo, value = license_line.partition('!') + if name in ('license_compression_enclosures', + 'license_compression_capacity') and value != '0': + self._compression_enabled = True + break + except exception.ProcessExecutionError: + LOG.exception(_('Failed to get license information.')) # Get the iSCSI and FC names of the Storwize/SVC nodes ssh_cmd = 'svcinfo lsnode -delim !' @@ -341,7 +339,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): """Generate and store a randomly-generated CHAP secret for the host.""" chap_secret = utils.generate_password() - ssh_cmd = ('chhost -chapsecret "%(chap_secret)s" %(host_name)s' + ssh_cmd = ('svctask 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 @@ -355,7 +353,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): LOG.debug(_('enter: _get_chap_secret_for_host: host name %s') % host_name) - ssh_cmd = 'lsiscsiauth -delim !' + ssh_cmd = 'svcinfo lsiscsiauth -delim !' out, err = self._run_ssh(ssh_cmd) if not len(out.strip()): @@ -421,7 +419,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): def _find_host_from_wwpn(self, connector): for wwpn in connector['wwpns']: - ssh_cmd = 'lsfabric -wwpn %s -delim !' % wwpn + ssh_cmd = 'svcinfo lsfabric -wwpn %s -delim !' % wwpn out, err = self._run_ssh(ssh_cmd) if not len(out.strip()): @@ -451,7 +449,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): def _find_host_exhaustive(self, connector, hosts): for host in hosts: - ssh_cmd = 'lshost -delim ! %s' % host + ssh_cmd = 'svcinfo lshost -delim ! %s' % host out, err = self._run_ssh(ssh_cmd) self._assert_ssh_return(len(out.strip()), '_find_host_exhaustive', @@ -482,7 +480,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): LOG.debug(_('enter: _get_host_from_connector: prefix %s') % prefix) # Get list of host in the storage - ssh_cmd = 'lshost -delim !' + ssh_cmd = 'svcinfo lshost -delim !' out, err = self._run_ssh(ssh_cmd) if not len(out.strip()): @@ -536,7 +534,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): # 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"' % + ssh_cmd = ('svctask 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, @@ -544,7 +542,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): # Add any additional ports to the host for port in ports: - ssh_cmd = ('addhostport -force %s %s' % (port, host_name)) + ssh_cmd = ('svctask 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') % @@ -555,7 +553,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): """Return the defined storage mappings for a host.""" return_data = {} - ssh_cmd = 'lshostvdiskmap -delim ! %s' % host_name + ssh_cmd = 'svcinfo lshostvdiskmap -delim ! %s' % host_name out, err = self._run_ssh(ssh_cmd) mappings = out.strip().split('\n') @@ -597,7 +595,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): # Volume is not mapped to host, create a new LUN if not mapped_flag: - ssh_cmd = ('mkvdiskhostmap -host %(host_name)s -scsi ' + ssh_cmd = ('svctask mkvdiskhostmap -host %(host_name)s -scsi ' '%(result_lun)s %(volume_name)s' % {'host_name': host_name, 'result_lun': result_lun, @@ -618,7 +616,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): LOG.debug(_('enter: _delete_host: host %s ') % host_name) - ssh_cmd = 'rmhost %s ' % host_name + ssh_cmd = 'svctask 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, @@ -628,7 +626,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): def _get_conn_fc_wwpns(self, host_name): wwpns = [] - cmd = 'lsfabric -host %s' % host_name + cmd = 'svcinfo lsfabric -host %s' % host_name generator = self._port_conf_generator(cmd) header = next(generator, None) if not header: @@ -789,7 +787,8 @@ class StorwizeSVCDriver(san.SanISCSIDriver): # 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) + ssh_cmd = 'svctask rmvdiskhostmap -host %s %s' % \ + (host_name, vol_name) out, err = self._run_ssh(ssh_cmd) # Verify CLI behaviour - no output is returned from # rmvdiskhostmap @@ -820,13 +819,13 @@ class StorwizeSVCDriver(san.SanISCSIDriver): parsed/matched to a single vdisk. """ - ssh_cmd = 'lsvdisk -bytes -delim ! %s ' % vdisk_name + ssh_cmd = 'svcinfo 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 + ssh_cmd = 'svcinfo lsvdiskfcmappings -nohdr %s' % vdisk_name out, err = self._run_ssh(ssh_cmd) mapping_ids = [] @@ -908,7 +907,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): ssh_cmd_se_opt = ssh_cmd_se_opt + ( ' -grainsize %d' % opts['grainsize']) - ssh_cmd = ('mkvdisk -name %(name)s -mdiskgrp %(mdiskgrp)s ' + ssh_cmd = ('svctask mkvdisk -name %(name)s -mdiskgrp %(mdiskgrp)s ' '-iogrp 0 -size %(size)s -unit ' '%(unit)s %(easytier)s %(ssh_cmd_se_opt)s' % {'name': name, @@ -934,7 +933,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): 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 ' + fc_map_cli_cmd = ('svctask mkfcmap -source %(src)s -target %(tgt)s ' '-autodelete %(copyflag)s' % {'src': source, 'tgt': target, @@ -989,7 +988,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): def _call_prepare_fc_map(self, fc_map_id, source, target): try: - out, err = self._run_ssh('prestartfcmap %s' % fc_map_id) + out, err = self._run_ssh('svctask prestartfcmap %s' % fc_map_id) except exception.ProcessExecutionError as e: with excutils.save_and_reraise_exception(): LOG.error(_('_prepare_fc_map: Failed to prepare FlashCopy ' @@ -1046,7 +1045,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): def _start_fc_map(self, fc_map_id, source, target): try: - out, err = self._run_ssh('startfcmap %s' % fc_map_id) + out, err = self._run_ssh('svctask startfcmap %s' % fc_map_id) except exception.ProcessExecutionError as e: with excutils.save_and_reraise_exception(): LOG.error(_('_start_fc_map: Failed to start FlashCopy ' @@ -1117,7 +1116,8 @@ class StorwizeSVCDriver(san.SanISCSIDriver): LOG.debug(_('enter: _get_flashcopy_mapping_attributes: mapping %s') % fc_map_id) - fc_ls_map_cmd = ('lsfcmap -filtervalue id=%s -delim !' % fc_map_id) + fc_ls_map_cmd = 'svcinfo lsfcmap -filtervalue id=%s -delim !' % \ + fc_map_id out, err = self._run_ssh(fc_ls_map_cmd) if not len(out.strip()): return None @@ -1198,8 +1198,8 @@ class StorwizeSVCDriver(san.SanISCSIDriver): if copy_rate == '0': # Case #2: A vdisk that has snapshots if source == name: - ssh_cmd = ('chfcmap -copyrate 50 -autodelete ' - 'on %s' % map_id) + ssh_cmd = ('svctask chfcmap -copyrate 50 ' + '-autodelete on %s' % map_id) out, err = self._run_ssh(ssh_cmd) wait_for_copy = True # Case #3: A snapshot @@ -1209,19 +1209,19 @@ class StorwizeSVCDriver(san.SanISCSIDriver): {'name': name, 'src': source, 'tgt': target}) self._driver_assert(target == name, msg) if status in ['copying', 'prepared']: - self._run_ssh('stopfcmap %s' % map_id) + self._run_ssh('svctask stopfcmap %s' % map_id) elif status == 'stopping': wait_for_copy = True else: - self._run_ssh('rmfcmap -force %s' % map_id) + self._run_ssh('svctask 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) + self._run_ssh('svctask stopfcmap %s' % map_id) + self._run_ssh('svctask rmfcmap -force %s' % map_id) elif status == 'idle_or_copied': # Prepare failed - self._run_ssh('rmfcmap -force %s' % map_id) + self._run_ssh('svctask rmfcmap -force %s' % map_id) else: wait_for_copy = True if wait_for_copy: @@ -1229,7 +1229,8 @@ class StorwizeSVCDriver(san.SanISCSIDriver): mapping_ids = self._get_vdisk_fc_mappings(name) forceflag = '-force' if force else '' - ssh_cmd = 'rmvdisk %(frc)s %(name)s' % {'frc': forceflag, 'name': name} + cmd_params = {'frc': forceflag, 'name': name} + ssh_cmd = 'svctask rmvdisk %(frc)s %(name)s' % cmd_params out, err = self._run_ssh(ssh_cmd) # No output should be returned from rmvdisk self._assert_ssh_return(len(out.strip()) == 0, @@ -1336,7 +1337,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): pool = self.configuration.storwize_svc_volpool_name #Get storage system name - ssh_cmd = 'lssystem -delim !' + ssh_cmd = 'svcinfo lssystem -delim !' attributes = self._execute_command_and_parse_attributes(ssh_cmd) if not attributes or not attributes['name']: exception_message = (_('_update_volume_status: ' @@ -1348,7 +1349,7 @@ class StorwizeSVCDriver(san.SanISCSIDriver): backend_name = '%s_%s' % (attributes['name'], pool) data['volume_backend_name'] = backend_name - ssh_cmd = 'lsmdiskgrp -bytes -delim ! %s' % pool + ssh_cmd = 'svcinfo 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')) @@ -1522,3 +1523,87 @@ class StorwizeSVCDriver(san.SanISCSIDriver): LOG.error(msg) raise exception.VolumeBackendAPIException( data=msg) + + +class CLIResponse(object): + '''Parse SVC CLI output and generate iterable''' + + def __init__(self, raw, delim='!', with_header=True): + super(CLIResponse, self).__init__() + 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, basestring): + v = [v] + if isinstance(v, list): + vs.append(v) + for item in zip(*vs): + yield item + + def __getitem__(self, key): + return self.result[key] + + 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, basestring): + 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() + 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_