mirror of
https://github.com/virt-manager/virt-manager.git
synced 2024-12-23 17:34:21 +03:00
storage: More support for network pools
Including parsing multiple hosts, filling reasonably defaults, making sure the createpool wizard does the correct thing, etc.
This commit is contained in:
parent
838d7f015c
commit
e61311fc8a
@ -3,6 +3,7 @@
|
||||
<uuid>10310811-7115-1161-0111-410310811711</uuid>
|
||||
<source>
|
||||
<host name="some.random.hostname"/>
|
||||
<name>pool-gluster</name>
|
||||
<dir path="/some/source/path"/>
|
||||
<name>gv0</name>
|
||||
</source>
|
||||
</pool>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<pool type="netfs">
|
||||
<name>pool-netfs-list0</name>
|
||||
<uuid>11010111-6102-1154-5108-105115116481</uuid>
|
||||
<source>
|
||||
<format type="nfs"/>
|
||||
<host name="example.com"/>
|
||||
<format type="nfs"/>
|
||||
<dir path="/testshare"/>
|
||||
</source>
|
||||
<name>pool-netfs-list0</name>
|
||||
<uuid>11010111-6102-1154-5108-105115116481</uuid>
|
||||
<target>
|
||||
<path>/var/lib/libvirt/images/pool-netfs-list0</path>
|
||||
</target>
|
||||
|
@ -69,8 +69,8 @@ def createPool(conn, ptype, poolname=None, fmt=None, target_path=None,
|
||||
pool_inst.type = ptype
|
||||
pool_inst.uuid = uuid
|
||||
|
||||
if pool_inst.supports_property("host"):
|
||||
pool_inst.host = "some.random.hostname"
|
||||
if pool_inst.supports_property("hosts"):
|
||||
pool_inst.add_host("some.random.hostname")
|
||||
if pool_inst.supports_property("source_path"):
|
||||
pool_inst.source_path = source_path or "/some/source/path"
|
||||
if pool_inst.supports_property("target_path"):
|
||||
@ -221,6 +221,11 @@ class TestStorage(unittest.TestCase):
|
||||
|
||||
createPool(self.conn, StoragePool.TYPE_GLUSTER, "pool-gluster")
|
||||
|
||||
|
||||
##############################
|
||||
# Tests for pool-sources API #
|
||||
##############################
|
||||
|
||||
def _enumerateCompare(self, name, pool_list):
|
||||
for pool in pool_list:
|
||||
pool.name = name + str(pool_list.index(pool))
|
||||
|
@ -1,4 +1,4 @@
|
||||
<volume>
|
||||
<volume type="file">
|
||||
<name>pool-dir-vol</name>
|
||||
<capacity>2000</capacity>
|
||||
<allocation>1000</allocation>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<uuid>10310811-7115-1161-0111-410310811711</uuid>
|
||||
<source>
|
||||
<host name="my.host"/>
|
||||
<name>pool-gluster</name>
|
||||
<dir path="/foo"/>
|
||||
<name>gv0</name>
|
||||
</source>
|
||||
</pool>
|
||||
|
@ -1,8 +0,0 @@
|
||||
<pool type="gluster">
|
||||
<name>pool-gluster</name>
|
||||
<uuid>10310811-7115-1161-0111-410310811711</uuid>
|
||||
<source>
|
||||
<host name="some.random.hostname"/>
|
||||
<name>pool-gluster</name>
|
||||
</source>
|
||||
</pool>
|
14
tests/xmlparse-xml/pool-rbd-out.xml
Normal file
14
tests/xmlparse-xml/pool-rbd-out.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<pool type="rbd">
|
||||
<name>rbd-pool</name>
|
||||
<uuid>4bcd023e-990e-fcf6-d95c-52dd0cd938c8</uuid>
|
||||
<capacity unit="bytes">47256127143936</capacity>
|
||||
<allocation unit="bytes">5537792235090</allocation>
|
||||
<available unit="bytes">35978000121856</available>
|
||||
<source>
|
||||
<host name="ceph-mon-1.example.com" port="1234"/>
|
||||
<host name="foo.bar" port="6789"/>
|
||||
<host name="ceph-mon-3.example.com" port="1000"/>
|
||||
<name>rbd</name>
|
||||
<host name="frobber" port="5555"/>
|
||||
</source>
|
||||
</pool>
|
13
tests/xmlparse-xml/pool-rbd.xml
Normal file
13
tests/xmlparse-xml/pool-rbd.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<pool type='rbd'>
|
||||
<name>rbd-pool</name>
|
||||
<uuid>4bcd023e-990e-fcf6-d95c-52dd0cd938c8</uuid>
|
||||
<capacity unit='bytes'>47256127143936</capacity>
|
||||
<allocation unit='bytes'>5537792235090</allocation>
|
||||
<available unit='bytes'>35978000121856</available>
|
||||
<source>
|
||||
<host name='ceph-mon-1.example.com' port='6789'/>
|
||||
<host name='ceph-mon-2.example.com' port='6789'/>
|
||||
<host name='ceph-mon-3.example.com' port='6789'/>
|
||||
<name>rbd</name>
|
||||
</source>
|
||||
</pool>
|
@ -1097,7 +1097,6 @@ class XMLParseTest(unittest.TestCase):
|
||||
check("capacity", 984373075968, 200000)
|
||||
check("allocation", 756681687040, 150000)
|
||||
check("available", 227691388928, 50000)
|
||||
check("source_dir", None, None)
|
||||
|
||||
check("format", "auto", "ext3")
|
||||
check("source_path", "/some/source/path", "/dev/foo/bar")
|
||||
@ -1114,8 +1113,9 @@ class XMLParseTest(unittest.TestCase):
|
||||
pool = virtinst.StoragePool(conn, parsexml=file(infile).read())
|
||||
|
||||
check = self._make_checker(pool)
|
||||
check("host", "some.random.hostname", "my.host")
|
||||
check("iqn", "foo.bar.baz.iqn", "my.iqn")
|
||||
check = self._make_checker(pool.hosts[0])
|
||||
check("name", "some.random.hostname", "my.host")
|
||||
|
||||
utils.diff_compare(pool.get_xml_config(), outfile)
|
||||
utils.test_create(conn, pool.get_xml_config(), "storagePoolDefineXML")
|
||||
@ -1131,8 +1131,29 @@ class XMLParseTest(unittest.TestCase):
|
||||
pool = virtinst.StoragePool(conn, parsexml=file(infile).read())
|
||||
|
||||
check = self._make_checker(pool)
|
||||
check("host", "some.random.hostname", "my.host")
|
||||
check("source_dir", None, "/foo")
|
||||
check("source_path", "/some/source/path", "/foo")
|
||||
check = self._make_checker(pool.hosts[0])
|
||||
check("name", "some.random.hostname", "my.host")
|
||||
|
||||
utils.diff_compare(pool.get_xml_config(), outfile)
|
||||
utils.test_create(conn, pool.get_xml_config(), "storagePoolDefineXML")
|
||||
|
||||
def testRBDPool(self):
|
||||
basename = "pool-rbd"
|
||||
infile = "tests/xmlparse-xml/%s.xml" % basename
|
||||
outfile = "tests/xmlparse-xml/%s-out.xml" % basename
|
||||
pool = virtinst.StoragePool(conn, parsexml=file(infile).read())
|
||||
|
||||
check = self._make_checker(pool.hosts[0])
|
||||
check("name", "ceph-mon-1.example.com")
|
||||
check("port", 6789, 1234)
|
||||
check = self._make_checker(pool.hosts[1])
|
||||
check("name", "ceph-mon-2.example.com", "foo.bar")
|
||||
check("port", 6789)
|
||||
check = self._make_checker(pool.hosts[2])
|
||||
check("name", "ceph-mon-3.example.com")
|
||||
check("port", 6789, 1000)
|
||||
pool.add_host("frobber", "5555")
|
||||
|
||||
utils.diff_compare(pool.get_xml_config(), outfile)
|
||||
utils.test_create(conn, pool.get_xml_config(), "storagePoolDefineXML")
|
||||
@ -1144,6 +1165,7 @@ class XMLParseTest(unittest.TestCase):
|
||||
vol = virtinst.StorageVolume(conn, parsexml=file(infile).read())
|
||||
|
||||
check = self._make_checker(vol)
|
||||
check("type", None, "file")
|
||||
check("key", None, "fookey")
|
||||
check("capacity", 10737418240, 2000)
|
||||
check("allocation", 5368709120, 1000)
|
||||
|
@ -253,18 +253,23 @@ class vmmCreatePool(vmmGObjectUI):
|
||||
|
||||
src = self._pool.supports_property("source_path")
|
||||
src_b = src and not self.conn.is_remote()
|
||||
src_name = self._pool.type == StoragePool.TYPE_GLUSTER
|
||||
tgt = self._pool.supports_property("target_path")
|
||||
tgt_b = tgt and not self.conn.is_remote()
|
||||
host = self._pool.supports_property("host")
|
||||
host = self._pool.supports_property("hosts")
|
||||
fmt = self._pool.supports_property("formats")
|
||||
iqn = self._pool.supports_property("iqn")
|
||||
builddef, buildsens = self.get_build_default()
|
||||
|
||||
# We don't show source_name for logical pools, since we use
|
||||
# pool-sources to avoid the need for it
|
||||
src_name = (self._pool.supports_property("source_name") and
|
||||
self._pool.type != self._pool.TYPE_LOGICAL)
|
||||
|
||||
# Source path browsing is meaningless for net pools
|
||||
if self._pool.type in [StoragePool.TYPE_NETFS,
|
||||
StoragePool.TYPE_ISCSI,
|
||||
StoragePool.TYPE_SCSI]:
|
||||
StoragePool.TYPE_SCSI,
|
||||
StoragePool.TYPE_GLUSTER]:
|
||||
src_b = False
|
||||
|
||||
show_row("pool-target", tgt)
|
||||
@ -502,7 +507,7 @@ class vmmCreatePool(vmmGObjectUI):
|
||||
try:
|
||||
self._pool.target_path = target
|
||||
if host:
|
||||
self._pool.host = host
|
||||
self._pool.add_host(host)
|
||||
if source:
|
||||
self._pool.source_path = source
|
||||
if fmt:
|
||||
|
@ -86,6 +86,14 @@ def _get_default_pool_path(conn):
|
||||
return path
|
||||
|
||||
|
||||
class _Host(XMLBuilder):
|
||||
_XML_PROP_ORDER = ["name", "port"]
|
||||
_XML_ROOT_NAME = "host"
|
||||
|
||||
name = XMLProperty("./@name")
|
||||
port = XMLProperty("./@port", is_int=True)
|
||||
|
||||
|
||||
class StoragePool(_StorageObject):
|
||||
"""
|
||||
Base class for building and installing libvirt storage pool xml
|
||||
@ -100,6 +108,8 @@ class StoragePool(_StorageObject):
|
||||
TYPE_SCSI = "scsi"
|
||||
TYPE_MPATH = "mpath"
|
||||
TYPE_GLUSTER = "gluster"
|
||||
TYPE_RBD = "rbd"
|
||||
TYPE_SHEEPDOG = "sheepdog"
|
||||
|
||||
# Pool type descriptions for use in higher level programs
|
||||
_descs = {}
|
||||
@ -112,6 +122,8 @@ class StoragePool(_StorageObject):
|
||||
_descs[TYPE_SCSI] = _("SCSI Host Adapter")
|
||||
_descs[TYPE_MPATH] = _("Multipath Device Enumerator")
|
||||
_descs[TYPE_GLUSTER] = _("Gluster Filesystem")
|
||||
_descs[TYPE_RBD] = _("RADOS Block Device/Ceph")
|
||||
_descs[TYPE_SHEEPDOG] = _("Sheepdog Filesystem")
|
||||
|
||||
@staticmethod
|
||||
def get_pool_types():
|
||||
@ -167,7 +179,9 @@ class StoragePool(_StorageObject):
|
||||
obj = StoragePool(conn)
|
||||
obj.type = pool_type
|
||||
obj.source_path = parseobj.source_path
|
||||
obj.host = parseobj.host
|
||||
for host in parseobj.hosts:
|
||||
parseobj.remove_host(host)
|
||||
obj.add_host_obj(host)
|
||||
obj.source_name = parseobj.source_name
|
||||
obj.format = parseobj.format
|
||||
ret.append(obj)
|
||||
@ -309,7 +323,8 @@ class StoragePool(_StorageObject):
|
||||
return self._random_uuid
|
||||
|
||||
def _make_source_xpath(self):
|
||||
if self.type == self.TYPE_NETFS:
|
||||
if (self.type == self.TYPE_NETFS or
|
||||
self.type == self.TYPE_GLUSTER):
|
||||
return "./source/dir/@path"
|
||||
if self.type == self.TYPE_SCSI:
|
||||
return "./source/adapter/@name"
|
||||
@ -319,11 +334,13 @@ class StoragePool(_StorageObject):
|
||||
if not self.supports_property("source_name"):
|
||||
return None
|
||||
|
||||
# If a source name isn't explicitly set, try to determine it from
|
||||
# existing parameters
|
||||
srcname = self.name
|
||||
|
||||
if ("target_path" in self._propstore and
|
||||
if self.type == StoragePool.TYPE_NETFS:
|
||||
srcname = self.name
|
||||
elif self.type == StoragePool.TYPE_RBD:
|
||||
srcname = "rbd"
|
||||
elif self.type == StoragePool.TYPE_GLUSTER:
|
||||
srcname = "gv0"
|
||||
elif ("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
|
||||
@ -346,9 +363,9 @@ class StoragePool(_StorageObject):
|
||||
_XML_ROOT_NAME = "pool"
|
||||
_XML_PROP_ORDER = ["name", "type", "uuid",
|
||||
"capacity", "allocation", "available",
|
||||
"format", "host",
|
||||
"format", "hosts",
|
||||
"source_path", "source_name", "target_path",
|
||||
"source_dir", "permissions"]
|
||||
"permissions"]
|
||||
|
||||
type = XMLProperty("./@type",
|
||||
doc=_("Storage device type the pool will represent."))
|
||||
@ -362,7 +379,6 @@ class StoragePool(_StorageObject):
|
||||
|
||||
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",
|
||||
@ -373,7 +389,17 @@ class StoragePool(_StorageObject):
|
||||
|
||||
target_path = XMLProperty("./target/path",
|
||||
default_cb=_get_default_target_path)
|
||||
source_dir = XMLProperty("./source/dir/@path")
|
||||
|
||||
def add_host_obj(self, obj):
|
||||
self._add_child(obj)
|
||||
def add_host(self, name, port=None):
|
||||
obj = _Host(self.conn)
|
||||
obj.name = name
|
||||
obj.port = port
|
||||
self._add_child(obj)
|
||||
def remove_host(self, obj):
|
||||
self._remove_child(obj)
|
||||
hosts = XMLChildProperty(_Host, relative_xpath="./source")
|
||||
|
||||
|
||||
######################
|
||||
@ -383,10 +409,12 @@ class StoragePool(_StorageObject):
|
||||
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, self.TYPE_GLUSTER],
|
||||
"source_dir" : [self.TYPE_GLUSTER, self.TYPE_NETFS],
|
||||
"host": [self.TYPE_NETFS, self.TYPE_ISCSI, self.TYPE_GLUSTER],
|
||||
self.TYPE_DISK, self.TYPE_ISCSI, self.TYPE_SCSI,
|
||||
self.TYPE_GLUSTER],
|
||||
"source_name": [self.TYPE_LOGICAL, self.TYPE_GLUSTER,
|
||||
self.TYPE_RBD, self.TYPE_SHEEPDOG],
|
||||
"hosts": [self.TYPE_NETFS, self.TYPE_ISCSI, self.TYPE_GLUSTER,
|
||||
self.TYPE_RBD, self.TYPE_SHEEPDOG],
|
||||
"format": [self.TYPE_FS, self.TYPE_NETFS, self.TYPE_DISK],
|
||||
"iqn": [self.TYPE_ISCSI],
|
||||
"target_path" : [self.TYPE_DIR, self.TYPE_FS, self.TYPE_NETFS,
|
||||
@ -412,7 +440,8 @@ class StoragePool(_StorageObject):
|
||||
return self.type in [
|
||||
StoragePool.TYPE_DIR, StoragePool.TYPE_FS,
|
||||
StoragePool.TYPE_NETFS, StoragePool.TYPE_LOGICAL,
|
||||
StoragePool.TYPE_DISK, StoragePool.TYPE_GLUSTER]
|
||||
StoragePool.TYPE_DISK,
|
||||
StoragePool.TYPE_RBD, StoragePool.TYPE_SHEEPDOG]
|
||||
|
||||
def get_vm_disk_type(self):
|
||||
"""
|
||||
@ -430,7 +459,7 @@ class StoragePool(_StorageObject):
|
||||
##################
|
||||
|
||||
def validate(self):
|
||||
if self.supports_property("host") and not self.host:
|
||||
if self.supports_property("host") and not self.hosts:
|
||||
raise RuntimeError(_("Hostname is required"))
|
||||
if (self.supports_property("source_path") and
|
||||
not self.type == self.TYPE_LOGICAL and
|
||||
@ -527,6 +556,9 @@ class StorageVolume(_StorageObject):
|
||||
|
||||
TYPE_FILE = getattr(libvirt, "VIR_STORAGE_VOL_FILE", 0)
|
||||
TYPE_BLOCK = getattr(libvirt, "VIR_STORAGE_VOL_BLOCK", 1)
|
||||
TYPE_DIR = getattr(libvirt, "VIR_STORAGE_VOL_DIR", 2)
|
||||
TYPE_NETWORK = getattr(libvirt, "VIR_STORAGE_VOL_DIR", 3)
|
||||
TYPE_NETDIR = getattr(libvirt, "VIR_STORAGE_VOL_DIR", 4)
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -534,7 +566,7 @@ class StorageVolume(_StorageObject):
|
||||
|
||||
self._input_vol = None
|
||||
self._pool = None
|
||||
self._pool_type = None
|
||||
self._pool_xml = None
|
||||
|
||||
# Indicate that the volume installation has finished. Used to
|
||||
# definitively tell the storage progress thread to stop polling.
|
||||
@ -551,8 +583,8 @@ class StorageVolume(_StorageObject):
|
||||
if newpool.info()[0] != libvirt.VIR_STORAGE_POOL_RUNNING:
|
||||
raise ValueError(_("pool '%s' must be active." % newpool.name()))
|
||||
self._pool = newpool
|
||||
self._pool_type = StoragePool(self.conn,
|
||||
parsexml=self._pool.XMLDesc(0)).type
|
||||
self._pool_xml = StoragePool(self.conn,
|
||||
parsexml=self._pool.XMLDesc(0))
|
||||
pool = property(_get_pool, _set_pool)
|
||||
|
||||
def _get_input_vol(self):
|
||||
@ -622,9 +654,15 @@ class StorageVolume(_StorageObject):
|
||||
return None
|
||||
|
||||
def _get_vol_type(self):
|
||||
if (self._pool_type == StoragePool.TYPE_DISK or
|
||||
self._pool_type == StoragePool.TYPE_LOGICAL):
|
||||
if self.type:
|
||||
return self.type
|
||||
if (self._pool_xml.type == StoragePool.TYPE_DISK or
|
||||
self._pool_xml.type == StoragePool.TYPE_LOGICAL):
|
||||
return self.TYPE_BLOCK
|
||||
if (self._pool_xml.type == StoragePool.TYPE_GLUSTER or
|
||||
self._pool_xml.type == StoragePool.TYPE_RBD or
|
||||
self._pool_xml.type == StoragePool.TYPE_SHEEPDOG):
|
||||
return self.TYPE_NETWORK
|
||||
return self.TYPE_FILE
|
||||
file_type = property(_get_vol_type)
|
||||
|
||||
@ -637,6 +675,7 @@ class StorageVolume(_StorageObject):
|
||||
_XML_PROP_ORDER = ["name", "key", "capacity", "allocation", "format",
|
||||
"target_path", "permissions"]
|
||||
|
||||
type = XMLProperty("./@type")
|
||||
key = XMLProperty("./key")
|
||||
capacity = XMLProperty("./capacity", is_int=True,
|
||||
validate_cb=_validate_capacity)
|
||||
@ -659,22 +698,26 @@ class StorageVolume(_StorageObject):
|
||||
# Public API helpers #
|
||||
######################
|
||||
|
||||
def supports_property(self, propname):
|
||||
users = {
|
||||
"format": [self.TYPE_FILE],
|
||||
}
|
||||
def _supports_format(self):
|
||||
if self.file_type == self.TYPE_FILE:
|
||||
return True
|
||||
if self._pool_xml.type == StoragePool.TYPE_GLUSTER:
|
||||
return True
|
||||
return False
|
||||
|
||||
if users.get(propname):
|
||||
return self.file_type in users[propname]
|
||||
|
||||
def supports_property(self, propname):
|
||||
if propname == "format":
|
||||
return self._supports_format()
|
||||
return hasattr(self, propname)
|
||||
|
||||
def list_formats(self):
|
||||
if self.file_type == self.TYPE_FILE:
|
||||
if self._supports_format():
|
||||
return self.ALL_FORMATS
|
||||
return []
|
||||
|
||||
def list_create_formats(self):
|
||||
if self.file_type == self.TYPE_FILE:
|
||||
if self._supports_format():
|
||||
return ["raw", "cow", "qcow", "qcow2", "qed", "vmdk", "vpc", "vdi"]
|
||||
return None
|
||||
|
||||
@ -684,7 +727,7 @@ class StorageVolume(_StorageObject):
|
||||
##################
|
||||
|
||||
def validate(self):
|
||||
if self._pool_type == StoragePool.TYPE_LOGICAL:
|
||||
if self._pool_xml.type == StoragePool.TYPE_LOGICAL:
|
||||
if self.allocation != self.capacity:
|
||||
logging.warn(_("Sparse logical volumes are not supported, "
|
||||
"setting allocation equal to capacity"))
|
||||
@ -734,12 +777,8 @@ class StorageVolume(_StorageObject):
|
||||
logging.debug("Storage volume '%s' install complete.",
|
||||
self.name)
|
||||
return vol
|
||||
except libvirt.libvirtError, e:
|
||||
if util.is_error_nosupport(e):
|
||||
raise RuntimeError("Libvirt version does not support "
|
||||
"storage cloning.")
|
||||
raise
|
||||
except Exception, e:
|
||||
logging.debug("Error creating storage volume", exc_info=True)
|
||||
raise RuntimeError("Couldn't create storage volume "
|
||||
"'%s': '%s'" % (self.name, str(e)))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user