From b8287f42a8553ed63a1282f8b63a04d5d9314cf7 Mon Sep 17 00:00:00 2001 From: "Grzegorz Grasza (xek)" Date: Fri, 9 Mar 2018 13:33:08 +0100 Subject: [PATCH 10/11] HP Hardware RAID setup implementation Co-Authored-By: Piotr Prokop --- ironic_lib/system_installer/hpssaclisetup.py | 374 ++++++++++++++++- .../examples/ssacli_outputs/ld_show_detail.txt | 44 ++ ironic_lib/tests/test_hpraid.py | 457 +++++++++++++++++++++ 3 files changed, 874 insertions(+), 1 deletion(-) create mode 100644 ironic_lib/tests/examples/ssacli_outputs/ld_show_detail.txt create mode 100644 ironic_lib/tests/test_hpraid.py diff --git a/ironic_lib/system_installer/hpssaclisetup.py b/ironic_lib/system_installer/hpssaclisetup.py index 9cbecf2..38dc318 100644 --- a/ironic_lib/system_installer/hpssaclisetup.py +++ b/ironic_lib/system_installer/hpssaclisetup.py @@ -13,14 +13,386 @@ # See the License for the specific language governing permissions and # limitations under the License. +import re +import subprocess + +from oslo_log import log + from ironic_lib.system_installer.base import Setup +from ironic_lib.system_installer import exceptions +from ironic_lib.system_installer import partitionsetup +from ironic_lib.system_installer import tools + +LOG = log.getLogger() class HpSsaCliSetup(Setup): """Hardware RAID setup implementation""" + default_stripe_size = 64 conf_key = 'hwraid' + UNASSIGNED = 'Unassigned' + + def __init__(self, conf): + super(HpSsaCliSetup, self).__init__(conf) + self.conf_part = conf + + # ssacli ctrl all show + @classmethod + def _get_controllers(self): + """Get the list of HP RAID controllers + + Returns list of controllers' slot ids + """ + + output = subprocess.check_output(['ssacli', 'ctrl', 'all', 'show']) + controllers = [] + for line in output.split('\n'): + if not line: + continue + + # Smart Array P410 in Slot 4 (sn: PACCRID12490C7R) + re_slot = re.match('Smart\sArray\s[a-zA-Z0-9]+\sin\sSlot\s(\d+)', + line) + if re_slot: + slot = str(re_slot.groups()[0]) + controllers.append(slot) + + return controllers + + # ssacli ctrl slot=4 pd all show + def _get_pd_in_controllers(self): + + """Get physical drives in each controller + + Return: dictionary where key is controller's id and value is a list + of physical drives managed by this controller + """ + + mapping = {} + for controller in self._get_controllers(): + mapping[controller] = [] + output = subprocess.check_output(['ssacli', 'ctrl', + 'slot={}'.format(controller), + 'pd', 'all', 'show']) + + # physicaldrive 2I:1:1 (port 2I:box 1:bay 1, SAS HDD, 300 GB, OK) + for line in output.split('\n'): + re_pd = re.match('physicaldrive\s([A-Z0-9]+:\d+:\d+)', + line.strip()) + if re_pd: + mapping[controller].append(re_pd.groups()[0]) + + return mapping + + # ssacli ctrl slot=4 ld all show + @classmethod + def _get_array_to_ld(self): + + """Get logical drives and their array identifier + + Return: dictionary with ld id and its array identifier + """ + + mapping = {} + for controller in self._get_controllers(): + mapping[controller] = {} + array = None + output = subprocess.check_output(['ssacli', 'ctrl', + 'slot={}'.format(controller), + 'ld', 'all', 'show']) + + for line in output.split('\n'): + # Array A + # logicaldrive 1 (558.7 GB, RAID 5, OK) + re_array = re.match('Array\s([A-Z])', line.strip()) + if re_array: + array = re_array.groups()[0] + continue + + re_id = re.match('logicaldrive\s(\d+) ', line.strip()) + if re_id and array: + ld_id = re_id.groups()[0] + mapping[controller][array] = str(ld_id) + array = None + continue + + return mapping + + @classmethod + def _get_pds_in_lds(self): + """Get physical drives that logical drive consists of""" + + mapping = {} + lds = self._get_array_to_ld() + + for controller in self._get_controllers(): + mapping[controller] = {} + output = subprocess.check_output(['ssacli', 'ctrl', + 'slot={}'.format(controller), + 'pd', 'all', 'show']) + + array = ld = None + # physicaldrive 2I:1:1 (port 2I:box 1:bay 1, SAS HDD, 300 GB, OK) + for line in output.split('\n'): + # Array A + # logicaldrive 1 (558.7 GB, RAID 5, OK) + re_array = re.match('Array\s([A-Z])', line.strip()) + if re_array: + array = re_array.groups()[0] + ld = lds[controller][array] + mapping[controller][ld] = [] + continue + + # If PD is not in Array it can be unassigned + elif line.strip() == self.UNASSIGNED: + ld = self.UNASSIGNED + mapping[controller][ld] = [] + continue + + re_pd = re.match('physicaldrive\s([A-Z0-9]+:\d+:\d+)', + line.strip()) + + if re_pd and array: + pd = re_pd.groups()[0] + mapping[controller][ld].append(pd) + continue + + return mapping + + # ssacli ctrl slot=4 create type=ld drives=2I:1:1,2I:1:2,2I:1:3 raid=5 + # logicaldrivelabel=raid5 + def _make_raid(self, pds, raidtype, stripe_size, controller, label, + pds_in_lds): + """Make RAID""" + if raidtype == 10: + raidtype = '1+0' + + for pd in pds: + ld = [ld for ld in pds_in_lds[controller] if pd in + pds_in_lds[controller][ld]] + if len(ld) != 1: + raise exceptions.EnvError('Physical Disk {} is not a' + ' JBOD'.format(pd)) + + # we assume all disks are JBOD(single disk RAID0) + # so we delete them to be able to made a + # new RAID from those disks + self._delete_raid(ld[0], controller) + + subprocess.check_call(['ssacli', 'ctrl', 'slot={}'.format(controller), + 'create', 'type=ld', + 'drives={}'.format(",".join(pds)), + 'raid={}'.format(raidtype), + 'logicaldrivelabel={}'.format(label), + 'stripsize={}'.format(stripe_size)]) + + # ssacli ctrl slot=4 ld 1 delete forced + @classmethod + def _delete_raid(self, ld, controller): + """Delete RAID""" + subprocess.check_call(['ssacli', 'ctrl', 'slot={}'.format(controller), + 'ld', ld, 'delete', 'forced']) + + # ssacli ctrl slot=4 create type=arrayr0 drives=2I:1:1,2I:1:2,2I:1:3 + @classmethod + def _make_disks_jbod(self, pds, controller): + """Make each disk RAID0""" + subprocess.check_call(['ssacli', 'ctrl', 'slot={}'.format(controller), + 'create', 'type=arrayr0', + 'drives={}'.format(','.join(pds))]) + + @classmethod + def _get_lds_info(self): + + """Get logical drives and their array identifier + + Return: dictionary with ld id and its array identifier + """ + mapping = {} + pds = self._get_pds_in_lds() + for controller in self._get_controllers(): + mapping[controller] = {} + ld_id = disk = label = raidtype = None + output = subprocess.check_output(['ssacli', 'ctrl', + 'slot={}'.format(controller), + 'ld', 'all', 'show', 'detail']) + + for line in output.split('\n'): + # Array A + # logicaldrive 1 (558.7 GB, RAID 5, OK) + + re_id = re.match('Logical\sDrive:\s(\d+)', line.strip()) + if re_id: + ld_id = re_id.groups()[0] + mapping[controller][ld_id] = {} + # label is not mandatory, default it to None + mapping[controller][ld_id]['label'] = None + disks_num = len(pds[controller][ld_id]) + mapping[controller][ld_id]['disks_num'] = disks_num + continue + + if not ld_id: + continue + + re_raidtype = re.match('Fault\sTolerance:\s([0-9\+]+)', + line.strip()) + if re_raidtype: + raidtype = re_raidtype.groups()[0] + if raidtype == '1+0': + raidtype = 10 + + mapping[controller][ld_id]['raidtype'] = int(raidtype) + continue + + re_disk = re.match('Disk\sName:\s([\/a-z]+)', line.strip()) + if re_disk: + disk = re_disk.groups()[0] + mapping[controller][ld_id]['disk'] = disk + continue + + re_label = re.match('Logical\sDrive\sLabel:\s([0-9a-zA-Z]+)', + line.strip()) + if re_label: + label = re_label.groups()[0] + mapping[controller][ld_id]['label'] = label + continue + + return mapping + + def validate_conf(self): + """Validate the contents of self.conf. + + Raises: ironic_lib.system_installer.exceptions.ConfError. + """ + for raid in self.conf.keys(): + raidtype = self.conf[raid]['raidtype'] + # logical disks to make RAID from + partitions = self.conf[raid]['partitions'] + if not tools.validate_raid(raidtype, partitions): + raise exceptions.ConfError('Raid {} cannot be created,' + 'too few disks specified or its ' + 'number is not' + ' even'.format(raidtype)) + + stripe_size = int(self.conf[raid].get('stripe_size', 0)) + if stripe_size and stripe_size not in [64, 128, 256, 512, 1024]: + raise exceptions.ConfError('Unsupported stripe_size:' + ' {}'.format(stripe_size)) + + def validate_env(self): + + """Validate environment, check available commands and devices""" + # check if there is ssacli binary + subprocess.check_call(['which', 'ssacli']) + # check if this machine has any HP controller + if len(self._get_controllers()) < 1: + raise exceptions.EnvError('No HP RAID controller found') + + def get_src_names(self): + """Return a list of source device/filesystem names.""" + src = [] + for raid in self.conf.keys(): + src.extend(self.conf[raid]['partitions']) + + return src + + def get_dst_names(self): + """Return a list of device/filesystem names that will be created.""" + return [raid for raid in self.conf.keys()] + + def _get_disk_with_label(self, lds, raid, controller): + """Return physical disk with specified label""" + for ld in lds[controller]: + if lds[controller][ld]['label'] == raid: + return lds[controller][ld]['disk'] + + raise exceptions.EnvError('Couldnt find disk with label' + ' {}'.format(raid)) + + @classmethod + def clean_disks(self, excluded_devices): + + """Clean all raids except for excluded ones. + + Create single disk RAID0 arrays -> JBOD for HP out of all physical + disks that are not part of any raids. + """ + + lds = self._get_lds_info() + pds = self._get_pds_in_lds() + for ctrl in lds: + # if there are any Unassigned disks in controller changed them to + # JBOD + if self.UNASSIGNED in pds[ctrl]: + self._make_disks_jbod(pds[ctrl][self.UNASSIGNED], ctrl) + + for ld in lds[ctrl]: + # skip cleaning raids in excluded_devices + if lds[ctrl][ld]['disk'] in excluded_devices.values(): + continue + + raidtype = lds[ctrl][ld]['raidtype'] + disks_num = lds[ctrl][ld]['disks_num'] + + # skip disks that are already JBOD + if raidtype == 0 and disks_num == 1: + continue + + LOG.debug('Deleteing raid {} on ctrl {}'.format(ld, ctrl)) + # delete raid + self._delete_raid(ld, ctrl) + # transform disks that made ld to JBOD + LOG.debug('Making disks: {} on ctrl' + ' JBOD'.format(pds[ctrl][ld], ctrl)) + self._make_disks_jbod(pds[ctrl][ld], ctrl) @staticmethod def is_hpraid_controller(): - return False + try: + subprocess.check_call(['which', 'ssacli']) + subprocess.check_call(['ssacli', 'ctrl', 'all', 'show']) + except subprocess.CalledProcessError: + return False + + return True + + def setup_disks(self, devices): + """Format disks or setup RAID/LVM. Return created devices dict.""" + # retrieve mapping between physical disks and adapter:enc pair + devices.update(partitionsetup.PartitionSetup(self.conf_part) + .get_not_partitioned_candidates()) + + created_raids = {} + raids = [raid for raid in self.conf.keys() + if raid not in devices.keys()] + + lds = self._get_lds_info() + stripe_size = self.default_stripe_size + + for raid in raids: + raidtype = self.conf[raid]['raidtype'] + # get stripe size + if self.conf[raid].get('stripe_size'): + stripe_size = int(self.conf[raid].get('stripe_size')) + + # logical disks to make RAID from + partitions = self.conf[raid]['partitions'] + # retrieve controller for disks and validate if all of them are on + # one controller + controller = tools.get_controller_for_disks(partitions, devices, + lds) + # get physical drives IDs + pds_in_lds = self._get_pds_in_lds() + pds = tools.get_pds_for_raid(partitions, devices, controller, lds, + pds_in_lds) + # make raid + self._make_raid(pds, raidtype, stripe_size, controller, raid, + pds_in_lds) + # reread information about Logical Drives + lds = self._get_lds_info() + created_raid = self._get_disk_with_label(lds, raid, controller) + created_raids[raid] = created_raid + + devices.update(created_raids) + return devices diff --git a/ironic_lib/tests/examples/ssacli_outputs/ld_show_detail.txt b/ironic_lib/tests/examples/ssacli_outputs/ld_show_detail.txt new file mode 100644 index 0000000..0175ba1 --- /dev/null +++ b/ironic_lib/tests/examples/ssacli_outputs/ld_show_detail.txt @@ -0,0 +1,44 @@ + +Smart Array P410 in Slot 4 + + Array A + + Logical Drive: 1 + Size: 558.7 GB + Fault Tolerance: 5 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 512 KB + Status: OK + Caching: Enabled + Parity Initialization Status: Queued + Unique Identifier: 600508B1001C7EAC6D72CA90F88CF41E + Disk Name: /dev/sda + Mount Points: None + Logical Drive Label: raid5 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array B + + Logical Drive: 4 + Size: 279.4 GB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + Caching: Enabled + Unique Identifier: 600508B1001C6832A52936CC3600547F + Disk Name: /dev/sdd + Mount Points: None + Drive Type: Data + LD Acceleration Method: Controller Cache + + + diff --git a/ironic_lib/tests/test_hpraid.py b/ironic_lib/tests/test_hpraid.py new file mode 100644 index 0000000..07745c2 --- /dev/null +++ b/ironic_lib/tests/test_hpraid.py @@ -0,0 +1,457 @@ +# Copyright (c) 2018 Intel Corporation +# +# 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 subprocess +import unittest +try: + from unittest import mock +except ImportError: + import mock + +import subprocess +import yaml + +from ironic_lib.system_installer import exceptions +from ironic_lib.system_installer import HpSsaCliSetup +from ironic_lib.system_installer import partitionsetup + + +class HpSsaCliSetupTest(unittest.TestCase): + + @mock.patch('subprocess.check_call') + def setUp(self, call): + with open('ironic_lib/tests/examples/megacli.yml') as f: + conf = yaml.load(f) + self.hpraid = HpSsaCliSetup(conf['disk_config']) + + @mock.patch('subprocess.check_output') + def test_get_controllers_one_controller(self, exe): + exe.return_value = '\n'.join(['', 'Smart Array P410 in Slot 4' + '(sn: PACCRID12490C7R)', '']) + self.assertListEqual(['4'], self.hpraid._get_controllers()) + exe.assert_called_once_with('ssacli ctrl all show'.split()) + + @mock.patch('subprocess.check_output') + def test_get_controllers_couple_controllers(self, exe): + exe.return_value = '\n'.join(['', 'Smart Array P410 in Slot 4' + '(sn: PACCRID12490C7R)', + 'Smart Array P4200 in Slot 6' + '(sn: PACCRID10C7R)', + 'Smart Array P40 in Slot 8' + '(sn: PACCR0C7R)', '']) + self.assertListEqual(['4', '6', '8'], self.hpraid._get_controllers()) + exe.assert_called_once_with('ssacli ctrl all show'.split()) + + @mock.patch('subprocess.check_output') + def test_get_controllers_no_controllers(self, exe): + exe.return_value = '\n'.join(['', '']) + self.assertListEqual([], self.hpraid._get_controllers()) + exe.assert_called_once_with('ssacli ctrl all show'.split()) + + @mock.patch.object(HpSsaCliSetup, '_get_controllers') + @mock.patch('subprocess.check_output') + def test_get_pd_in_controllers(self, exe, controllers): + pd_output = ['', 'Smart Array P410 in Slot 4', '', + ' Array A', '', + ' physicaldrive 2I:1:1 (port 2I:box 1:bay' + ' 1, SAS HDD, 300 GB, OK)', + ' physicaldrive 2I:1:2 (port 2I:box 1:bay' + '2, SAS HDD, 300 GB, OK)', + ' physicaldrive 2I:1:3 (port 2I:box 1:bay' + '3, SAS HDD, 300 GB, OK)', + ' physicaldrive 2I:1:4 (port 2I:box 1:bay' + '4, SAS HDD, 300 GB, OK)'] + exe.return_value = '\n'.join(pd_output) + + controllers.return_value = ['4'] + self.assertDictEqual({'4': ['2I:1:1', '2I:1:2', '2I:1:3', '2I:1:4']}, + self.hpraid._get_pd_in_controllers()) + exe.assert_called_once_with('ssacli ctrl slot=4 pd all show'.split()) + + @mock.patch.object(HpSsaCliSetup, '_get_controllers') + @mock.patch('subprocess.check_output') + def test_get_pd_in_controllers_empty(self, exe, controllers): + exe.return_value = '\n'.join(['', 'Smart Array P410 in Slot 4', '', + ' Array A', '']) + + controllers.return_value = ['4'] + self.assertDictEqual({'4': []}, + self.hpraid._get_pd_in_controllers()) + exe.assert_called_once_with('ssacli ctrl slot=4 pd all show'.split()) + + @mock.patch.object(HpSsaCliSetup, '_get_controllers') + @mock.patch('subprocess.check_output') + def test_get_array_to_ld(self, exe, controllers): + controllers.return_value = ['4'] + ld_output = ['', 'Smart Array P410 in Slot 4', '', ' Array A', '', + ' logicaldrive 1 (558.7 GB, RAID 5, OK)', '', + ' Array B', '', + ' logicaldrive 4 (279.4 GB, RAID 0, OK)', ''] + + exe.return_value = '\n'.join(ld_output) + mapping = {'4': {'A': '1', 'B': '4'}} + self.assertDictEqual(mapping, self.hpraid._get_array_to_ld()) + + @mock.patch.object(HpSsaCliSetup, '_get_controllers') + @mock.patch('subprocess.check_output') + def test_get_array_to_ld_empty(self, exe, controllers): + controllers.return_value = ['4'] + ld_output = ['', 'Smart Array P410 in Slot 4', '', ''] + + exe.return_value = '\n'.join(ld_output) + mapping = {'4': {}} + self.assertDictEqual(mapping, self.hpraid._get_array_to_ld()) + + @mock.patch.object(HpSsaCliSetup, '_get_array_to_ld') + @mock.patch.object(HpSsaCliSetup, '_get_controllers') + @mock.patch('subprocess.check_output') + def test_get_pds_in_lds(self, exe, controllers, lds): + controllers.return_value = ['4'] + lds.return_value = {'4': {'A': '1', 'B': '4'}} + pd_output = ['', 'Smart Array P410 in Slot 4', '', ' Array A', '', + ' physicaldrive 2I:1:1 (port 2I:box 1:bay 1, SAS ' + 'HDD, 300 GB, OK)', + ' physicaldrive 2I:1:2 (port 2I:box 1:bay 1, SAS ' + 'HDD, 300 GB, OK)', + ' physicaldrive 2I:1:3 (port 2I:box 1:bay 1, SAS ' + 'HDD, 300 GB, OK)', '', ' Array B', '', + ' physicaldrive 2I:1:4 (port 2I:box 1:bay 1, SAS ' + 'HDD, 300 GB, OK)'] + exe.return_value = '\n'.join(pd_output) + expected_output = {'4': {'1': ['2I:1:1', '2I:1:2', '2I:1:3'], + '4': ['2I:1:4']}} + self.assertDictEqual(expected_output, self.hpraid._get_pds_in_lds()) + exe.assert_called_once_with('ssacli ctrl slot=4 pd all show'.split()) + + @mock.patch.object(HpSsaCliSetup, '_get_array_to_ld') + @mock.patch.object(HpSsaCliSetup, '_get_controllers') + @mock.patch('subprocess.check_output') + def test_get_pds_in_lds_unassigned(self, exe, controllers, lds): + controllers.return_value = ['4'] + lds.return_value = {'4': {'A': '1', 'B': '4'}} + pd_output = ['', 'Smart Array P410 in Slot 4', '', ' Array A', '', + ' physicaldrive 2I:1:4 (port 2I:box 1:bay 1, SAS ' + 'HDD, 300 GB, OK)', + '', ' Unassigned', '', + ' physicaldrive 2I:1:1 (port 2I:box 1:bay 1, SAS ' + 'HDD, 300 GB, OK)', + ' physicaldrive 2I:1:2 (port 2I:box 1:bay 1, SAS ' + 'HDD, 300 GB, OK)', + ' physicaldrive 2I:1:3 (port 2I:box 1:bay 1, SAS ' + 'HDD, 300 GB, OK)'] + exe.return_value = '\n'.join(pd_output) + expected_output = {'4': {'Unassigned': ['2I:1:1', '2I:1:2', '2I:1:3'], + '1': ['2I:1:4']}} + self.assertDictEqual(expected_output, self.hpraid._get_pds_in_lds()) + exe.assert_called_once_with('ssacli ctrl slot=4 pd all show'.split()) + + @mock.patch.object(HpSsaCliSetup, '_delete_raid') + @mock.patch('subprocess.check_call') + def test_make_raid(self, exe, delete): + pds_in_lds = {'4': {'1': ['2I:1:1'], + '4': ['2I:1:2'], + '2': ['2I:1:3']}} + self.hpraid._make_raid(['2I:1:1', '2I:1:2', '2I:1:3'], 5, 64, '4', + 'raid5', pds_in_lds) + delete.assert_any_call('1', '4') + delete.assert_any_call('4', '4') + delete.assert_any_call('2', '4') + exe.assert_called_once_with('ssacli ctrl slot=4 create type=ld' + ' drives=2I:1:1,2I:1:2,2I:1:3' + ' raid=5 logicaldrivelabel=raid5' + ' stripsize=64'.split()) + + @mock.patch.object(HpSsaCliSetup, '_delete_raid') + @mock.patch('subprocess.check_call') + def test_make_raid_10(self, exe, delete): + pds_in_lds = {'4': {'1': ['2I:1:1'], + '4': ['2I:1:2'], + '7': ['2I:1:4'], + '2': ['2I:1:3']}} + self.hpraid._make_raid(['2I:1:1', '2I:1:2', '2I:1:3', '2I:1:4'], 10, + 64, '4', 'raid10', pds_in_lds) + delete.assert_any_call('1', '4') + delete.assert_any_call('4', '4') + delete.assert_any_call('2', '4') + delete.assert_any_call('7', '4') + exe.assert_called_once_with('ssacli ctrl slot=4 create type=ld' + ' drives=2I:1:1,2I:1:2,2I:1:3,2I:1:4' + ' raid=1+0' + ' logicaldrivelabel=raid10' + ' stripsize=64'.split()) + + @mock.patch('subprocess.check_call') + def test_delete_raid(self, exe): + self.hpraid._delete_raid('1', '4') + exe.assert_called_once_with('ssacli ctrl slot=4 ld 1 delete' + ' forced'.split()) + + @mock.patch('subprocess.check_call') + def test_make_disk_jbod(self, exe): + self.hpraid._make_disks_jbod(['2I:1:1', '2I:1:2', '2I:1:3'], '4') + exe.assert_called_once_with('ssacli ctrl slot=4 create type=arrayr0' + ' drives=2I:1:1,2I:1:2,2I:1:3'.split()) + + # ssacli ctrl slot=4 ld all show detail + @mock.patch.object(HpSsaCliSetup, '_get_pds_in_lds') + @mock.patch.object(HpSsaCliSetup, '_get_controllers') + @mock.patch('subprocess.check_output') + def test_get_lds_info(self, exe, controllers, pds): + pds.return_value = {'4': {'1': ['2I:1:1', '2I:1:2', '2I:1:3'], + '4': ['2I:1:4']}} + controllers.return_value = ['4'] + with open('ironic_lib/tests/examples/' + 'ssacli_outputs/ld_show_detail.txt') as f: + exe.return_value = f.read() + + expected_output = {'4': {'1': {'label': 'raid5', 'disk': '/dev/sda', + 'raidtype': 5, 'disks_num': 3}, + '4': {'label': None, 'disk': '/dev/sdd', + 'raidtype': 0, 'disks_num': 1}}} + self.assertDictEqual(expected_output, self.hpraid._get_lds_info()) + exe.assert_called_once_with('ssacli ctrl slot=4 ld all show' + ' detail'.split()) + + @mock.patch.object(HpSsaCliSetup, '_get_controllers') + @mock.patch('subprocess.check_call') + def test_validate_env(self, exe, controllers): + controllers.return_value = ['4'] + self.hpraid.validate_env() + exe.assert_called_once_with('which ssacli'.split()) + + @mock.patch.object(HpSsaCliSetup, '_get_controllers') + @mock.patch('subprocess.check_call') + def test_validate_env_fails(self, exe, controllers): + controllers.return_value = [] + with self.assertRaises(exceptions.EnvError): + self.hpraid.validate_env() + + @mock.patch('ironic_lib.system_installer.tools.validate_raid') + def test_validate_conf(self, raid): + self.hpraid.validate_conf() + raid.assert_called_once_with(1, ['disk0', 'disk1']) + + @mock.patch('ironic_lib.system_installer.tools.validate_raid') + def test_validate_conf_fail(self, raid): + self.hpraid.conf['raid0']['stripe_size'] = 100 + with self.assertRaises(exceptions.ConfError): + self.hpraid.validate_conf() + raid.assert_called_once_with(1, ['disk0', 'disk1']) + + @mock.patch('ironic_lib.system_installer.tools.validate_raid') + def test_validate_conf_512kB(self, raid): + self.hpraid.conf['raid0']['stripe_size'] = 512 + self.hpraid.validate_conf() + raid.assert_called_once_with(1, ['disk0', 'disk1']) + + def test_get_src_names(self): + self.assertEqual(['disk0', 'disk1'], self.hpraid.get_src_names()) + + def test_get_dst_names(self): + self.assertEqual(['raid0'], self.hpraid.get_dst_names()) + + def test_get_disk_with_label(self): + lds = {'4': {'1': {'label': 'raid5', 'disk': '/dev/sda', 'raidtype': 5, + 'disks_num': 3}, + '4': {'label': None, 'disk': '/dev/sdd', 'raidtype': 0, + 'disks_num': 2}}} + raid = 'raid5' + controller = '4' + + self.assertEqual('/dev/sda', + self.hpraid._get_disk_with_label(lds, raid, + controller)) + + def test_get_disk_with_label_fail(self): + lds = {'4': {'1': {'label': 'raid5', 'disk': '/dev/sda', 'raidtype': 5, + 'disks_num': 3}, + '4': {'label': None, 'disk': '/dev/sdd', 'raidtype': 0, + 'disks_num': 2}}} + raid = 'raid1' + controller = '4' + + with self.assertRaises(exceptions.EnvError): + self.assertEqual('/dev/sda', + self.hpraid._get_disk_with_label(lds, raid, + controller)) + + @mock.patch.object(HpSsaCliSetup, '_get_pds_in_lds') + @mock.patch.object(HpSsaCliSetup, '_get_disk_with_label') + @mock.patch.object(HpSsaCliSetup, '_make_raid') + @mock.patch('ironic_lib.system_installer.tools.get_pds_for_raid') + @mock.patch('ironic_lib.system_installer.tools.get_controller_for_disks') + @mock.patch.object(partitionsetup.PartitionSetup, + 'get_not_partitioned_candidates') + @mock.patch.object(HpSsaCliSetup, '_get_lds_info') + def test_setup_disk(self, lds, part, controller, pds, make, disk, + pds_in_lds): + lds.side_effect = [{'4': {'1': {'label': None, 'disk': '/dev/sda', + 'raidtype': 0, 'disks_num': 1}, + '4': {'label': None, 'disk': '/dev/sdb', + 'raidtype': 0, 'disks_num': 1}}}, + {'4': {'1': {'label': 'raid0', 'disk': '/dev/sda', + 'raidtype': 1, 'disks_num': 2}}}] + part.return_value = {"disk0": "/dev/sda", "disk1": "/dev/sdb"} + controller.return_value = '4' + pds_in_lds_dict = {'0': {'1': ['2I:1:1'], + '4': ['2I:1:2']}} + pds_in_lds.return_value = pds_in_lds_dict + pds.return_value = ['2I:1:1', '2I:1:2'] + disk.return_value = '/dev/sda' + devices = {'raid0': '/dev/sda', + 'disk0': '/dev/sda', + 'disk1': '/dev/sdb'} + self.assertDictEqual(devices, self.hpraid.setup_disks({})) + make.assert_called_once_with(['2I:1:1', '2I:1:2'], 1, 64, '4', 'raid0', + pds_in_lds_dict) + + @mock.patch.object(HpSsaCliSetup, '_get_pds_in_lds') + @mock.patch.object(HpSsaCliSetup, '_get_disk_with_label') + @mock.patch.object(HpSsaCliSetup, '_make_raid') + @mock.patch('ironic_lib.system_installer.tools.get_pds_for_raid') + @mock.patch('ironic_lib.system_installer.tools.get_controller_for_disks') + @mock.patch.object(partitionsetup.PartitionSetup, + 'get_not_partitioned_candidates') + @mock.patch.object(HpSsaCliSetup, '_get_lds_info') + def test_setup_disk_raid_in_dev(self, lds, part, controller, pds, make, + disk, pds_in_lds): + with open('ironic_lib/tests/examples/megacli_2_raids.yml') as f: + conf = yaml.load(f) + lds.side_effect = [{'4': {'1': {'label': None, 'disk': '/dev/sda', + 'raidtype': 0, 'disks_num': 1}, + '4': {'label': None, 'disk': '/dev/sdc', + 'raidtype': 0, 'disks_num': 1}, + '2': {'label': None, 'disk': '/dev/sdd', + 'raidtype': 0, 'disks_num': 1}, + '3': {'label': None, 'disk': '/dev/sde', + 'raidtype': 0, 'disks_num': 1}, + '6': {'label': 'raid1', 'disk': '/dev/sdb', + 'raidtype': 1, 'disks_num': 2}}}, + {'4': {'1': {'label': 'raid0', 'disk': '/dev/sda', + 'raidtype': 1, 'disks_num': 2}, + '2': {'label': None, 'disk': '/dev/sdd', + 'raidtype': 0, 'disks_num': 1}, + '4': {'label': None, 'disk': '/dev/sdc', + 'raidtype': 0, 'disks_num': 1}, + '6': {'label': 'raid1', 'disk': '/dev/sdb', + 'raidtype': 1, 'disks_num': 2}}}] + + hpraid = HpSsaCliSetup(conf['disk_config']) + controller.return_value = '4' + pds_in_lds_dict = {'0': {'1': ['2I:1:1'], + '4': ['2I:1:3'], + '2': ['2I:1:5'], + '3': ['2I:1:2'], + '4': ['2I:1:7', '2I:1:9']}} + pds_in_lds.return_value = pds_in_lds_dict + disks = {"disk0": "/dev/sda", "disk1": "/dev/sde", + 'disk2': '/dev/sdc', 'disk3': '/dev/sdd'} + part.return_value = disks + devices = {'raid0': '/dev/sda', 'raid1': '/dev/sdb', + 'disk0': '/dev/sda', 'disk1': '/dev/sde', + 'disk2': '/dev/sdc', 'disk3': '/dev/sdd'} + disk.return_value = '/dev/sda' + pds.return_value = ['2I:1:1', '2I:1:2'] + self.assertEqual(hpraid.setup_disks({'raid1': '/dev/sdb'}), devices) + make.assert_called_once_with(['2I:1:1', '2I:1:2'], 1, 64, '4', 'raid0', + pds_in_lds_dict) + + @mock.patch.object(HpSsaCliSetup, '_get_pds_in_lds') + @mock.patch.object(HpSsaCliSetup, '_get_pd_in_controllers') + @mock.patch.object(HpSsaCliSetup, '_get_lds_info') + @mock.patch.object(HpSsaCliSetup, '_make_disks_jbod') + @mock.patch.object(HpSsaCliSetup, '_delete_raid') + def test_clean_disks(self, delete, jbod, lds, pds_in_ctrl, pds_in_lds): + lds.return_value = {'4': {'1': {'label': 'raid0', 'disk': '/dev/sda', + 'raidtype': 0, 'disks_num': 3}, + '4': {'label': 'raid1', 'disk': '/dev/sdb', + 'raidtype': 1, 'disks_num': 2}}} + pds_in_ctrl.return_value = {'4': ['2I:1:1', '2I:1:2', '2I:1:3', + '2I:1:4', '2I:1:5']} + pds_in_lds.return_value = {'4': {'1': ['2I:1:1', '2I:1:2', '2I:1:3'], + '4': ['2I:1:4', '2I:1:5']}} + self.hpraid.clean_disks({}) + delete.assert_any_call('1', '4') + delete.assert_any_call('4', '4') + jbod.assert_any_call(['2I:1:1', '2I:1:2', '2I:1:3'], '4') + jbod.assert_any_call(['2I:1:4', '2I:1:5'], '4') + + @mock.patch.object(HpSsaCliSetup, '_get_pds_in_lds') + @mock.patch.object(HpSsaCliSetup, '_get_pd_in_controllers') + @mock.patch.object(HpSsaCliSetup, '_get_lds_info') + @mock.patch.object(HpSsaCliSetup, '_make_disks_jbod') + @mock.patch.object(HpSsaCliSetup, '_delete_raid') + def test_clean_disks_preserve(self, delete, jbod, lds, pds_in_ctrl, + pds_in_lds): + lds.return_value = {'4': {'1': {'label': 'raid0', 'disk': '/dev/sda', + 'raidtype': 0, 'disks_num': 3}, + '4': {'label': 'raid1', 'disk': '/dev/sdb', + 'raidtype': 1, 'disks_num': 2}}} + pds_in_ctrl.return_value = {'4': ['2I:1:1', '2I:1:2', '2I:1:3', + '2I:1:4', '2I:1:5']} + pds_in_lds.return_value = {'4': {'1': ['2I:1:1', '2I:1:2', '2I:1:3'], + '4': ['2I:1:4', '2I:1:5']}} + preserved = {'raid0': '/dev/sda'} + self.hpraid.clean_disks(preserved) + delete.assert_called_once_with('4', '4') + jbod.assert_called_once_with(['2I:1:4', '2I:1:5'], '4') + + @mock.patch.object(HpSsaCliSetup, '_get_pds_in_lds') + @mock.patch.object(HpSsaCliSetup, '_get_pd_in_controllers') + @mock.patch.object(HpSsaCliSetup, '_get_lds_info') + @mock.patch.object(HpSsaCliSetup, '_make_disks_jbod') + @mock.patch.object(HpSsaCliSetup, '_delete_raid') + def test_clean_disks_preserve_omit_jbod(self, delete, jbod, lds, + pds_in_ctrl, pds_in_lds): + lds.return_value = {'4': {'1': {'label': 'raid0', 'disk': '/dev/sda', + 'raidtype': 0, 'disks_num': 1}, + '4': {'label': 'raid1', 'disk': '/dev/sdb', + 'raidtype': 1, 'disks_num': 2}}} + pds_in_ctrl.return_value = {'4': ['2I:1:1', '2I:1:2', '2I:1:3']} + pds_in_lds.return_value = {'4': {'1': ['2I:1:1'], + '4': ['2I:1:2', '2I:1:3']}} + self.hpraid.clean_disks({}) + delete.assert_called_once_with('4', '4') + jbod.assert_called_once_with(['2I:1:2', '2I:1:3'], '4') + + @mock.patch.object(HpSsaCliSetup, '_get_pds_in_lds') + @mock.patch.object(HpSsaCliSetup, '_get_pd_in_controllers') + @mock.patch.object(HpSsaCliSetup, '_get_lds_info') + @mock.patch.object(HpSsaCliSetup, '_make_disks_jbod') + @mock.patch.object(HpSsaCliSetup, '_delete_raid') + def test_clean_disks_preserve_unassigned(self, delete, jbod, lds, + pds_in_ctrl, pds_in_lds): + lds.return_value = {'4': {}} + pds_in_ctrl.return_value = {'4': ['2I:1:1', '2I:1:2', '2I:1:3']} + pds_in_lds.return_value = {'4': {'Unassigned': ['2I:1:1', '2I:1:2', + '2I:1:3']}} + preserved = {} + self.hpraid.clean_disks(preserved) + delete.assert_not_called() + jbod.assert_called_once_with(['2I:1:1', '2I:1:2', '2I:1:3'], '4') + + @mock.patch('subprocess.check_call') + def test_is_hpraid_controller(self, exe): + self.assertTrue(self.hpraid.is_hpraid_controller()) + + @mock.patch('subprocess.check_call') + def test_is_hpraid_controller_fails_which(self, exe): + exe.side_effect = [subprocess.CalledProcessError("1", "ssacli"), ''] + self.assertFalse(self.hpraid.is_hpraid_controller()) + + @mock.patch('subprocess.check_call') + def test_is_hpraid_controller_fails_ctrl(self, exe): + exe.side_effect = ['', subprocess.CalledProcessError("1", "ssacli")] + self.assertFalse(self.hpraid.is_hpraid_controller()) -- 2.16.2