1
0
mirror of https://github.com/gryf/openstack.git synced 2025-12-17 19:40:19 +01:00
Files
openstack/ironic-lib/0006-LVM-Setup-implementation.patch
2018-03-23 16:22:20 +01:00

919 lines
31 KiB
Diff

From d14c463e95c0db7b1c5e9b6745565a134d55e355 Mon Sep 17 00:00:00 2001
From: PiotrProkop <piotr.prokop@intel.com>
Date: Fri, 29 Dec 2017 11:30:43 +0100
Subject: [PATCH 06/11] LVM Setup implementation
Co-Authored-By: Piotr Prokop <piotr.prokop@intel.com>
Co-Authored-By: Marta Mucek <marta.mucek@intel.com>
---
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