|
@@ -1,247 +0,0 @@
|
|
|
-#!/usr/bin/python
|
|
|
-"""
|
|
|
-Ansible module for partitioning.
|
|
|
-"""
|
|
|
-
|
|
|
-from __future__ import print_function
|
|
|
-
|
|
|
-# There is no pyparted on our Jenkins worker
|
|
|
-# pylint: disable=import-error
|
|
|
-import parted
|
|
|
-
|
|
|
-DOCUMENTATION = """
|
|
|
----
|
|
|
-module: partitionpool
|
|
|
-short_description; Partition a disk into parititions.
|
|
|
-description:
|
|
|
- - Creates partitions on given disk based on partition sizes and their weights.
|
|
|
- Unless 'force' option is set to True, it ignores already partitioned disks.
|
|
|
-
|
|
|
- When the disk is empty or 'force' is set to True, it always creates a new
|
|
|
- GPT partition table on the disk. Then it creates number of partitions, based
|
|
|
- on their weights.
|
|
|
-
|
|
|
- This module should be used when a system admin wants to split existing disk(s)
|
|
|
- into pools of partitions of specific sizes. It is not intended as generic disk
|
|
|
- partitioning module!
|
|
|
-
|
|
|
- Independent on 'force' parameter value and actual disk state, the task
|
|
|
- always fills 'partition_pool' fact with all partitions on given disks,
|
|
|
- together with their sizes (in bytes). E.g.:
|
|
|
- partition_sizes = [
|
|
|
- { name: sda1, Size: 1048576000 },
|
|
|
- { name: sda2, Size: 1048576000 },
|
|
|
- { name: sdb1, Size: 1048576000 },
|
|
|
- ...
|
|
|
- ]
|
|
|
-
|
|
|
-options:
|
|
|
- disk:
|
|
|
- description:
|
|
|
- - Disk to partition.
|
|
|
- size:
|
|
|
- description:
|
|
|
- - Sizes of partitions to create and their weights. Has form of:
|
|
|
- <size1>[:<weigth1>][,<size2>[:<weight2>][,...]]
|
|
|
- - Any <size> can end with 'm' or 'M' for megabyte, 'g/G' for gigabyte
|
|
|
- and 't/T' for terabyte. Megabyte is used when no unit is specified.
|
|
|
- - If <weight> is missing, 1.0 is used.
|
|
|
- - From each specified partition <sizeX>, number of these partitions are
|
|
|
- created so they occupy spaces represented by <weightX>, proportionally to
|
|
|
- other weights.
|
|
|
-
|
|
|
- - Example 1: size=100G says, that the whole disk is split in number of 100 GiB
|
|
|
- partitions. On 1 TiB disk, 10 partitions will be created.
|
|
|
-
|
|
|
- - Example 2: size=100G:1,10G:1 says that ratio of space occupied by 100 GiB
|
|
|
- partitions and 10 GiB partitions is 1:1. Therefore, on 1 TiB disk, 500 GiB
|
|
|
- will be split into five 100 GiB partition and 500 GiB will be split into fifty
|
|
|
- 10GiB partitions.
|
|
|
- - size=100G:1,10G:1 = 5x 100 GiB and 50x 10 GiB partitions (on 1 TiB disk).
|
|
|
-
|
|
|
- - Example 3: size=200G:1,100G:2 says that the ratio of space occupied by 200 GiB
|
|
|
- partitions and 100GiB partition is 1:2. Therefore, on 1 TiB disk, 1/3
|
|
|
- (300 GiB) should be occupied by 200 GiB partitions. Only one fits there,
|
|
|
- so only one is created (we always round nr. of partitions *down*). The rest
|
|
|
- (800 GiB) is split into eight 100 GiB partitions, even though it's more
|
|
|
- than 2/3 of total space - free space is always allocated as much as possible.
|
|
|
- - size=200G:1,100G:2 = 1x 200 GiB and 8x 100 GiB partitions (on 1 TiB disk).
|
|
|
-
|
|
|
- - Example: size=200G:1,100G:1,50G:1 says that the ratio of space occupied by
|
|
|
- 200 GiB, 100 GiB and 50 GiB partitions is 1:1:1. Therefore 1/3 of 1 TiB disk
|
|
|
- is dedicated to 200 GiB partitions. Only one fits there and only one is
|
|
|
- created. The rest (800 GiB) is distributed according to remaining weights:
|
|
|
- 100 GiB vs 50 GiB is 1:1, we create four 100 GiB partitions (400 GiB in total)
|
|
|
- and eight 50 GiB partitions (again, 400 GiB).
|
|
|
- - size=200G:1,100G:1,50G:1 = 1x 200 GiB, 4x 100 GiB and 8x 50 GiB partitions
|
|
|
- (on 1 TiB disk).
|
|
|
-
|
|
|
- force:
|
|
|
- description:
|
|
|
- - If True, it will always overwite partition table on the disk and create new one.
|
|
|
- - If False (default), it won't change existing partition tables.
|
|
|
-
|
|
|
-"""
|
|
|
-
|
|
|
-
|
|
|
-# It's not class, it's more a simple struct with almost no functionality.
|
|
|
-# pylint: disable=too-few-public-methods
|
|
|
-class PartitionSpec(object):
|
|
|
- """ Simple class to represent required partitions."""
|
|
|
- def __init__(self, size, weight):
|
|
|
- """ Initialize the partition specifications."""
|
|
|
- # Size of the partitions
|
|
|
- self.size = size
|
|
|
- # Relative weight of this request
|
|
|
- self.weight = weight
|
|
|
- # Number of partitions to create, will be calculated later
|
|
|
- self.count = -1
|
|
|
-
|
|
|
- def set_count(self, count):
|
|
|
- """ Set count of parititions of this specification. """
|
|
|
- self.count = count
|
|
|
-
|
|
|
-
|
|
|
-def assign_space(total_size, specs):
|
|
|
- """
|
|
|
- Satisfy all the PartitionSpecs according to their weight.
|
|
|
- In other words, calculate spec.count of all the specs.
|
|
|
- """
|
|
|
- total_weight = 0.0
|
|
|
- for spec in specs:
|
|
|
- total_weight += float(spec.weight)
|
|
|
-
|
|
|
- for spec in specs:
|
|
|
- num_blocks = int((float(spec.weight) / total_weight) * (total_size / float(spec.size)))
|
|
|
- spec.set_count(num_blocks)
|
|
|
- total_size -= num_blocks * spec.size
|
|
|
- total_weight -= spec.weight
|
|
|
-
|
|
|
-
|
|
|
-def partition(diskname, specs, force=False, check_mode=False):
|
|
|
- """
|
|
|
- Create requested partitions.
|
|
|
- Returns nr. of created partitions or 0 when the disk was already partitioned.
|
|
|
- """
|
|
|
- count = 0
|
|
|
-
|
|
|
- dev = parted.getDevice(diskname)
|
|
|
- try:
|
|
|
- disk = parted.newDisk(dev)
|
|
|
- except parted.DiskException:
|
|
|
- # unrecognizable format, treat as empty disk
|
|
|
- disk = None
|
|
|
-
|
|
|
- if disk and len(disk.partitions) > 0 and not force:
|
|
|
- print("skipping", diskname)
|
|
|
- return 0
|
|
|
-
|
|
|
- # create new partition table, wiping all existing data
|
|
|
- disk = parted.freshDisk(dev, 'gpt')
|
|
|
- # calculate nr. of partitions of each size
|
|
|
- assign_space(dev.getSize(), specs)
|
|
|
- last_megabyte = 1
|
|
|
- for spec in specs:
|
|
|
- for _ in range(spec.count):
|
|
|
- # create the partition
|
|
|
- start = parted.sizeToSectors(last_megabyte, "MiB", dev.sectorSize)
|
|
|
- length = parted.sizeToSectors(spec.size, "MiB", dev.sectorSize)
|
|
|
- geo = parted.Geometry(device=dev, start=start, length=length)
|
|
|
- filesystem = parted.FileSystem(type='ext4', geometry=geo)
|
|
|
- part = parted.Partition(
|
|
|
- disk=disk,
|
|
|
- type=parted.PARTITION_NORMAL,
|
|
|
- fs=filesystem,
|
|
|
- geometry=geo)
|
|
|
- disk.addPartition(partition=part, constraint=dev.optimalAlignedConstraint)
|
|
|
- last_megabyte += spec.size
|
|
|
- count += 1
|
|
|
- try:
|
|
|
- if not check_mode:
|
|
|
- disk.commit()
|
|
|
- except parted.IOException:
|
|
|
- # partitions have been written, but we have been unable to inform the
|
|
|
- # kernel of the change, probably because they are in use.
|
|
|
- # Ignore it and hope for the best...
|
|
|
- pass
|
|
|
- return count
|
|
|
-
|
|
|
-
|
|
|
-def parse_spec(text):
|
|
|
- """ Parse string with partition specification. """
|
|
|
- tokens = text.split(",")
|
|
|
- specs = []
|
|
|
- for token in tokens:
|
|
|
- if ":" not in token:
|
|
|
- token += ":1"
|
|
|
-
|
|
|
- (sizespec, weight) = token.split(':')
|
|
|
- weight = float(weight) # throws exception with reasonable error string
|
|
|
-
|
|
|
- units = {"m": 1, "g": 1 << 10, "t": 1 << 20, "p": 1 << 30}
|
|
|
- unit = units.get(sizespec[-1].lower(), None)
|
|
|
- if not unit:
|
|
|
- # there is no unit specifier, it must be just the number
|
|
|
- size = float(sizespec)
|
|
|
- unit = 1
|
|
|
- else:
|
|
|
- size = float(sizespec[:-1])
|
|
|
- spec = PartitionSpec(int(size * unit), weight)
|
|
|
- specs.append(spec)
|
|
|
- return specs
|
|
|
-
|
|
|
-
|
|
|
-def get_partitions(diskpath):
|
|
|
- """ Return array of partition names for given disk """
|
|
|
- dev = parted.getDevice(diskpath)
|
|
|
- disk = parted.newDisk(dev)
|
|
|
- partitions = []
|
|
|
- for part in disk.partitions:
|
|
|
- (_, _, pname) = part.path.rsplit("/")
|
|
|
- partitions.append({"name": pname, "size": part.getLength() * dev.sectorSize})
|
|
|
-
|
|
|
- return partitions
|
|
|
-
|
|
|
-
|
|
|
-def main():
|
|
|
- """ Ansible module main method. """
|
|
|
- module = AnsibleModule( # noqa: F405
|
|
|
- argument_spec=dict(
|
|
|
- disks=dict(required=True, type='str'),
|
|
|
- force=dict(required=False, default="no", type='bool'),
|
|
|
- sizes=dict(required=True, type='str')
|
|
|
- ),
|
|
|
- supports_check_mode=True,
|
|
|
- )
|
|
|
-
|
|
|
- disks = module.params['disks']
|
|
|
- force = module.params['force']
|
|
|
- if force is None:
|
|
|
- force = False
|
|
|
- sizes = module.params['sizes']
|
|
|
-
|
|
|
- try:
|
|
|
- specs = parse_spec(sizes)
|
|
|
- except ValueError as ex:
|
|
|
- err = "Error parsing sizes=" + sizes + ": " + str(ex)
|
|
|
- module.fail_json(msg=err)
|
|
|
-
|
|
|
- partitions = []
|
|
|
- changed_count = 0
|
|
|
- for disk in disks.split(","):
|
|
|
- try:
|
|
|
- changed_count += partition(disk, specs, force, module.check_mode)
|
|
|
- except Exception as ex:
|
|
|
- err = "Error creating partitions on " + disk + ": " + str(ex)
|
|
|
- raise
|
|
|
- # module.fail_json(msg=err)
|
|
|
- partitions += get_partitions(disk)
|
|
|
-
|
|
|
- module.exit_json(changed=(changed_count > 0), ansible_facts={"partition_pool": partitions})
|
|
|
-
|
|
|
-
|
|
|
-# ignore pylint errors related to the module_utils import
|
|
|
-# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, wrong-import-order, wrong-import-position
|
|
|
-# import module snippets
|
|
|
-from ansible.module_utils.basic import * # noqa: E402,F403
|
|
|
-main()
|