From 08c36e4bf89ff9b69dbd5ede928dacbaa7dbf1c5 Mon Sep 17 00:00:00 2001 From: Yolanda Robla Date: Mon, 1 May 2017 10:52:52 +0200 Subject: [PATCH] Add refactor of tree-like vs graph Introducing the refactors of the block device to allow a tree-like configuration, and start using it for the partitions level. Based on patch I3600c6a3d663c697b59d91bd3fbb5e408af345e4 Change-Id: I58bb3c256a1dfd100d29266571c333c2d43334f7 Co-Authored-By: Andreas Florath --- .../block_device/level0/localloop.py | 2 + .../block_device/level1/__init__.py | 7 +- .../block_device/level1/partitioning.py | 84 +++++++++++++++++-- diskimage_builder/block_device/tree_config.py | 60 +++++++++++++ 4 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 diskimage_builder/block_device/tree_config.py diff --git a/diskimage_builder/block_device/level0/localloop.py b/diskimage_builder/block_device/level0/localloop.py index de18e972..f54fa95c 100644 --- a/diskimage_builder/block_device/level0/localloop.py +++ b/diskimage_builder/block_device/level0/localloop.py @@ -15,6 +15,7 @@ from diskimage_builder.block_device.blockdevicesetupexception \ import BlockDeviceSetupException from diskimage_builder.block_device.plugin_base import NodePluginBase +from diskimage_builder.block_device.tree_config import TreeConfig from diskimage_builder.block_device.utils import parse_abs_size_spec from diskimage_builder.graph.digraph import Digraph import logging @@ -31,6 +32,7 @@ class LocalLoop(NodePluginBase): This class handles local loop devices that can be used for VM image installation. """ + tree_config = TreeConfig("local_loop") def __init__(self, config, default_config): logger.debug("Creating LocalLoop object; config [%s] " diff --git a/diskimage_builder/block_device/level1/__init__.py b/diskimage_builder/block_device/level1/__init__.py index 6c3b57a8..145b0b45 100644 --- a/diskimage_builder/block_device/level1/__init__.py +++ b/diskimage_builder/block_device/level1/__init__.py @@ -1,4 +1,5 @@ -# Copyright 2016 Andreas Florath (andreas@florath.net) +# Copyright +# 2016-2017 Andreas Florath (andreas@florath.net) # # 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 @@ -13,5 +14,7 @@ # under the License. from diskimage_builder.block_device.level1.partitioning import Partitioning +from diskimage_builder.block_device.level1.partitioning \ + import PartitioningTreeConfig -__all__ = [Partitioning] +__all__ = [Partitioning, PartitioningTreeConfig] diff --git a/diskimage_builder/block_device/level1/partitioning.py b/diskimage_builder/block_device/level1/partitioning.py index c3c30317..a1a66e7b 100644 --- a/diskimage_builder/block_device/level1/partitioning.py +++ b/diskimage_builder/block_device/level1/partitioning.py @@ -12,11 +12,11 @@ # License for the specific language governing permissions and limitations # under the License. -import collections from diskimage_builder.block_device.blockdevicesetupexception \ import BlockDeviceSetupException from diskimage_builder.block_device.level1.mbr import MBR from diskimage_builder.block_device.plugin_base import PluginBase +from diskimage_builder.block_device.tree_config import TreeConfig from diskimage_builder.block_device.utils import parse_abs_size_spec from diskimage_builder.block_device.utils import parse_rel_size_spec from diskimage_builder.graph.digraph import Digraph @@ -28,15 +28,50 @@ import subprocess logger = logging.getLogger(__name__) +class PartitionTreeConfig(object): + + @staticmethod + def config_tree_to_digraph(config_key, config_value, pconfig, dconfig, + base_name, plugin_manager): + logger.debug("called [%s] [%s] [%s]" + % (config_key, config_value, base_name)) + assert config_key == Partition.type_string + + for partition in config_value: + name = partition['name'] + nconfig = {'name': name} + for k, v in partition.items(): + if k not in plugin_manager: + nconfig[k] = v + else: + plugin_manager[k].plugin \ + .tree_config.config_tree_to_digraph( + k, v, dconfig, name, plugin_manager) + pconfig.append(nconfig) + + logger.debug("finished [%s] [%s]" % (nconfig, dconfig)) + + class Partition(Digraph.Node): - def __init__(self, name, flags, size, ptype, base, partitioning): + type_string = "partitions" + tree_config = TreeConfig("partitions") + + def __init__(self, name, flags, size, ptype, base, partitioning, + prev_partition): Digraph.Node.__init__(self, name) + self.name = name self.flags = flags self.size = size self.ptype = ptype self.base = base self.partitioning = partitioning + self.prev_partition = prev_partition + + def __repr__(self): + return "" \ + % (self.name, self.base, self.size, + self.prev_partition.name if self.prev_partition else "UNSET") def get_flags(self): return self.flags @@ -51,6 +86,9 @@ class Partition(Digraph.Node): bnode = dg.find(self.base) assert bnode is not None dg.create_edge(bnode, self) + if self.prev_partition is not None: + logger.debug("Insert edge [%s]" % self) + dg.create_edge(self.prev_partition, self) def create(self, result, rollback): self.partitioning.create(result, rollback) @@ -68,8 +106,34 @@ class Partition(Digraph.Node): pass +class PartitioningTreeConfig(object): + + @staticmethod + def config_tree_to_digraph(config_key, config_value, dconfig, + default_base_name, plugin_manager): + logger.debug("called [%s] [%s] [%s]" + % (config_key, config_value, default_base_name)) + assert config_key == "partitioning" + base_name = config_value['base'] if 'base' in config_value \ + else default_base_name + nconfig = {'base': base_name} + for k, v in config_value.items(): + if k != 'partitions': + nconfig[k] = v + else: + pconfig = [] + PartitionTreeConfig.config_tree_to_digraph( + k, v, pconfig, dconfig, base_name, plugin_manager) + nconfig['partitions'] = pconfig + + dconfig.append({config_key: nconfig}) + logger.debug("finished new [%s] complete [%s]" % (nconfig, dconfig)) + + class Partitioning(PluginBase): + tree_config = PartitioningTreeConfig() + flag_boot = 1 flag_primary = 2 @@ -109,7 +173,9 @@ class Partitioning(PluginBase): if 'partitions' not in config: self._config_error("Partitioning config needs 'partitions'") - self.partitions = collections.OrderedDict() + self.partitions = [] + prev_partition = None + for part_cfg in config['partitions']: if 'name' not in part_cfg: self.config_error("Missing 'name' in partition config") @@ -133,8 +199,10 @@ class Partitioning(PluginBase): ptype = int(part_cfg['type'], 16) if 'type' in part_cfg else 0x83 - self.partitions[part_name] \ - = Partition(part_name, flags, size, ptype, self.base, self) + np = Partition(part_name, flags, size, ptype, self.base, self, + prev_partition) + self.partitions.append(np) + prev_partition = np logger.debug(part_cfg) def _config_error(self, msg): @@ -147,7 +215,8 @@ class Partitioning(PluginBase): return fd.tell() def insert_nodes(self, dg): - for _, part in self.partitions.items(): + for part in self.partitions: + logger.debug("Insert node [%s]" % part) dg.add_node(part) def _exec_sudo(self, cmd): @@ -212,7 +281,8 @@ class Partitioning(PluginBase): partition_devices = set() disk_size = self._size_of_block_dev(image_path) with MBR(image_path, disk_size, self.align) as part_impl: - for part_name, part_cfg in self.partitions.items(): + for part_cfg in self.partitions: + part_name = part_cfg.get_name() part_bootflag = Partitioning.flag_boot \ in part_cfg.get_flags() part_primary = Partitioning.flag_primary \ diff --git a/diskimage_builder/block_device/tree_config.py b/diskimage_builder/block_device/tree_config.py new file mode 100644 index 00000000..d7c5531b --- /dev/null +++ b/diskimage_builder/block_device/tree_config.py @@ -0,0 +1,60 @@ +# Copyright 2016-2017 Andreas Florath (andreas@florath.net) +# +# 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 logging + +logger = logging.getLogger(__name__) + + +class TreeConfig(object): + """Supports simple tree-like configuration + + When using the new (complete) configuration there is a need to + specify the complete digraph of block level configurations. This + provides great flexibility for the cost of complex configurations: + each and every single element must be completely specified. In + many simple use cases the configuration flexibility is not + needed. + + With the help of this object the simple to use and short tree-like + configuration is converted automatically into the complete digraph + configuration which can be used to create the block device + elements. + """ + + def __init__(self, type_string): + self.type_string = type_string + + def config_tree_to_digraph(self, config_key, config_value, dconfig, + default_base_name, plugin_manager): + logger.debug("called [%s] [%s] [%s]" + % (config_key, config_value, default_base_name)) + base_name = config_value['base'] if 'base' in config_value \ + else default_base_name + name = config_value['name'] \ + if 'name' in config_value \ + else "%s_%s" % (config_key, base_name) + assert config_key == self.type_string + + nconfig = {'base': base_name, 'name': name} + for k, v in config_value.items(): + if k not in plugin_manager: + nconfig[k] = v + else: + plugin_manager[k].plugin \ + .tree_config.config_tree_to_digraph( + k, v, dconfig, name, plugin_manager) + + dconfig.append({self.type_string: nconfig}) + logger.debug("finished new [%s] complete [%s]" % (nconfig, dconfig))