1
0
mirror of https://github.com/gryf/openstack.git synced 2025-12-17 03:20:25 +01:00
Files
openstack/ironic-lib/0002-Base-classes-and-examples.patch
2018-03-23 16:37:05 +01:00

3039 lines
95 KiB
Diff

From b03e3f9c8d0844e94f88e3f449c7eecc973c91fe Mon Sep 17 00:00:00 2001
From: Grzegorz Grasza <grzegorz@grasza.com>
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 <grzegorz.grasza@intel.com>
Co-Authored-By: Piotr Prokop <piotr.prokop@intel.com>
Co-Authored-By: Roman Dobosz <roman.dobosz@intel.com>
Co-Authored-By: Marta Mucek <marta.mucek@intel.com>
---
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: {} <config.yaml> <image.qcow2>".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