Esempio n. 1
0
def _check_partition(device, number, part_type, start, end):
    '''
    Check if the proposed partition match the current one.

    Returns a tri-state value:
      - `True`: the proposed partition match
      - `False`: the proposed partition do not match
      - `None`: the proposed partition is a new partition
    '''
    # The `start` and `end` fields are expressed with units (the same
    # kind of units that `parted` allows). To make a fair comparison
    # we need to normalize each field to the same units that we can
    # use to read the current partitions. A good candidate is sector
    # ('s'). The problem is that we need to reimplement the same
    # conversion logic from `parted` here [1], as we need the same
    # round logic when we convert from 'MiB' to 's', for example.
    #
    # To avoid this duplicity of code we can do a trick: for each
    # field in the proposed partition we request a `partition.list`
    # with the same unit. We make `parted` to make the conversion for
    # us, in exchange for an slower algorithm.
    #
    # We can change it once we decide to take care of alignment.
    #
    # [1] Check libparted/unit.c

    number = str(number)
    partitions = _get_cached_partitions(device)
    if number not in partitions:
        return None

    if part_type != partitions[number]['type']:
        return False

    for value, name in ((start, 'start'), (end, 'end')):
        value, unit = disk.units(value)
        p_value = _get_cached_partitions(device, unit)[number][name]
        p_value = disk.units(p_value)[0]
        min_value = value - OVERLAPPING_ERROR
        max_value = value + OVERLAPPING_ERROR
        if not min_value <= p_value <= max_value:
            return False

    return True
Esempio n. 2
0
def _get_first_overlapping_partition(device, start):
    '''
    Return the first partition that contains the start point.

    '''
    # Check if there is a partition in the system that start at
    # specified point.
    value, unit = disk.units(start)
    value += OVERLAPPING_ERROR

    partitions = _get_cached_partitions(device, unit)
    partition_number = None
    partition_start = 0
    for number, partition in partitions.items():
        p_start = disk.units(partition['start'])[0]
        p_end = disk.units(partition['end'])[0]
        if p_start <= value <= p_end:
            if partition_number is None or partition_start < p_start:
                partition_number = number
                partition_start = p_start
    return partition_number
Esempio n. 3
0
def prepare_partition_data(partitions):
    '''Helper function to prepare the patition data from the pillar.'''

    # Validate and normalize the `partitions` pillar. The state will
    # expect a dictionary with this schema:
    #
    # partitions_normalized = {
    #     '/dev/sda': {
    #         'label': 'gpt',
    #         'pmbr_boot': False,
    #         'partitions': [
    #             {
    #                 'part_id': '/dev/sda1',
    #                 'part_type': 'primary'
    #                 'fs_type': 'ext2',
    #                 'flags': ['esp'],
    #                 'start': '0MB',
    #                 'end': '100%',
    #             },
    #         ],
    #     },
    # }

    is_uefi = __grains__['efi']

    # Get the fallback values for label and initial_gap
    config = partitions.get('config', {})
    global_label = config.get('label', LABEL)
    global_initial_gap = config.get('initial_gap', INITIAL_GAP)

    partitions_normalized = {}
    for device, device_info in partitions['devices'].items():
        label = device_info.get('label', global_label)
        initial_gap = device_info.get('initial_gap', global_initial_gap)
        if initial_gap:
            initial_gap_num, units = disk.units(initial_gap, default=None)
        else:
            initial_gap_num, units = 0, None

        device_normalized = {
            'label': label,
            'pmbr_boot': label == 'gpt' and not is_uefi,
            'partitions': []
        }
        partitions_normalized[device] = device_normalized

        # Control the start of the next partition
        start_size = initial_gap_num
        # Flag to detect if `rest` size was used before
        rest = False

        for index, partition in enumerate(device_info.get('partitions', [])):
            # Detect if there is another partition after we create one
            # that complete the free space
            if rest:
                raise SaltInvocationError(
                    'Partition defined after one filled all the rest free '
                    'space. Use `rest` only on the last partition.')

            # Validate the partition type
            part_type = partition.get('type')
            if part_type not in VALID_PART_TYPE:
                raise SaltInvocationError(
                    'Partition type {} not recognized'.format(part_type))

            # If part_id is not given, we can create a partition name
            # based on the position of the partition and the name of
            # the device
            #
            # TODO(aplanas) The partition number will be deduced, so
            # the require section in mkfs_partition will fail
            part_id = '{}{}{}'.format(
                device, 'p' if __salt__['filters.is_raid'](device) else '',
                partitions.get('number', index + 1))
            part_id = partition.get('id', part_id)

            # For parted we usually need to set a ext2 filesystem
            # type, except for SWAP or UEFI
            fs_type = {
                'swap': 'linux-swap',
                'efi': 'fat16'
            }.get(part_type, 'ext2')

            # Check if we are changing units inside the device
            if partition['size'] == 'rest':
                rest = True
                # If units is not set, we default to '%'
                units = units or '%'
                start = '{}{}'.format(start_size, units)
                end = '100%'
            else:
                size, size_units = disk.units(partition['size'])
                if units and size_units and units != size_units:
                    raise SaltInvocationError(
                        'Units needs to be the same for the partitions inside '
                        'a device. Found {} but expected {}. Note that '
                        '`initial_gap` is also considered.'.format(
                            size_units, units))
                # If units and size_units is not set, we default to UNITS
                units = units or size_units or UNITS
                start = '{}{}'.format(start_size, units)
                end = '{}{}'.format(start_size + size, units)
                start_size += size

            flags = None
            if part_type in ('raid', 'lvm'):
                flags = [part_type]
            elif part_type == 'boot' and label == 'gpt' and not is_uefi:
                flags = ['bios_grub']
            elif part_type == 'efi' and label == 'gpt' and is_uefi:
                flags = ['esp']

            device_normalized['partitions'].append({
                'part_id': part_id,
                # TODO(aplanas) If msdos we need to create extended
                # and logical
                'part_type': 'primary',
                'fs_type': fs_type,
                'start': start,
                'end': end,
                'flags': flags,
            })

    return partitions_normalized
