| # |
| # |
| |
| # Copyright (C) 2013 Google Inc. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # 1. Redistributions of source code must retain the above copyright notice, |
| # this list of conditions and the following disclaimer. |
| # |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
| # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| """Utility functions for storage. |
| |
| """ |
| |
| import logging |
| |
| from ganeti import constants |
| from ganeti import errors |
| from ganeti.utils import io as utils_io |
| from ganeti.utils import process as utils_process |
| |
| |
| def GetDiskTemplatesOfStorageTypes(*storage_types): |
| """Given the storage type, returns a list of disk templates based on that |
| storage type.""" |
| return [dt for dt in constants.DISK_TEMPLATES |
| if constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[dt] in storage_types] |
| |
| |
| def IsDiskTemplateEnabled(disk_template, enabled_disk_templates): |
| """Checks if a particular disk template is enabled. |
| |
| """ |
| return disk_template in enabled_disk_templates |
| |
| |
| def IsFileStorageEnabled(enabled_disk_templates): |
| """Checks if file storage is enabled. |
| |
| """ |
| return IsDiskTemplateEnabled(constants.DT_FILE, enabled_disk_templates) |
| |
| |
| def IsSharedFileStorageEnabled(enabled_disk_templates): |
| """Checks if shared file storage is enabled. |
| |
| """ |
| return IsDiskTemplateEnabled(constants.DT_SHARED_FILE, enabled_disk_templates) |
| |
| |
| def IsLvmEnabled(enabled_disk_templates): |
| """Check whether or not any lvm-based disk templates are enabled.""" |
| return len(constants.DTS_LVM & set(enabled_disk_templates)) != 0 |
| |
| |
| def LvmGetsEnabled(enabled_disk_templates, new_enabled_disk_templates): |
| """Checks whether lvm was not enabled before, but will be enabled after |
| the operation. |
| |
| """ |
| if IsLvmEnabled(enabled_disk_templates): |
| return False |
| return len(constants.DTS_LVM & set(new_enabled_disk_templates)) != 0 |
| |
| |
| def _GetDefaultStorageUnitForDiskTemplate(cfg, disk_template): |
| """Retrieves the identifier of the default storage entity for the given |
| storage type. |
| |
| @type cfg: C{objects.ConfigData} |
| @param cfg: the configuration data |
| @type disk_template: string |
| @param disk_template: a disk template, for example 'drbd' |
| @rtype: string |
| @return: identifier for a storage unit, for example the vg_name for lvm |
| storage |
| |
| """ |
| storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template] |
| cluster = cfg.GetClusterInfo() |
| if disk_template in constants.DTS_LVM: |
| return (storage_type, cfg.GetVGName()) |
| elif disk_template == constants.DT_FILE: |
| return (storage_type, cluster.file_storage_dir) |
| elif disk_template == constants.DT_SHARED_FILE: |
| return (storage_type, cluster.shared_file_storage_dir) |
| elif disk_template == constants.DT_GLUSTER: |
| return (storage_type, cluster.gluster_storage_dir) |
| else: |
| return (storage_type, None) |
| |
| |
| def DiskTemplateSupportsSpaceReporting(disk_template): |
| """Check whether the disk template supports storage space reporting.""" |
| return (constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template] |
| in constants.STS_REPORT) |
| |
| |
| def GetStorageUnits(cfg, disk_templates): |
| """Get the cluster's storage units for the given disk templates. |
| |
| If any lvm-based disk template is requested, spindle information |
| is added to the request. |
| |
| @type cfg: L{config.ConfigWriter} |
| @param cfg: Cluster configuration |
| @type disk_templates: list of string |
| @param disk_templates: list of disk templates for which the storage |
| units will be computed |
| @rtype: list of tuples (string, string) |
| @return: list of storage units, each storage unit being a tuple of |
| (storage_type, storage_key); storage_type is in |
| C{constants.STORAGE_TYPES} and the storage_key a string to |
| identify an entity of that storage type, for example a volume group |
| name for LVM storage or a file for file storage. |
| |
| """ |
| storage_units = [] |
| for disk_template in disk_templates: |
| if DiskTemplateSupportsSpaceReporting(disk_template): |
| storage_units.append( |
| _GetDefaultStorageUnitForDiskTemplate(cfg, disk_template)) |
| return storage_units |
| |
| |
| def LookupSpaceInfoByDiskTemplate(storage_space_info, disk_template): |
| """Looks up the storage space info for a given disk template. |
| |
| @type storage_space_info: list of dicts |
| @param storage_space_info: result of C{GetNodeInfo} |
| @type disk_template: string |
| @param disk_template: disk template to get storage space info |
| @rtype: tuple |
| @return: returns the element of storage_space_info that matches the given |
| disk template |
| |
| """ |
| storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template] |
| return LookupSpaceInfoByStorageType(storage_space_info, storage_type) |
| |
| |
| def LookupSpaceInfoByStorageType(storage_space_info, storage_type): |
| """Looks up the storage space info for a given storage type. |
| |
| Note that this lookup can be ambiguous if storage space reporting for several |
| units of the same storage type was requested. This function is only supposed |
| to be used for legacy code in situations where it actually is unambiguous. |
| |
| @type storage_space_info: list of dicts |
| @param storage_space_info: result of C{GetNodeInfo} |
| @type storage_type: string |
| @param storage_type: a storage type, which is included in the storage_units |
| list |
| @rtype: tuple |
| @return: returns the element of storage_space_info that matches the given |
| storage type |
| |
| """ |
| result = None |
| for unit_info in storage_space_info: |
| if unit_info["type"] == storage_type: |
| if result is None: |
| result = unit_info |
| else: |
| # There is more than one storage type in the query, log a warning |
| logging.warning("Storage space information requested for" |
| " ambiguous storage type '%s'.", storage_type) |
| return result |
| |
| |
| def GetDiskLabels(prefix, num_disks, start=0): |
| """Generate disk labels for a number of disks |
| |
| Note that disk labels are generated in the range [start..num_disks[ |
| (e.g., as in range(start, num_disks)) |
| |
| @type prefix: string |
| @param prefix: disk label prefix (e.g., "/dev/sd") |
| |
| @type num_disks: int |
| @param num_disks: number of disks (i.e., disk labels) |
| |
| @type start: int |
| @param start: optional start index |
| |
| @rtype: generator |
| @return: generator for the disk labels |
| |
| """ |
| def _GetDiskSuffix(i): |
| n = ord('z') - ord('a') + 1 |
| if i < n: |
| return chr(ord('a') + i) |
| else: |
| mod = int(i % n) |
| pref = _GetDiskSuffix((i - mod) / (n + 1)) |
| suf = _GetDiskSuffix(mod) |
| return pref + suf |
| |
| for i in range(start, num_disks): |
| yield prefix + _GetDiskSuffix(i) |
| |
| |
| def CreateBdevPartitionMapping(image_path): |
| """Create dm device for each partition of disk image. |
| |
| This operation will allocate a loopback and a device-mapper device to map |
| partitions. |
| You must call L{ReleaseBdevPartitionMapping} to clean up resources allocated |
| by this function call. |
| |
| @type image_path: string |
| @param image_path: path of multi-partition disk image |
| @rtype: tuple(string, list(string)) or NoneType |
| @return: returns the tuple(loopback_device, list(device_mapper_files)) if |
| image_path is a multi-partition disk image. otherwise, returns None. |
| |
| """ |
| # Unfortunately, there are two different losetup commands in this world. |
| # One has the '-s' switch and the other has the '--show' switch to provide the |
| # same functionality. |
| result = utils_process.RunCmd(["losetup", "-f", "-s", image_path]) |
| if result.failed and "invalid option -- 's'" in result.stderr: |
| result = utils_process.RunCmd(["losetup", "-f", "--show", image_path]) |
| if result.failed: |
| raise errors.CommandError("Failed to setup loop device for %s: %s" % |
| (image_path, result.output)) |
| loop_dev_path = result.stdout.strip() |
| logging.debug("Loop dev %s allocated for %s", loop_dev_path, image_path) |
| |
| result = utils_process.RunCmd(["kpartx", "-a", "-v", loop_dev_path]) |
| if result.failed: |
| # Just try to cleanup allocated loop device |
| utils_process.RunCmd(["losetup", "-d", loop_dev_path]) |
| raise errors.CommandError("Failed to add partition mapping for %s: %s" % |
| (image_path, result.output)) |
| dm_devs = [x.split(" ") for x in result.stdout.split("\n") if x] |
| if dm_devs: |
| dm_dev_paths = [utils_io.PathJoin("/dev/mapper", x[2]) for x in dm_devs] |
| return (loop_dev_path, dm_dev_paths) |
| else: |
| # image_path is not a multi partition disk image, no need to use |
| # device-mapper. |
| logging.debug("Release loop dev %s allocated for %s", |
| loop_dev_path, image_path) |
| ReleaseBdevPartitionMapping(loop_dev_path) |
| return None |
| |
| |
| def ReleaseBdevPartitionMapping(loop_dev_path): |
| """Release allocated dm devices and loopback devices. |
| |
| @type loop_dev_path: string |
| @param loop_dev_path: path of loopback device returned by |
| L{CreateBdevPartitionMapping} |
| |
| """ |
| result = utils_process.RunCmd(["kpartx", "-d", loop_dev_path]) |
| if result.failed: |
| raise errors.CommandError("Failed to release partition mapping of %s: %s" % |
| (loop_dev_path, result.output)) |
| |
| # The invocation of udevadm settle was added here because users had issues |
| # with the loopback device still being busy after kpartx / earlier commands |
| # did their work. |
| result = utils_process.RunCmd(["udevadm", "settle"]) |
| if result.failed: |
| raise errors.CommandError("Waiting on udev failed: %s" % result.output) |
| |
| result = utils_process.RunCmd(["losetup", "-d", loop_dev_path]) |
| if result.failed: |
| raise errors.CommandError("Failed to detach %s: %s" % |
| (loop_dev_path, result.output)) |
| |
| |
| def osminor(dev): |
| """Return the device minor number from a raw device number. |
| |
| This is a replacement for os.minor working around the issue that |
| Python's os.minor still has the old definition. See Ganeti issue |
| 1058 for more details. |
| """ |
| return (dev & 0xff) | ((dev >> 12) & ~0xff) |