diff options
author | Scott Moser <smoser@ubuntu.com> | 2016-08-10 09:06:15 -0600 |
---|---|---|
committer | Scott Moser <smoser@ubuntu.com> | 2016-08-10 09:06:15 -0600 |
commit | c3c3dc693c14175e110b5fe125d4d5f98ace9700 (patch) | |
tree | 8858702c2c8a6ad4bf1bb861a4565e0a9c28e588 /cloudinit/config/cc_disk_setup.py | |
parent | 5bd3493d732e5b1902872958e8681f17cbc81ce5 (diff) | |
download | cloud-init-trunk.tar.gz |
cloud-init development has moved its revision control to git.
It is available at
https://code.launchpad.net/cloud-init
Clone with
git clone https://git.launchpad.net/cloud-init
or
git clone git+ssh://git.launchpad.net/cloud-init
For more information see
https://git.launchpad.net/cloud-init/tree/HACKING.rst
Diffstat (limited to 'cloudinit/config/cc_disk_setup.py')
-rw-r--r-- | cloudinit/config/cc_disk_setup.py | 863 |
1 files changed, 0 insertions, 863 deletions
diff --git a/cloudinit/config/cc_disk_setup.py b/cloudinit/config/cc_disk_setup.py deleted file mode 100644 index b642f1f8..00000000 --- a/cloudinit/config/cc_disk_setup.py +++ /dev/null @@ -1,863 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Ben Howard <ben.howard@canonical.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 3, as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -from cloudinit.settings import PER_INSTANCE -from cloudinit import util -import logging -import os -import shlex - -frequency = PER_INSTANCE - -# Define the commands to use -UDEVADM_CMD = util.which('udevadm') -SFDISK_CMD = util.which("sfdisk") -SGDISK_CMD = util.which("sgdisk") -LSBLK_CMD = util.which("lsblk") -BLKID_CMD = util.which("blkid") -BLKDEV_CMD = util.which("blockdev") -WIPEFS_CMD = util.which("wipefs") - -LOG = logging.getLogger(__name__) - - -def handle(_name, cfg, cloud, log, _args): - """ - See doc/examples/cloud-config_disk-setup.txt for documentation on the - format. - """ - disk_setup = cfg.get("disk_setup") - if isinstance(disk_setup, dict): - update_disk_setup_devices(disk_setup, cloud.device_name_to_device) - log.debug("Partitioning disks: %s", str(disk_setup)) - for disk, definition in disk_setup.items(): - if not isinstance(definition, dict): - log.warn("Invalid disk definition for %s" % disk) - continue - - try: - log.debug("Creating new partition table/disk") - util.log_time(logfunc=LOG.debug, - msg="Creating partition on %s" % disk, - func=mkpart, args=(disk, definition)) - except Exception as e: - util.logexc(LOG, "Failed partitioning operation\n%s" % e) - - fs_setup = cfg.get("fs_setup") - if isinstance(fs_setup, list): - log.debug("setting up filesystems: %s", str(fs_setup)) - update_fs_setup_devices(fs_setup, cloud.device_name_to_device) - for definition in fs_setup: - if not isinstance(definition, dict): - log.warn("Invalid file system definition: %s" % definition) - continue - - try: - log.debug("Creating new filesystem.") - device = definition.get('device') - util.log_time(logfunc=LOG.debug, - msg="Creating fs for %s" % device, - func=mkfs, args=(definition,)) - except Exception as e: - util.logexc(LOG, "Failed during filesystem operation\n%s" % e) - - -def update_disk_setup_devices(disk_setup, tformer): - # update 'disk_setup' dictionary anywhere were a device may occur - # update it with the response from 'tformer' - for origname in disk_setup.keys(): - transformed = tformer(origname) - if transformed is None or transformed == origname: - continue - if transformed in disk_setup: - LOG.info("Replacing %s in disk_setup for translation of %s", - origname, transformed) - del disk_setup[transformed] - - disk_setup[transformed] = disk_setup[origname] - disk_setup[transformed]['_origname'] = origname - del disk_setup[origname] - LOG.debug("updated disk_setup device entry '%s' to '%s'", - origname, transformed) - - -def update_fs_setup_devices(disk_setup, tformer): - # update 'fs_setup' dictionary anywhere were a device may occur - # update it with the response from 'tformer' - for definition in disk_setup: - if not isinstance(definition, dict): - LOG.warn("entry in disk_setup not a dict: %s", definition) - continue - - origname = definition.get('device') - - if origname is None: - continue - - (dev, part) = util.expand_dotted_devname(origname) - - tformed = tformer(dev) - if tformed is not None: - dev = tformed - LOG.debug("%s is mapped to disk=%s part=%s", - origname, tformed, part) - definition['_origname'] = origname - definition['device'] = tformed - - if part and 'partition' in definition: - definition['_partition'] = definition['partition'] - definition['partition'] = part - - -def value_splitter(values, start=None): - """ - Returns the key/value pairs of output sent as string - like: FOO='BAR' HOME='127.0.0.1' - """ - _values = shlex.split(values) - if start: - _values = _values[start:] - - for key, value in [x.split('=') for x in _values]: - yield key, value - - -def enumerate_disk(device, nodeps=False): - """ - Enumerate the elements of a child device. - - Parameters: - device: the kernel device name - nodeps <BOOL>: don't enumerate children devices - - Return a dict describing the disk: - type: the entry type, i.e disk or part - fstype: the filesystem type, if it exists - label: file system label, if it exists - name: the device name, i.e. sda - """ - - lsblk_cmd = [LSBLK_CMD, '--pairs', '--output', 'NAME,TYPE,FSTYPE,LABEL', - device] - - if nodeps: - lsblk_cmd.append('--nodeps') - - info = None - try: - info, _err = util.subp(lsblk_cmd) - except Exception as e: - raise Exception("Failed during disk check for %s\n%s" % (device, e)) - - parts = [x for x in (info.strip()).splitlines() if len(x.split()) > 0] - - for part in parts: - d = { - 'name': None, - 'type': None, - 'fstype': None, - 'label': None, - } - - for key, value in value_splitter(part): - d[key.lower()] = value - - yield d - - -def device_type(device): - """ - Return the device type of the device by calling lsblk. - """ - - for d in enumerate_disk(device, nodeps=True): - if "type" in d: - return d["type"].lower() - return None - - -def is_device_valid(name, partition=False): - """ - Check if the device is a valid device. - """ - d_type = "" - try: - d_type = device_type(name) - except Exception: - LOG.warn("Query against device %s failed" % name) - return False - - if partition and d_type == 'part': - return True - elif not partition and d_type == 'disk': - return True - return False - - -def check_fs(device): - """ - Check if the device has a filesystem on it - - Output of blkid is generally something like: - /dev/sda: LABEL="Backup500G" UUID="..." TYPE="ext4" - - Return values are device, label, type, uuid - """ - out, label, fs_type, uuid = None, None, None, None - - blkid_cmd = [BLKID_CMD, '-c', '/dev/null', device] - try: - out, _err = util.subp(blkid_cmd, rcs=[0, 2]) - except Exception as e: - raise Exception("Failed during disk check for %s\n%s" % (device, e)) - - if out: - if len(out.splitlines()) == 1: - for key, value in value_splitter(out, start=1): - if key.lower() == 'label': - label = value - elif key.lower() == 'type': - fs_type = value - elif key.lower() == 'uuid': - uuid = value - - return label, fs_type, uuid - - -def is_filesystem(device): - """ - Returns true if the device has a file system. - """ - _, fs_type, _ = check_fs(device) - return fs_type - - -def find_device_node(device, fs_type=None, label=None, valid_targets=None, - label_match=True, replace_fs=None): - """ - Find a device that is either matches the spec, or the first - - The return is value is (<device>, <bool>) where the device is the - device to use and the bool is whether the device matches the - fs_type and label. - - Note: This works with GPT partition tables! - """ - # label of None is same as no label - if label is None: - label = "" - - if not valid_targets: - valid_targets = ['disk', 'part'] - - raw_device_used = False - for d in enumerate_disk(device): - - if d['fstype'] == replace_fs and label_match is False: - # We found a device where we want to replace the FS - return ('/dev/%s' % d['name'], False) - - if (d['fstype'] == fs_type and - ((label_match and d['label'] == label) or not label_match)): - # If we find a matching device, we return that - return ('/dev/%s' % d['name'], True) - - if d['type'] in valid_targets: - - if d['type'] != 'disk' or d['fstype']: - raw_device_used = True - - if d['type'] == 'disk': - # Skip the raw disk, its the default - pass - - elif not d['fstype']: - return ('/dev/%s' % d['name'], False) - - if not raw_device_used: - return (device, False) - - LOG.warn("Failed to find device during available device search.") - return (None, False) - - -def is_disk_used(device): - """ - Check if the device is currently used. Returns true if the device - has either a file system or a partition entry - is no filesystem found on the disk. - """ - - # If the child count is higher 1, then there are child nodes - # such as partition or device mapper nodes - if len(list(enumerate_disk(device))) > 1: - return True - - # If we see a file system, then its used - _, check_fstype, _ = check_fs(device) - if check_fstype: - return True - - return False - - -def get_dyn_func(*args): - """ - Call the appropriate function. - - The first value is the template for function name - The second value is the template replacement - The remain values are passed to the function - - For example: get_dyn_func("foo_%s", 'bar', 1, 2, 3,) - would call "foo_bar" with args of 1, 2, 3 - """ - if len(args) < 2: - raise Exception("Unable to determine dynamic funcation name") - - func_name = (args[0] % args[1]) - func_args = args[2:] - - try: - if func_args: - return globals()[func_name](*func_args) - else: - return globals()[func_name] - - except KeyError: - raise Exception("No such function %s to call!" % func_name) - - -def get_mbr_hdd_size(device): - size_cmd = [SFDISK_CMD, '--show-size', device] - size = None - try: - size, _err = util.subp(size_cmd) - except Exception as e: - raise Exception("Failed to get %s size\n%s" % (device, e)) - - return int(size.strip()) - - -def get_gpt_hdd_size(device): - out, _ = util.subp([SGDISK_CMD, '-p', device]) - return out.splitlines()[0].split()[2] - - -def get_hdd_size(table_type, device): - """ - Returns the hard disk size. - This works with any disk type, including GPT. - """ - return get_dyn_func("get_%s_hdd_size", table_type, device) - - -def check_partition_mbr_layout(device, layout): - """ - Returns true if the partition layout matches the one on the disk - - Layout should be a list of values. At this time, this only - verifies that the number of partitions and their labels is correct. - """ - - read_parttbl(device) - prt_cmd = [SFDISK_CMD, "-l", device] - try: - out, _err = util.subp(prt_cmd, data="%s\n" % layout) - except Exception as e: - raise Exception("Error running partition command on %s\n%s" % ( - device, e)) - - found_layout = [] - for line in out.splitlines(): - _line = line.split() - if len(_line) == 0: - continue - - if device in _line[0]: - # We don't understand extended partitions yet - if _line[-1].lower() in ['extended', 'empty']: - continue - - # Find the partition types - type_label = None - for x in sorted(range(1, len(_line)), reverse=True): - if _line[x].isdigit() and _line[x] != '/': - type_label = _line[x] - break - - found_layout.append(type_label) - return found_layout - - -def check_partition_gpt_layout(device, layout): - prt_cmd = [SGDISK_CMD, '-p', device] - try: - out, _err = util.subp(prt_cmd) - except Exception as e: - raise Exception("Error running partition command on %s\n%s" % ( - device, e)) - - out_lines = iter(out.splitlines()) - # Skip header - for line in out_lines: - if line.strip().startswith('Number'): - break - - return [line.strip().split()[-1] for line in out_lines] - - -def check_partition_layout(table_type, device, layout): - """ - See if the partition lay out matches. - - This is future a future proofing function. In order - to add support for other disk layout schemes, add a - function called check_partition_%s_layout - """ - found_layout = get_dyn_func( - "check_partition_%s_layout", table_type, device, layout) - - if isinstance(layout, bool): - # if we are using auto partitioning, or "True" be happy - # if a single partition exists. - if layout and len(found_layout) >= 1: - return True - return False - - else: - if len(found_layout) != len(layout): - return False - else: - # This just makes sure that the number of requested - # partitions and the type labels are right - for x in range(1, len(layout) + 1): - if isinstance(layout[x - 1], tuple): - _, part_type = layout[x] - if int(found_layout[x]) != int(part_type): - return False - return True - - return False - - -def get_partition_mbr_layout(size, layout): - """ - Calculate the layout of the partition table. Partition sizes - are defined as percentage values or a tuple of percentage and - partition type. - - For example: - [ 33, [66: 82] ] - - Defines the first partition to be a size of 1/3 the disk, - while the remaining 2/3's will be of type Linux Swap. - """ - - if not isinstance(layout, list) and isinstance(layout, bool): - # Create a single partition - return "0," - - if ((len(layout) == 0 and isinstance(layout, list)) or - not isinstance(layout, list)): - raise Exception("Partition layout is invalid") - - last_part_num = len(layout) - if last_part_num > 4: - raise Exception("Only simply partitioning is allowed.") - - part_definition = [] - part_num = 0 - for part in layout: - part_type = 83 # Default to Linux - percent = part - part_num += 1 - - if isinstance(part, list): - if len(part) != 2: - raise Exception("Partition was incorrectly defined: %s" % part) - percent, part_type = part - - part_size = int((float(size) * (float(percent) / 100)) / 1024) - - if part_num == last_part_num: - part_definition.append(",,%s" % part_type) - else: - part_definition.append(",%s,%s" % (part_size, part_type)) - - sfdisk_definition = "\n".join(part_definition) - if len(part_definition) > 4: - raise Exception("Calculated partition definition is too big\n%s" % - sfdisk_definition) - - return sfdisk_definition - - -def get_partition_gpt_layout(size, layout): - if isinstance(layout, bool): - return [(None, [0, 0])] - - partition_specs = [] - for partition in layout: - if isinstance(partition, list): - if len(partition) != 2: - raise Exception( - "Partition was incorrectly defined: %s" % partition) - percent, partition_type = partition - else: - percent = partition - partition_type = None - - part_size = int(float(size) * (float(percent) / 100)) - partition_specs.append((partition_type, [0, '+{}'.format(part_size)])) - - # The last partition should use up all remaining space - partition_specs[-1][-1][-1] = 0 - return partition_specs - - -def purge_disk_ptable(device): - # wipe the first and last megabyte of a disk (or file) - # gpt stores partition table both at front and at end. - null = '\0' - start_len = 1024 * 1024 - end_len = 1024 * 1024 - with open(device, "rb+") as fp: - fp.write(null * (start_len)) - fp.seek(-end_len, os.SEEK_END) - fp.write(null * end_len) - fp.flush() - - read_parttbl(device) - - -def purge_disk(device): - """ - Remove parition table entries - """ - - # wipe any file systems first - for d in enumerate_disk(device): - if d['type'] not in ["disk", "crypt"]: - wipefs_cmd = [WIPEFS_CMD, "--all", "/dev/%s" % d['name']] - try: - LOG.info("Purging filesystem on /dev/%s" % d['name']) - util.subp(wipefs_cmd) - except Exception: - raise Exception("Failed FS purge of /dev/%s" % d['name']) - - purge_disk_ptable(device) - - -def get_partition_layout(table_type, size, layout): - """ - Call the appropriate function for creating the table - definition. Returns the table definition - - This is a future proofing function. To add support for - other layouts, simply add a "get_partition_%s_layout" - function. - """ - return get_dyn_func("get_partition_%s_layout", table_type, size, layout) - - -def read_parttbl(device): - """ - Use partprobe instead of 'udevadm'. Partprobe is the only - reliable way to probe the partition table. - """ - blkdev_cmd = [BLKDEV_CMD, '--rereadpt', device] - udev_cmd = [UDEVADM_CMD, 'settle'] - try: - util.subp(udev_cmd) - util.subp(blkdev_cmd) - util.subp(udev_cmd) - except Exception as e: - util.logexc(LOG, "Failed reading the partition table %s" % e) - - -def exec_mkpart_mbr(device, layout): - """ - Break out of mbr partition to allow for future partition - types, i.e. gpt - """ - # Create the partitions - prt_cmd = [SFDISK_CMD, "--Linux", "-uM", device] - try: - util.subp(prt_cmd, data="%s\n" % layout) - except Exception as e: - raise Exception("Failed to partition device %s\n%s" % (device, e)) - - read_parttbl(device) - - -def exec_mkpart_gpt(device, layout): - try: - util.subp([SGDISK_CMD, '-Z', device]) - for index, (partition_type, (start, end)) in enumerate(layout): - index += 1 - util.subp([SGDISK_CMD, - '-n', '{}:{}:{}'.format(index, start, end), device]) - if partition_type is not None: - util.subp( - [SGDISK_CMD, - '-t', '{}:{}'.format(index, partition_type), device]) - except Exception: - LOG.warn("Failed to partition device %s" % device) - raise - - -def exec_mkpart(table_type, device, layout): - """ - Fetches the function for creating the table type. - This allows to dynamically find which function to call. - - Paramaters: - table_type: type of partition table to use - device: the device to work on - layout: layout definition specific to partition table - """ - return get_dyn_func("exec_mkpart_%s", table_type, device, layout) - - -def mkpart(device, definition): - """ - Creates the partition table. - - Parameters: - definition: dictionary describing how to create the partition. - - The following are supported values in the dict: - overwrite: Should the partition table be created regardless - of any pre-exisiting data? - layout: the layout of the partition table - table_type: Which partition table to use, defaults to MBR - device: the device to work on. - """ - # ensure that we get a real device rather than a symbolic link - device = os.path.realpath(device) - - LOG.debug("Checking values for %s definition" % device) - overwrite = definition.get('overwrite', False) - layout = definition.get('layout', False) - table_type = definition.get('table_type', 'mbr') - - # Check if the default device is a partition or not - LOG.debug("Checking against default devices") - - if (isinstance(layout, bool) and not layout) or not layout: - LOG.debug("Device is not to be partitioned, skipping") - return # Device is not to be partitioned - - # This prevents you from overwriting the device - LOG.debug("Checking if device %s is a valid device", device) - if not is_device_valid(device): - raise Exception("Device %s is not a disk device!", device) - - # Remove the partition table entries - if isinstance(layout, str) and layout.lower() == "remove": - LOG.debug("Instructed to remove partition table entries") - purge_disk(device) - return - - LOG.debug("Checking if device layout matches") - if check_partition_layout(table_type, device, layout): - LOG.debug("Device partitioning layout matches") - return True - - LOG.debug("Checking if device is safe to partition") - if not overwrite and (is_disk_used(device) or is_filesystem(device)): - LOG.debug("Skipping partitioning on configured device %s" % device) - return - - LOG.debug("Checking for device size") - device_size = get_hdd_size(table_type, device) - - LOG.debug("Calculating partition layout") - part_definition = get_partition_layout(table_type, device_size, layout) - LOG.debug(" Layout is: %s" % part_definition) - - LOG.debug("Creating partition table on %s", device) - exec_mkpart(table_type, device, part_definition) - - LOG.debug("Partition table created for %s", device) - - -def lookup_force_flag(fs): - """ - A force flag might be -F or -F, this look it up - """ - flags = { - 'ext': '-F', - 'btrfs': '-f', - 'xfs': '-f', - 'reiserfs': '-f', - } - - if 'ext' in fs.lower(): - fs = 'ext' - - if fs.lower() in flags: - return flags[fs] - - LOG.warn("Force flag for %s is unknown." % fs) - return '' - - -def mkfs(fs_cfg): - """ - Create a file system on the device. - - label: defines the label to use on the device - fs_cfg: defines how the filesystem is to look - The following values are required generally: - device: which device or cloud defined default_device - filesystem: which file system type - overwrite: indiscriminately create the file system - partition: when device does not define a partition, - setting this to a number will mean - device + partition. When set to 'auto', the - first free device or the first device which - matches both label and type will be used. - - 'any' means the first filesystem that matches - on the device. - - When 'cmd' is provided then no other parameter is required. - """ - label = fs_cfg.get('label') - device = fs_cfg.get('device') - partition = str(fs_cfg.get('partition', 'any')) - fs_type = fs_cfg.get('filesystem') - fs_cmd = fs_cfg.get('cmd', []) - fs_opts = fs_cfg.get('extra_opts', []) - fs_replace = fs_cfg.get('replace_fs', False) - overwrite = fs_cfg.get('overwrite', False) - - # ensure that we get a real device rather than a symbolic link - device = os.path.realpath(device) - - # This allows you to define the default ephemeral or swap - LOG.debug("Checking %s against default devices", device) - - if not partition or partition.isdigit(): - # Handle manual definition of partition - if partition.isdigit(): - device = "%s%s" % (device, partition) - LOG.debug("Manual request of partition %s for %s", - partition, device) - - # Check to see if the fs already exists - LOG.debug("Checking device %s", device) - check_label, check_fstype, _ = check_fs(device) - LOG.debug("Device %s has %s %s", device, check_label, check_fstype) - - if check_label == label and check_fstype == fs_type: - LOG.debug("Existing file system found at %s", device) - - if not overwrite: - LOG.debug("Device %s has required file system", device) - return - else: - LOG.warn("Destroying filesystem on %s", device) - - else: - LOG.debug("Device %s is cleared for formating", device) - - elif partition and str(partition).lower() in ('auto', 'any'): - # For auto devices, we match if the filesystem does exist - odevice = device - LOG.debug("Identifying device to create %s filesytem on", label) - - # any mean pick the first match on the device with matching fs_type - label_match = True - if partition.lower() == 'any': - label_match = False - - device, reuse = find_device_node(device, fs_type=fs_type, label=label, - label_match=label_match, - replace_fs=fs_replace) - LOG.debug("Automatic device for %s identified as %s", odevice, device) - - if reuse: - LOG.debug("Found filesystem match, skipping formating.") - return - - if not reuse and fs_replace and device: - LOG.debug("Replacing file system on %s as instructed." % device) - - if not device: - LOG.debug("No device aviable that matches request. " - "Skipping fs creation for %s", fs_cfg) - return - elif not partition or str(partition).lower() == 'none': - LOG.debug("Using the raw device to place filesystem %s on" % label) - - else: - LOG.debug("Error in device identification handling.") - return - - LOG.debug("File system %s will be created on %s", label, device) - - # Make sure the device is defined - if not device: - LOG.warn("Device is not known: %s", device) - return - - # Check that we can create the FS - if not (fs_type or fs_cmd): - raise Exception("No way to create filesystem '%s'. fs_type or fs_cmd " - "must be set.", label) - - # Create the commands - if fs_cmd: - fs_cmd = fs_cfg['cmd'] % { - 'label': label, - 'filesystem': fs_type, - 'device': device, - } - else: - # Find the mkfs command - mkfs_cmd = util.which("mkfs.%s" % fs_type) - if not mkfs_cmd: - mkfs_cmd = util.which("mk%s" % fs_type) - - if not mkfs_cmd: - LOG.warn("Cannot create fstype '%s'. No mkfs.%s command", fs_type, - fs_type) - return - - fs_cmd = [mkfs_cmd, device] - - if label: - fs_cmd.extend(["-L", label]) - - # File systems that support the -F flag - if overwrite or device_type(device) == "disk": - fs_cmd.append(lookup_force_flag(fs_type)) - - # Add the extends FS options - if fs_opts: - fs_cmd.extend(fs_opts) - - LOG.debug("Creating file system %s on %s", label, device) - LOG.debug(" Using cmd: %s", " ".join(fs_cmd)) - try: - util.subp(fs_cmd) - except Exception as e: - raise Exception("Failed to exec of '%s':\n%s" % (fs_cmd, e)) |