Esempio n. 4
0
def _get_partition_number(device, part_type, start, end):
    '''
    Return a partition number for a [start, end] range and a partition
    type.

    If the range is allocated and the partition type match, return the
    partition number. If the type do not match but is a logical
    partition inside an extended one, return the next partition
    number.

    If the range is not allocated, return the next partition number.

    '''

    unit = disk.units(start)[1]
    partitions = _get_cached_partitions(device, unit)

    # Check if there is a partition in the system that start or
    # containst the start point
    number = _get_first_overlapping_partition(device, start)
    if number:
        if partitions[number]['type'] == part_type:
            return number
        elif not (partitions[number]['type'] == 'extended'
                  and part_type == 'logical'):
            raise EnumerateException('Do not overlap partitions')

    def __primary_partition_free_slot(partitions, label):
        if label == 'msdos':
            max_primary = 4
        else:
            max_primary = 1024
        for i in range(1, max_primary + 1):
            i = str(i)
            if i not in partitions:
                return i

    # The partition is not already there, we guess the next number
    label = _get_cached_info(device)['partition table']
    if part_type == 'primary':
        candidate = __primary_partition_free_slot(partitions, label)
        if not candidate:
            raise EnumerateException('No free slot for primary partition')
        return candidate
    elif part_type == 'extended':
        if label == 'gpt':
            raise EnumerateException('Extended partitions not allowed in gpt')
        if 'extended' in (info['type'] for info in partitions.values()):
            raise EnumerateException('Already found a extended partition')
        candidate = __primary_partition_free_slot(partitions, label)
        if not candidate:
            raise EnumerateException('No free slot for extended partition')
        return candidate
    elif part_type == 'logical':
        if label == 'gpt':
            raise EnumerateException('Extended partitions not allowed in gpt')
        if 'extended' not in (part['type'] for part in partitions.values()):
            raise EnumerateException('Missing extended partition')
        candidate = max(
            (int(part['number'])
             for part in partitions.values() if part['type'] == 'logical'),
            default=4)
        return str(candidate + 1)
Esempio n. 5
0
def _get_partition_number(device, part_type, start, end):
    """
    Return a partition number for a [start, end] range and a partition
    type.

    If the range is allocated and the partition type match, return the
    partition number. If the type do not match but is a logical
    partition inside an extended one, return the next partition
    number.

    If the range is not allocated, return the next partition number.

    """

    unit = disk.units(start)[1]
    partitions = _get_cached_partitions(device, unit)

    # Check if there is a partition in the system that start or
    # containst the start point
    number = _get_first_overlapping_partition(device, start)
    if number:
        if partitions[number]["type"] == part_type:
            return number
        elif not (partitions[number]["type"] == "extended"
                  and part_type == "logical"):
            raise EnumerateException("Do not overlap partitions")

    def __primary_partition_free_slot(partitions, label):
        if label == "msdos":
            max_primary = 4
        else:
            max_primary = 1024
        for i in range(1, max_primary + 1):
            i = str(i)
            if i not in partitions:
                return i

    # The partition is not already there, we guess the next number
    label = _get_cached_info(device)["partition table"]
    if part_type == "primary":
        candidate = __primary_partition_free_slot(partitions, label)
        if not candidate:
            raise EnumerateException("No free slot for primary partition")
        return candidate
    elif part_type == "extended":
        if label == "gpt":
            raise EnumerateException("Extended partitions not allowed in gpt")
        if "extended" in (info["type"] for info in partitions.values()):
            raise EnumerateException("Already found a extended partition")
        candidate = __primary_partition_free_slot(partitions, label)
        if not candidate:
            raise EnumerateException("No free slot for extended partition")
        return candidate
    elif part_type == "logical":
        if label == "gpt":
            raise EnumerateException("Extended partitions not allowed in gpt")
        if "extended" not in (part["type"] for part in partitions.values()):
            raise EnumerateException("Missing extended partition")
        candidate = max(
            (int(part["number"])
             for part in partitions.values() if part["type"] == "logical"),
            default=4,
        )
        return str(candidate + 1)
