mirror of
https://github.com/gryf/openstack.git
synced 2025-12-17 03:20:25 +01:00
Merge pull request #5 from xek/partitionsetup
disk_partitioner partitioning implementation
This commit is contained in:
706
ironic-lib/0003-Disk-partitioning-implementation.patch
Normal file
706
ironic-lib/0003-Disk-partitioning-implementation.patch
Normal file
@@ -0,0 +1,706 @@
|
|||||||
|
From afc04dfa6a798356dea0ce5d78dd32cf9c386f42 Mon Sep 17 00:00:00 2001
|
||||||
|
From: "Grzegorz Grasza (xek)" <grzegorz.grasza@intel.com>
|
||||||
|
Date: Thu, 11 Jan 2018 16:49:05 +0100
|
||||||
|
Subject: [PATCH 03/11] Disk partitioning implementation
|
||||||
|
|
||||||
|
Co-Authored-By: Grzegorz Grasza <grzegorz.grasza@intel.com>
|
||||||
|
Co-Authored-By: Marta Mucek <marta.mucek@intel.com>
|
||||||
|
---
|
||||||
|
ironic_lib/system_installer/exceptions.py | 4 +
|
||||||
|
ironic_lib/system_installer/filesystemsetup.py | 9 +
|
||||||
|
ironic_lib/system_installer/partitionsetup.py | 219 +++++++++++++++++
|
||||||
|
ironic_lib/system_installer/tools.py | 7 +
|
||||||
|
ironic_lib/tests/test_examples.py | 5 +-
|
||||||
|
ironic_lib/tests/test_partitionsetup.py | 324 +++++++++++++++++++++++++
|
||||||
|
ironic_lib/tests/test_tools.py | 35 +++
|
||||||
|
7 files changed, 601 insertions(+), 2 deletions(-)
|
||||||
|
create mode 100644 ironic_lib/tests/test_partitionsetup.py
|
||||||
|
create mode 100644 ironic_lib/tests/test_tools.py
|
||||||
|
|
||||||
|
diff --git a/ironic_lib/system_installer/exceptions.py b/ironic_lib/system_installer/exceptions.py
|
||||||
|
index 6dcfa46..2f7998f 100644
|
||||||
|
--- a/ironic_lib/system_installer/exceptions.py
|
||||||
|
+++ b/ironic_lib/system_installer/exceptions.py
|
||||||
|
@@ -24,3 +24,7 @@ class ConfError(SystemInstallerException):
|
||||||
|
|
||||||
|
class EnvError(SystemInstallerException):
|
||||||
|
"""Error in the setup environment"""
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+class AssignFail(EnvError):
|
||||||
|
+ pass
|
||||||
|
diff --git a/ironic_lib/system_installer/filesystemsetup.py b/ironic_lib/system_installer/filesystemsetup.py
|
||||||
|
index e3157d2..6ed64d6 100644
|
||||||
|
--- a/ironic_lib/system_installer/filesystemsetup.py
|
||||||
|
+++ b/ironic_lib/system_installer/filesystemsetup.py
|
||||||
|
@@ -32,3 +32,12 @@ class FilesystemSetup(Setup):
|
||||||
|
|
||||||
|
def get_disks_by_labels(self):
|
||||||
|
return {}
|
||||||
|
+
|
||||||
|
+ def is_efi_boot(self):
|
||||||
|
+ return False
|
||||||
|
+
|
||||||
|
+ def get_boot_partition(self):
|
||||||
|
+ pass # TODO(xek)
|
||||||
|
+
|
||||||
|
+ def get_fstype(self, label):
|
||||||
|
+ return '' # TODO(xek)
|
||||||
|
diff --git a/ironic_lib/system_installer/partitionsetup.py b/ironic_lib/system_installer/partitionsetup.py
|
||||||
|
index af318d1..8f22a9d 100644
|
||||||
|
--- a/ironic_lib/system_installer/partitionsetup.py
|
||||||
|
+++ b/ironic_lib/system_installer/partitionsetup.py
|
||||||
|
@@ -13,7 +13,22 @@
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
+from collections import OrderedDict
|
||||||
|
+import json
|
||||||
|
+
|
||||||
|
+import bitmath
|
||||||
|
+from oslo_concurrency import processutils
|
||||||
|
+from oslo_log import log
|
||||||
|
+
|
||||||
|
+from ironic_lib.disk_partitioner import DiskPartitioner
|
||||||
|
+from ironic_lib import disk_utils
|
||||||
|
from ironic_lib.system_installer.base import Setup
|
||||||
|
+from ironic_lib.system_installer import exceptions
|
||||||
|
+from ironic_lib.system_installer.filesystemsetup import FilesystemSetup
|
||||||
|
+from ironic_lib.system_installer import tools
|
||||||
|
+from ironic_lib import utils
|
||||||
|
+
|
||||||
|
+LOG = log.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
class PartitionSetup(Setup):
|
||||||
|
@@ -21,7 +36,211 @@ class PartitionSetup(Setup):
|
||||||
|
|
||||||
|
conf_key = 'blockdev'
|
||||||
|
|
||||||
|
+ def __init__(self, conf):
|
||||||
|
+ if FilesystemSetup.conf_key in conf:
|
||||||
|
+ self._filesystems = FilesystemSetup(conf)
|
||||||
|
+ else: # this case is rare, mainly for testing and running independent
|
||||||
|
+ self._filesystems = FilesystemSetup({FilesystemSetup.conf_key: {}})
|
||||||
|
+ self._boot_partition = self._filesystems.get_boot_partition()
|
||||||
|
+ self._efi_boot = self._filesystems.is_efi_boot()
|
||||||
|
+ default_partition_table = 'mbr'
|
||||||
|
+ if self._efi_boot:
|
||||||
|
+ default_partition_table = 'gpt'
|
||||||
|
+ types_translation = {'mbr': 'msdos'}
|
||||||
|
+ table = conf.get('partition_table', default_partition_table)
|
||||||
|
+ self._partition_type = types_translation.get(table, table)
|
||||||
|
+ super(PartitionSetup, self).__init__(conf)
|
||||||
|
+
|
||||||
|
+ def validate_conf(self):
|
||||||
|
+ for disk_name, disk in self.conf.items():
|
||||||
|
+ minsizes = 0
|
||||||
|
+ number_of_partitions = 0
|
||||||
|
+ for label, partition in disk.get('partitions', {}).items():
|
||||||
|
+ number_of_partitions += 1
|
||||||
|
+ if 'minsize' in partition:
|
||||||
|
+ minsizes += 1
|
||||||
|
+ if minsizes > 1:
|
||||||
|
+ raise exceptions.ConfError(
|
||||||
|
+ 'More than one minsize not supported')
|
||||||
|
+ if number_of_partitions > 4 and \
|
||||||
|
+ self._partition_type != 'gpt':
|
||||||
|
+ raise exceptions.ConfError(
|
||||||
|
+ 'More than 4 partitions per disk are not allowed')
|
||||||
|
+
|
||||||
|
+ def validate_env(self):
|
||||||
|
+ """Check that disks exist"""
|
||||||
|
+ lsblk_version, err = utils.execute("lsblk --version", shell=True)
|
||||||
|
+ if float('.'.join(lsblk_version.split()[-1].split('.')[:2])) < 2.27:
|
||||||
|
+ raise exceptions.EnvError('lsblk version >= 2.27 is required')
|
||||||
|
+ if len(self._get_physical_drives()) < len([
|
||||||
|
+ v for v in self.conf.values() if 'candidates' in v]):
|
||||||
|
+ raise exceptions.EnvError('Not enough physical drives')
|
||||||
|
+ candidates = self._assign_candidates()
|
||||||
|
+ for label, partition in self.conf.items():
|
||||||
|
+ if 'candidates' in partition:
|
||||||
|
+ try:
|
||||||
|
+ candidates[label]
|
||||||
|
+ except KeyError:
|
||||||
|
+ raise exceptions.EnvError(
|
||||||
|
+ 'No candidates for blockdev {}'.format(label))
|
||||||
|
+
|
||||||
|
def get_dst_names(self):
|
||||||
|
partitions = [list(v.get('partitions', {}))
|
||||||
|
for v in self.conf.values()]
|
||||||
|
return sum(partitions, [])
|
||||||
|
+
|
||||||
|
+ def setup_disks(self, devices):
|
||||||
|
+ devices.update(self._assign_candidates(devices))
|
||||||
|
+ disksizes = {d['name']: d['size'] for d in self._get_physical_drives()}
|
||||||
|
+ for label, disk in self.conf.items():
|
||||||
|
+ if not disk.get('partitions'):
|
||||||
|
+ continue
|
||||||
|
+ try:
|
||||||
|
+ device_name = devices[label]
|
||||||
|
+ except KeyError:
|
||||||
|
+ raise exceptions.EnvError(
|
||||||
|
+ 'No candidates for blockdev {}'.format(label))
|
||||||
|
+ remaining_size = bitmath.Byte(disksizes[device_name])
|
||||||
|
+ partitioner = DiskPartitioner(device_name, self._partition_type)
|
||||||
|
+ last_partition = None
|
||||||
|
+ names = []
|
||||||
|
+ for name, partition in disk['partitions'].items():
|
||||||
|
+ names.append(name)
|
||||||
|
+ if 'size' in partition:
|
||||||
|
+ if partition['size'] == 'memsize':
|
||||||
|
+ size = bitmath.parse_string_unsafe(
|
||||||
|
+ tools.get_memsize_kB())
|
||||||
|
+ else:
|
||||||
|
+ size = bitmath.parse_string_unsafe(partition['size'])
|
||||||
|
+ # TODO(xek): create partition labels for preserve function?
|
||||||
|
+ partitioner.add_partition(
|
||||||
|
+ int(size.to_MiB().value),
|
||||||
|
+ fs_type=partition.get(
|
||||||
|
+ 'type', self._filesystems.get_fstype(label)),
|
||||||
|
+ boot_flag=self._boot_flag(name))
|
||||||
|
+ remaining_size -= size
|
||||||
|
+ if remaining_size <= 0:
|
||||||
|
+ raise exceptions.EnvError(
|
||||||
|
+ "Partitions don't fit on disk")
|
||||||
|
+ elif 'minsize' in partition:
|
||||||
|
+ last_partition = partition
|
||||||
|
+ else:
|
||||||
|
+ raise exceptions.EnvError(
|
||||||
|
+ 'No candidates for {}'.format(name))
|
||||||
|
+ if last_partition:
|
||||||
|
+ # TODO(xek): create partition labels for preserve function?
|
||||||
|
+ partitioner.add_partition(
|
||||||
|
+ int(remaining_size.to_MiB().value - 1
|
||||||
|
+ - disk_utils.MAX_CONFIG_DRIVE_SIZE_MB),
|
||||||
|
+ boot_flag=self._boot_flag(name))
|
||||||
|
+ partitioner.commit()
|
||||||
|
+ created = self._gather_created_devices(device_name)
|
||||||
|
+ devices.update(zip(names, created))
|
||||||
|
+
|
||||||
|
+ return devices
|
||||||
|
+
|
||||||
|
+ def get_not_partitioned_candidates(self, candidates=None):
|
||||||
|
+ if not candidates:
|
||||||
|
+ candidates = self._assign_candidates()
|
||||||
|
+ try:
|
||||||
|
+ return {label: candidates[label]
|
||||||
|
+ for label, disk in self.conf.items()
|
||||||
|
+ if not disk.get('partitions')}
|
||||||
|
+ except KeyError as e:
|
||||||
|
+ raise exceptions.EnvError(
|
||||||
|
+ 'No candidates for blockdev {}'.format(e.args[0]))
|
||||||
|
+
|
||||||
|
+ def _boot_flag(self, name):
|
||||||
|
+ if self._boot_partition == name:
|
||||||
|
+ if self._partition_type == 'gpt' and not self._efi_boot:
|
||||||
|
+ return 'bios_grub'
|
||||||
|
+ return 'boot'
|
||||||
|
+ return None
|
||||||
|
+
|
||||||
|
+ def _assign_candidates(self, devices={}):
|
||||||
|
+ "Returns dict of {label: candidate}, given a dict of already assigned."
|
||||||
|
+ def assign_disks(devices, partitions):
|
||||||
|
+ if not partitions:
|
||||||
|
+ return {}
|
||||||
|
+ for label, partition in partitions.items():
|
||||||
|
+ candidates = self._filter_candidates(
|
||||||
|
+ partition['candidates'], devices)
|
||||||
|
+ for device in candidates:
|
||||||
|
+ assigned = {label: device['name']}
|
||||||
|
+ devices_ = devices[:]
|
||||||
|
+ devices_.remove(device)
|
||||||
|
+ partitions_ = partitions.copy()
|
||||||
|
+ del partitions_[label]
|
||||||
|
+ try:
|
||||||
|
+ assigned.update(assign_disks(devices_, partitions_))
|
||||||
|
+ return assigned
|
||||||
|
+ except exceptions.AssignFail:
|
||||||
|
+ pass
|
||||||
|
+ raise exceptions.AssignFail('No candidates')
|
||||||
|
+ # Don't assign disk candidates to devices with no candidates entry.
|
||||||
|
+ with_candidates = OrderedDict([
|
||||||
|
+ (label, part) for label, part in self.conf.items()
|
||||||
|
+ if 'candidates' in part and label not in devices])
|
||||||
|
+ # Already assigned disks are present in the devices dict.
|
||||||
|
+ return assign_disks(self._get_physical_drives(), with_candidates)
|
||||||
|
+
|
||||||
|
+ @staticmethod
|
||||||
|
+ def _filter_candidates(hints, devices):
|
||||||
|
+ " Filter devices according to hints. "
|
||||||
|
+ if hints == 'any':
|
||||||
|
+ return devices
|
||||||
|
+ if 'serial' in hints:
|
||||||
|
+ return [d for d in devices if d['serial'] == hints['serial']]
|
||||||
|
+ if 'model' in hints: # assuming all instances of model have same size
|
||||||
|
+ return [d for d in devices if d['model'] == hints['model']]
|
||||||
|
+ if 'type' in hints:
|
||||||
|
+ devices = [d for d in devices if d['type'] == hints['type']]
|
||||||
|
+ if 'size' in hints:
|
||||||
|
+ devices = [d for d in devices
|
||||||
|
+ if d['size'] <= int(hints['max_disk_size_gb']) * 1e+9]
|
||||||
|
+ return devices
|
||||||
|
+
|
||||||
|
+ @staticmethod
|
||||||
|
+ def _get_physical_drives():
|
||||||
|
+ try:
|
||||||
|
+ drives_json, err = utils.execute(
|
||||||
|
+ 'lsblk', '-Jbde1,7', '-o', 'NAME,ROTA,SERIAL,MODEL,SIZE')
|
||||||
|
+ if err:
|
||||||
|
+ raise processutils.ProcessExecutionError(err)
|
||||||
|
+ blockdevices = json.loads(drives_json)['blockdevices']
|
||||||
|
+ for d in blockdevices:
|
||||||
|
+ d['name'] = '/dev/' + d['name']
|
||||||
|
+ d['size'] = int(d['size'])
|
||||||
|
+ rotating = int(d['rota'])
|
||||||
|
+ if rotating:
|
||||||
|
+ d['type'] = 'HDD'
|
||||||
|
+ elif 'nvme' in d['name']:
|
||||||
|
+ d['type'] = 'NVMe'
|
||||||
|
+ else:
|
||||||
|
+ d['type'] = 'SSD'
|
||||||
|
+ del d['rota']
|
||||||
|
+ return [b for b in blockdevices if all(b.values())]
|
||||||
|
+ except (processutils.ProcessExecutionError, OSError):
|
||||||
|
+ raise exceptions.EnvError("Error getting details of devices.")
|
||||||
|
+
|
||||||
|
+ @staticmethod
|
||||||
|
+ def _gather_created_devices(dev):
|
||||||
|
+ try:
|
||||||
|
+ drives_json, err = utils.execute('lsblk', '-Jpo', 'NAME', dev)
|
||||||
|
+ if err:
|
||||||
|
+ raise processutils.ProcessExecutionError(err)
|
||||||
|
+ devices = json.loads(drives_json)['blockdevices']
|
||||||
|
+ except (processutils.ProcessExecutionError, OSError):
|
||||||
|
+ raise exceptions.EnvError("Error getting details of devices.")
|
||||||
|
+
|
||||||
|
+ result = []
|
||||||
|
+
|
||||||
|
+ def add_devices(tree):
|
||||||
|
+ for dev in tree:
|
||||||
|
+ if 'children' in dev:
|
||||||
|
+ add_devices(dev['children'])
|
||||||
|
+ else:
|
||||||
|
+ result.append(dev['name'])
|
||||||
|
+
|
||||||
|
+ add_devices(devices)
|
||||||
|
+ return result
|
||||||
|
diff --git a/ironic_lib/system_installer/tools.py b/ironic_lib/system_installer/tools.py
|
||||||
|
index 71f6d60..aaacf67 100644
|
||||||
|
--- a/ironic_lib/system_installer/tools.py
|
||||||
|
+++ b/ironic_lib/system_installer/tools.py
|
||||||
|
@@ -34,3 +34,10 @@ def ordered_load(disk_conf):
|
||||||
|
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
||||||
|
construct_mapping)
|
||||||
|
return yaml.load(disk_conf, OrderedLoader)
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def get_memsize_kB():
|
||||||
|
+ """Get memory size of machine"""
|
||||||
|
+ with open('/proc/meminfo', 'r') as f:
|
||||||
|
+ # take first line which is MemTotal and grab its value with unit
|
||||||
|
+ return f.read().split('\n')[0].split()[1]
|
||||||
|
diff --git a/ironic_lib/tests/test_examples.py b/ironic_lib/tests/test_examples.py
|
||||||
|
index 05b5cc7..e69bc2d 100644
|
||||||
|
--- a/ironic_lib/tests/test_examples.py
|
||||||
|
+++ b/ironic_lib/tests/test_examples.py
|
||||||
|
@@ -15,6 +15,7 @@
|
||||||
|
|
||||||
|
from oslotest import base as test_base
|
||||||
|
|
||||||
|
+from ironic_lib.system_installer import exceptions
|
||||||
|
from ironic_lib.system_installer import SystemInstaller
|
||||||
|
|
||||||
|
EXAMPLES_DIR = 'ironic_lib/tests/examples/'
|
||||||
|
@@ -61,7 +62,7 @@ class SystemInstallerExamplesTestCase(test_base.BaseTestCase):
|
||||||
|
|
||||||
|
def test_yse_bnode_4disk(self):
|
||||||
|
y = SystemInstaller(open(EXAMPLES_DIR + 'yse_bnode_4disk.yaml').read())
|
||||||
|
- y.validate_conf()
|
||||||
|
+ self.assertRaises(exceptions.ConfError, y.validate_conf)
|
||||||
|
|
||||||
|
def test_yse_bnode_4disk_gpt(self):
|
||||||
|
y = SystemInstaller(
|
||||||
|
@@ -71,7 +72,7 @@ class SystemInstallerExamplesTestCase(test_base.BaseTestCase):
|
||||||
|
def test_yse_bnode_fsprofile(self):
|
||||||
|
y = SystemInstaller(
|
||||||
|
open(EXAMPLES_DIR + 'yse_bnode_fsprofile.yaml').read())
|
||||||
|
- y.validate_conf()
|
||||||
|
+ self.assertRaises(exceptions.ConfError, y.validate_conf)
|
||||||
|
|
||||||
|
def test_megacli_partitions(self):
|
||||||
|
y = SystemInstaller(
|
||||||
|
diff --git a/ironic_lib/tests/test_partitionsetup.py b/ironic_lib/tests/test_partitionsetup.py
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..69c14b4
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/ironic_lib/tests/test_partitionsetup.py
|
||||||
|
@@ -0,0 +1,324 @@
|
||||||
|
+# 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.
|
||||||
|
+
|
||||||
|
+from collections import OrderedDict
|
||||||
|
+import unittest
|
||||||
|
+
|
||||||
|
+import mock
|
||||||
|
+
|
||||||
|
+from ironic_lib.system_installer import exceptions
|
||||||
|
+from ironic_lib.system_installer import partitionsetup
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+class PartitionSetupTestCase(unittest.TestCase):
|
||||||
|
+
|
||||||
|
+ def test_validate_conf(self):
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {'sda': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {'size': '512M'},
|
||||||
|
+ 'd0p2': {'minsize': '2G'}}
|
||||||
|
+ }}})
|
||||||
|
+ partition_setup.validate_conf()
|
||||||
|
+
|
||||||
|
+ def test_validate_conf_error(self):
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {'sda': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {'size': '512M'},
|
||||||
|
+ 'd0p2': {'minsize': '2G'},
|
||||||
|
+ 'd0p3': {'minsize': '2G'}}
|
||||||
|
+ }}})
|
||||||
|
+ with self.assertRaises(exceptions.ConfError):
|
||||||
|
+ partition_setup.validate_conf()
|
||||||
|
+
|
||||||
|
+ def test_get_dst_names(self):
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {
|
||||||
|
+ 'sda': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {'size': '512M'},
|
||||||
|
+ 'd0p2': {'minsize': '2G'}}},
|
||||||
|
+ 'sdb': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd1p1': {'size': '512M'},
|
||||||
|
+ 'd1p2': {'minsize': '2G'}}}}})
|
||||||
|
+ self.assertEqual(set(partition_setup.get_dst_names()),
|
||||||
|
+ {'d0p1', 'd0p2', 'd1p1', 'd1p2'})
|
||||||
|
+
|
||||||
|
+ def test_get_not_partitioned_candidates_empty(self):
|
||||||
|
+ with mock.patch.object(
|
||||||
|
+ partitionsetup.PartitionSetup, '_get_physical_drives',
|
||||||
|
+ return_value=[
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': 'sda',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'},
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': 'sdb',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'}]):
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {
|
||||||
|
+ 'sda_label': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {'size': '512M'},
|
||||||
|
+ 'd0p2': {'minsize': '2G'}}},
|
||||||
|
+ 'sdb_label': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd1p1': {'size': '512M'},
|
||||||
|
+ 'd1p2': {'minsize': '2G'}}}}})
|
||||||
|
+ self.assertEqual(partition_setup.get_not_partitioned_candidates(),
|
||||||
|
+ {})
|
||||||
|
+
|
||||||
|
+ def test_get_not_partitioned_candidates(self):
|
||||||
|
+ with mock.patch.object(
|
||||||
|
+ partitionsetup.PartitionSetup, '_get_physical_drives',
|
||||||
|
+ return_value=[
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': '/dev/sda',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'},
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': '/dev/sdb',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'}]):
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': OrderedDict([
|
||||||
|
+ ('sda_label', {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {'size': '512M'},
|
||||||
|
+ 'd0p2': {'minsize': '2G'}}
|
||||||
|
+ }),
|
||||||
|
+ ('sdb_label', {
|
||||||
|
+ 'candidates': 'any'
|
||||||
|
+ })])})
|
||||||
|
+ self.assertDictEqual(
|
||||||
|
+ partition_setup.get_not_partitioned_candidates(),
|
||||||
|
+ {'sdb_label': '/dev/sdb'})
|
||||||
|
+
|
||||||
|
+ def test_validate_env(self):
|
||||||
|
+ with mock.patch.object(
|
||||||
|
+ partitionsetup.PartitionSetup, '_get_physical_drives',
|
||||||
|
+ return_value=[
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': '/dev/sda',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'}]):
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {'sda': {'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {'size': '512M'},
|
||||||
|
+ 'd0p2': {'minsize': '2G'}}}}})
|
||||||
|
+ partition_setup.validate_env()
|
||||||
|
+
|
||||||
|
+ def test_validate_env_match_fail(self):
|
||||||
|
+ with mock.patch.object(
|
||||||
|
+ partitionsetup.PartitionSetup, '_get_physical_drives',
|
||||||
|
+ return_value=[
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': '/dev/sda',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'}]):
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {
|
||||||
|
+ 'sda': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {'size': '512M'},
|
||||||
|
+ 'd0p2': {'minsize': '2G'}}},
|
||||||
|
+ 'sdb': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd1p1': {'size': '512M'},
|
||||||
|
+ 'd1p2': {'minsize': '2G'}}}}})
|
||||||
|
+ with self.assertRaises(exceptions.EnvError):
|
||||||
|
+ partition_setup.validate_env()
|
||||||
|
+
|
||||||
|
+ @mock.patch.object(partitionsetup.PartitionSetup, '_get_physical_drives')
|
||||||
|
+ def test_validate_env_physical_count(self, get_drives_mock):
|
||||||
|
+ get_drives_mock.return_value = [
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': 'sdb',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'},
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': 'sdc',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'}]
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {
|
||||||
|
+ 'sda': {'candidates': 'any'},
|
||||||
|
+ 'sdb': {'candidates': 'any'},
|
||||||
|
+ 'raid1': {
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {'size': '512M'},
|
||||||
|
+ 'd0p2': {'minsize': '2G'},
|
||||||
|
+ 'd0p3': {'minsize': '2G'}}
|
||||||
|
+ }}})
|
||||||
|
+ partition_setup.validate_env()
|
||||||
|
+
|
||||||
|
+ @mock.patch.object(partitionsetup.PartitionSetup, '_get_physical_drives')
|
||||||
|
+ def test_validate_env_physical_count_error(self, get_drives_mock):
|
||||||
|
+ get_drives_mock.return_value = [
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': 'sdb',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'}]
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {
|
||||||
|
+ 'sda': {'candidates': 'any'},
|
||||||
|
+ 'sdb': {'candidates': 'any'},
|
||||||
|
+ 'raid1': {
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {'size': '512M'},
|
||||||
|
+ 'd0p2': {'minsize': '2G'},
|
||||||
|
+ 'd0p3': {'minsize': '2G'}}
|
||||||
|
+ }}})
|
||||||
|
+ with self.assertRaises(exceptions.EnvError):
|
||||||
|
+ partition_setup.validate_env()
|
||||||
|
+
|
||||||
|
+ def test_setup_disks(self):
|
||||||
|
+
|
||||||
|
+ def execute_result(command, *args, **kwargs):
|
||||||
|
+ if command == 'lsblk':
|
||||||
|
+ return ("""{
|
||||||
|
+ "blockdevices": [
|
||||||
|
+ {"name": "/dev/sda",
|
||||||
|
+ "children": [
|
||||||
|
+ {"name": "/dev/sda1"},
|
||||||
|
+ {"name": "/dev/sda2"},
|
||||||
|
+ {"name": "/dev/sda3",
|
||||||
|
+ "children": [
|
||||||
|
+ {"name": "/dev/sda5"},
|
||||||
|
+ {"name": "/dev/sda6"}
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }""", '')
|
||||||
|
+ return ('', '')
|
||||||
|
+
|
||||||
|
+ physical_drives = [
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': '/dev/sda',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'}]
|
||||||
|
+
|
||||||
|
+ with mock.patch.object(
|
||||||
|
+ partitionsetup.PartitionSetup, '_get_physical_drives',
|
||||||
|
+ return_value=physical_drives), \
|
||||||
|
+ mock.patch('ironic_lib.utils.execute',
|
||||||
|
+ side_effect=execute_result) as exec_mock:
|
||||||
|
+
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'partition_table': 'gpt',
|
||||||
|
+ 'blockdev': {'root_disk': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': OrderedDict([
|
||||||
|
+ ('d0p1', {'size': '512M'}),
|
||||||
|
+ ('d0p2', {'size': '512M', 'type': 'ntfs'}),
|
||||||
|
+ ('d0p3', {'size': '512M'}),
|
||||||
|
+ ('d0p4', {'minsize': '2G'})])}}})
|
||||||
|
+ result = partition_setup.setup_disks({})
|
||||||
|
+ self.assertDictEqual(
|
||||||
|
+ result, {'d0p1': '/dev/sda1', 'd0p2': '/dev/sda2',
|
||||||
|
+ 'd0p3': '/dev/sda5', 'd0p4': '/dev/sda6',
|
||||||
|
+ 'root_disk': '/dev/sda'})
|
||||||
|
+ self.assertIn('gpt', exec_mock.mock_calls[0][1])
|
||||||
|
+ self.assertIn('ntfs', exec_mock.mock_calls[2][1])
|
||||||
|
+
|
||||||
|
+ def test_setup_disks_size_fail(self):
|
||||||
|
+ with mock.patch.object(
|
||||||
|
+ partitionsetup.PartitionSetup, '_get_physical_drives',
|
||||||
|
+ return_value=[
|
||||||
|
+ {'model': 'fake_model',
|
||||||
|
+ 'name': '/dev/sda',
|
||||||
|
+ 'serial': 'fake_serial',
|
||||||
|
+ 'size': 250000000000,
|
||||||
|
+ 'type': 'HDD'}]):
|
||||||
|
+
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {'sda': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': OrderedDict([
|
||||||
|
+ ('d0p1', {'size': '512G'}),
|
||||||
|
+ ('d0p2', {'size': '512G'}),
|
||||||
|
+ ('d0p3', {'size': '512G'}),
|
||||||
|
+ ('d0p4', {'minsize': '2G'})])
|
||||||
|
+ }}})
|
||||||
|
+ with self.assertRaises(exceptions.EnvError):
|
||||||
|
+ partition_setup.setup_disks({})
|
||||||
|
+
|
||||||
|
+ def test_assign_candidates(self):
|
||||||
|
+ with mock.patch.object(
|
||||||
|
+ partitionsetup.PartitionSetup, '_get_physical_drives',
|
||||||
|
+ return_value=[{u'name': u'/dev/sda',
|
||||||
|
+ u'model': u'INTEL SSDSC2BB24',
|
||||||
|
+ 'type': 'SSD',
|
||||||
|
+ u'serial': u'55cd2e404c02b114',
|
||||||
|
+ u'size': 240057409536},
|
||||||
|
+ {u'name': u'/dev/sdb',
|
||||||
|
+ u'model': u'INTEL SSDSC2BB24',
|
||||||
|
+ 'type': 'SSD',
|
||||||
|
+ u'serial': u'55cd2e404c02b181',
|
||||||
|
+ u'size': 240057409536}]):
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {'sda': {
|
||||||
|
+ 'candidates': 'any',
|
||||||
|
+ 'partitions': {
|
||||||
|
+ 'd0p1': {
|
||||||
|
+ 'size': '512M'},
|
||||||
|
+ 'd0p2': {
|
||||||
|
+ 'minsize': '2G'}}}}})
|
||||||
|
+ partition_setup._assign_candidates()
|
||||||
|
+
|
||||||
|
+ def test_assign_candidates_fail(self):
|
||||||
|
+ with mock.patch.object(
|
||||||
|
+ partitionsetup.PartitionSetup, '_get_physical_drives',
|
||||||
|
+ return_value=[{u'name': u'/dev/sda',
|
||||||
|
+ u'model': u'INTEL SSDSC2BB24',
|
||||||
|
+ 'type': 'SSD',
|
||||||
|
+ u'serial': u'55cd2e404c02b114',
|
||||||
|
+ u'size': 240057409536},
|
||||||
|
+ {u'name': u'/dev/sdb',
|
||||||
|
+ u'model': u'INTEL SSDSC2BB24',
|
||||||
|
+ 'type': 'SSD',
|
||||||
|
+ u'serial': u'55cd2e404c02b181',
|
||||||
|
+ u'size': 240057409536}]):
|
||||||
|
+ partition_setup = partitionsetup.PartitionSetup(
|
||||||
|
+ {'blockdev': {'sda': {'candidates': 'any'},
|
||||||
|
+ 'sdb': {'candidates': 'any'},
|
||||||
|
+ 'sdc': {'candidates': 'any'}}})
|
||||||
|
+ with self.assertRaises(exceptions.EnvError):
|
||||||
|
+ partition_setup._assign_candidates()
|
||||||
|
diff --git a/ironic_lib/tests/test_tools.py b/ironic_lib/tests/test_tools.py
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..6e0cd12
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/ironic_lib/tests/test_tools.py
|
||||||
|
@@ -0,0 +1,35 @@
|
||||||
|
+# 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 ironic_lib.system_installer.tools as tools
|
||||||
|
+import mock
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+class ToolsTest(unittest.TestCase):
|
||||||
|
+
|
||||||
|
+ @mock.patch('ironic_lib.system_installer.tools.open')
|
||||||
|
+ def test_get_memsize(self, op):
|
||||||
|
+ meminfo = "\n".join(["MemTotal: 32825036 kB",
|
||||||
|
+ "MemFree: 18416836 kB",
|
||||||
|
+ "MemAvailable: 25979788 kB",
|
||||||
|
+ "Buffers: 1561440 kB"])
|
||||||
|
+
|
||||||
|
+ op.return_value = mock.mock_open(read_data=meminfo).return_value
|
||||||
|
+
|
||||||
|
+ memsize = tools.get_memsize_kB()
|
||||||
|
+ print(memsize)
|
||||||
|
+ self.assertEqual("32825036", memsize)
|
||||||
|
--
|
||||||
|
2.14.1
|
||||||
|
|
||||||
Reference in New Issue
Block a user