diff --git a/ironic-lib/0002-Base-classes-and-examples.patch b/ironic-lib/0002-Base-classes-and-examples.patch new file mode 100644 index 0000000..4a0d410 --- /dev/null +++ b/ironic-lib/0002-Base-classes-and-examples.patch @@ -0,0 +1,3038 @@ +From b03e3f9c8d0844e94f88e3f449c7eecc973c91fe Mon Sep 17 00:00:00 2001 +From: Grzegorz Grasza +Date: Thu, 16 Nov 2017 15:40:59 +0100 +Subject: [PATCH 02/11] Base classes and examples + +Examples and basic tests for disk_config validation +Each kind of setup operation is an independent class +Adds README and HACKING documentation + +Co-Authored-By: Grzegorz Grasza +Co-Authored-By: Piotr Prokop +Co-Authored-By: Roman Dobosz +Co-Authored-By: Marta Mucek +--- + doc/source/system_installer.rst | 92 ++ + ironic_lib/system_installer/__init__.py | 181 ++++ + ironic_lib/system_installer/base.py | 53 ++ + ironic_lib/system_installer/exceptions.py | 26 + + ironic_lib/system_installer/filesystemsetup.py | 34 + + ironic_lib/system_installer/hpssaclisetup.py | 26 + + ironic_lib/system_installer/lvmsetup.py | 28 + + ironic_lib/system_installer/megaclisetup.py | 26 + + ironic_lib/system_installer/partitionsetup.py | 27 + + ironic_lib/system_installer/schema.json | 225 +++++ + ironic_lib/system_installer/swraidsetup.py | 29 + + ironic_lib/system_installer/systemsetup.py | 20 + + ironic_lib/system_installer/tools.py | 36 + + .../tests/examples/Disk_openhouse_preserve_hv.yaml | 44 + + .../examples/Rhel_layout_1disk_1partition.yaml | 18 + + .../tests/examples/Rhel_layout_4disk_raid10.yaml | 101 +++ + .../tests/examples/Rhel_layout_6disk_raid5.yaml | 125 +++ + .../tests/examples/Rhel_layout_cm3_rhel_mysql.yaml | 43 + + ironic_lib/tests/examples/example1.yaml | 39 + + ironic_lib/tests/examples/example_conf1.yaml | 16 + + ironic_lib/tests/examples/m10n_chef.yaml | 22 + + ironic_lib/tests/examples/megacli_partitions.yaml | 26 + + ironic_lib/tests/examples/use_2nd_disk.yaml | 40 + + ironic_lib/tests/examples/yse_bnode_4disk.yaml | 111 +++ + ironic_lib/tests/examples/yse_bnode_4disk_gpt.yaml | 115 +++ + ironic_lib/tests/examples/yse_bnode_fsprofile.yaml | 114 +++ + ironic_lib/tests/test_examples.py | 79 ++ + ironic_lib/tests/test_system_installer.py | 976 +++++++++++++++++++++ + ironic_lib/tests/test_system_installer_base.py | 48 + + requirements.txt | 4 + + tools/system_installer.py | 26 + + tox.ini | 4 +- + 32 files changed, 2753 insertions(+), 1 deletion(-) + create mode 100644 doc/source/system_installer.rst + create mode 100644 ironic_lib/system_installer/__init__.py + create mode 100644 ironic_lib/system_installer/base.py + create mode 100644 ironic_lib/system_installer/exceptions.py + create mode 100644 ironic_lib/system_installer/filesystemsetup.py + create mode 100644 ironic_lib/system_installer/hpssaclisetup.py + create mode 100644 ironic_lib/system_installer/lvmsetup.py + create mode 100644 ironic_lib/system_installer/megaclisetup.py + create mode 100644 ironic_lib/system_installer/partitionsetup.py + create mode 100644 ironic_lib/system_installer/schema.json + create mode 100644 ironic_lib/system_installer/swraidsetup.py + create mode 100644 ironic_lib/system_installer/systemsetup.py + create mode 100644 ironic_lib/system_installer/tools.py + create mode 100644 ironic_lib/tests/examples/Disk_openhouse_preserve_hv.yaml + create mode 100644 ironic_lib/tests/examples/Rhel_layout_1disk_1partition.yaml + create mode 100644 ironic_lib/tests/examples/Rhel_layout_4disk_raid10.yaml + create mode 100644 ironic_lib/tests/examples/Rhel_layout_6disk_raid5.yaml + create mode 100644 ironic_lib/tests/examples/Rhel_layout_cm3_rhel_mysql.yaml + create mode 100644 ironic_lib/tests/examples/example1.yaml + create mode 100644 ironic_lib/tests/examples/example_conf1.yaml + create mode 100644 ironic_lib/tests/examples/m10n_chef.yaml + create mode 100644 ironic_lib/tests/examples/megacli_partitions.yaml + create mode 100644 ironic_lib/tests/examples/use_2nd_disk.yaml + create mode 100644 ironic_lib/tests/examples/yse_bnode_4disk.yaml + create mode 100644 ironic_lib/tests/examples/yse_bnode_4disk_gpt.yaml + create mode 100644 ironic_lib/tests/examples/yse_bnode_fsprofile.yaml + create mode 100644 ironic_lib/tests/test_examples.py + create mode 100644 ironic_lib/tests/test_system_installer.py + create mode 100644 ironic_lib/tests/test_system_installer_base.py + create mode 100755 tools/system_installer.py + +diff --git a/doc/source/system_installer.rst b/doc/source/system_installer.rst +new file mode 100644 +index 0000000..32ce518 +--- /dev/null ++++ b/doc/source/system_installer.rst +@@ -0,0 +1,92 @@ ++================ ++System Installer ++================ ++ ++This is a Python module for formatting disks. The configuration format is ++described in the ``ironic_lib/system_installer/schema.json``. See example ++configurations in ``ironic_lib/tests/examples/``. ++ ++Getting Started ++=============== ++ ++Example usage: ++ ++ ++.. code-block:: python ++ ++ from ironic_lib.system_installer import SystemInstaller, ConfError ++ ++ system_installer = SystemInstaller(yaml_configuration_string) ++ try: ++ system_installer.validate_conf() ++ except ConfError: ++ print('bad configuration provided') ++ else: ++ system_installer.install(qcow2_image_path) ++ ++Preserve flag handling ++====================== ++ ++In case a preserve flag is set on a filesystem, all previous disks partitioning, ++RAID setup etc. is skipped. Partition devices are mapped by their labels and ++those without the preserve flag are reformatted, while the filesystems marked ++with ``preserve`` are only added to fstab. They are also not mounted during the ++system installation. ++ ++ ++Code structure ++============== ++ ++The main module is located in ``ironic_lib/system_installer/__init__.py``. ++To limit the spagetti code and make the code more readable, it is structured ++the same way as the configuration file. The SystemInstaller class calls methods ++on a list of classes derived from ``ironic_lib/system_installer/base.py``, ++which perform separate functions, like partitioning, setting up RAIDs and LVMs, ++creating filesystems and finally installing the system. ++ ++Implementing a new setup class ++============================== ++ ++The default ``__init__`` method saves the part of the config with which the ++particular class should be concerned (like only the LVM configuration) ++in ``self.conf``. ++ ++All classes implementing the Setup interface should implement at least the ++``get_src_names``, ``get_dst_names`` and ``setup_disks`` methods. ++ ++The first two methods are used in configuration validation. They return lists ++of labels used throught the configuration. ``get_src_names`` returns the labels ++of devices which are needed as input to the disk setup. This method should ++parse the configuration and return a list of all input device names. ++``get_dst_names`` should return another list, which contains the output names ++of created devices. ++ ++The last required method is ``setup_disks``. It takes a dictionary ++containing a translation of names/labels used in the configuration to device ++names needed as input for system commandline tools, implementing the ++particular class behavior. This method should perform the disk setup and ++return the same dictionary, enchanced with new keys (labels used in the config) ++and values (new device filenames). If the input dictionary already contains ++labels of devices which would normally be processed by the class, this means ++they are already configured and should not be touched. The same applies to ++devices present in the input dictionary - they should be excluded from ++searches for new input devices, because they are already used. ++ ++First optional method is ``clean_disks``, it is run before the ``setup_disks`` ++method and in reverse (meaning, that FilesystemSetup is run before ++PartitioningSetup). It accepts a dictionary with labels and devices it ++shouldn't touch. ++ ++Two last methods that you may want to implement is additional validation of ++the configuration in ``validate_conf``, which should raise ``ConfError`` in ++case of a configuration error and ``validate_env`` (raising ``EnvError``, ++which should do basic checks on the environment, before running the disk setup ++process. ++ ++The new class should be imported in ``__init__.py`` and run in a proper order ++to perform the disk setup. ++ ++Unit tests and examples should be added in the ``ironic_lib/tests/`` and ++``ironic_lib/tests/examples/`` directories. ++ ++Any global validation checks are implemented in ``__init__.py``. +diff --git a/ironic_lib/system_installer/__init__.py b/ironic_lib/system_installer/__init__.py +new file mode 100644 +index 0000000..4dbf38c +--- /dev/null ++++ b/ironic_lib/system_installer/__init__.py +@@ -0,0 +1,181 @@ ++# 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 json ++import pkgutil ++ ++import jsonschema ++from oslo_log import log ++ ++from ironic_lib.system_installer.exceptions import ConfError ++from ironic_lib.system_installer.exceptions import EnvError ++from ironic_lib.system_installer.filesystemsetup import FilesystemSetup ++from ironic_lib.system_installer.hpssaclisetup import HpSsaCliSetup ++from ironic_lib.system_installer.lvmsetup import LvmSetup ++from ironic_lib.system_installer.megaclisetup import MegaCliSetup ++from ironic_lib.system_installer.partitionsetup import PartitionSetup ++from ironic_lib.system_installer.swraidsetup import SwRaidSetup ++from ironic_lib.system_installer.systemsetup import SystemSetup ++from ironic_lib.system_installer.tools import ordered_load ++ ++ ++LOG = log.getLogger() ++ ++SCHEMA = json.loads(pkgutil.get_data(__name__, 'schema.json').decode('utf-8')) ++ ++ ++class SystemInstaller(object): ++ def __init__(self, conf): ++ self.conf = ordered_load(conf)['disk_config'] ++ ++ def validate_conf(self): ++ """Check that the configuration is consistent""" ++ try: ++ config = {"disk_config": self.conf} ++ jsonschema.validate(config, SCHEMA) ++ except Exception as e: ++ raise ConfError( ++ "Config doesn't match the schema: {}".format(e.args[0])) ++ ++ if MegaCliSetup.conf_key in self.conf: ++ megacli = MegaCliSetup(self.conf) ++ megacli.validate_conf() ++ filesystemsetup = FilesystemSetup(self.conf) ++ filesystemsetup.validate_conf() ++ filesystems = filesystemsetup.get_src_names() ++ if LvmSetup.conf_key in self.conf: ++ lvm = LvmSetup(self.conf) ++ lvm.validate_conf() ++ pvs = lvm.get_src_names() ++ lvs = lvm.get_dst_names() ++ else: ++ pvs = [] ++ lvs = [] ++ if SwRaidSetup.conf_key in self.conf: ++ swraid = SwRaidSetup(self.conf) ++ swraid.validate_conf() ++ raids = swraid.get_dst_names() ++ raid_partitions = swraid.get_src_names() ++ else: ++ raids = [] ++ raid_partitions = [] ++ partition_setup = PartitionSetup(self.conf) ++ partition_setup.validate_conf() ++ partitions = partition_setup.get_dst_names() ++ ++ if not set(filesystems).issubset(set(partitions + lvs + raids)): ++ raise ConfError('Partitions for filesystems are undefined.') ++ ++ if not set(pvs).issubset(set(partitions + raids)): ++ raise ConfError('Partitions for volumes are undefined.') ++ ++ if not set(raid_partitions).issubset(set(partitions + raids)): ++ raise ConfError('Partitions for RAIDs are undefined.') ++ ++ def validate_env(self): ++ """Run validate_env on all subclasses of Setup""" ++ if MegaCliSetup.conf_key in self.conf: ++ if MegaCliSetup.is_megacli_controller(): ++ MegaCliSetup(self.conf).validate_env() ++ elif HpSsaCliSetup.is_hpraid_controller(): ++ HpSsaCliSetup(self.conf).validate_env() ++ else: ++ raise EnvError('No HP or MegaRAID controller found') ++ ++ for setup in [PartitionSetup, SwRaidSetup, LvmSetup, ++ FilesystemSetup, SystemSetup]: ++ if setup.conf_key in self.conf: ++ setup(self.conf).validate_env() ++ ++ def validate_env_preserve(self): ++ """Run validate_env on all subclasses of Setup""" ++ for setup in [FilesystemSetup, SystemSetup]: ++ if setup.conf_key in self.conf: ++ setup(self.conf).validate_env() ++ ++ def clean_disks(self): ++ """Run clean_disks on all subclasses of Setup, in reverse""" ++ for setup in [SystemSetup, LvmSetup, SwRaidSetup, FilesystemSetup, ++ PartitionSetup]: ++ setup.clean_disks({}) ++ ++ if MegaCliSetup.is_megacli_controller(): ++ MegaCliSetup.clean_disks({}) ++ elif HpSsaCliSetup.is_hpraid_controller(): ++ HpSsaCliSetup.clean_disks({}) ++ ++ def partition_disks(self, devices): ++ return PartitionSetup(self.conf).setup_disks(devices) ++ ++ def make_swraid(self, devices): ++ if SwRaidSetup.conf_key in self.conf: ++ return SwRaidSetup(self.conf).setup_disks(devices) ++ return devices ++ ++ def make_hwraid(self, devices): ++ if MegaCliSetup.conf_key in self.conf: ++ if MegaCliSetup.is_megacli_controller(): ++ return MegaCliSetup(self.conf).setup_disks(devices) ++ elif HpSsaCliSetup.is_hpraid_controller(): ++ return HpSsaCliSetup(self.conf).setup_disks(devices) ++ else: ++ raise EnvError('No HP or MegaRAID controller found') ++ ++ return devices ++ ++ def make_lvm(self, devices): ++ if LvmSetup.conf_key in self.conf: ++ return LvmSetup(self.conf).setup_disks(devices) ++ return devices ++ ++ def format_partitions(self, devices): ++ return FilesystemSetup(self.conf).setup_disks(devices) ++ ++ def install_system(self, devices, image_path): ++ return SystemSetup(self.conf).setup_disks(devices, image_path) ++ ++ def install(self, image_path): ++ self.validate_conf() ++ devices = {} # device map ++ preserve_run = False ++ ++ filesystemsetup = FilesystemSetup(self.conf) ++ if filesystemsetup.has_preserve(): ++ try: ++ devices = filesystemsetup.get_disks_by_labels() ++ preserve_run = True ++ except EnvError as e: ++ # Partition isn't found, go ahead and create it. ++ LOG.warning(e.args[0]) ++ for disk in filesystemsetup.conf.values(): ++ if 'preserve' in disk: ++ del disk['preserve'] ++ # This is potentially dangerous, because if the system isn't ++ # initialized correctly and we don't find the labels, we ++ # will overwrite the data that the user wants to preserve. ++ LOG.warning('Creating new partitions.') ++ else: ++ self.validate_env_preserve() ++ LOG.info('Preserve flag set, formatting other partitions.') ++ ++ if not preserve_run: ++ self.clean_disks() ++ self.validate_env() ++ devices = self.make_hwraid(devices) ++ devices = self.partition_disks(devices) ++ devices = self.make_swraid(devices) ++ devices = self.make_lvm(devices) ++ devices = self.format_partitions(devices) ++ return self.install_system(devices, image_path) +diff --git a/ironic_lib/system_installer/base.py b/ironic_lib/system_installer/base.py +new file mode 100644 +index 0000000..662306f +--- /dev/null ++++ b/ironic_lib/system_installer/base.py +@@ -0,0 +1,53 @@ ++# 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. ++ ++# Base classes ++ ++ ++class Setup(object): ++ """Base class for implementing various steps of system installation""" ++ ++ conf_key = '' ++ ++ def __init__(self, conf): ++ self.conf = conf[self.conf_key] ++ ++ def validate_conf(self): ++ """Validate the contents of self.conf. ++ ++ Raises: system_installer.exceptions.ConfError. ++ """ ++ ++ def validate_env(self): ++ """Validate the environment: check available commands and devices. ++ ++ Raises: system_installer.exceptions.EnvError. ++ """ ++ ++ def get_src_names(self): ++ """Return a list of source device/filesystem names""" ++ raise NotImplemented() ++ ++ def get_dst_names(self): ++ """Return a list of device/filesystem names that will be created""" ++ raise NotImplemented() ++ ++ def setup_disks(self, devices, image_path=None): ++ """Format disks or setup RAID/LVM. Return created devices dict""" ++ raise NotImplemented() ++ ++ @classmethod ++ def clean_disks(self, devices): ++ """Reset state of RAID/LVM setups, free all disks""" +diff --git a/ironic_lib/system_installer/exceptions.py b/ironic_lib/system_installer/exceptions.py +new file mode 100644 +index 0000000..6dcfa46 +--- /dev/null ++++ b/ironic_lib/system_installer/exceptions.py +@@ -0,0 +1,26 @@ ++# 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. ++ ++ ++class SystemInstallerException(Exception): ++ pass ++ ++ ++class ConfError(SystemInstallerException): ++ """Error in the configuration""" ++ ++ ++class EnvError(SystemInstallerException): ++ """Error in the setup environment""" +diff --git a/ironic_lib/system_installer/filesystemsetup.py b/ironic_lib/system_installer/filesystemsetup.py +new file mode 100644 +index 0000000..e3157d2 +--- /dev/null ++++ b/ironic_lib/system_installer/filesystemsetup.py +@@ -0,0 +1,34 @@ ++# 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 ironic_lib.system_installer.base import Setup ++ ++ ++class FilesystemSetup(Setup): ++ """Basic disk formatting implementation""" ++ ++ conf_key = 'filesystems' ++ ++ def get_src_names(self): ++ return list(self.conf) ++ ++ def has_preserve(self): ++ for disk in self.conf.values(): ++ if disk.get('preserve', False): ++ return True ++ return False ++ ++ def get_disks_by_labels(self): ++ return {} +diff --git a/ironic_lib/system_installer/hpssaclisetup.py b/ironic_lib/system_installer/hpssaclisetup.py +new file mode 100644 +index 0000000..9cbecf2 +--- /dev/null ++++ b/ironic_lib/system_installer/hpssaclisetup.py +@@ -0,0 +1,26 @@ ++# 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 ironic_lib.system_installer.base import Setup ++ ++ ++class HpSsaCliSetup(Setup): ++ """Hardware RAID setup implementation""" ++ ++ conf_key = 'hwraid' ++ ++ @staticmethod ++ def is_hpraid_controller(): ++ return False +diff --git a/ironic_lib/system_installer/lvmsetup.py b/ironic_lib/system_installer/lvmsetup.py +new file mode 100644 +index 0000000..890c95d +--- /dev/null ++++ b/ironic_lib/system_installer/lvmsetup.py +@@ -0,0 +1,28 @@ ++# 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 ironic_lib.system_installer.base import Setup ++ ++ ++class LvmSetup(Setup): ++ """LVM setup implementation""" ++ ++ conf_key = 'lvm' ++ ++ 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()], []) +diff --git a/ironic_lib/system_installer/megaclisetup.py b/ironic_lib/system_installer/megaclisetup.py +new file mode 100644 +index 0000000..76cae96 +--- /dev/null ++++ b/ironic_lib/system_installer/megaclisetup.py +@@ -0,0 +1,26 @@ ++# 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 ironic_lib.system_installer.base import Setup ++ ++ ++class MegaCliSetup(Setup): ++ """Hardware RAID setup implementation""" ++ ++ conf_key = 'hwraid' ++ ++ @staticmethod ++ def is_megacli_controller(): ++ return False +diff --git a/ironic_lib/system_installer/partitionsetup.py b/ironic_lib/system_installer/partitionsetup.py +new file mode 100644 +index 0000000..af318d1 +--- /dev/null ++++ b/ironic_lib/system_installer/partitionsetup.py +@@ -0,0 +1,27 @@ ++# 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 ironic_lib.system_installer.base import Setup ++ ++ ++class PartitionSetup(Setup): ++ """Basic partitioning implementation""" ++ ++ conf_key = 'blockdev' ++ ++ def get_dst_names(self): ++ partitions = [list(v.get('partitions', {})) ++ for v in self.conf.values()] ++ return sum(partitions, []) +diff --git a/ironic_lib/system_installer/schema.json b/ironic_lib/system_installer/schema.json +new file mode 100644 +index 0000000..5650150 +--- /dev/null ++++ b/ironic_lib/system_installer/schema.json +@@ -0,0 +1,225 @@ ++{ ++ "$schema": "http://json-schema.org/schema#", ++ "definitions": { ++ "partition": { ++ "type": "object", ++ "properties": { ++ "size": { ++ "type": "string", ++ "description": "Absolute size of partition" ++ }, ++ "minsize": { ++ "type": "string", ++ "description": "Minimum size of partition, if any space left expand this partition" ++ }, ++ "type": { ++ "type": "string", ++ "description": "Type of partition" ++ } ++ }, ++ "oneOf": [ ++ {"required": ["minsize"]}, ++ {"required": ["size"]} ++ ], ++ "additionalProperties": false ++ }, ++ "blockdevice": { ++ "type": "object", ++ "properties": { ++ "candidates": { ++ "oneOf": [ ++ { ++ "type": "string", ++ "description": "Use any device.", ++ "enum": ["any"], ++ "default": "any" ++ }, ++ { ++ "type": "object", ++ "description": "Dict of device hints to choose appropriate disk", ++ "properties": { ++ "serial": { ++ "type": "string", ++ "description": "Serial number of the disk" ++ }, ++ "model": { ++ "type": "string", ++ "description": "Model of the disk" ++ }, ++ "disk_type": { ++ "type": "string", ++ "enum": ["SSD", "HDD", "NVMe"], ++ "description": "Type of disk to use" ++ }, ++ "max_disk_size_gb": { ++ "type": "string", ++ "description": "Maximum size of the disk to use" ++ } ++ }, ++ "additionalProperties": false ++ } ++ ] ++ }, ++ "partitions": { ++ "type": "object", ++ "description": "Dictionary of partitions to create", ++ "additionalProperties": {"$ref": "#/definitions/partition"} ++ } ++ }, ++ "additionalProperties": false ++ }, ++ "logical_volume": { ++ "type": "object", ++ "properties": { ++ "size": { ++ "type": "string", ++ "description": "Absolute size of LV" ++ }, ++ "minsize": { ++ "type": "string", ++ "description": "Minimum size of LV, if any space left expand this LV" ++ } ++ }, ++ "oneOf": [ ++ {"required": ["minsize"]}, ++ {"required": ["size"]} ++ ], ++ "additionalProperties": false ++ }, ++ "volume_group": { ++ "type": "object", ++ "properties": { ++ "LVs": { ++ "type": "object", ++ "description": "Dictionary of partitions to create", ++ "additionalProperties": {"$ref": "#/definitions/logical_volume"} ++ }, ++ "PVs": { ++ "type": "array", ++ "items": { ++ "type": "string" ++ } ++ } ++ }, ++ "additionalProperties": false ++ }, ++ "filesystem": { ++ "type": "object", ++ "properties": { ++ "label": { ++ "type": "string", ++ "description": "Label of filesystem" ++ }, ++ "mountpoint": { ++ "type": "string", ++ "description": "Where to mount this filesystem." ++ }, ++ "fstype": { ++ "type": "string", ++ "description": "Filesystem to create on given partition", ++ "enum": ["xfs", "ext4", "ext3", "swap", "btrfs", "vfat"], ++ "default": "xfs" ++ }, ++ "mountopts": { ++ "type": "string", ++ "description": "Options to include in /etc/fstab" ++ }, ++ "mkfsopts": { ++ "type": "string", ++ "description": "Options to use when creating filesystem" ++ }, ++ "preserve": { ++ "type": "number", ++ "enum": [0, 1], ++ "description": "If set to 1 prevent from wiping data on a given disk" ++ } ++ }, ++ "additionalProperties": false ++ }, ++ "hwraid": { ++ "type": "object", ++ "properties": { ++ "raidtype": { ++ "type": "number", ++ "enum": [0, 1, 10, 5, 6], ++ "description": "Type of software raid to use" ++ }, ++ "stripe_size": { ++ "type": "number", ++ "enum": [64, 128, 256, 512, 1024], ++ "default": 512, ++ "description": "Stripe size in KB" ++ }, ++ "partitions": { ++ "description": "List of partitions to make software raid from", ++ "type": "array", ++ "items": { ++ "type": "string" ++ } ++ } ++ }, ++ "additionalProperties": false ++ }, ++ "software_raid": { ++ "type": "object", ++ "properties": { ++ "raidtype": { ++ "type": "number", ++ "enum": [0, 1, 10, 5], ++ "description": "Type of software raid to use" ++ }, ++ "partitions": { ++ "description": "List of partitions to make software raid from", ++ "type": "array", ++ "items": { ++ "type": "string" ++ } ++ } ++ }, ++ "additionalProperties": false ++ } ++ }, ++ "type": "object", ++ "properties": { ++ "disk_config": { ++ "type": "object", ++ "properties": { ++ "partition_table": { ++ "type": "string", ++ "enum": ["gpt", "mbr"], ++ "default": "mbr", ++ "description": "Partition table type to use" ++ }, ++ "blockdev": { ++ "type": "object", ++ "description": "Dictionary of objects representing physical disk", ++ "additionalProperties": {"$ref": "#/definitions/blockdevice"} ++ }, ++ "lvm": { ++ "type": "object", ++ "description": "Dictionary of volume groups", ++ "additionalProperties": {"$ref": "#/definitions/volume_group"} ++ }, ++ "filesystems": { ++ "type": "object", ++ "description": "Dictionary of filesystems to create", ++ "additionalProperties": {"$ref": "#/definitions/filesystem"} ++ }, ++ "swraid": { ++ "type": "object", ++ "description": "Dictionary of software raids to create", ++ "additionalProperties": {"$ref": "#/definitions/software_raid"} ++ }, ++ "hwraid": { ++ "type": "object", ++ "description": "Dictionary of hardware raids to create", ++ "additionalProperties": {"$ref": "#/definitions/hwraid"} ++ } ++ }, ++ "additionalProperties": false, ++ "required": ["filesystems", "blockdev"] ++ } ++ }, ++ "additionalProperties": false, ++ "required": ["disk_config"] ++} +diff --git a/ironic_lib/system_installer/swraidsetup.py b/ironic_lib/system_installer/swraidsetup.py +new file mode 100644 +index 0000000..2965aaa +--- /dev/null ++++ b/ironic_lib/system_installer/swraidsetup.py +@@ -0,0 +1,29 @@ ++# 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 ironic_lib.system_installer.base import Setup ++ ++ ++class SwRaidSetup(Setup): ++ """Software RAID setup implementation""" ++ ++ conf_key = 'swraid' ++ ++ def get_src_names(self): ++ raid_partitions = [r['partitions'] for r in self.conf.values()] ++ return sum(raid_partitions, []) ++ ++ def get_dst_names(self): ++ return list(self.conf) +diff --git a/ironic_lib/system_installer/systemsetup.py b/ironic_lib/system_installer/systemsetup.py +new file mode 100644 +index 0000000..e03c5b4 +--- /dev/null ++++ b/ironic_lib/system_installer/systemsetup.py +@@ -0,0 +1,20 @@ ++# 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 ironic_lib.system_installer.filesystemsetup import FilesystemSetup ++ ++ ++class SystemSetup(FilesystemSetup): ++ """Basic disk formatting implementation""" +diff --git a/ironic_lib/system_installer/tools.py b/ironic_lib/system_installer/tools.py +new file mode 100644 +index 0000000..71f6d60 +--- /dev/null ++++ b/ironic_lib/system_installer/tools.py +@@ -0,0 +1,36 @@ ++# 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 collections ++import json ++import yaml ++ ++ ++def ordered_load(disk_conf): ++ """Load JSON or YAML configuration as OrderedDicts""" ++ try: # Parse JSON as OrderedDicts ++ return json.loads(disk_conf, object_pairs_hook=collections.OrderedDict) ++ except ValueError: ++ # Parse YAML as OrderedDicts ++ class OrderedLoader(yaml.SafeLoader): ++ pass ++ ++ def construct_mapping(loader, node): ++ loader.flatten_mapping(node) ++ return collections.OrderedDict(loader.construct_pairs(node)) ++ OrderedLoader.add_constructor( ++ yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, ++ construct_mapping) ++ return yaml.load(disk_conf, OrderedLoader) +diff --git a/ironic_lib/tests/examples/Disk_openhouse_preserve_hv.yaml b/ironic_lib/tests/examples/Disk_openhouse_preserve_hv.yaml +new file mode 100644 +index 0000000..39cb2b0 +--- /dev/null ++++ b/ironic_lib/tests/examples/Disk_openhouse_preserve_hv.yaml +@@ -0,0 +1,44 @@ ++# Preserves /openstack partition ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ partitions: ++ d0p1: ++ size: 512M ++ d0p2: ++ minsize: 577G ++ lvm: ++ sys: ++ LVs: ++ openstack: ++ minsize: 1G ++ home: ++ size: 30G ++ root: ++ size: 16G ++ swap: ++ size: 200G ++ var: ++ size: 10G ++ tmp: ++ size: 4G ++ PVs: ++ - d0p2 ++ filesystems: ++ d0p1: ++ label: /boot ++ mountpoint: /boot ++ home: ++ mountpoint: /home ++ root: ++ mountpoint: / ++ swap: ++ fstype: swap ++ var: ++ mountpoint: /var ++ tmp: ++ mountpoint: /tmp ++ openstack: ++ mountpoint: /openstack ++ preserve: 1 +diff --git a/ironic_lib/tests/examples/Rhel_layout_1disk_1partition.yaml b/ironic_lib/tests/examples/Rhel_layout_1disk_1partition.yaml +new file mode 100644 +index 0000000..2eac8a0 +--- /dev/null ++++ b/ironic_lib/tests/examples/Rhel_layout_1disk_1partition.yaml +@@ -0,0 +1,18 @@ ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ partitions: ++ d0p1: ++ minsize: 2G ++ d0p2: ++ size: memsize ++ type: linux-swap ++ filesystems: ++ d0p1: ++ fstype: ext3 ++ label: / ++ mountpoint: / ++ d0p2: ++ fstype: swap ++ +\ No newline at end of file +diff --git a/ironic_lib/tests/examples/Rhel_layout_4disk_raid10.yaml b/ironic_lib/tests/examples/Rhel_layout_4disk_raid10.yaml +new file mode 100644 +index 0000000..9a0f269 +--- /dev/null ++++ b/ironic_lib/tests/examples/Rhel_layout_4disk_raid10.yaml +@@ -0,0 +1,101 @@ ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ partitions: ++ d0p0: ++ size: 4G ++ d0p1: ++ size: 4G ++ d0p2: ++ size: 4G ++ d0p3: ++ minsize: 2G ++ disk1: ++ candidates: any ++ partitions: ++ d1p0: ++ size: 4G ++ d1p1: ++ size: 4G ++ d1p2: ++ size: 4G ++ d1p3: ++ minsize: 2G ++ disk2: ++ candidates: any ++ partitions: ++ d2p0: ++ size: 4G ++ type: linux-swap ++ d2p1: ++ size: 4G ++ d2p2: ++ size: 4G ++ d2p3: ++ minsize: 2G ++ disk3: ++ candidates: any ++ partitions: ++ d3p0: ++ size: 4G ++ type: linux-swap ++ d3p1: ++ size: 4G ++ d3p2: ++ size: 4G ++ d3p3: ++ minsize: 2G ++ swraid: ++ md0: ++ raidtype: 1 ++ partitions: ++ - d0p0 ++ - d1p0 ++ md1: ++ raidtype: 5 ++ partitions: ++ - d0p1 ++ - d1p1 ++ - d2p1 ++ - d3p1 ++ md2: ++ raidtype: 5 ++ partitions: ++ - d0p2 ++ - d1p2 ++ - d2p2 ++ - d3p2 ++ md3: ++ raidtype: 0 ++ partitions: ++ - d0p3 ++ - d1p3 ++ md4: ++ raidtype: 0 ++ partitions: ++ - d2p3 ++ - d3p3 ++ md5: ++ raidtype: 1 ++ partitions: ++ - md3 ++ - md4 ++ ++ filesystems: ++ md0: ++ label: ROOT ++ mountpoint: / ++ md1: ++ label: VAR ++ mountpoint: /var ++ md2: ++ label: TMP ++ mountpoint: /tmp ++ md5: ++ label: HOME ++ mountpoint: /home ++ d2p0: ++ fstype: swap ++ d3p0: ++ fstype: swap +diff --git a/ironic_lib/tests/examples/Rhel_layout_6disk_raid5.yaml b/ironic_lib/tests/examples/Rhel_layout_6disk_raid5.yaml +new file mode 100644 +index 0000000..a0307e9 +--- /dev/null ++++ b/ironic_lib/tests/examples/Rhel_layout_6disk_raid5.yaml +@@ -0,0 +1,125 @@ ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ partitions: ++ d0p0: ++ size: 4G ++ d0p1: ++ size: 4G ++ d0p2: ++ size: 4G ++ d0p3: ++ minsize: 2G ++ disk1: ++ candidates: any ++ partitions: ++ d1p0: ++ size: 4G ++ type: linux-swap ++ d1p1: ++ size: 4G ++ d1p2: ++ size: 4G ++ d1p3: ++ minsize: 2G ++ disk2: ++ candidates: any ++ partitions: ++ d2p0: ++ size: 4G ++ d2p1: ++ size: 4G ++ d2p2: ++ size: 4G ++ d2p3: ++ minsize: 2G ++ disk3: ++ candidates: any ++ partitions: ++ d3p0: ++ size: 4G ++ type: linux-swap ++ d3p1: ++ size: 4G ++ d3p2: ++ size: 4G ++ d3p3: ++ minsize: 2G ++ disk4: ++ candidates: any ++ partitions: ++ d4p0: ++ size: 4G ++ d4p1: ++ size: 4G ++ d4p2: ++ size: 4G ++ d4p3: ++ minsize: 2G ++ disk5: ++ candidates: any ++ partitions: ++ d5p0: ++ size: 4G ++ type: linux-swap ++ d5p1: ++ size: 4G ++ d5p2: ++ size: 4G ++ d5p3: ++ minsize: 2G ++ swraid: ++ md0: ++ raidtype: 1 ++ partitions: ++ - d0p0 ++ - d2p0 ++ - d4p0 ++ md1: ++ raidtype: 5 ++ partitions: ++ - d0p1 ++ - d1p1 ++ - d2p1 ++ - d3p1 ++ - d4p1 ++ - d5p1 ++ md2: ++ raidtype: 5 ++ partitions: ++ - d0p2 ++ - d1p2 ++ - d2p2 ++ - d3p2 ++ - d4p2 ++ - d5p2 ++ md3: ++ raidtype: 5 ++ partitions: ++ - d0p3 ++ - d1p3 ++ - d2p3 ++ - d3p3 ++ - d4p3 ++ - d5p3 ++ ++ filesystems: ++ md0: ++ label: ROOT ++ mountpoint: / ++ md1: ++ label: VAR ++ mountpoint: /var ++ md2: ++ label: TMP ++ mountpoint: /tmp ++ md3: ++ label: HOME ++ mountpoint: /home ++ d1p0: ++ fstype: swap ++ d3p0: ++ fstype: swap ++ d5p0: ++ fstype: swap +diff --git a/ironic_lib/tests/examples/Rhel_layout_cm3_rhel_mysql.yaml b/ironic_lib/tests/examples/Rhel_layout_cm3_rhel_mysql.yaml +new file mode 100644 +index 0000000..ac85f97 +--- /dev/null ++++ b/ironic_lib/tests/examples/Rhel_layout_cm3_rhel_mysql.yaml +@@ -0,0 +1,43 @@ ++# profile with mkfsopts and mountopts ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ partitions: ++ d0p1: ++ size: 256M ++ d0p2: ++ minsize: 2G ++ lvm: ++ sys: ++ LVs: ++ root: ++ size: 10G ++ swap: ++ size: memsize ++ var: ++ size: 4G ++ tmp: ++ size: 4G ++ home: ++ minsize: 2G ++ PVs: ++ - d0p2 ++ filesystems: ++ d0p1: ++ fstype: ext3 ++ label: /boot ++ mountpoint: /boot ++ root: ++ mountpoint: / ++ swap: ++ fstype: swap ++ var: ++ mountpoint: /var ++ tmp: ++ mountpoint: /tmp ++ home: ++ fstype: xfs ++ mkfsopts: -d noalign ++ mountopts: noatime,nodiratime,logbufs=8,nobarrier ++ mountpoint: /home +diff --git a/ironic_lib/tests/examples/example1.yaml b/ironic_lib/tests/examples/example1.yaml +new file mode 100644 +index 0000000..bf5396e +--- /dev/null ++++ b/ironic_lib/tests/examples/example1.yaml +@@ -0,0 +1,39 @@ ++disk_config: ++ partition_table: gpt ++ blockdev: ++ sda: ++ candidates: any ++ partitions: ++ d0p1: ++ size: 512M ++ d0p2: ++ minsize: 2G ++ lvm: ++ sys: ++ LVs: ++ home: ++ minsize: 1G ++ root: ++ size: 10G ++ swap: ++ size: memsize ++ var: ++ size: 4G ++ tmp: ++ size: 4G ++ 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/examples/example_conf1.yaml b/ironic_lib/tests/examples/example_conf1.yaml +new file mode 100644 +index 0000000..9d8f176 +--- /dev/null ++++ b/ironic_lib/tests/examples/example_conf1.yaml +@@ -0,0 +1,16 @@ ++disk_config: ++ blockdev: ++ sda: ++ candidates: ++ serial: 55cd2e404c02bac5 ++ partitions: ++ d0p1: ++ size: 512M ++ d0p2: ++ minsize: 2G ++ filesystems: ++ d0p1: ++ label: /boot ++ mountpoint: /boot ++ d0p2: ++ mountpoint: / +diff --git a/ironic_lib/tests/examples/m10n_chef.yaml b/ironic_lib/tests/examples/m10n_chef.yaml +new file mode 100644 +index 0000000..176e9eb +--- /dev/null ++++ b/ironic_lib/tests/examples/m10n_chef.yaml +@@ -0,0 +1,22 @@ ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ partitions: ++ sda1: ++ size: 8G ++ sda2: ++ size: 2G ++ sda3: ++ minsize: 2G ++ filesystems: ++ sda1: ++ fstype: ext3 ++ label: / ++ mountpoint: / ++ sda2: ++ fstype: swap ++ sda3: ++ fstype: ext3 ++ label: VAR ++ mountpoint: /var +diff --git a/ironic_lib/tests/examples/megacli_partitions.yaml b/ironic_lib/tests/examples/megacli_partitions.yaml +new file mode 100644 +index 0000000..36608ba +--- /dev/null ++++ b/ironic_lib/tests/examples/megacli_partitions.yaml +@@ -0,0 +1,26 @@ ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ disk1: ++ candidates: any ++ disk2: ++ candidates: any ++ raid0: ++ partitions: ++ p1: ++ size: 512M ++ p2: ++ minsize: 2G ++ hwraid: ++ raid0: ++ raidtype: 0 ++ partitions: ++ - disk0 ++ - disk1 ++ - disk2 ++ filesystems: ++ p2: ++ mountpoint: / ++ p1: ++ mountpoint: /boot +diff --git a/ironic_lib/tests/examples/use_2nd_disk.yaml b/ironic_lib/tests/examples/use_2nd_disk.yaml +new file mode 100644 +index 0000000..948fd21 +--- /dev/null ++++ b/ironic_lib/tests/examples/use_2nd_disk.yaml +@@ -0,0 +1,40 @@ ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ disk1: ++ candidates: any ++ partitions: ++ d1p1: ++ size: 512M ++ d1p2: ++ minsize: 2G ++ lvm: ++ sys: ++ LVs: ++ home: ++ minsize: 1G ++ root: ++ size: 10G ++ swap: ++ size: memsize ++ var: ++ size: 4G ++ tmp: ++ size: 4G ++ PVs: ++ - d1p2 ++ filesystems: ++ d1p1: ++ label: /boot ++ mountpoint: /boot ++ home: ++ mountpoint: /home ++ root: ++ mountpoint: / ++ swap: ++ fstype: swap ++ var: ++ mountpoint: /var ++ tmp: ++ mountpoint: /tmp +diff --git a/ironic_lib/tests/examples/yse_bnode_4disk.yaml b/ironic_lib/tests/examples/yse_bnode_4disk.yaml +new file mode 100644 +index 0000000..5226232 +--- /dev/null ++++ b/ironic_lib/tests/examples/yse_bnode_4disk.yaml +@@ -0,0 +1,111 @@ ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ partitions: ++ d0p1: ++ size: 1G ++ d0p2: ++ size: 48G ++ type: linux-swap ++ d0p3: ++ size: 32G ++ d0p4: ++ size: 30G ++ d0p5: ++ minsize: 2G ++ disk1: ++ candidates: any ++ partitions: ++ d1p1: ++ size: 1G ++ d1p2: ++ size: 48G ++ type: linux-swap ++ d1p3: ++ size: 32G ++ d1p4: ++ size: 30G ++ d1p5: ++ minsize: 2G ++ disk2: ++ candidates: any ++ partitions: ++ d2p1: ++ size: 1G ++ d2p2: ++ size: 48G ++ type: linux-swap ++ d2p3: ++ size: 32G ++ d2p4: ++ size: 30G ++ d2p5: ++ minsize: 2G ++ disk3: ++ candidates: any ++ partitions: ++ d3p1: ++ size: 1G ++ d3p2: ++ size: 48G ++ type: linux-swap ++ d3p3: ++ size: 32G ++ d3p4: ++ size: 30G ++ d3p5: ++ minsize: 2G ++ swraid: ++ md3: ++ raidtype: 0 ++ partitions: ++ - d0p3 ++ - d1p3 ++ - d2p3 ++ - d3p3 ++ md4: ++ raidtype: 0 ++ partitions: ++ - d0p4 ++ - d1p4 ++ - d2p4 ++ - d3p4 ++ md5: ++ raidtype: 0 ++ partitions: ++ - d0p5 ++ - d1p5 ++ - d2p5 ++ - d3p5 ++ filesystems: ++ d0p1: ++ mountpoint: /boot ++ label: BOOT ++ md3: ++ fstype: xfs ++ mountopts: noatime,inode64 ++ mountpoint: / ++ label: ROOT ++ md4: ++ fstype: xfs ++ mountopts: noatime,inode64 ++ mountpoint: /home ++ label: HOME ++ md5: ++ fstype: xfs ++ mountopts: noatime,inode64 ++ mountpoint: /export/crawlspace ++ label: EXCR ++ d0p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' ++ d1p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' ++ d2p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' ++ d3p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' +diff --git a/ironic_lib/tests/examples/yse_bnode_4disk_gpt.yaml b/ironic_lib/tests/examples/yse_bnode_4disk_gpt.yaml +new file mode 100644 +index 0000000..a3eeaef +--- /dev/null ++++ b/ironic_lib/tests/examples/yse_bnode_4disk_gpt.yaml +@@ -0,0 +1,115 @@ ++disk_config: ++ partition_table: gpt ++ blockdev: ++ disk0: ++ candidates: any ++ partitions: ++ bios: ++ size: 200M ++ d0p1: ++ size: 800M ++ d0p2: ++ size: 48G ++ type: linux-swap ++ d0p3: ++ size: 32G ++ d0p4: ++ size: 30G ++ d0p5: ++ minsize: 2G ++ disk1: ++ candidates: any ++ partitions: ++ d1p1: ++ size: 1G ++ d1p2: ++ size: 48G ++ type: linux-swap ++ d1p3: ++ size: 32G ++ d1p4: ++ size: 30G ++ d1p5: ++ minsize: 2G ++ disk2: ++ candidates: any ++ partitions: ++ d2p1: ++ size: 1G ++ d2p2: ++ size: 48G ++ type: linux-swap ++ d2p3: ++ size: 32G ++ d2p4: ++ size: 30G ++ d2p5: ++ minsize: 2G ++ disk3: ++ candidates: any ++ partitions: ++ d3p1: ++ size: 1G ++ d3p2: ++ size: 48G ++ type: linux-swap ++ d3p3: ++ size: 32G ++ d3p4: ++ size: 30G ++ d3p5: ++ minsize: 2G ++ swraid: ++ md3: ++ raidtype: 0 ++ partitions: ++ - d0p3 ++ - d1p3 ++ - d2p3 ++ - d3p3 ++ md4: ++ raidtype: 0 ++ partitions: ++ - d0p4 ++ - d1p4 ++ - d2p4 ++ - d3p4 ++ md5: ++ raidtype: 0 ++ partitions: ++ - d0p5 ++ - d1p5 ++ - d2p5 ++ - d3p5 ++ filesystems: ++ bios: ++ label: BOOT ++ d0p1: ++ mountpoint: /boot ++ md3: ++ fstype: xfs ++ mountopts: noatime,inode64 ++ mountpoint: / ++ label: ROOT ++ md4: ++ fstype: xfs ++ mountopts: noatime,inode64 ++ mountpoint: /home ++ label: HOME ++ md5: ++ fstype: xfs ++ mountopts: noatime,inode64 ++ mountpoint: /export/crawlspace ++ label: EXCR ++ d0p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' ++ d1p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' ++ d2p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' ++ d3p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' +diff --git a/ironic_lib/tests/examples/yse_bnode_fsprofile.yaml b/ironic_lib/tests/examples/yse_bnode_fsprofile.yaml +new file mode 100644 +index 0000000..6f9785d +--- /dev/null ++++ b/ironic_lib/tests/examples/yse_bnode_fsprofile.yaml +@@ -0,0 +1,114 @@ ++disk_config: ++ blockdev: ++ disk0: ++ candidates: any ++ partitions: ++ d0p1: ++ size: 1G ++ d0p2: ++ size: 48G ++ type: linux-swap ++ d0p3: ++ size: 32G ++ d0p4: ++ size: 128G ++ d0p5: ++ minsize: 2G ++ disk1: ++ candidates: any ++ partitions: ++ d1p1: ++ size: 1G ++ d1p2: ++ size: 48G ++ type: linux-swap ++ d1p3: ++ size: 32G ++ d1p4: ++ size: 128G ++ d1p5: ++ minsize: 2G ++ disk2: ++ candidates: any ++ partitions: ++ d2p1: ++ size: 1G ++ d2p2: ++ size: 48G ++ type: linux-swap ++ d2p3: ++ size: 32G ++ d2p4: ++ size: 128G ++ d2p5: ++ minsize: 2G ++ disk3: ++ candidates: any ++ partitions: ++ d3p1: ++ size: 1G ++ d3p2: ++ size: 48G ++ type: linux-swap ++ d3p3: ++ size: 32G ++ d3p4: ++ size: 128G ++ d3p5: ++ minsize: 2G ++ swraid: ++ md3: ++ raidtype: 0 ++ partitions: ++ - d0p3 ++ - d1p3 ++ - d2p3 ++ - d3p3 ++ md4: ++ raidtype: 0 ++ partitions: ++ - d0p4 ++ - d1p4 ++ - d2p4 ++ - d3p4 ++ md5: ++ raidtype: 0 ++ partitions: ++ - d0p5 ++ - d1p5 ++ - d2p5 ++ - d3p5 ++ filesystems: ++ d0p1: ++ mountpoint: /boot ++ label: BOOT ++ md3: ++ fstype: ext4 ++ mkfsopts: -E lazy_itable_init=1 -O uninit_bg ++ mountopts: 'defaults,noatime,nodiratime' ++ mountpoint: / ++ label: ROOT ++ md4: ++ fstype: ext4 ++ mkfsopts: -E lazy_itable_init=1 -O uninit_bg ++ mountopts: 'defaults,noatime,nodiratime' ++ mountpoint: /home ++ label: HOME ++ md5: ++ fstype: ext4 ++ mkfsopts: -E lazy_itable_init=1 -O uninit_bg ++ mountopts: 'defaults,noatime,nodiratime' ++ mountpoint: /export/crawlspace ++ label: EXCR ++ d0p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' ++ d1p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' ++ d2p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' ++ d3p2: ++ fstype: swap ++ mountopts: 'defaults,pri=1' +diff --git a/ironic_lib/tests/test_examples.py b/ironic_lib/tests/test_examples.py +new file mode 100644 +index 0000000..05b5cc7 +--- /dev/null ++++ b/ironic_lib/tests/test_examples.py +@@ -0,0 +1,79 @@ ++# 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 oslotest import base as test_base ++ ++from ironic_lib.system_installer import SystemInstaller ++ ++EXAMPLES_DIR = 'ironic_lib/tests/examples/' ++ ++ ++class SystemInstallerExamplesTestCase(test_base.BaseTestCase): ++ ++ def test_example1(self): ++ y = SystemInstaller(open(EXAMPLES_DIR + 'example1.yaml').read()) ++ y.validate_conf() ++ ++ def test_disk_openhouse_preserve_hv(self): ++ y = SystemInstaller( ++ open(EXAMPLES_DIR + 'Disk_openhouse_preserve_hv.yaml').read()) ++ y.validate_conf() ++ ++ def test_rhel_layout_1disk_1partition(self): ++ y = SystemInstaller( ++ open(EXAMPLES_DIR + 'Rhel_layout_1disk_1partition.yaml').read()) ++ y.validate_conf() ++ ++ def test_rhel_layout_4disk_raid10(self): ++ y = SystemInstaller( ++ open(EXAMPLES_DIR + 'Rhel_layout_4disk_raid10.yaml').read()) ++ y.validate_conf() ++ ++ def test_rhel_layout_cm3_rhel_mysql(self): ++ y = SystemInstaller( ++ open(EXAMPLES_DIR + 'Rhel_layout_cm3_rhel_mysql.yaml').read()) ++ y.validate_conf() ++ ++ def test_rhel_layout_6disk_raid5(self): ++ y = SystemInstaller( ++ open(EXAMPLES_DIR + 'Rhel_layout_6disk_raid5.yaml').read()) ++ y.validate_conf() ++ ++ def test_m10n_chef(self): ++ y = SystemInstaller(open(EXAMPLES_DIR + 'm10n_chef.yaml').read()) ++ y.validate_conf() ++ ++ def test_use_2nd_disk(self): ++ y = SystemInstaller(open(EXAMPLES_DIR + 'use_2nd_disk.yaml').read()) ++ y.validate_conf() ++ ++ def test_yse_bnode_4disk(self): ++ y = SystemInstaller(open(EXAMPLES_DIR + 'yse_bnode_4disk.yaml').read()) ++ y.validate_conf() ++ ++ def test_yse_bnode_4disk_gpt(self): ++ y = SystemInstaller( ++ open(EXAMPLES_DIR + 'yse_bnode_4disk_gpt.yaml').read()) ++ y.validate_conf() ++ ++ def test_yse_bnode_fsprofile(self): ++ y = SystemInstaller( ++ open(EXAMPLES_DIR + 'yse_bnode_fsprofile.yaml').read()) ++ y.validate_conf() ++ ++ def test_megacli_partitions(self): ++ y = SystemInstaller( ++ open(EXAMPLES_DIR + 'megacli_partitions.yaml').read()) ++ y.validate_conf() +diff --git a/ironic_lib/tests/test_system_installer.py b/ironic_lib/tests/test_system_installer.py +new file mode 100644 +index 0000000..ac95b7d +--- /dev/null ++++ b/ironic_lib/tests/test_system_installer.py +@@ -0,0 +1,976 @@ ++# 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 json ++import unittest ++ ++import mock ++ ++from ironic_lib import system_installer as si ++from ironic_lib.system_installer.exceptions import ConfError ++from ironic_lib.system_installer.exceptions import EnvError ++from ironic_lib.system_installer.filesystemsetup import FilesystemSetup ++from ironic_lib.system_installer.hpssaclisetup import HpSsaCliSetup ++from ironic_lib.system_installer.lvmsetup import LvmSetup ++from ironic_lib.system_installer.megaclisetup import MegaCliSetup ++from ironic_lib.system_installer.partitionsetup import PartitionSetup ++from ironic_lib.system_installer.swraidsetup import SwRaidSetup ++from ironic_lib.system_installer.systemsetup import SystemSetup ++ ++ ++class TestSystemInstallerValidateConf(unittest.TestCase): ++ ++ def test_validate_conf_fail_on_json_error(self): ++ cfg_dict = {'disk_config': ['some', 'wrong', 'values']} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ @mock.patch.object(MegaCliSetup, 'validate_conf') ++ def test_validate_conf_fail_on_hwraid_setup(self, ms): ++ ms.side_effect = ConfError ++ cfg_dict = {'disk_config': {'hwraid': ['some', 'wrong', 'values']}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ @mock.patch.object(FilesystemSetup, 'validate_conf') ++ def test_validate_conf_fail_on_fs_setup(self, fs): ++ fs.side_effect = ConfError ++ cfg_dict = {'disk_config': {'filesystems': ['some', 'wrong', ++ 'values']}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ @mock.patch.object(LvmSetup, 'validate_conf') ++ @mock.patch.object(FilesystemSetup, 'validate_conf') ++ def test_validate_conf_fail_on_lvm_setup(self, fs, ls): ++ ls.side_effect = ConfError ++ cfg_dict = {'disk_config': {'filesystems': {}, ++ 'lvm': ['some', 'wrong', 'values']}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ @mock.patch.object(LvmSetup, 'get_src_names') ++ @mock.patch.object(LvmSetup, 'validate_conf') ++ @mock.patch.object(FilesystemSetup, 'validate_conf') ++ def test_get_src_names_fail_on_lvm_setup(self, fs, ls, lsrc): ++ lsrc.side_effect = ConfError ++ cfg_dict = {'disk_config': {'filesystems': {}, ++ 'lvm': {'sys': ['some', 'wrong', ++ 'values']}}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ @mock.patch.object(LvmSetup, 'get_dst_names') ++ @mock.patch.object(LvmSetup, 'get_src_names') ++ @mock.patch.object(LvmSetup, 'validate_conf') ++ @mock.patch.object(FilesystemSetup, 'validate_conf') ++ def test_get_dst_names_fail_on_lvm_setup(self, fs, ls, lsrc, ldst): ++ ldst.side_effect = ConfError ++ cfg_dict = {'disk_config': {'filesystems': {}, ++ 'lvm': {'sys': ['some', 'wrong', ++ 'values']}}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ @mock.patch.object(SwRaidSetup, 'validate_conf') ++ @mock.patch.object(FilesystemSetup, 'validate_conf') ++ def test_validate_conf_fail_on_softraid_setup(self, fs, ss): ++ ss.side_effect = ConfError ++ cfg_dict = {'disk_config': {'filesystems': {}, ++ 'swraid': ['some', 'wrong', 'values']}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ @mock.patch.object(SwRaidSetup, 'get_dst_names') ++ @mock.patch.object(SwRaidSetup, 'validate_conf') ++ @mock.patch.object(FilesystemSetup, 'validate_conf') ++ def test_get_dst_names_fail_on_softraid_setup(self, fs, ss, sdst): ++ sdst.side_effect = AttributeError ++ cfg_dict = { ++ 'disk_config': { ++ "blockdev": { ++ "sda": { ++ "candidates": "any", ++ "partitions": { ++ "d0p1": {"size": "512M"}, ++ "d0p2": { ++ "minsize": "2G" ++ } ++ } ++ } ++ }, ++ 'filesystems': {}, ++ 'swraid': { ++ 'md0': { ++ 'partitions': ['a', 'b', 'c'], ++ 'raidtype': 1 ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(AttributeError, sysinst.validate_conf) ++ ++ @mock.patch.object(SwRaidSetup, 'get_src_names') ++ @mock.patch.object(SwRaidSetup, 'get_dst_names') ++ @mock.patch.object(SwRaidSetup, 'validate_conf') ++ @mock.patch.object(FilesystemSetup, 'validate_conf') ++ def test_get_src_names_fail_on_softraid_setup(self, fs, ss, sdst, ssrc): ++ ssrc.side_effect = AttributeError ++ cfg_dict = { ++ 'disk_config': { ++ "blockdev": { ++ "sda": { ++ "candidates": "any", ++ "partitions": { ++ "d0p1": { ++ "size": "512M" ++ }, ++ "d0p2": { ++ "minsize": "2G" ++ } ++ } ++ } ++ }, ++ 'filesystems': {}, ++ 'swraid': { ++ 'md0': { ++ 'partitions': ['a', 'b', 'c'], ++ 'raidtype': 1 ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(AttributeError, sysinst.validate_conf) ++ ++ @mock.patch.object(PartitionSetup, 'validate_conf') ++ @mock.patch.object(FilesystemSetup, 'validate_conf') ++ def test_validate_conf_fail_on_part_setup(self, fs, ps): ++ ps.side_effect = ConfError ++ cfg_dict = {'disk_config': {'filesystems': {}, ++ 'blockdev': {}}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ @mock.patch.object(PartitionSetup, 'validate_conf') ++ @mock.patch.object(FilesystemSetup, 'validate_conf') ++ def test_validate_conf_pass_on_empty_conf(self, fs, ps): ++ cfg_dict = {'disk_config': {'filesystems': {}, ++ 'blockdev': {}}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertIsNone(sysinst.validate_conf()) ++ ++ def test_validate_conf_fail_on_wrong_fs_part_map(self): ++ cfg_dict = {'disk_config': {'filesystems': {'/': {'mountpoint': '/'}}, ++ 'blockdev': {}}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ def test_validate_conf_fail_on_wrong_lvm_part_map(self): ++ cfg_dict = { ++ 'disk_config': { ++ 'filesystems': { ++ 'home': {'mountpoint': '/'} ++ }, ++ 'blockdev': {}, ++ 'lvm': { ++ 'sys': { ++ 'LVs': { ++ 'home': {'minsize': '1G'} ++ }, ++ 'PVs': ['d0p2'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ def test_validate_conf_fail_on_wrong_raid_part_map(self): ++ # sdst.side_effect = [['d0p1']] ++ cfg_dict = { ++ 'disk_config': { ++ 'filesystems': { ++ 'md0': {'mountpoint': '/'} ++ }, ++ 'blockdev': { ++ 'disk0': { ++ 'd0p0': { ++ 'candidates': 'any' ++ } ++ }, ++ 'disk1': { ++ 'd1p0': { ++ 'candidates': 'any' ++ } ++ } ++ }, ++ 'swraid': { ++ 'md0': { ++ 'raidtype': 1, ++ 'partitions': ['d0p0', 'd1p1'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertRaises(ConfError, sysinst.validate_conf) ++ ++ @unittest.skip('Validating MegaCli not implemented yet') ++ def test_validate_conf_megaraid(self): ++ cfg_dict = { ++ 'disk_config': { ++ 'hwraid': { ++ 'raid0': { ++ 'raidtype': 0, ++ 'partitions': ['disk0', 'disk1'] ++ } ++ }, ++ 'blockdev': { ++ 'disk0': { ++ 'candidates': 'any' ++ }, ++ 'disk1': { ++ 'candidates': 'any' ++ }, ++ 'raid0': { ++ 'partitons': { ++ 'p1': { ++ 'minsize': '2G', ++ } ++ } ++ } ++ }, ++ 'filesystems': { ++ 'p1': {'mountpoint': '/'} ++ }, ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.validate_conf() ++ ++ ++class TestSystemInstallerValidateEnv(unittest.TestCase): ++ ++ @mock.patch.object(SystemSetup, 'validate_env') ++ @mock.patch.object(FilesystemSetup, 'validate_env') ++ def test_validate_env_preserve_on_dummy_config(self, fs, sys): ++ cfg_dict = {'disk_config': {}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.validate_env_preserve() ++ ++ fs.assert_not_called() ++ sys.assert_not_called() ++ ++ @mock.patch.object(SystemSetup, 'validate_env') ++ @mock.patch.object(FilesystemSetup, 'validate_env') ++ def test_validate_env_preserve_on_filesystems(self, fs, sys): ++ cfg_dict = {'disk_config': {'filesystems': {}}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.validate_env_preserve() ++ ++ fs.assert_called() ++ sys.assert_called() ++ ++ @mock.patch.object(SystemSetup, 'validate_env') ++ @mock.patch.object(SwRaidSetup, 'validate_env') ++ @mock.patch.object(MegaCliSetup, 'validate_env') ++ @mock.patch.object(HpSsaCliSetup, 'validate_env') ++ @mock.patch.object(PartitionSetup, 'validate_env') ++ @mock.patch.object(LvmSetup, 'validate_env') ++ @mock.patch.object(FilesystemSetup, 'validate_env') ++ def test_validate_env_on_dummy_config(self, fs, ls, ps, hp, ms, ss, sys): ++ cfg_dict = {'disk_config': {}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.validate_env() ++ ++ fs.assert_not_called() ++ ls.assert_not_called() ++ ps.assert_not_called() ++ ms.assert_not_called() ++ ss.assert_not_called() ++ hp.assert_not_called() ++ sys.assert_not_called() ++ ++ @mock.patch.object(SystemSetup, 'validate_env') ++ @mock.patch.object(SwRaidSetup, 'validate_env') ++ @mock.patch.object(MegaCliSetup, 'validate_env') ++ @mock.patch.object(HpSsaCliSetup, 'validate_env') ++ @mock.patch.object(PartitionSetup, 'validate_env') ++ @mock.patch.object(LvmSetup, 'validate_env') ++ @mock.patch.object(FilesystemSetup, 'validate_env') ++ def test_validate_env_on_lvm_config(self, fs, ls, ps, hp, ms, ss, sys): ++ cfg_dict = { ++ 'disk_config': { ++ 'lvm': { ++ 'sys': { ++ 'LVs': { ++ 'home': {'minsize': '1G'} ++ }, ++ 'PVs': ['d0p2'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.validate_env() ++ ++ fs.assert_not_called() ++ ls.assert_called_once() ++ ps.assert_not_called() ++ ms.assert_not_called() ++ hp.assert_not_called() ++ ss.assert_not_called() ++ sys.assert_not_called() ++ ++ @mock.patch.object(SystemSetup, 'validate_env') ++ @mock.patch.object(SwRaidSetup, 'validate_env') ++ @mock.patch.object(MegaCliSetup, 'validate_env') ++ @mock.patch.object(HpSsaCliSetup, 'validate_env') ++ @mock.patch.object(PartitionSetup, 'validate_env') ++ @mock.patch.object(LvmSetup, 'validate_env') ++ @mock.patch.object(FilesystemSetup, 'validate_env') ++ def test_validate_env_on_swraid_config(self, fs, ls, ps, hp, ms, ss, sys): ++ cfg_dict = { ++ 'disk_config': { ++ 'swraid': { ++ 'md0': { ++ 'raidtype': 1, ++ 'partitions': ['d0p0', 'd1p1'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.validate_env() ++ ++ fs.assert_not_called() ++ ls.assert_not_called() ++ ps.assert_not_called() ++ ms.assert_not_called() ++ hp.assert_not_called() ++ ss.assert_called_once() ++ sys.assert_not_called() ++ ++ @mock.patch.object(SystemSetup, 'validate_env') ++ @mock.patch.object(SwRaidSetup, 'validate_env') ++ @mock.patch.object(MegaCliSetup, 'validate_env') ++ @mock.patch.object(HpSsaCliSetup, 'validate_env') ++ @mock.patch.object(PartitionSetup, 'validate_env') ++ @mock.patch.object(LvmSetup, 'validate_env') ++ @mock.patch.object(FilesystemSetup, 'validate_env') ++ def test_validate_env_on_partsetup_config(self, fs, ls, ps, hp, ms, ss, ++ sys): ++ cfg_dict = { ++ 'disk_config': { ++ 'blockdev': { ++ 'disk0': { ++ 'd0p0': { ++ 'candidates': 'any' ++ } ++ }, ++ 'disk1': { ++ 'd1p0': { ++ 'candidates': 'any' ++ } ++ }, ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.validate_env() ++ ++ fs.assert_not_called() ++ ls.assert_not_called() ++ ps.assert_called_once() ++ ms.assert_not_called() ++ hp.assert_not_called() ++ ss.assert_not_called() ++ sys.assert_not_called() ++ ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(SystemSetup, 'validate_env') ++ @mock.patch.object(SwRaidSetup, 'validate_env') ++ @mock.patch.object(MegaCliSetup, 'validate_env') ++ @mock.patch.object(HpSsaCliSetup, 'validate_env') ++ @mock.patch.object(PartitionSetup, 'validate_env') ++ @mock.patch.object(LvmSetup, 'validate_env') ++ @mock.patch.object(FilesystemSetup, 'validate_env') ++ def test_validate_env_on_hwraid_config_megacli(self, fs, ls, ps, hp, ms, ++ ss, sys, is_megacli, ++ is_hpraid): ++ cfg_dict = { ++ 'disk_config': { ++ 'hwraid': { ++ 'raid0': { ++ 'raidtype': 5, ++ 'partitions': ['disk0', 'disk1'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ is_megacli.return_value = True ++ is_hpraid.return_value = False ++ ++ sysinst.validate_env() ++ ++ fs.assert_not_called() ++ ls.assert_not_called() ++ ps.assert_not_called() ++ ms.assert_called_once() ++ hp.assert_not_called() ++ ss.assert_not_called() ++ sys.assert_not_called() ++ ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(SystemSetup, 'validate_env') ++ @mock.patch.object(SwRaidSetup, 'validate_env') ++ @mock.patch.object(MegaCliSetup, 'validate_env') ++ @mock.patch.object(HpSsaCliSetup, 'validate_env') ++ @mock.patch.object(PartitionSetup, 'validate_env') ++ @mock.patch.object(LvmSetup, 'validate_env') ++ @mock.patch.object(FilesystemSetup, 'validate_env') ++ def test_validate_env_on_hwraid_config_hpraid(self, fs, ls, ps, hp, ms, ++ ss, sys, is_megacli, ++ is_hpraid): ++ cfg_dict = { ++ 'disk_config': { ++ 'hwraid': { ++ 'raid0': { ++ 'raidtype': 5, ++ 'partitions': ['disk0', 'disk1'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ is_megacli.return_value = False ++ is_hpraid.return_value = True ++ ++ sysinst.validate_env() ++ ++ fs.assert_not_called() ++ ls.assert_not_called() ++ ps.assert_not_called() ++ ms.assert_not_called() ++ hp.assert_called_once() ++ ss.assert_not_called() ++ sys.assert_not_called() ++ ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(SystemSetup, 'validate_env') ++ @mock.patch.object(SwRaidSetup, 'validate_env') ++ @mock.patch.object(MegaCliSetup, 'validate_env') ++ @mock.patch.object(HpSsaCliSetup, 'validate_env') ++ @mock.patch.object(PartitionSetup, 'validate_env') ++ @mock.patch.object(LvmSetup, 'validate_env') ++ @mock.patch.object(FilesystemSetup, 'validate_env') ++ def test_validate_env_on_hwraid_config_fails(self, fs, ls, ps, hp, ms, ++ ss, sys, is_megacli, ++ is_hpraid): ++ cfg_dict = { ++ 'disk_config': { ++ 'hwraid': { ++ 'raid0': { ++ 'raidtype': 5, ++ 'partitions': ['disk0', 'disk1'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ is_megacli.return_value = False ++ is_hpraid.return_value = False ++ ++ with self.assertRaises(EnvError): ++ sysinst.validate_env() ++ ++ fs.assert_not_called() ++ ls.assert_not_called() ++ ps.assert_not_called() ++ ms.assert_not_called() ++ hp.assert_not_called() ++ ss.assert_not_called() ++ sys.assert_not_called() ++ ++ ++class TestSystemInstallerCleanDisks(unittest.TestCase): ++ ++ @mock.patch.object(SystemSetup, 'clean_disks') ++ @mock.patch.object(SwRaidSetup, 'clean_disks') ++ @mock.patch.object(MegaCliSetup, 'clean_disks') ++ @mock.patch.object(HpSsaCliSetup, 'clean_disks') ++ @mock.patch.object(PartitionSetup, 'clean_disks') ++ @mock.patch.object(LvmSetup, 'clean_disks') ++ @mock.patch.object(FilesystemSetup, 'clean_disks') ++ def test_clean_disks_on_dummy_config(self, fs, ls, ps, hp, ms, ss, sys): ++ cfg_dict = {'disk_config': {}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.validate_env() ++ ++ fs.assert_not_called() ++ ls.assert_not_called() ++ ps.assert_not_called() ++ ms.assert_not_called() ++ hp.assert_not_called() ++ ss.assert_not_called() ++ sys.assert_not_called() ++ ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(SystemSetup, 'clean_disks') ++ @mock.patch.object(SwRaidSetup, 'clean_disks') ++ @mock.patch.object(MegaCliSetup, 'clean_disks') ++ @mock.patch.object(HpSsaCliSetup, 'clean_disks') ++ @mock.patch.object(PartitionSetup, 'clean_disks') ++ @mock.patch.object(LvmSetup, 'clean_disks') ++ @mock.patch.object(FilesystemSetup, 'clean_disks') ++ def test_clean_disks_on_lvm_config(self, fs, ls, ps, hp, ms, ss, sys, ++ is_hpraid, is_megacli): ++ is_hpraid.return_value = False ++ is_megacli.return_value = False ++ cfg_dict = { ++ 'disk_config': { ++ 'lvm': { ++ 'sys': { ++ 'LVs': { ++ 'home': {'minsize': '1G'} ++ }, ++ 'PVs': ['d0p2'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.clean_disks() ++ ++ fs.assert_called_once() ++ ls.assert_called_once() ++ ps.assert_called_once() ++ ms.assert_not_called() ++ hp.assert_not_called() ++ ss.assert_called_once() ++ sys.assert_called_once() ++ ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(SystemSetup, 'clean_disks') ++ @mock.patch.object(SwRaidSetup, 'clean_disks') ++ @mock.patch.object(MegaCliSetup, 'clean_disks') ++ @mock.patch.object(HpSsaCliSetup, 'clean_disks') ++ @mock.patch.object(PartitionSetup, 'clean_disks') ++ @mock.patch.object(LvmSetup, 'clean_disks') ++ @mock.patch.object(FilesystemSetup, 'clean_disks') ++ def test_clean_disks_on_swraid_config(self, fs, ls, ps, hp, ms, ss, sys, ++ is_hpraid, is_megacli): ++ is_hpraid.return_value = False ++ is_megacli.return_value = False ++ cfg_dict = { ++ 'disk_config': { ++ 'swraid': { ++ 'md0': { ++ 'raidtype': 1, ++ 'partitions': ['d0p0', 'd1p1'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.clean_disks() ++ ++ fs.assert_called_once() ++ ls.assert_called_once() ++ ps.assert_called_once() ++ ms.assert_not_called() ++ hp.assert_not_called() ++ ss.assert_called_once() ++ sys.assert_called_once() ++ ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(SystemSetup, 'clean_disks') ++ @mock.patch.object(SwRaidSetup, 'clean_disks') ++ @mock.patch.object(MegaCliSetup, 'clean_disks') ++ @mock.patch.object(HpSsaCliSetup, 'clean_disks') ++ @mock.patch.object(PartitionSetup, 'clean_disks') ++ @mock.patch.object(LvmSetup, 'clean_disks') ++ @mock.patch.object(FilesystemSetup, 'clean_disks') ++ def test_clean_disks_on_partsetup_config(self, fs, ls, ps, hp, ms, ss, ++ sys, is_hpraid, is_megacli): ++ is_hpraid.return_value = False ++ is_megacli.return_value = False ++ cfg_dict = { ++ 'disk_config': { ++ 'blockdev': { ++ 'disk0': { ++ 'd0p0': { ++ 'candidates': 'any' ++ } ++ }, ++ 'disk1': { ++ 'd1p0': { ++ 'candidates': 'any' ++ } ++ }, ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.clean_disks() ++ ++ fs.assert_called_once() ++ ls.assert_called_once() ++ ps.assert_called_once() ++ ms.assert_not_called() ++ hp.assert_not_called() ++ ss.assert_called_once() ++ sys.assert_called_once() ++ ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(SystemSetup, 'clean_disks') ++ @mock.patch.object(SwRaidSetup, 'clean_disks') ++ @mock.patch.object(MegaCliSetup, 'clean_disks') ++ @mock.patch.object(HpSsaCliSetup, 'clean_disks') ++ @mock.patch.object(PartitionSetup, 'clean_disks') ++ @mock.patch.object(LvmSetup, 'clean_disks') ++ @mock.patch.object(FilesystemSetup, 'clean_disks') ++ def test_clean_disks_on_syssetup_config_megacli(self, fs, ls, ps, hp, ms, ++ ss, sys, is_hpraid, ++ is_megacli): ++ cfg_dict = { ++ 'disk_config': { ++ 'hwraid': { ++ 'raid0': { ++ 'raidtype': 5, ++ 'partitions': ['disk0', 'disk1'] ++ } ++ } ++ } ++ } ++ is_hpraid.return_value = False ++ is_megacli.return_value = True ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.clean_disks() ++ ++ fs.assert_called_once() ++ ls.assert_called_once() ++ ps.assert_called_once() ++ hp.assert_not_called() ++ ms.assert_called_once() ++ ss.assert_called_once() ++ sys.assert_called_once() ++ ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(SystemSetup, 'clean_disks') ++ @mock.patch.object(SwRaidSetup, 'clean_disks') ++ @mock.patch.object(MegaCliSetup, 'clean_disks') ++ @mock.patch.object(HpSsaCliSetup, 'clean_disks') ++ @mock.patch.object(PartitionSetup, 'clean_disks') ++ @mock.patch.object(LvmSetup, 'clean_disks') ++ @mock.patch.object(FilesystemSetup, 'clean_disks') ++ def test_clean_disks_on_syssetup_config_hpraid(self, fs, ls, ps, hp, ms, ++ ss, sys, is_hpraid, ++ is_megacli): ++ cfg_dict = { ++ 'disk_config': { ++ 'hwraid': { ++ 'raid0': { ++ 'raidtype': 5, ++ 'partitions': ['disk0', 'disk1'] ++ } ++ } ++ } ++ } ++ is_hpraid.return_value = True ++ is_megacli.return_value = False ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.clean_disks() ++ ++ fs.assert_called_once() ++ ls.assert_called_once() ++ ps.assert_called_once() ++ hp.assert_called_once() ++ ms.assert_not_called() ++ ss.assert_called_once() ++ sys.assert_called_once() ++ ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(SystemSetup, 'clean_disks') ++ @mock.patch.object(SwRaidSetup, 'clean_disks') ++ @mock.patch.object(MegaCliSetup, 'clean_disks') ++ @mock.patch.object(HpSsaCliSetup, 'clean_disks') ++ @mock.patch.object(PartitionSetup, 'clean_disks') ++ @mock.patch.object(LvmSetup, 'clean_disks') ++ @mock.patch.object(FilesystemSetup, 'clean_disks') ++ def test_clean_disks_on_syssetup_config_nohwraid(self, fs, ls, ps, hp, ms, ++ ss, sys, is_hpraid, ++ is_megacli): ++ cfg_dict = { ++ 'disk_config': { ++ 'hwraid': { ++ 'raid0': { ++ 'raidtype': 5, ++ 'partitions': ['disk0', 'disk1'] ++ } ++ } ++ } ++ } ++ is_hpraid.return_value = False ++ is_megacli.return_value = False ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ ++ sysinst.clean_disks() ++ ++ fs.assert_called_once() ++ ls.assert_called_once() ++ ps.assert_called_once() ++ hp.assert_not_called() ++ ms.assert_not_called() ++ ss.assert_called_once() ++ sys.assert_called_once() ++ ++ ++class TestSystemInstaller(unittest.TestCase): ++ ++ @mock.patch.object(SwRaidSetup, 'setup_disks') ++ def test_make_swraid_with_swriaid(self, sd): ++ cfg_dict = { ++ 'disk_config': { ++ 'swraid': { ++ 'md0': { ++ 'raidtype': 1, ++ 'partitions': ['d0p0', 'd1p1'] ++ } ++ } ++ } ++ } ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.make_swraid(['device']) ++ sd.assert_called_with(['device']) ++ ++ @mock.patch.object(SwRaidSetup, 'setup_disks') ++ def test_make_swraid_without_swriaid(self, sd): ++ cfg_dict = {'disk_config': {}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertListEqual(sysinst.make_swraid(['device']), ['device']) ++ sd.assert_not_called() ++ ++ @mock.patch.object(PartitionSetup, 'setup_disks') ++ def test_partition_disks(self, ps): ++ cfg_dict = { ++ 'disk_config': { ++ 'blockdev': { ++ 'disk0': { ++ 'd0p0': { ++ 'candidates': 'any' ++ } ++ }, ++ 'disk1': { ++ 'd1p0': { ++ 'candidates': 'any' ++ } ++ }, ++ } ++ } ++ } ++ ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.partition_disks(['device']) ++ ps.assert_called_once() ++ ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(MegaCliSetup, 'setup_disks') ++ def test_make_hwraid_with_swraid_megacli(self, sd, megacli): ++ megacli.return_value = True ++ cfg_dict = { ++ 'disk_config': { ++ 'hwraid': { ++ 'raid0': { ++ 'raidtype': 5, ++ 'partitions': ['disk0', 'disk1'] ++ } ++ } ++ } ++ } ++ ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.make_hwraid(['device']) ++ sd.assert_called_with(['device']) ++ ++ @mock.patch.object(MegaCliSetup, 'is_megacli_controller') ++ @mock.patch.object(HpSsaCliSetup, 'is_hpraid_controller') ++ @mock.patch.object(HpSsaCliSetup, 'setup_disks') ++ def test_make_hwraid_with_swraid_hpraid(self, sd, hpraid, megacli): ++ megacli.return_value = False ++ hpraid.return_value = True ++ cfg_dict = { ++ 'disk_config': { ++ 'hwraid': { ++ 'raid0': { ++ 'raidtype': 5, ++ 'partitions': ['disk0', 'disk1'] ++ } ++ } ++ } ++ } ++ ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.make_hwraid(['device']) ++ sd.assert_called_with(['device']) ++ ++ @mock.patch.object(MegaCliSetup, 'setup_disks') ++ def test_make_hwraid_without_swriaid(self, ms): ++ cfg_dict = {'disk_config': {}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertListEqual(sysinst.make_hwraid(['device']), ['device']) ++ ms.assert_not_called() ++ ++ @mock.patch.object(LvmSetup, 'setup_disks') ++ def test_make_lvs_with_lvm(self, ls): ++ cfg_dict = { ++ 'disk_config': { ++ 'lvm': { ++ 'sys': { ++ 'LVs': { ++ 'home': {'minsize': '1G'} ++ }, ++ 'PVs': ['d0p2'] ++ } ++ } ++ } ++ } ++ ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.make_lvm(['device']) ++ ls.assert_called_with(['device']) ++ ++ @mock.patch.object(LvmSetup, 'setup_disks') ++ def test_make_lvs_without_lvm(self, ls): ++ cfg_dict = {'disk_config': {}} ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ self.assertListEqual(sysinst.make_lvm(['device']), ['device']) ++ ls.assert_not_called() ++ ++ @mock.patch.object(FilesystemSetup, 'setup_disks') ++ def test_format_partitons(self, fs): ++ cfg_dict = { ++ 'disk_config': { ++ 'blockdev': { ++ 'disk0': { ++ 'd0p0': { ++ 'candidates': 'any' ++ } ++ }, ++ 'disk1': { ++ 'd1p0': { ++ 'candidates': 'any' ++ } ++ }, ++ }, ++ 'filesystems': { ++ 'd1p0': {'mountpoint': '/'} ++ }, ++ } ++ } ++ ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.format_partitions(['device']) ++ fs.assert_called_once() ++ ++ @mock.patch.object(SystemSetup, 'setup_disks') ++ def test_install_system(self, ss): ++ cfg_dict = { ++ 'disk_config': { ++ 'filesystems': { ++ 'd1p0': {'mountpoint': '/'} ++ }, ++ } ++ } ++ ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.install_system(['device'], 'image_path') ++ ss.assert_called_once() ++ ++ @mock.patch.object(si.SystemInstaller, 'install_system') ++ @mock.patch.object(si.SystemInstaller, 'format_partitions') ++ @mock.patch.object(si.SystemInstaller, 'make_lvm') ++ @mock.patch.object(si.SystemInstaller, 'make_swraid') ++ @mock.patch.object(si.SystemInstaller, 'partition_disks') ++ @mock.patch.object(si.SystemInstaller, 'make_hwraid') ++ @mock.patch.object(si.SystemInstaller, 'clean_disks') ++ @mock.patch.object(si.SystemInstaller, 'validate_env') ++ @mock.patch.object(si.SystemInstaller, 'validate_conf') ++ def test_install(self, vc, ve, cd, mh, pd, ms, ml, fp, ins): ++ cfg_dict = {'disk_config': {'filesystems': {}}} ++ ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.install('image_path') ++ ++ vc.assert_called_once() ++ ve.assert_called_once() ++ cd.assert_called_once() ++ mh.assert_called_once() ++ pd.assert_called_once() ++ ms.assert_called_once() ++ ml.assert_called_once() ++ fp.assert_called_once() ++ ins.assert_called_once() ++ ++ @mock.patch.object(FilesystemSetup, 'get_disks_by_labels') ++ @mock.patch.object(si.SystemInstaller, 'install_system') ++ @mock.patch.object(si.SystemInstaller, 'format_partitions') ++ @mock.patch.object(si.SystemInstaller, 'make_lvm') ++ @mock.patch.object(si.SystemInstaller, 'make_swraid') ++ @mock.patch.object(si.SystemInstaller, 'partition_disks') ++ @mock.patch.object(si.SystemInstaller, 'make_hwraid') ++ @mock.patch.object(si.SystemInstaller, 'clean_disks') ++ @mock.patch.object(si.SystemInstaller, 'validate_env_preserve') ++ @mock.patch.object(si.SystemInstaller, 'validate_conf') ++ def test_install_preserve(self, vc, ve, cd, mh, pd, ms, ml, fp, ins, dl): ++ dl.return_value = {} ++ cfg_dict = {'disk_config': {'filesystems': {'test': {'preserve': 1}}}} ++ ++ sysinst = si.SystemInstaller(json.dumps(cfg_dict)) ++ sysinst.install('image_path') ++ ++ vc.assert_called_once() ++ ve.assert_called_once() ++ self.assertFalse(cd.called) ++ self.assertFalse(mh.called) ++ self.assertFalse(pd.called) ++ self.assertFalse(ms.called) ++ self.assertFalse(ml.called) ++ fp.assert_called_once() ++ ins.assert_called_once() +diff --git a/ironic_lib/tests/test_system_installer_base.py b/ironic_lib/tests/test_system_installer_base.py +new file mode 100644 +index 0000000..8e1b879 +--- /dev/null ++++ b/ironic_lib/tests/test_system_installer_base.py +@@ -0,0 +1,48 @@ ++# 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 ++ ++from ironic_lib.system_installer import base ++ ++ ++class TestSystemInstallerValidateConf(unittest.TestCase): ++ ++ def test_clean_disks_arguments(self): ++ setup = base.Setup({'': ''}) ++ setup.clean_disks({}) ++ ++ def test_validate_conf(self): ++ setup = base.Setup({'': ''}) ++ setup.validate_conf() ++ ++ def test_validate_env(self): ++ setup = base.Setup({'': ''}) ++ setup.validate_env() ++ ++ def test_clean_disks_arguments_negative(self): ++ setup = base.Setup({'': ''}) ++ with self.assertRaises(TypeError): ++ setup.clean_disks({}, 'bla') ++ ++ def test_validate_conf_negative(self): ++ setup = base.Setup({'': ''}) ++ with self.assertRaises(TypeError): ++ setup.validate_conf('bla') ++ ++ def test_validate_env_negative(self): ++ setup = base.Setup({'': ''}) ++ with self.assertRaises(TypeError): ++ setup.validate_env('bla') +diff --git a/requirements.txt b/requirements.txt +index b90d8d4..7f0e350 100644 +--- a/requirements.txt ++++ b/requirements.txt +@@ -3,6 +3,10 @@ + # process, which may cause wedges in the gate later. + + pbr>=1.8 # Apache-2.0 ++backports.functools_lru_cache ++bitmath ++jsonschema ++pyyaml + oslo.concurrency>=3.8.0 # Apache-2.0 + oslo.config!=3.18.0,>=3.14.0 # Apache-2.0 + oslo.i18n>=2.1.0 # Apache-2.0 +diff --git a/tools/system_installer.py b/tools/system_installer.py +new file mode 100755 +index 0000000..ca1cbc7 +--- /dev/null ++++ b/tools/system_installer.py +@@ -0,0 +1,26 @@ ++#!/usr/bin/python ++# 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 sys import argv ++ ++from ironic_lib.system_installer import SystemInstaller ++ ++ ++try: ++ with open(argv[1]) as f: ++ SystemInstaller(f.read()).install(argv[2]) ++except IndexError: ++ print("Usage: {} ".format(argv[0])) +diff --git a/tox.ini b/tox.ini +index 0ce3cdb..038bc1a 100644 +--- a/tox.ini ++++ b/tox.ini +@@ -11,7 +11,9 @@ setenv = VIRTUAL_ENV={envdir} + PYTHONDONTWRITEBYTECODE = 1 + LANGUAGE=en_US + TESTS_DIR=./ironic_lib/tests/ +-deps = -r{toxinidir}/test-requirements.txt ++deps = ++ -r{toxinidir}/test-requirements.txt ++ -r{toxinidir}/requirements.txt + commands = ostestr {posargs} + + [flake8] +-- +2.14.1 +