Esempio n. 6
0
def prepare_partition_data(partitions):
    """Helper function to prepare the patition data from the pillar."""

    # Validate and normalize the `partitions` pillar. The state will
    # expect a dictionary with this schema:
    #
    # partitions_normalized = {
    #     '/dev/sda': {
    #         'label': 'gpt',
    #         'pmbr_boot': False,
    #         'partitions': [
    #             {
    #                 'part_id': '/dev/sda1',
    #                 'part_type': 'primary'
    #                 'fs_type': 'ext2',
    #                 'flags': ['esp'],
    #                 'start': '0MB',
    #                 'end': '100%',
    #             },
    #         ],
    #     },
    # }

    is_uefi = __grains__["efi"]

    # Get the fallback values for label and initial_gap
    config = partitions.get("config", {})
    global_label = config.get("label", LABEL)
    global_initial_gap = config.get("initial_gap", INITIAL_GAP)

    partitions_normalized = {}
    for device, device_info in partitions["devices"].items():
        label = device_info.get("label", global_label)
        initial_gap = device_info.get("initial_gap", global_initial_gap)
        if initial_gap:
            initial_gap_num, units = disk.units(initial_gap, default=None)
        else:
            initial_gap_num, units = 0, None

        device_normalized = {
            "label": label,
            "pmbr_boot": label == "gpt" and not is_uefi,
            "partitions": [],
        }
        partitions_normalized[device] = device_normalized

        # Control the start of the next partition
        start_size = initial_gap_num
        # Flag to detect if `rest` size was used before
        rest = False

        for index, partition in enumerate(device_info.get("partitions", [])):
            # Detect if there is another partition after we create one
            # that complete the free space
            if rest:
                raise SaltInvocationError(
                    "Partition defined after one filled all the rest free "
                    "space. Use `rest` only on the last partition."
                )

            # Validate the partition type
            part_type = partition.get("type")
            if part_type not in VALID_PART_TYPE:
                raise SaltInvocationError(
                    "Partition type {} not recognized".format(part_type)
                )

            # If part_id is not given, we can create a partition name
            # based on the position of the partition and the name of
            # the device
            #
            # TODO(aplanas) The partition number will be deduced, so
            # the require section in mkfs_partition will fail
            part_id = "{}{}{}".format(
                device,
                "p" if __salt__["filters.is_raid"](device) else "",
                partitions.get("number", index + 1),
            )
            part_id = partition.get("id", part_id)

            # For parted we usually need to set a ext2 filesystem
            # type, except for SWAP or UEFI
            fs_type = {"swap": "linux-swap", "efi": "fat16"}.get(part_type, "ext2")

            # Check if we are changing units inside the device
            if partition["size"] == "rest":
                rest = True
                # If units is not set, we default to '%'
                units = units or "%"
                start = "{}{}".format(start_size, units)
                end = "100%"
            else:
                size, size_units = disk.units(partition["size"])
                if units and size_units and units != size_units:
                    raise SaltInvocationError(
                        "Units needs to be the same for the partitions inside "
                        "a device. Found {} but expected {}. Note that "
                        "`initial_gap` is also considered.".format(size_units, units)
                    )
                # If units and size_units is not set, we default to UNITS
                units = units or size_units or UNITS
                start = "{}{}".format(start_size, units)
                end = "{}{}".format(start_size + size, units)
                start_size += size

            flags = None
            if part_type in ("raid", "lvm"):
                flags = [part_type]
            elif part_type == "boot" and label == "gpt" and not is_uefi:
                flags = ["bios_grub"]
            elif part_type == "efi" and label == "gpt" and is_uefi:
                flags = ["esp"]

            device_normalized["partitions"].append(
                {
                    "part_id": part_id,
                    # TODO(aplanas) If msdos we need to create extended
                    # and logical
                    "part_type": "primary",
                    "fs_type": fs_type,
                    "start": start,
                    "end": end,
                    "flags": flags,
                }
            )

    return partitions_normalized