2013-03-18 05:06:52 +08:00
|
|
|
#
|
2013-04-02 19:51:28 +08:00
|
|
|
# Copyright 2008, 2013 Red Hat, Inc.
|
2013-03-18 05:06:52 +08:00
|
|
|
# Cole Robinson <crobinso@redhat.com>
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# 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, write to the Free Software
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
|
|
# MA 02110-1301 USA.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
import logging
|
|
|
|
|
|
|
|
import libvirt
|
|
|
|
import urlgrabber
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
from virtinst.xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty
|
2013-04-11 07:48:07 +08:00
|
|
|
from virtinst import util
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
|
|
|
DEFAULT_DEV_TARGET = "/dev"
|
|
|
|
DEFAULT_LVM_TARGET_BASE = "/dev/"
|
|
|
|
DEFAULT_DIR_TARGET_BASE = "/var/lib/libvirt/images/"
|
|
|
|
DEFAULT_SCSI_TARGET = "/dev/disk/by-path"
|
|
|
|
DEFAULT_MPATH_TARGET = "/dev/mapper"
|
|
|
|
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
class _StoragePermissions(XMLBuilder):
|
|
|
|
_XML_ROOT_NAME = "permissions"
|
|
|
|
_XML_PROP_ORDER = ["mode", "owner", "group", "label"]
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
mode = XMLProperty("./mode")
|
|
|
|
owner = XMLProperty("./owner")
|
|
|
|
group = XMLProperty("./group")
|
|
|
|
label = XMLProperty("./label")
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
class _StorageObject(XMLBuilder):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
Base class for building any libvirt storage object.
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
Meaningless to directly instantiate.
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
######################
|
|
|
|
# Validation helpers #
|
|
|
|
######################
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
def _check_name_collision(self, name):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
def _validate_name(self, name):
|
|
|
|
if name == self.name:
|
|
|
|
return
|
|
|
|
util.validate_name(_("Storage object"), name)
|
|
|
|
self._check_name_collision(name)
|
|
|
|
return name
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
##############
|
|
|
|
# Properties #
|
|
|
|
##############
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
name = XMLProperty("./name", validate_cb=_validate_name,
|
|
|
|
doc=_("Name for the storage object."))
|
|
|
|
permissions = XMLChildProperty(_StoragePermissions,
|
|
|
|
relative_xpath="./target",
|
|
|
|
is_single=True)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
class StoragePool(_StorageObject):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
Base class for building and installing libvirt storage pool xml
|
|
|
|
"""
|
2013-04-12 20:26:21 +08:00
|
|
|
# @group Types: TYPE_*
|
2013-03-18 05:06:52 +08:00
|
|
|
TYPE_DIR = "dir"
|
|
|
|
TYPE_FS = "fs"
|
|
|
|
TYPE_NETFS = "netfs"
|
|
|
|
TYPE_LOGICAL = "logical"
|
|
|
|
TYPE_DISK = "disk"
|
|
|
|
TYPE_ISCSI = "iscsi"
|
|
|
|
TYPE_SCSI = "scsi"
|
|
|
|
TYPE_MPATH = "mpath"
|
|
|
|
|
|
|
|
# Pool type descriptions for use in higher level programs
|
2013-09-20 08:18:12 +08:00
|
|
|
_descs = {}
|
|
|
|
_descs[TYPE_DIR] = _("Filesystem Directory")
|
|
|
|
_descs[TYPE_FS] = _("Pre-Formatted Block Device")
|
|
|
|
_descs[TYPE_NETFS] = _("Network Exported Directory")
|
|
|
|
_descs[TYPE_LOGICAL] = _("LVM Volume Group")
|
|
|
|
_descs[TYPE_DISK] = _("Physical Disk Device")
|
|
|
|
_descs[TYPE_ISCSI] = _("iSCSI Target")
|
|
|
|
_descs[TYPE_SCSI] = _("SCSI Host Adapter")
|
|
|
|
_descs[TYPE_MPATH] = _("Multipath Device Enumerator")
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_pool_types():
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
2013-09-20 08:18:12 +08:00
|
|
|
Return list of appropriate pool types
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
2013-09-20 08:18:12 +08:00
|
|
|
return StoragePool._descs.keys()
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
@staticmethod
|
2013-03-18 05:06:52 +08:00
|
|
|
def get_pool_type_desc(pool_type):
|
2013-09-20 08:18:12 +08:00
|
|
|
"""
|
|
|
|
Return human readable description for passed pool type
|
|
|
|
"""
|
|
|
|
return StoragePool._descs.get(pool_type, "%s pool" % pool_type)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
@staticmethod
|
|
|
|
def pool_list_from_sources(conn, pool_type, host=None):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
Return a list of StoragePool instances built from libvirt's pool
|
|
|
|
source enumeration (if supported).
|
|
|
|
|
|
|
|
@param conn: Libvirt connection
|
|
|
|
@param name: Name for the new pool
|
|
|
|
@param pool_type: Pool type string from I{Types}
|
|
|
|
@param host: Option host string to poll for sources
|
|
|
|
"""
|
2013-07-06 23:20:28 +08:00
|
|
|
if not conn.check_conn_support(conn.SUPPORT_CONN_FINDPOOLSOURCES):
|
2013-03-18 05:06:52 +08:00
|
|
|
return []
|
|
|
|
|
|
|
|
if host:
|
|
|
|
source_xml = "<source><host name='%s'/></source>" % host
|
|
|
|
else:
|
|
|
|
source_xml = "<source/>"
|
|
|
|
|
|
|
|
try:
|
|
|
|
xml = conn.findStoragePoolSources(pool_type, source_xml, 0)
|
|
|
|
except libvirt.libvirtError, e:
|
2013-07-06 23:20:28 +08:00
|
|
|
if util.is_error_nosupport(e):
|
2013-03-18 05:06:52 +08:00
|
|
|
return []
|
|
|
|
raise
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
def source_parser(node):
|
|
|
|
ret = []
|
|
|
|
|
|
|
|
child = node.children
|
|
|
|
while child:
|
|
|
|
if child.name == "source":
|
|
|
|
xml = "<pool>\n%s\n</pool>" % (
|
|
|
|
util.xml_indent(child.serialize(format=1), 2))
|
|
|
|
parseobj = StoragePool(conn, parsexml=xml)
|
|
|
|
parseobj.type = pool_type
|
|
|
|
|
|
|
|
obj = StoragePool(conn)
|
|
|
|
obj.type = pool_type
|
|
|
|
obj.source_path = parseobj.source_path
|
|
|
|
obj.host = parseobj.host
|
|
|
|
obj.source_name = parseobj.source_name
|
|
|
|
obj.format = parseobj.format
|
|
|
|
ret.append(obj)
|
|
|
|
child = child.next
|
|
|
|
return ret
|
|
|
|
|
|
|
|
return util.parse_node_helper(xml, "sources", source_parser)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def build_default_pool(conn):
|
|
|
|
"""
|
|
|
|
Helper to build the 'default' storage pool
|
|
|
|
"""
|
|
|
|
if not conn.check_conn_support(conn.SUPPORT_CONN_STORAGE):
|
|
|
|
return
|
|
|
|
|
|
|
|
pool = None
|
|
|
|
name = "default"
|
|
|
|
path = "/var/lib/libvirt/images"
|
|
|
|
if conn.is_session_uri():
|
|
|
|
path = os.path.expanduser("~/VirtualMachines")
|
|
|
|
|
|
|
|
try:
|
|
|
|
pool = conn.storagePoolLookupByName(name)
|
|
|
|
except libvirt.libvirtError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if pool:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
logging.debug("Attempting to build default pool with target '%s'",
|
|
|
|
path)
|
|
|
|
defpool = StoragePool(conn)
|
|
|
|
defpool.type = defpool.TYPE_DIR
|
|
|
|
defpool.name = name
|
|
|
|
defpool.target_path = path
|
|
|
|
newpool = defpool.install(build=True, create=True, autostart=True)
|
|
|
|
conn.clear_cache()
|
|
|
|
return newpool
|
|
|
|
except Exception, e:
|
|
|
|
raise RuntimeError(
|
|
|
|
_("Couldn't create default storage pool '%s': %s") %
|
|
|
|
(path, str(e)))
|
|
|
|
|
2013-09-23 05:34:53 +08:00
|
|
|
@staticmethod
|
|
|
|
def lookup_pool_by_path(conn, path):
|
|
|
|
"""
|
|
|
|
Return the first pool with matching matching target path.
|
|
|
|
return the first we find, active or inactive. This iterates over
|
|
|
|
all pools and dumps their xml, so it is NOT quick.
|
|
|
|
Favor running pools over inactive pools.
|
|
|
|
@returns: virStoragePool object if found, None otherwise
|
|
|
|
"""
|
|
|
|
if not conn.check_conn_support(conn.SUPPORT_CONN_STORAGE):
|
|
|
|
return None
|
|
|
|
|
|
|
|
def check_pool(pool, path):
|
|
|
|
xml = pool.get_xml(refresh_if_nec=False)
|
|
|
|
xml_path = StoragePool(conn, parsexml=xml).target_path
|
|
|
|
if xml_path is not None and os.path.abspath(xml_path) == path:
|
|
|
|
return pool
|
|
|
|
|
|
|
|
for pool in conn.fetch_all_pools():
|
|
|
|
p = check_pool(pool, path)
|
|
|
|
if p:
|
|
|
|
return p.get_backend()
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
_StorageObject.__init__(self, *args, **kwargs)
|
|
|
|
self._random_uuid = None
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
######################
|
|
|
|
# Validation helpers #
|
|
|
|
######################
|
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
def _check_name_collision(self, name):
|
|
|
|
pool = None
|
|
|
|
try:
|
|
|
|
pool = self.conn.storagePoolLookupByName(name)
|
|
|
|
except libvirt.libvirtError:
|
|
|
|
pass
|
|
|
|
if pool:
|
|
|
|
raise ValueError(_("Name '%s' already in use by another pool." %
|
|
|
|
name))
|
|
|
|
|
|
|
|
def _get_default_target_path(self):
|
2013-09-20 08:18:12 +08:00
|
|
|
if (self.type == self.TYPE_DIR or
|
|
|
|
self.type == self.TYPE_NETFS or
|
|
|
|
self.type == self.TYPE_FS):
|
|
|
|
return (DEFAULT_DIR_TARGET_BASE + self.name)
|
|
|
|
if self.type == self.TYPE_LOGICAL:
|
|
|
|
name = self.name
|
|
|
|
if self.source_name:
|
|
|
|
name = self.source_name
|
|
|
|
return DEFAULT_LVM_TARGET_BASE + name
|
|
|
|
if self.type == self.TYPE_DISK:
|
|
|
|
return DEFAULT_DEV_TARGET
|
|
|
|
if self.type == self.TYPE_ISCSI or self.type == self.TYPE_SCSI:
|
|
|
|
return DEFAULT_SCSI_TARGET
|
|
|
|
if self.type == self.TYPE_MPATH:
|
|
|
|
return DEFAULT_MPATH_TARGET
|
|
|
|
raise RuntimeError("No default target_path for type=%s" % self.type)
|
|
|
|
|
|
|
|
def _get_default_uuid(self):
|
|
|
|
if self._random_uuid is None:
|
|
|
|
self._random_uuid = util.generate_uuid(self.conn)
|
|
|
|
return self._random_uuid
|
|
|
|
|
|
|
|
def _source_path_xpath(self):
|
|
|
|
if self.type == self.TYPE_NETFS:
|
|
|
|
return "./source/dir/@path"
|
|
|
|
if self.type == self.TYPE_SCSI:
|
|
|
|
return "./source/adapter/@name"
|
|
|
|
return "./source/device/@path"
|
|
|
|
|
|
|
|
def _default_source_name(self):
|
|
|
|
if not self.supports_property("source_name"):
|
|
|
|
return None
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
# If a source name isn't explictly set, try to determine it from
|
|
|
|
# existing parameters
|
|
|
|
srcname = self.name
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
if ("target_path" in self._propstore and
|
|
|
|
self.target_path and
|
|
|
|
self.target_path.startswith(DEFAULT_LVM_TARGET_BASE)):
|
|
|
|
# If there is a target path, parse it for an expected VG
|
|
|
|
# location, and pull the name from there
|
|
|
|
vg = self.target_path[len(DEFAULT_LVM_TARGET_BASE):]
|
|
|
|
srcname = vg.split("/", 1)[0]
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
return srcname
|
|
|
|
|
|
|
|
def _default_format_cb(self):
|
|
|
|
if not self.supports_property("format"):
|
|
|
|
return None
|
|
|
|
return "auto"
|
|
|
|
|
|
|
|
|
|
|
|
##############
|
|
|
|
# Properties #
|
|
|
|
##############
|
|
|
|
|
|
|
|
_XML_ROOT_NAME = "pool"
|
|
|
|
_XML_PROP_ORDER = ["name", "type", "uuid",
|
|
|
|
"capacity", "allocation", "available",
|
|
|
|
"format", "host",
|
|
|
|
"source_path", "source_name", "target_path",
|
|
|
|
"permissions"]
|
|
|
|
|
|
|
|
type = XMLProperty("./@type",
|
|
|
|
doc=_("Storage device type the pool will represent."))
|
|
|
|
uuid = XMLProperty("./uuid",
|
|
|
|
validate_cb=lambda s, v: util.validate_uuid(v),
|
|
|
|
default_cb=_get_default_uuid)
|
|
|
|
|
|
|
|
capacity = XMLProperty("./capacity", is_int=True)
|
|
|
|
allocation = XMLProperty("./allocation", is_int=True)
|
|
|
|
available = XMLProperty("./available", is_int=True)
|
|
|
|
|
|
|
|
format = XMLProperty("./source/format/@type",
|
|
|
|
default_cb=_default_format_cb)
|
|
|
|
host = XMLProperty("./source/host/@name")
|
|
|
|
iqn = XMLProperty("./source/initiator/iqn/@name",
|
|
|
|
doc=_("iSCSI initiator qualified name"))
|
|
|
|
source_path = XMLProperty(name="source path",
|
|
|
|
make_getter_xpath_cb=_source_path_xpath,
|
|
|
|
make_setter_xpath_cb=_source_path_xpath)
|
|
|
|
source_name = XMLProperty("./source/name",
|
|
|
|
default_cb=_default_source_name,
|
|
|
|
doc=_("Name of the Volume Group"))
|
|
|
|
|
|
|
|
target_path = XMLProperty("./target/path",
|
|
|
|
default_cb=_get_default_target_path)
|
|
|
|
|
|
|
|
|
|
|
|
######################
|
|
|
|
# Public API helpers #
|
|
|
|
######################
|
|
|
|
|
|
|
|
def supports_property(self, propname):
|
|
|
|
users = {
|
|
|
|
"source_path": [self.TYPE_FS, self.TYPE_NETFS, self.TYPE_LOGICAL,
|
|
|
|
self.TYPE_DISK, self.TYPE_ISCSI, self.TYPE_SCSI],
|
|
|
|
"source_name": [self.TYPE_LOGICAL],
|
|
|
|
"host": [self.TYPE_NETFS, self.TYPE_ISCSI],
|
|
|
|
"format": [self.TYPE_FS, self.TYPE_NETFS, self.TYPE_DISK],
|
|
|
|
"iqn": [self.TYPE_ISCSI],
|
|
|
|
}
|
|
|
|
|
|
|
|
if users.get(propname):
|
|
|
|
return self.type in users[propname]
|
|
|
|
return hasattr(self, propname)
|
|
|
|
|
|
|
|
def list_formats(self):
|
|
|
|
if self.type == self.TYPE_FS:
|
|
|
|
return ["auto", "ext2", "ext3", "ext4", "ufs", "iso9660", "udf",
|
|
|
|
"gfs", "gfs2", "vfat", "hfs+", "xfs"]
|
|
|
|
if self.type == self.TYPE_NETFS:
|
|
|
|
return ["auto", "nfs", "glusterfs"]
|
|
|
|
if self.type == self.TYPE_DISK:
|
|
|
|
return ["auto", "bsd", "dos", "dvh", "gpt", "mac", "pc98", "sun"]
|
|
|
|
return []
|
|
|
|
|
|
|
|
def supports_volume_creation(self):
|
|
|
|
return self.type in [
|
|
|
|
StoragePool.TYPE_DIR, StoragePool.TYPE_FS,
|
|
|
|
StoragePool.TYPE_NETFS, StoragePool.TYPE_LOGICAL,
|
|
|
|
StoragePool.TYPE_DISK]
|
|
|
|
|
2013-09-23 05:34:53 +08:00
|
|
|
def get_vm_disk_type(self):
|
|
|
|
"""
|
|
|
|
Return the /disk/@type value if the pool source is used as
|
|
|
|
VirtualDisk path
|
|
|
|
"""
|
|
|
|
xpath = self._source_path_xpath()
|
|
|
|
if "/dir/" in xpath:
|
|
|
|
return "dir"
|
|
|
|
return "block"
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
|
|
|
|
##################
|
|
|
|
# Build routines #
|
|
|
|
##################
|
|
|
|
|
|
|
|
def validate(self):
|
|
|
|
if self.supports_property("host") and not self.host:
|
|
|
|
raise RuntimeError(_("Hostname is required"))
|
|
|
|
if (self.supports_property("source_path") and
|
|
|
|
not self.type == self.TYPE_LOGICAL and
|
|
|
|
not self.source_path):
|
|
|
|
raise RuntimeError(_("Source path is required"))
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
if (self.type == self.TYPE_DISK and self.format == "auto"):
|
|
|
|
# There is no explicit "auto" type for disk pools, but leaving out
|
|
|
|
# the format type seems to do the job for existing formatted disks
|
|
|
|
self.format = None
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
def install(self, meter=None, create=False, build=False, autostart=False):
|
|
|
|
"""
|
|
|
|
Install storage pool xml.
|
|
|
|
"""
|
2013-09-20 08:18:12 +08:00
|
|
|
if (self.type == self.TYPE_LOGICAL and
|
|
|
|
build and not self.source_path):
|
|
|
|
raise ValueError(_("Must explicitly specify source path if "
|
|
|
|
"building pool"))
|
|
|
|
if (self.type == self.TYPE_DISK and
|
|
|
|
build and self.format == "auto"):
|
|
|
|
raise ValueError(_("Must explicitly specify disk format if "
|
|
|
|
"formatting disk device."))
|
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
xml = self.get_xml_config()
|
|
|
|
logging.debug("Creating storage pool '%s' with xml:\n%s",
|
|
|
|
self.name, xml)
|
|
|
|
|
|
|
|
if not meter:
|
|
|
|
meter = urlgrabber.progress.BaseMeter()
|
|
|
|
|
|
|
|
try:
|
|
|
|
pool = self.conn.storagePoolDefineXML(xml, 0)
|
|
|
|
except Exception, e:
|
|
|
|
raise RuntimeError(_("Could not define storage pool: %s" % str(e)))
|
|
|
|
|
|
|
|
errmsg = None
|
|
|
|
if build:
|
|
|
|
try:
|
|
|
|
pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW)
|
|
|
|
except Exception, e:
|
|
|
|
errmsg = _("Could not build storage pool: %s" % str(e))
|
|
|
|
|
|
|
|
if create and not errmsg:
|
|
|
|
try:
|
|
|
|
pool.create(0)
|
|
|
|
except Exception, e:
|
|
|
|
errmsg = _("Could not start storage pool: %s" % str(e))
|
|
|
|
|
|
|
|
if autostart and not errmsg:
|
|
|
|
try:
|
|
|
|
pool.setAutostart(True)
|
|
|
|
except Exception, e:
|
|
|
|
errmsg = _("Could not set pool autostart flag: %s" % str(e))
|
|
|
|
|
|
|
|
if errmsg:
|
|
|
|
# Try and clean up the leftover pool
|
|
|
|
try:
|
|
|
|
pool.undefine()
|
|
|
|
except Exception, e:
|
|
|
|
logging.debug("Error cleaning up pool after failure: " +
|
|
|
|
"%s" % str(e))
|
|
|
|
raise RuntimeError(errmsg)
|
|
|
|
|
|
|
|
return pool
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
class StorageVolume(_StorageObject):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
Base class for building and installing libvirt storage volume xml
|
|
|
|
"""
|
2013-09-20 08:18:12 +08:00
|
|
|
@staticmethod
|
2013-03-18 05:06:52 +08:00
|
|
|
def find_free_name(name, pool_object=None, pool_name=None, conn=None,
|
|
|
|
suffix="", collidelist=None, start_num=0):
|
|
|
|
"""
|
|
|
|
Finds a name similar (or equal) to passed 'name' that is not in use
|
|
|
|
by another pool
|
|
|
|
|
|
|
|
This function scans the list of existing Volumes on the passed or
|
|
|
|
looked up pool object for a collision with the passed name. If the
|
|
|
|
name is in use, it append "-1" to the name and tries again, then "-2",
|
|
|
|
continuing to 100000 (which will hopefully never be reached.") If
|
|
|
|
suffix is specified, attach it to the (potentially incremented) name
|
|
|
|
before checking for collision.
|
|
|
|
|
|
|
|
Ex name="test", suffix=".img" -> name-3.img
|
|
|
|
|
|
|
|
@param collidelist: An extra list of names to check for collision
|
|
|
|
@type collidelist: C{list}
|
|
|
|
@returns: A free name
|
|
|
|
@rtype: C{str}
|
|
|
|
"""
|
|
|
|
collidelist = collidelist or []
|
|
|
|
pool_object = StorageVolume.lookup_pool_by_name(
|
|
|
|
pool_object=pool_object,
|
|
|
|
pool_name=pool_name,
|
|
|
|
conn=conn)
|
|
|
|
pool_object.refresh(0)
|
|
|
|
|
2013-04-11 22:27:02 +08:00
|
|
|
return util.generate_name(name, pool_object.storageVolLookupByName,
|
2013-03-18 05:06:52 +08:00
|
|
|
suffix, collidelist=collidelist,
|
|
|
|
start_num=start_num)
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
@staticmethod
|
2013-03-18 05:06:52 +08:00
|
|
|
def lookup_pool_by_name(pool_object=None, pool_name=None, conn=None):
|
|
|
|
"""
|
|
|
|
Returns pool object determined from passed parameters.
|
|
|
|
|
|
|
|
Largely a convenience function for the other static functions.
|
|
|
|
"""
|
|
|
|
if pool_object is None and pool_name is None:
|
|
|
|
raise ValueError(_("Must specify pool_object or pool_name"))
|
|
|
|
|
|
|
|
if pool_name is not None and pool_object is None:
|
|
|
|
if conn is None:
|
|
|
|
raise ValueError(_("'conn' must be specified with 'pool_name'"))
|
2013-07-06 23:20:28 +08:00
|
|
|
if not conn.check_conn_support(conn.SUPPORT_CONN_STORAGE):
|
2013-03-18 05:06:52 +08:00
|
|
|
raise ValueError(_("Connection does not support storage "
|
|
|
|
"management."))
|
|
|
|
try:
|
|
|
|
pool_object = conn.storagePoolLookupByName(pool_name)
|
|
|
|
except Exception, e:
|
2013-04-14 02:34:52 +08:00
|
|
|
raise ValueError(_("Couldn't find storage pool '%s': %s" %
|
2013-03-18 05:06:52 +08:00
|
|
|
(pool_name, str(e))))
|
|
|
|
|
|
|
|
if not isinstance(pool_object, libvirt.virStoragePool):
|
|
|
|
raise ValueError(_("pool_object must be a virStoragePool"))
|
|
|
|
|
|
|
|
return pool_object
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
TYPE_FILE = getattr(libvirt, "VIR_STORAGE_VOL_FILE", 0)
|
|
|
|
TYPE_BLOCK = getattr(libvirt, "VIR_STORAGE_VOL_BLOCK", 1)
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
_StorageObject.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
self._input_vol = None
|
|
|
|
self._pool = None
|
|
|
|
self._pool_type = None
|
|
|
|
|
|
|
|
# Indicate that the volume installation has finished. Used to
|
|
|
|
# definitively tell the storage progress thread to stop polling.
|
|
|
|
self._install_finished = True
|
|
|
|
|
|
|
|
|
|
|
|
######################
|
|
|
|
# Non XML properties #
|
|
|
|
######################
|
|
|
|
|
|
|
|
def _get_pool(self):
|
2013-03-18 05:06:52 +08:00
|
|
|
return self._pool
|
2013-09-20 08:18:12 +08:00
|
|
|
def _set_pool(self, newpool):
|
2013-03-18 05:06:52 +08:00
|
|
|
if newpool.info()[0] != libvirt.VIR_STORAGE_POOL_RUNNING:
|
|
|
|
raise ValueError(_("pool '%s' must be active." % newpool.name()))
|
|
|
|
self._pool = newpool
|
2013-09-20 08:18:12 +08:00
|
|
|
self._pool_type = StoragePool(self.conn,
|
|
|
|
parsexml=self._pool.XMLDesc(0)).type
|
|
|
|
pool = property(_get_pool, _set_pool)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
def _get_input_vol(self):
|
2013-03-18 05:06:52 +08:00
|
|
|
return self._input_vol
|
2013-09-20 08:18:12 +08:00
|
|
|
def _set_input_vol(self, vol):
|
2013-03-18 05:06:52 +08:00
|
|
|
if vol is None:
|
|
|
|
self._input_vol = None
|
|
|
|
return
|
|
|
|
|
|
|
|
if not isinstance(vol, libvirt.virStorageVol):
|
|
|
|
raise ValueError(_("input_vol must be a virStorageVol"))
|
2013-04-12 21:51:26 +08:00
|
|
|
|
2013-07-06 23:20:28 +08:00
|
|
|
if not self.conn.check_pool_support(self.conn,
|
|
|
|
self.conn.SUPPORT_STORAGE_CREATEVOLFROM):
|
2013-03-18 05:06:52 +08:00
|
|
|
raise ValueError(_("Creating storage from an existing volume is"
|
|
|
|
" not supported by this libvirt version."))
|
2013-09-20 08:18:12 +08:00
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
self._input_vol = vol
|
2013-09-20 08:18:12 +08:00
|
|
|
input_vol = property(_get_input_vol, _set_input_vol,
|
2013-03-18 05:06:52 +08:00
|
|
|
doc=_("virStorageVolume pointer to clone/use as "
|
|
|
|
"input."))
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
def sync_input_vol(self):
|
|
|
|
# Pull paramaters from input vol into this class
|
|
|
|
parsevol = StorageVolume(self.conn,
|
|
|
|
parsexml=self._input_vol.XMLDesc(0))
|
|
|
|
|
|
|
|
self.pool = self._input_vol.storagePoolLookupByVolume()
|
|
|
|
self.capacity = parsevol.capacity
|
|
|
|
self.allocation = parsevol.allocation
|
|
|
|
self.format = parsevol.format
|
|
|
|
|
|
|
|
|
|
|
|
##########################
|
|
|
|
# XML validation helpers #
|
|
|
|
##########################
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
def _check_name_collision(self, name):
|
|
|
|
vol = None
|
|
|
|
try:
|
|
|
|
vol = self.pool.storageVolLookupByName(name)
|
|
|
|
except libvirt.libvirtError:
|
|
|
|
pass
|
|
|
|
if vol:
|
|
|
|
raise ValueError(_("Name '%s' already in use by another volume." %
|
|
|
|
name))
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
def _validate_allocation(self, val):
|
|
|
|
ret = self.is_size_conflict(allocation=val)
|
|
|
|
if ret[0]:
|
|
|
|
raise ValueError(ret[1])
|
|
|
|
elif ret[1]:
|
|
|
|
logging.warn(ret[1])
|
|
|
|
return val
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
def _validate_capacity(self, val):
|
|
|
|
ret = self.is_size_conflict(capacity=val)
|
|
|
|
if ret[0]:
|
|
|
|
raise ValueError(ret[1])
|
|
|
|
elif ret[1]:
|
|
|
|
logging.warn(ret[1])
|
|
|
|
return val
|
|
|
|
|
|
|
|
def _default_format(self):
|
|
|
|
if self.file_type == self.TYPE_FILE:
|
|
|
|
return "raw"
|
|
|
|
return None
|
|
|
|
|
|
|
|
def _get_vol_type(self):
|
|
|
|
if (self._pool_type == StoragePool.TYPE_DISK or
|
|
|
|
self._pool_type == StoragePool.TYPE_LOGICAL):
|
|
|
|
return self.TYPE_BLOCK
|
|
|
|
return self.TYPE_FILE
|
|
|
|
file_type = property(_get_vol_type)
|
|
|
|
|
|
|
|
|
|
|
|
##################
|
|
|
|
# XML properties #
|
|
|
|
##################
|
|
|
|
|
|
|
|
_XML_ROOT_NAME = "volume"
|
|
|
|
_XML_PROP_ORDER = ["name", "capacity", "allocation", "format",
|
|
|
|
"target_path", "permissions"]
|
|
|
|
|
|
|
|
capacity = XMLProperty("./capacity", is_int=True,
|
|
|
|
validate_cb=_validate_capacity)
|
|
|
|
allocation = XMLProperty("./allocation", is_int=True,
|
|
|
|
validate_cb=_validate_allocation)
|
|
|
|
format = XMLProperty("./target/format/@type", default_cb=_default_format)
|
|
|
|
target_path = XMLProperty("./target/path")
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
######################
|
|
|
|
# Public API helpers #
|
|
|
|
######################
|
|
|
|
|
|
|
|
def supports_property(self, propname):
|
|
|
|
users = {
|
|
|
|
"format": [self.TYPE_FILE],
|
|
|
|
}
|
|
|
|
|
|
|
|
if users.get(propname):
|
|
|
|
return self.file_type in users[propname]
|
|
|
|
return hasattr(self, propname)
|
|
|
|
|
|
|
|
def list_formats(self):
|
|
|
|
if self.file_type == self.TYPE_FILE:
|
|
|
|
return ["raw", "bochs", "cloop", "cow", "dmg", "iso", "qcow",
|
|
|
|
"qcow2", "qed", "vmdk", "vpc"]
|
|
|
|
return []
|
|
|
|
|
|
|
|
def list_create_formats(self):
|
|
|
|
if self.file_type == self.TYPE_FILE:
|
|
|
|
return ["raw", "cow", "qcow", "qcow2", "qed", "vmdk", "vpc"]
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
##################
|
|
|
|
# Build routines #
|
|
|
|
##################
|
|
|
|
|
|
|
|
def validate(self):
|
|
|
|
if self._pool_type == StoragePool.TYPE_LOGICAL:
|
|
|
|
if self.allocation != self.capacity:
|
|
|
|
logging.warn(_("Sparse logical volumes are not supported, "
|
|
|
|
"setting allocation equal to capacity"))
|
|
|
|
self.allocation = self.capacity
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
def install(self, meter=None):
|
|
|
|
"""
|
|
|
|
Build and install storage volume from xml
|
|
|
|
"""
|
|
|
|
xml = self.get_xml_config()
|
|
|
|
logging.debug("Creating storage volume '%s' with xml:\n%s",
|
|
|
|
self.name, xml)
|
|
|
|
|
|
|
|
t = threading.Thread(target=self._progress_thread,
|
|
|
|
name="Checking storage allocation",
|
|
|
|
args=(meter,))
|
|
|
|
t.setDaemon(True)
|
|
|
|
|
|
|
|
if not meter:
|
|
|
|
meter = urlgrabber.progress.BaseMeter()
|
|
|
|
|
|
|
|
try:
|
2013-06-10 03:40:25 +08:00
|
|
|
self._install_finished = False
|
|
|
|
t.start()
|
|
|
|
meter.start(size=self.capacity,
|
|
|
|
text=_("Allocating '%s'") % self.name)
|
|
|
|
|
|
|
|
if self.input_vol:
|
|
|
|
vol = self.pool.createXMLFrom(xml, self.input_vol, 0)
|
|
|
|
else:
|
|
|
|
vol = self.pool.createXML(xml, 0)
|
|
|
|
|
2013-07-23 20:02:45 +08:00
|
|
|
self._install_finished = True
|
|
|
|
t.join()
|
2013-06-10 03:40:25 +08:00
|
|
|
meter.end(self.capacity)
|
|
|
|
logging.debug("Storage volume '%s' install complete.",
|
|
|
|
self.name)
|
|
|
|
return vol
|
|
|
|
except libvirt.libvirtError, e:
|
2013-07-06 23:20:28 +08:00
|
|
|
if util.is_error_nosupport(e):
|
2013-06-10 03:40:25 +08:00
|
|
|
raise RuntimeError("Libvirt version does not support "
|
|
|
|
"storage cloning.")
|
|
|
|
raise
|
|
|
|
except Exception, e:
|
|
|
|
raise RuntimeError("Couldn't create storage volume "
|
|
|
|
"'%s': '%s'" % (self.name, str(e)))
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
def _progress_thread(self, meter):
|
|
|
|
lookup_attempts = 10
|
|
|
|
vol = None
|
|
|
|
|
|
|
|
if not meter:
|
|
|
|
return
|
|
|
|
|
|
|
|
while lookup_attempts > 0:
|
|
|
|
try:
|
|
|
|
vol = self.pool.storageVolLookupByName(self.name)
|
|
|
|
break
|
|
|
|
except:
|
|
|
|
lookup_attempts -= 1
|
|
|
|
time.sleep(.2)
|
|
|
|
if self._install_finished:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
break
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
if vol is None:
|
2013-03-18 05:06:52 +08:00
|
|
|
logging.debug("Couldn't lookup storage volume in prog thread.")
|
|
|
|
return
|
|
|
|
|
|
|
|
while not self._install_finished:
|
|
|
|
ignore, ignore, alloc = vol.info()
|
|
|
|
meter.update(alloc)
|
2013-06-10 03:19:35 +08:00
|
|
|
time.sleep(1)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
2013-09-20 08:18:12 +08:00
|
|
|
def is_size_conflict(self, capacity=None, allocation=None):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
Report if requested size exceeds its pool's available amount
|
|
|
|
|
|
|
|
@returns: 2 element tuple:
|
|
|
|
1. True if collision is fatal, false otherwise
|
|
|
|
2. String message if some collision was encountered.
|
|
|
|
@rtype: 2 element C{tuple}: (C{bool}, C{str})
|
|
|
|
"""
|
2013-09-20 08:18:12 +08:00
|
|
|
if capacity is None:
|
|
|
|
capacity = self.capacity
|
|
|
|
if allocation is None:
|
|
|
|
allocation = self.allocation
|
|
|
|
|
|
|
|
if not self.pool:
|
|
|
|
return (False, "")
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
# pool info is [pool state, capacity, allocation, available]
|
2013-03-18 05:06:52 +08:00
|
|
|
avail = self.pool.info()[3]
|
2013-09-20 08:18:12 +08:00
|
|
|
if allocation > avail:
|
2013-03-18 05:06:52 +08:00
|
|
|
return (True, _("There is not enough free space on the storage "
|
|
|
|
"pool to create the volume. "
|
2013-04-14 02:34:52 +08:00
|
|
|
"(%d M requested allocation > %d M available)" %
|
2013-09-20 08:18:12 +08:00
|
|
|
((allocation / (1024 * 1024)),
|
2013-03-18 05:06:52 +08:00
|
|
|
(avail / (1024 * 1024)))))
|
2013-09-20 08:18:12 +08:00
|
|
|
elif capacity > avail:
|
2013-03-18 05:06:52 +08:00
|
|
|
return (False, _("The requested volume capacity will exceed the "
|
|
|
|
"available pool space when the volume is fully "
|
|
|
|
"allocated. "
|
2013-04-14 02:34:52 +08:00
|
|
|
"(%d M requested capacity > %d M available)" %
|
2013-09-20 08:18:12 +08:00
|
|
|
((capacity / (1024 * 1024)),
|
2013-03-18 05:06:52 +08:00
|
|
|
(avail / (1024 * 1024)))))
|
|
|
|
return (False, "")
|