From 12ca1b92c4d91820768c9fef9dac4803f95b34d4 Mon Sep 17 00:00:00 2001 From: PiotrProkop Date: Thu, 4 Jan 2018 16:09:37 +0100 Subject: [PATCH] Passing-disk_profile-as-part-of-user_data --- ...ng-disk_profile-as-part-of-user_data.patch | 516 ++++++++++++++++++ 1 file changed, 516 insertions(+) create mode 100644 nova/0001-Passing-disk_profile-as-part-of-user_data.patch diff --git a/nova/0001-Passing-disk_profile-as-part-of-user_data.patch b/nova/0001-Passing-disk_profile-as-part-of-user_data.patch new file mode 100644 index 0000000..d35cb78 --- /dev/null +++ b/nova/0001-Passing-disk_profile-as-part-of-user_data.patch @@ -0,0 +1,516 @@ +From 6f76f6083ce597dd53fd1dbd31197280dd72dba5 Mon Sep 17 00:00:00 2001 +From: PiotrProkop +Date: Wed, 20 Dec 2017 15:48:39 +0100 +Subject: [PATCH] Passing disk_profile as part of user_data + +--- + nova/tests/unit/virt/ironic/fake_disk_configs.py | 140 +++++++++++++ + nova/tests/unit/virt/ironic/test_patcher.py | 29 +++ + nova/virt/ironic/patcher.py | 32 ++- + nova/virt/ironic/validation.py | 241 +++++++++++++++++++++++ + 4 files changed, 441 insertions(+), 1 deletion(-) + create mode 100644 nova/tests/unit/virt/ironic/fake_disk_configs.py + create mode 100644 nova/virt/ironic/validation.py + +diff --git a/nova/tests/unit/virt/ironic/fake_disk_configs.py b/nova/tests/unit/virt/ironic/fake_disk_configs.py +new file mode 100644 +index 0000000000..54bae4d5e0 +--- /dev/null ++++ b/nova/tests/unit/virt/ironic/fake_disk_configs.py +@@ -0,0 +1,140 @@ ++# Copyright 2017 Intel ++# All Rights Reserved. ++# ++# 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. ++ ++ ++CORRECT_DISK_CONFIG = """ ++disk_config: ++ 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 ++""" ++ ++INCORRECT_DISK_CONFIG = """ ++disk_config: ++ blockdev: ++ sda: ++ candidates: blue and red ++ foo: ++ bar: A ++ partitions: ++ d0p1: ++ size: 512M ++ d0p2: ++ minsize: 2G ++ lvm: ++ sys: ++ LVs: ++ home: ++ minsize: 1G ++ root: ++ size: 10G ++ swap: ++ size: memcached ++ 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 ++""" ++ ++BAD_YAML_DISK_CONFIG = """ ++disk_config: ++ blockdev: ++ sda: ++ candidates: blue and red ++ 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/nova/tests/unit/virt/ironic/test_patcher.py b/nova/tests/unit/virt/ironic/test_patcher.py +index 4def694881..4fa4251f63 100644 +--- a/nova/tests/unit/virt/ironic/test_patcher.py ++++ b/nova/tests/unit/virt/ironic/test_patcher.py +@@ -13,14 +13,19 @@ + # License for the specific language governing permissions and limitations + # under the License. + ++import base64 + import operator + + from oslo_config import cfg ++from oslo_serialization import jsonutils ++import yaml + + from nova import context as nova_context ++from nova import exception + from nova import objects + from nova import test + from nova.tests.unit import fake_instance ++from nova.tests.unit.virt.ironic import fake_disk_configs as fakes + from nova.tests.unit.virt.ironic import utils as ironic_utils + from nova.virt.ironic import patcher + +@@ -143,3 +148,27 @@ class IronicDriverFieldsTestCase(test.NoDBTestCase): + 'value': str(preserve), 'op': 'add', }] + expected += self._expected_deploy_patch + self.assertPatchEqual(expected, patch) ++ ++ def test_generic_get_deploy_patch_disk_profile(self): ++ node = ironic_utils.get_test_node(driver='fake') ++ self.instance.user_data = base64.b64encode(fakes.CORRECT_DISK_CONFIG) ++ patch = patcher.create(node).get_deploy_patch( ++ self.instance, self.image_meta, self.flavor) ++ for path in patch: ++ if path["path"] == '/instance_info/ybiip': ++ self.assertEqual(jsonutils.loads(path["value"]), ++ yaml.load(fakes.CORRECT_DISK_CONFIG)) ++ ++ def test_generic_get_deploy_patch_disk_profile_bad_schema(self): ++ node = ironic_utils.get_test_node(driver='fake') ++ self.instance.user_data = base64.b64encode(fakes.INCORRECT_DISK_CONFIG) ++ self.assertRaises(exception.InstanceDeployFailure, ++ patcher.create(node).get_deploy_patch, self.instance, ++ self.image_meta, self.flavor) ++ ++ def test_generic_get_deploy_patch_disk_profile_bad_yaml(self): ++ node = ironic_utils.get_test_node(driver='fake') ++ self.instance.user_data = base64.b64encode(fakes.BAD_YAML_DISK_CONFIG) ++ self.assertRaises(yaml.YAMLError, ++ patcher.create(node).get_deploy_patch, self.instance, ++ self.image_meta, self.flavor) +diff --git a/nova/virt/ironic/patcher.py b/nova/virt/ironic/patcher.py +index 651c7012c3..8aba100456 100644 +--- a/nova/virt/ironic/patcher.py ++++ b/nova/virt/ironic/patcher.py +@@ -17,13 +17,19 @@ + """ + Helper classes for Ironic HTTP PATCH creation. + """ ++import base64 + ++import jsonschema ++import logging + from oslo_serialization import jsonutils +- ++import yaml + + import nova.conf ++from nova import exception ++from nova.virt.ironic.validation import DISK_CONFIG_SCHEMA + + CONF = nova.conf.CONF ++LOG = logging.getLogger(__name__) + + + def create(node): +@@ -105,4 +111,28 @@ class GenericDriverFields(object): + if capabilities: + patch.append({'path': '/instance_info/capabilities', + 'op': 'add', 'value': jsonutils.dumps(capabilities)}) ++ ++ # add disk_profile to instance_info ++ if instance.user_data: ++ user_data = base64.b64decode(instance.user_data) ++ try: ++ user_data = yaml.load(user_data) ++ if 'disk_config' in user_data: ++ # validate disk_profile section from user_data against ++ # json schema ++ disk_config = {"disk_config": user_data['disk_config']} ++ jsonschema.validate(disk_config, DISK_CONFIG_SCHEMA) ++ patch.append({'path': '/instance_info/ybiip', ++ 'op': 'add', ++ 'value': jsonutils.dumps(disk_config)}) ++ ++ except yaml.YAMLError as e: ++ msg = "user_data is not a YAML file: %s" % e.message ++ LOG.error(msg) ++ raise ++ except jsonschema.ValidationError as e: ++ msg = "Disk_profile schema is invalid: %s" % e.message ++ LOG.error(msg) ++ raise exception.InstanceDeployFailure(msg) ++ + return patch +diff --git a/nova/virt/ironic/validation.py b/nova/virt/ironic/validation.py +new file mode 100644 +index 0000000000..1066068816 +--- /dev/null ++++ b/nova/virt/ironic/validation.py +@@ -0,0 +1,241 @@ ++# Copyright 2017 Intel ++# All Rights Reserved. ++# ++# 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. ++ ++DISK_CONFIG_SCHEMA = { ++ "$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", "xfat"], ++ "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"] ++} +-- +2.16.2 +