diff --git a/ironic-lib/0006-LVM-Setup-implementation.patch b/ironic-lib/0006-LVM-Setup-implementation.patch new file mode 100644 index 0000000..7a646c5 --- /dev/null +++ b/ironic-lib/0006-LVM-Setup-implementation.patch @@ -0,0 +1,918 @@ +From d14c463e95c0db7b1c5e9b6745565a134d55e355 Mon Sep 17 00:00:00 2001 +From: PiotrProkop +Date: Fri, 29 Dec 2017 11:30:43 +0100 +Subject: [PATCH 06/11] LVM Setup implementation + +Co-Authored-By: Piotr Prokop +Co-Authored-By: Marta Mucek +--- + ironic_lib/system_installer/lvmsetup.py | 228 ++++++++++ + ironic_lib/tests/examples/example2.yml | 31 ++ + .../tests/examples/example_lvm_filesystem.yaml | 32 ++ + .../tests/examples/example_multiple_pvs.yaml | 33 ++ + .../tests/examples/example_with_si_format.yml | 38 ++ + ironic_lib/tests/test_lvm.py | 479 +++++++++++++++++++++ + 6 files changed, 841 insertions(+) + create mode 100644 ironic_lib/tests/examples/example2.yml + create mode 100644 ironic_lib/tests/examples/example_lvm_filesystem.yaml + create mode 100644 ironic_lib/tests/examples/example_multiple_pvs.yaml + create mode 100644 ironic_lib/tests/examples/example_with_si_format.yml + create mode 100644 ironic_lib/tests/test_lvm.py + +diff --git a/ironic_lib/system_installer/lvmsetup.py b/ironic_lib/system_installer/lvmsetup.py +index 890c95d..89f61c4 100644 +--- a/ironic_lib/system_installer/lvmsetup.py ++++ b/ironic_lib/system_installer/lvmsetup.py +@@ -12,8 +12,14 @@ + # implied. + # See the License for the specific language governing permissions and + # limitations under the License. ++import json ++import subprocess ++ ++import bitmath + + from ironic_lib.system_installer.base import Setup ++from ironic_lib.system_installer import exceptions ++from ironic_lib.system_installer import tools + + + class LvmSetup(Setup): +@@ -21,8 +27,230 @@ class LvmSetup(Setup): + + conf_key = 'lvm' + ++ SI_SUFFIXES = ['k', 'M', 'G', 'T', 'P'] ++ ++ def _get_size_in_bytes(self, device): ++ size = subprocess.check_output(['lsblk', '-b', '-o', 'SIZE', '-n', ++ str(device)]) ++ return int(size) ++ ++ def _get_pvs_size_in_bytes(self, pvs, devices): ++ size = bitmath.Byte(0) ++ for pv in pvs: ++ size += bitmath.Byte(self._get_size_in_bytes(devices[pv])) ++ return size ++ ++ # we rather use G as Gi, k as ki etc. ++ def _convert_formats(self, value): ++ if value[-1] in self.SI_SUFFIXES: ++ return value + "iB" ++ return value ++ ++ def _get_lvs_size_in_bytes(self, lvs): ++ size = bitmath.Byte(0) ++ for lv in lvs.keys(): ++ if lvs[lv].get('minsize'): ++ minsize = self._convert_formats(lvs[lv]['minsize']) ++ size += bitmath.parse_string_unsafe(minsize) ++ elif lvs[lv].get('size'): ++ if lvs[lv]['size'] == 'memsize': ++ size += bitmath.KiB(int(tools.get_memsize_kB())) ++ else: ++ converted_size = self._convert_formats(lvs[lv]['size']) ++ size += bitmath.parse_string_unsafe(converted_size) ++ return size ++ ++ def validate_lvm_size(self, devices): ++ for vg in self.conf.keys(): ++ pv_size = self._get_pvs_size_in_bytes(self.conf[vg]['PVs'], ++ devices) ++ lv_size = self._get_lvs_size_in_bytes(self.conf[vg]['LVs']) ++ if pv_size < lv_size: ++ msg = "LVs requires more space than PVs can offer." ++ raise exceptions.ConfError(msg) ++ ++ def validate_conf(self): ++ for vg in self.conf.keys(): ++ if '-' in vg: ++ msg = "VG name can't contain \"-\" symbol" ++ raise exceptions.ConfError(msg) ++ + def get_src_names(self): + return sum([list(v['PVs']) for v in self.conf.values()], []) + + def get_dst_names(self): + return sum([list(v['LVs']) for v in self.conf.values()], []) ++ ++ def _get_vg_names(self): ++ """Extract volume groups names""" ++ return list(self.conf.keys()) ++ ++ def _get_pv_names(self, devices): ++ """Extract physical volumes names""" ++ return list(devices.values()) ++ ++ def _get_pvs_of_vg(self, vg, devices): ++ """Get physical volumes of given volume group""" ++ return [devices[v] for v in list(self.conf[vg]['PVs'])] ++ ++ def _get_lvs_of_vg(self, vg): ++ """Get logical volumes of given volume group""" ++ return self.conf[vg]['LVs'] ++ ++ def _create_pvs(self, vg, devices): ++ """create physical volumes out of precreated partitions/disks""" ++ existing_pvs = self._get_pvs() ++ for pv in self._get_pvs_of_vg(vg, devices): ++ if pv not in existing_pvs: ++ # -ff because a physical volume belonging ++ # to an existing volume group can't be recreated ++ subprocess.check_call(['pvcreate', '-ff', pv]) ++ ++ def _create_vg(self, vg, devices): ++ existing_vgs = self._get_vgs() ++ if vg not in existing_vgs: ++ cmd = ["vgcreate", "-y", vg] ++ cmd.extend(self._get_pvs_of_vg(vg, devices)) ++ subprocess.check_call(cmd) ++ ++ def _create_lv(self, lv, vg, size, minsize=False): ++ cmd = ['lvcreate'] ++ if minsize: ++ cmd.extend(['-l', '{}%FREE'.format(size)]) ++ else: ++ cmd.extend(['-L', str(size)]) ++ ++ cmd.extend(['-y', '-Wy', '-Zy', '-n', lv, vg]) ++ subprocess.check_call(cmd) ++ ++ return "/dev/{vg}/{lv}".format(vg=vg, lv=lv) ++ ++ def _create_lvs(self, vg): ++ """Create logical volumes for given volume group""" ++ lvs = self._get_lvs_of_vg(vg) ++ # filter out lvs with minsize set ++ minsize_lvs = [v for v in lvs if lvs[v].get('minsize')] ++ size_lvs = [v for v in lvs if lvs[v].get('size')] ++ # create lvs with a predefined size ++ existing_lvs = self._get_lvs() ++ created_lvs = dict() ++ for lv in size_lvs: ++ size = lvs[lv]['size'] ++ if size == 'memsize': ++ size = "{}K".format(tools.get_memsize_kB()) ++ ++ if "/dev/{vg}/{lv}".format(vg=vg, lv=lv) not in existing_lvs: ++ created_lvs[lv] = self._create_lv(lv, vg, size) ++ ++ # divide all available memory between machines with minsize set ++ size = int(100 / len(minsize_lvs)) ++ for lv in minsize_lvs: ++ created_lvs[lv] = self._create_lv(lv, vg, size, True) ++ ++ return created_lvs ++ ++ def _get_vgs(self): ++ vgs_output = subprocess.check_output(['vgs', '--reportformat', 'json']) ++ vgs = json.loads(vgs_output) ++ vgs = vgs['report'][0]['vg'] ++ vgs_names = [] ++ for vg in vgs: ++ vgs_names.append(vg['vg_name']) ++ ++ return vgs_names ++ ++ @classmethod ++ def _remove_vgs(self, vgs_to_remove): ++ for vg in vgs_to_remove: ++ subprocess.check_call(['vgremove', '-y', vg]) ++ ++ def _get_pvs(self): ++ pvs_output = subprocess.check_output(['pvs', '--reportformat', 'json']) ++ pvs = json.loads(pvs_output) ++ pvs = pvs['report'][0]['pv'] ++ pvs_names = [] ++ for pv in pvs: ++ pvs_names.append(pv['pv_name']) ++ return pvs_names ++ ++ @classmethod ++ def _remove_pvs(self, pvs_to_remove): ++ for pv in pvs_to_remove: ++ subprocess.check_call(['pvremove', '-y', pv]) ++ ++ @classmethod ++ def _get_lvs(self): ++ lvs_output = subprocess.check_output(['lvs', '--reportformat', 'json']) ++ lvs = json.loads(lvs_output) ++ lvs = lvs['report'][0]['lv'] ++ lvs_names = [] ++ for lv in lvs: ++ lvs_names.append( ++ '/dev/{vg}/{lv}'.format(vg=lv['vg_name'], lv=lv['lv_name'])) ++ return lvs_names ++ ++ @classmethod ++ def _remove_lvs(self, lvs_to_remove): ++ for lv in lvs_to_remove: ++ subprocess.check_call(['lvremove', '-y', lv]) ++ ++ def _is_dev_lv(self, device): ++ try: ++ subprocess.check_call(['lvdisplay', device]) ++ except subprocess.CalledProcessError: ++ return False ++ ++ return True ++ ++ @classmethod ++ def _get_empty_vgs(self): ++ empty_vgs = [] ++ vgs_output = subprocess.check_output(['vgs', '--reportformat', 'json']) ++ vgs_json = json.loads(vgs_output) ++ for vg in vgs_json['report'][0]['vg']: ++ if vg['lv_count'] == '0': ++ empty_vgs.append(vg['vg_name']) ++ ++ return empty_vgs ++ ++ @classmethod ++ def _get_empty_pvs(self): ++ empty_pvs = [] ++ pvs_output = subprocess.check_output(['pvs', '--reportformat', 'json']) ++ pvs_json = json.loads(pvs_output) ++ for pv in pvs_json['report'][0]['pv']: ++ if pv['vg_name'] == "": ++ empty_pvs.append(pv['pv_name']) ++ ++ return empty_pvs ++ ++ @classmethod ++ def clean_disks(self, excluded_devices): ++ # removing lvs ++ lvs_to_remove = [] ++ for lv in self._get_lvs(): ++ if lv in excluded_devices.values(): ++ continue ++ lvs_to_remove.append(lv) ++ ++ if lvs_to_remove: ++ self._remove_lvs(lvs_to_remove) ++ # removing vgs ++ empty_vgs = self._get_empty_vgs() ++ if empty_vgs: ++ self._remove_vgs(empty_vgs) ++ # removing pvs ++ empty_pvs = self._get_empty_pvs() ++ if empty_pvs: ++ self._remove_pvs(empty_pvs) ++ ++ def setup_disks(self, devices): ++ self.validate_lvm_size(devices) ++ self.validate_conf() ++ lvs = dict() ++ for vg in self._get_vg_names(): ++ self._create_pvs(vg, devices) ++ self._create_vg(vg, devices) ++ lvs.update(self._create_lvs(vg)) ++ devices.update(lvs) ++ return devices +diff --git a/ironic_lib/tests/examples/example2.yml b/ironic_lib/tests/examples/example2.yml +new file mode 100644 +index 0000000..58f9299 +--- /dev/null ++++ b/ironic_lib/tests/examples/example2.yml +@@ -0,0 +1,31 @@ ++disk_config: ++ blockdev: ++ sda: ++ candidates: any ++ partitions: ++ d0p1: ++ size: 10G ++ d0p2: ++ minsize: 2G ++ lvm: ++ sys: ++ LVs: ++ home: ++ minsize: 1G ++ root: ++ size: 10G ++ PVs: ++ - d0p2 ++ tmp: ++ LVs: ++ tmp: ++ minsize: 512M ++ PVs: ++ - d0p1 ++ filesystems: ++ home: ++ mountpoint: /home ++ root: ++ mountpoint: / ++ tmp: ++ mountpoint: /tmp +diff --git a/ironic_lib/tests/examples/example_lvm_filesystem.yaml b/ironic_lib/tests/examples/example_lvm_filesystem.yaml +new file mode 100644 +index 0000000..8264e9f +--- /dev/null ++++ b/ironic_lib/tests/examples/example_lvm_filesystem.yaml +@@ -0,0 +1,32 @@ ++disk_config: ++ blockdev: ++ sda: ++ candidates: any ++ partitions: ++ d0p1: ++ size: 16G ++ d0p2: ++ minsize: 2G ++ lvm: ++ sys: ++ LVs: ++ home: ++ minsize: 1G ++ root: ++ size: 10G ++ swap: ++ size: 32G ++ var: ++ size: 4G ++ tmp: ++ size: 4G ++ PVs: ++ - d0p2 ++ filesystems: ++ d0p1: ++ label: /boot ++ mountpoint: /boot ++ home: ++ mountpoint: /home ++ root: ++ mountpoint: / +\ No newline at end of file +diff --git a/ironic_lib/tests/examples/example_multiple_pvs.yaml b/ironic_lib/tests/examples/example_multiple_pvs.yaml +new file mode 100644 +index 0000000..f9d22a1 +--- /dev/null ++++ b/ironic_lib/tests/examples/example_multiple_pvs.yaml +@@ -0,0 +1,33 @@ ++disk_config: ++ blockdev: ++ sda: ++ candidates: any ++ partitions: ++ d0p1: ++ size: 512M ++ d0p2: ++ minsize: 2G ++ lvm: ++ sys: ++ LVs: ++ home: ++ minsize: 1G ++ root: ++ size: 10G ++ swap: ++ size: 32G ++ var: ++ size: 4G ++ tmp: ++ size: 4G ++ PVs: ++ - d0p2 ++ - d0p1 ++ filesystems: ++ var: ++ label: boot ++ mountpoint: /boot ++ home: ++ mountpoint: /home ++ root: ++ mountpoint: / +diff --git a/ironic_lib/tests/examples/example_with_si_format.yml b/ironic_lib/tests/examples/example_with_si_format.yml +new file mode 100644 +index 0000000..c77bbb3 +--- /dev/null ++++ b/ironic_lib/tests/examples/example_with_si_format.yml +@@ -0,0 +1,38 @@ ++disk_config: ++ blockdev: ++ sda: ++ candidates: any ++ partitions: ++ d0p1: ++ size: 512MB ++ d0p2: ++ minsize: 2GB ++ lvm: ++ sys: ++ LVs: ++ home: ++ minsize: 1GB ++ root: ++ size: 10GB ++ swap: ++ size: memsize ++ var: ++ size: 4GB ++ tmp: ++ size: 4GB ++ PVs: ++ - d0p2 ++ filesystems: ++ d0p1: ++ label: /boot ++ mountpoint: /boot ++ home: ++ mountpoint: /home ++ root: ++ mountpoint: / ++ swap: ++ fstype: swap ++ var: ++ mountpoint: /var ++ tmp: ++ mountpoint: /tmp +diff --git a/ironic_lib/tests/test_lvm.py b/ironic_lib/tests/test_lvm.py +new file mode 100644 +index 0000000..6738e18 +--- /dev/null ++++ b/ironic_lib/tests/test_lvm.py +@@ -0,0 +1,479 @@ ++# 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 unittest ++ ++import bitmath ++import mock ++import subprocess ++import yaml ++ ++from ironic_lib.system_installer import exceptions ++from ironic_lib.system_installer import LvmSetup ++ ++ ++class LvmTest(unittest.TestCase): ++ ++ def setUp(self): ++ with open('ironic_lib/tests/examples/example1.yaml') as f: ++ conf = yaml.load(f) ++ self.lvm = LvmSetup(conf['disk_config']) ++ self.devices = {"d0p2": "/dev/sda"} ++ ++ def test_validate_conf(self): ++ lvm_setup = LvmSetup({ ++ 'lvm': { ++ 'foo': {'LVs': {'home': {'minsize': '1G'}}, ++ 'PVs': ['d0p2']} ++ } ++ }) ++ lvm_setup.validate_conf() ++ ++ def test_validate_conf_error(self): ++ lvm_setup = LvmSetup({ ++ 'lvm': { ++ 'sys-1': {'LVs': {'home': {'minsize': '1G'}}, ++ 'PVs': ['d0p2']} ++ } ++ }) ++ with self.assertRaises(exceptions.ConfError): ++ lvm_setup.validate_conf() ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_size_in_bytes(self, exe): ++ exe.return_value = '40000' ++ self.assertEqual(40000, self.lvm._get_size_in_bytes("/dev/sda")) ++ exe.assert_any_call(['lsblk', '-b', '-o', 'SIZE', '-n', '/dev/sda']) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_pvs_size_in_bytes(self, exe): ++ exe.side_effect = ['40000', '20000'] ++ with open('ironic_lib/tests/examples/example_multiple_pvs.yaml') as f: ++ conf = yaml.load(f) ++ devices = self.devices = {"d0p2": "/dev/sda", "d0p1": "/dev/sdb"} ++ lvm_conf = conf['disk_config'] ++ lvm = LvmSetup(lvm_conf) ++ self.assertEqual(bitmath.Byte(60000), ++ lvm._get_pvs_size_in_bytes( ++ lvm_conf['lvm']['sys']['PVs'], devices)) ++ exe.assert_any_call(['lsblk', '-b', '-o', 'SIZE', '-n', '/dev/sda']) ++ exe.assert_any_call(['lsblk', '-b', '-o', 'SIZE', '-n', '/dev/sdb']) ++ ++ def test_convert_formats(self): ++ for suffix in self.lvm.SI_SUFFIXES: ++ self.assertEqual(suffix + "iB", self.lvm._convert_formats(suffix)) ++ test_suffixes = ["GB", "kB", "MB"] ++ for suffix in test_suffixes: ++ self.assertEqual(suffix, self.lvm._convert_formats(suffix)) ++ ++ @mock.patch('ironic_lib.system_installer.tools.get_memsize_kB') ++ def test_get_lvs_size_in_bytes(self, mem): ++ mem.return_value = '1' ++ lvs = self.lvm._get_lvs_of_vg('sys') ++ self.assertEqual(bitmath.Byte(20401095680), ++ self.lvm._get_lvs_size_in_bytes(lvs)) ++ ++ @mock.patch('ironic_lib.system_installer.tools.get_memsize_kB') ++ def test_get_lvs_size_in_bytes_si_format(self, mem): ++ mem.return_value = '1' ++ with open('ironic_lib/tests/examples/example_with_si_format.yml') as f: ++ conf = yaml.load(f) ++ lvm_conf = conf['disk_config'] ++ lvm = LvmSetup(lvm_conf) ++ lvs = lvm._get_lvs_of_vg('sys') ++ self.assertEqual(bitmath.Byte(19000001024), ++ lvm._get_lvs_size_in_bytes(lvs)) ++ ++ @mock.patch.object(LvmSetup, '_get_pvs_size_in_bytes') ++ @mock.patch('ironic_lib.system_installer.tools.get_memsize_kB') ++ def test_validate_lvm_size(self, mem, exe): ++ exe.return_value = 20401095680 ++ mem.return_value = '1' ++ self.lvm.validate_lvm_size(self.devices) ++ ++ @mock.patch.object(LvmSetup, '_get_pvs_size_in_bytes') ++ @mock.patch('ironic_lib.system_installer.tools.get_memsize_kB') ++ def test_validate_lvm_size_fails(self, mem, exe): ++ exe.return_value = 2040 ++ mem.return_value = '1' ++ with self.assertRaises(exceptions.ConfError): ++ self.lvm.validate_lvm_size(self.devices) ++ ++ def test_get_src_names(self): ++ self.assertListEqual(self.lvm.get_src_names(), ["d0p2"]) ++ ++ def test_get_vg_names(self): ++ self.assertListEqual(self.lvm._get_vg_names(), ['sys']) ++ ++ def test_get_pv_of_vg(self): ++ self.assertListEqual(self.lvm._get_pvs_of_vg('sys', self.devices), ++ ['/dev/sda']) ++ ++ def test_get_lvs_of_vg(self): ++ lvs = {'home': {'minsize': '1G'}, ++ 'root': {'size': '10G'}, ++ 'swap': {'size': 'memsize'}, ++ 'var': {'size': '4G'}, ++ 'tmp': {'size': '4G'}} ++ self.assertDictEqual(self.lvm._get_lvs_of_vg('sys'), lvs) ++ ++ @mock.patch('subprocess.check_call') ++ def test_create_lv(self, exe): ++ self.assertEqual(self.lvm._create_lv("home", "sys", "4G"), ++ "/dev/sys/home") ++ exe.assert_any_call(['lvcreate', '-L', '4G', '-y', '-Wy', '-Zy', ++ '-n', 'home', 'sys']) ++ ++ @mock.patch('subprocess.check_call') ++ def test_create_lv_minsize(self, exe): ++ self.assertEqual(self.lvm._create_lv("home", "sys", "100", True), ++ "/dev/sys/home") ++ exe.assert_any_call(['lvcreate', '-l', '100%FREE', '-y', '-Wy', '-Zy', ++ '-n', 'home', ++ 'sys']) ++ ++ @mock.patch.object(LvmSetup, '_get_lvs') ++ @mock.patch('subprocess.check_call') ++ @mock.patch('ironic_lib.system_installer.tools.get_memsize_kB') ++ def test_create_lvs(self, mem, exe, lvs): ++ lvs.return_value = [] ++ mem.return_value = '32825036' ++ created_lvs = {"home": "/dev/sys/home", "root": "/dev/sys/root", ++ "swap": "/dev/sys/swap", "var": "/dev/sys/var", ++ "tmp": "/dev/sys/tmp"} ++ self.assertEqual(self.lvm._create_lvs('sys'), created_lvs) ++ ++ exe.assert_any_call(['lvcreate', '-l', '100%FREE', '-y', '-Wy', '-Zy', ++ '-n', 'home', ++ 'sys']) ++ exe.assert_any_call(['lvcreate', '-L', '10G', '-y', '-Wy', '-Zy', ++ '-n', 'root', 'sys']) ++ exe.assert_any_call(['lvcreate', '-L', '32825036K', '-y', '-Wy', '-Zy', ++ '-n', 'swap', ++ 'sys']) ++ exe.assert_any_call(['lvcreate', '-L', '4G', '-y', '-Wy', '-Zy', ++ '-n', 'var', 'sys']) ++ exe.assert_any_call(['lvcreate', '-L', '4G', '-y', '-Wy', '-Zy', ++ '-n', 'tmp', 'sys']) ++ ++ @mock.patch.object(LvmSetup, '_get_pvs') ++ @mock.patch('subprocess.check_call') ++ def test_create_pvs_no_existing(self, exe, pvs): ++ pvs.return_value = [] ++ self.lvm._create_pvs('sys', self.devices) ++ exe.assert_any_call(['pvcreate', '-ff', '/dev/sda']) ++ ++ @mock.patch.object(LvmSetup, '_get_pvs') ++ @mock.patch('subprocess.check_call') ++ def test_create_omit_pvs_existing_pv(self, exe, pvs): ++ pvs.return_value = ['/dev/sys/root', '/dev/sys/home'] ++ self.lvm._create_pvs('sys', self.devices) ++ ++ @mock.patch.object(LvmSetup, '_get_pvs') ++ @mock.patch('subprocess.check_call') ++ def test_create_pvs_not_for_all_devices(self, exe, pvs): ++ pvs.return_value = [] ++ lvm = LvmSetup({ ++ 'lvm': { ++ 'foo': {'LVs': {'home': {'minsize': '1G'}}, ++ 'PVs': ['d0p2']} ++ } ++ }) ++ devices = {"d0p2": "/dev/sda", "d0p1": "/dev/sdb"} ++ vg = 'foo' ++ lvm._create_pvs(vg, devices) ++ exe.assert_called_once_with(['pvcreate', '-ff', '/dev/sda']) ++ ++ @mock.patch.object(LvmSetup, '_get_vgs') ++ @mock.patch('subprocess.check_call') ++ def test_create_vg_no_existing_vgs(self, exe, vgs): ++ vgs.return_value = [] ++ self.lvm._create_vg('sys', self.devices) ++ exe.assert_any_call(['vgcreate', '-y', 'sys', '/dev/sda']) ++ ++ @mock.patch.object(LvmSetup, '_get_vgs') ++ @mock.patch('subprocess.check_call') ++ def test_create_vg_omit_existing(self, exe, vgs): ++ vgs.return_value = ['fedora'] ++ self.lvm._create_vg('fedora', self.devices) ++ exe.assert_not_called() ++ ++ @mock.patch.object(LvmSetup, '_get_lvs') ++ @mock.patch.object(LvmSetup, '_get_vgs') ++ @mock.patch.object(LvmSetup, '_get_pvs') ++ @mock.patch.object(LvmSetup, 'validate_lvm_size') ++ @mock.patch('subprocess.check_call') ++ @mock.patch('subprocess.check_output') ++ @mock.patch('ironic_lib.system_installer.tools.get_memsize_kB') ++ def test_setup_disks(self, mem, output, call, val, pvs, vgs, lvs): ++ lvs.return_value = [] ++ vgs.return_value = [] ++ pvs.return_value = [] ++ with open('ironic_lib/tests/examples/example2.yml') as f: ++ conf = yaml.load(f) ++ devices = self.devices = {"d0p2": "/dev/sda", "d0p1": "/dev/sdb"} ++ mem.return_value = '32K' ++ lvm = LvmSetup(conf['disk_config']) ++ out = lvm.setup_disks(devices) ++ expected = {'root': '/dev/sys/root', 'home': '/dev/sys/home', ++ 'tmp': '/dev/tmp/tmp', 'd0p2': '/dev/sda', ++ 'd0p1': '/dev/sdb'} ++ self.assertEqual(out, expected) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_vgs(self, exe): ++ output = """{ ++ "report": [ ++ { ++ "vg": [ ++ {"vg_name":"fedora", "pv_count":"1", "lv_count":"2", ++ "snap_count":"0", "vg_attr":"wz--n-", ++ "vg_size":"371.41g", "vg_free":"0 "} ++ ] ++ } ++ ] ++ }""" ++ ++ exe.return_value = output ++ self.assertEqual(['fedora'], self.lvm._get_vgs()) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_vgs_empty(self, exe): ++ output = """{ ++ "report": [ ++ { ++ "vg": [ ++ ] ++ } ++ ] ++ }""" ++ ++ exe.return_value = output ++ self.assertEqual([], self.lvm._get_vgs()) ++ ++ @mock.patch.object(LvmSetup, 'validate_lvm_size') ++ @mock.patch.object(LvmSetup, '_create_pvs') ++ @mock.patch.object(LvmSetup, '_create_vg') ++ @mock.patch.object(LvmSetup, '_create_lvs') ++ def test_setup_disks_lvm_not_from_all_disks(self, lvs, vg, pvs, val): ++ devices = {'d0p1': '/dev/sda1', 'd0p2': '/dev/sda2'} ++ lvs.return_value = {'var': '/dev/sys/var', ++ 'tmp': '/dev/sys/tmp', ++ 'root': '/dev/sys/root', ++ 'swap': '/dev/sys/swap', ++ 'home': '/dev/sys/home'} ++ self.assertEqual({'d0p1': '/dev/sda1', ++ 'd0p2': '/dev/sda2', ++ 'var': '/dev/sys/var', ++ 'tmp': '/dev/sys/tmp', ++ 'root': '/dev/sys/root', ++ 'swap': '/dev/sys/swap', ++ 'home': '/dev/sys/home'}, ++ self.lvm.setup_disks(devices)) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_pvs(self, exe): ++ output = """{ ++ "report": [ ++ { ++ "pv": [ ++ {"pv_name":"/dev/sda1", "vg_name":"sys", ++ "pv_fmt":"lvm2", "pv_attr":"a--", ++ "pv_size":"484.00m", "pv_free":"0 "}, ++ {"pv_name":"/dev/sda2", "vg_name":"sys", ++ "pv_fmt":"lvm2", "pv_attr":"a--", "pv_size":"1.09t", ++ "pv_free":"0 "} ++ ] ++ } ++ ] ++ }""" ++ ++ exe.return_value = output ++ self.assertEqual(['/dev/sda1', '/dev/sda2'], self.lvm._get_pvs()) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_pvs_empty(self, exe): ++ output = """{ ++ "report": [ ++ { ++ "pv": [ ++ ] ++ } ++ ] ++ }""" ++ ++ exe.return_value = output ++ self.assertEqual([], self.lvm._get_pvs()) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_lvs(self, exe): ++ output = """{ ++ "report": [ ++ { ++ "lv": [ ++ {"lv_name":"home", "vg_name":"fedora", ++ "lv_attr":"-wi-ao----", ++ "lv_size":"<231.11g", "pool_lv":"", ++ "origin":"", "data_percent":"", "metadata_percent":"", ++ "move_pv":"", "mirror_log":"", "copy_percent":"", ++ "convert_lv":""}, ++ {"lv_name":"root", "vg_name":"fedora", ++ "lv_attr":"-wi-ao----", "lv_size":"140.30g", ++ "pool_lv":"", "origin":"", "data_percent":"", ++ "metadata_percent":"", "move_pv":"", ++ "mirror_log":"", "copy_percent":"", "convert_lv":""} ++ ] ++ } ++ ] ++ }""" ++ ++ exe.return_value = output ++ self.assertEqual(['/dev/fedora/home', '/dev/fedora/root'], ++ self.lvm._get_lvs()) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_lvs_empty(self, exe): ++ output = """{ ++ "report": [ ++ { ++ "lv": [ ++ ] ++ } ++ ] ++ }""" ++ ++ exe.return_value = output ++ self.assertEqual([], self.lvm._get_lvs()) ++ ++ @mock.patch('subprocess.check_call') ++ def test_remove_lvs(self, exe): ++ lvs = ['test'] ++ self.lvm._remove_lvs(lvs) ++ exe.assert_called_once_with(['lvremove', '-y', 'test']) ++ ++ @mock.patch('subprocess.check_call') ++ def test_remove_vgs(self, exe): ++ vgs = ['test'] ++ self.lvm._remove_vgs(vgs) ++ exe.assert_called_once_with(['vgremove', '-y', 'test']) ++ ++ @mock.patch('subprocess.check_call') ++ def test_remove_pvs(self, exe): ++ pvs = ['test'] ++ self.lvm._remove_pvs(pvs) ++ exe.assert_called_once_with(['pvremove', '-y', 'test']) ++ ++ @mock.patch.object(LvmSetup, '_get_empty_vgs') ++ @mock.patch.object(LvmSetup, '_get_empty_pvs') ++ @mock.patch.object(LvmSetup, '_remove_lvs') ++ @mock.patch.object(LvmSetup, '_get_lvs') ++ def test_clean_disks_no_preserve(self, lvs, remove_lvs, ++ empty_pvs, empty_vgs): ++ empty_vgs.return_value([]) ++ empty_pvs.return_value([]) ++ lvs.return_value = ['/dev/sys/home', '/dev/sys2/root'] ++ excluded_devices = {} ++ self.lvm.clean_disks(excluded_devices) ++ remove_lvs.assert_called_once_with(['/dev/sys/home', '/dev/sys2/root']) ++ ++ @mock.patch.object(LvmSetup, '_get_empty_vgs') ++ @mock.patch.object(LvmSetup, '_get_empty_pvs') ++ @mock.patch.object(LvmSetup, '_remove_lvs') ++ @mock.patch.object(LvmSetup, '_get_lvs') ++ def test_clean_disks_preserve_lv(self, lvs, remove_lvs, ++ empty_pvs, empty_vgs): ++ empty_vgs.return_value([]) ++ empty_pvs.return_value([]) ++ lvs.return_value = ['/dev/sys/home', '/dev/sys2/root'] ++ excluded_devices = {'home': '/dev/sys/home'} ++ self.lvm.clean_disks(excluded_devices) ++ remove_lvs.assert_called_once_with(['/dev/sys2/root']) ++ ++ @mock.patch('subprocess.check_call') ++ def test_is_dev_lv_true(self, call): ++ device = '/dev/sys/home' ++ self.assertTrue(self.lvm._is_dev_lv(device)) ++ ++ @mock.patch('subprocess.check_call') ++ def test_ist_dev_lv_false(self, call): ++ call.side_effect = subprocess.CalledProcessError(5, 'lvdisplay') ++ device = '/dev/sda' ++ self.assertFalse(self.lvm._is_dev_lv(device)) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_empty_vgs(self, exe): ++ vgs_output = """{ ++ "report": [ ++ { ++ "vg": [ ++ {"vg_name": "sys", "pv_count": "2", "lv_count": "5", ++ "snap_count": "0", "vg_attr": "wz--n-", ++ "vg_size": "1.09t", "vg_free": "0 "}, ++ {"vg_name": "sys2", "pv_count": "2", "lv_count": "0", ++ "snap_count": "0", "vg_attr": "wz--n-", ++ "vg_size": "1.09t", "vg_free": "0 "} ++ ] ++ } ++ ] ++ }""" ++ exe.return_value = vgs_output ++ self.assertEqual(self.lvm._get_empty_vgs(), ['sys2']) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_empty_vgs_no_existing_vgs(self, exe): ++ vgs_output = """{ ++ "report": [ ++ { ++ "vg": [ ++ ] ++ } ++ ] ++ }""" ++ ++ exe.return_value = vgs_output ++ self.assertEqual(self.lvm._get_empty_vgs(), []) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_empty_pvs(self, exe): ++ pvs_output = """{ ++ "report": [ ++ { ++ "pv": [ ++ {"pv_name":"/dev/sda1", "vg_name":"sys", ++ "pv_fmt":"lvm2", "pv_attr":"---", "pv_size":"488.00m", ++ "pv_free":"488.00m"}, ++ {"pv_name":"/dev/sda2", "vg_name":"", "pv_fmt":"lvm2", ++ "pv_attr":"---", "pv_size":"1.09t", "pv_free":"1.09t"} ++ ] ++ } ++ ] ++ }""" ++ ++ exe.return_value = pvs_output ++ self.assertEqual(self.lvm._get_empty_pvs(), ['/dev/sda2']) ++ ++ @mock.patch('subprocess.check_output') ++ def test_get_empty_pvs_no_existing_pvs(self, exe): ++ pvs_output = """{ ++ "report": [ ++ { ++ "pv": [ ++ ] ++ } ++ ] ++ }""" ++ ++ exe.return_value = pvs_output ++ self.assertEqual(self.lvm._get_empty_pvs(), []) +-- +2.16.2 +