# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # Copyright (c) 2010 Citrix Systems, Inc. # Copyright (c) 2011 Piston Cloud Computing, Inc # Copyright (c) 2011 OpenStack Foundation # (c) Copyright 2013 Hewlett-Packard Development Company, L.P. # # 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 os from oslo_concurrency import processutils from oslo_log import log as logging from oslo_utils import units import nova.conf from nova import exception from nova.i18n import _ import nova.privsep.fs CONF = nova.conf.CONF LOG = logging.getLogger(__name__) # TODO(sbauza): Remove the possibility to ask for a sparse LV. def create_volume(vg, lv, size, sparse=False): """Create LVM image. Creates a LVM image with given size. :param vg: existing volume group which should hold this image :param lv: name for this image (logical volume) :size: size of image in bytes :sparse: create sparse logical volume """ vg_info = get_volume_group_info(vg) free_space = vg_info['free'] def check_size(vg, lv, size): if size > free_space: raise RuntimeError(_('Insufficient Space on Volume Group %(vg)s.' ' Only %(free_space)db available,' ' but %(size)d bytes required' ' by volume %(lv)s.') % {'vg': vg, 'free_space': free_space, 'size': size, 'lv': lv}) if sparse: preallocated_space = 64 * units.Mi check_size(vg, lv, preallocated_space) if free_space < size: LOG.warning('Volume group %(vg)s will not be able' ' to hold sparse volume %(lv)s.' ' Virtual volume size is %(size)d bytes,' ' but free space on volume group is' ' only %(free_space)db.', {'vg': vg, 'free_space': free_space, 'size': size, 'lv': lv}) nova.privsep.fs.lvcreate(size, lv, vg, preallocated=preallocated_space) else: check_size(vg, lv, size) nova.privsep.fs.lvcreate(size, lv, vg) def get_volume_group_info(vg): """Return free/used/total space info for a volume group in bytes :param vg: volume group name :returns: A dict containing: :total: How big the filesystem is (in bytes) :free: How much space is free (in bytes) :used: How much space is used (in bytes) """ out, err = nova.privsep.fs.vginfo(vg) info = out.split('|') if len(info) != 2: raise RuntimeError(_("vg %s must be LVM volume group") % vg) return {'total': int(info[0]), 'free': int(info[1]), 'used': int(info[0]) - int(info[1])} def list_volumes(vg): """List logical volumes paths for given volume group. :param vg: volume group name :returns: Return a logical volume list for given volume group : Data format example : ['volume-aaa', 'volume-bbb', 'volume-ccc'] """ out, err = nova.privsep.fs.lvlist(vg) return [line.strip() for line in out.splitlines()] def volume_info(path): """Get logical volume info. :param path: logical volume path :returns: Return a dict object including info of given logical volume : Data format example : {'#Seg': '1', 'Move': '', 'Log': '', 'Meta%': '', 'Min': '-1', : ... : 'Free': '9983', 'LV': 'volume-aaa', 'Host': 'xyz.com', : 'Active': 'active', 'Path': '/dev/vg/volume-aaa', '#LV': '3', : 'Maj': '-1', 'VSize': '50.00g', 'VFree': '39.00g', 'Pool': '', : 'VG Tags': '', 'KMaj': '253', 'Convert': '', 'LProfile': '', : '#Ext': '12799', 'Attr': '-wi-a-----', 'VG': 'vg', : ... : 'LSize': '1.00g', '#PV': '1', '#VMdaCps': 'unmanaged'} """ out, err = nova.privsep.fs.lvinfo(path) info = [line.split('|') for line in out.splitlines()] if len(info) != 2: raise RuntimeError(_("Path %s must be LVM logical volume") % path) return dict(zip(*info)) def get_volume_size(path): """Get logical volume size in bytes. :param path: logical volume path :raises: processutils.ProcessExecutionError if getting the volume size fails in some unexpected way. :raises: exception.VolumeBDMPathNotFound if the volume path does not exist. """ try: out, _err = nova.privsep.fs.blockdev_size(path) except processutils.ProcessExecutionError: if not os.path.exists(path): raise exception.VolumeBDMPathNotFound(path=path) else: raise return int(out) def clear_volume(path): """Obfuscate the logical volume. :param path: logical volume path """ if CONF.libvirt.volume_clear == 'none': return volume_clear_size = int(CONF.libvirt.volume_clear_size) * units.Mi try: volume_size = get_volume_size(path) except exception.VolumeBDMPathNotFound: LOG.warning('Ignoring missing logical volume %(path)s', {'path': path}) return if volume_clear_size != 0 and volume_clear_size < volume_size: volume_size = volume_clear_size nova.privsep.fs.clear(path, volume_size, shred=(CONF.libvirt.volume_clear == 'shred')) def remove_volumes(paths): """Remove one or more logical volume.""" errors = [] for path in paths: clear_volume(path) try: nova.privsep.fs.lvremove(path) except processutils.ProcessExecutionError as exp: errors.append(str(exp)) if errors: raise exception.VolumesNotRemoved(reason=(', ').join(errors))