From e61311fc8af234370ba4cbb7037e7b72619de0b0 Mon Sep 17 00:00:00 2001 From: Cole Robinson Date: Tue, 9 Dec 2014 16:05:02 -0500 Subject: [PATCH] storage: More support for network pools Including parsing multiple hosts, filling reasonably defaults, making sure the createpool wizard does the correct thing, etc. --- tests/storage-xml/pool-gluster.xml | 3 +- tests/storage-xml/pool-netfs-list0.xml | 6 +- tests/storage.py | 9 +- tests/xmlparse-xml/pool-dir-vol-out.xml | 2 +- tests/xmlparse-xml/pool-gluster-out.xml | 2 +- tests/xmlparse-xml/pool-gluster.xml | 8 -- tests/xmlparse-xml/pool-rbd-out.xml | 14 +++ tests/xmlparse-xml/pool-rbd.xml | 13 +++ tests/xmlparse.py | 30 ++++++- virtManager/createpool.py | 13 ++- virtinst/storage.py | 111 ++++++++++++++++-------- 11 files changed, 151 insertions(+), 60 deletions(-) delete mode 100644 tests/xmlparse-xml/pool-gluster.xml create mode 100644 tests/xmlparse-xml/pool-rbd-out.xml create mode 100644 tests/xmlparse-xml/pool-rbd.xml diff --git a/tests/storage-xml/pool-gluster.xml b/tests/storage-xml/pool-gluster.xml index 1fb127611..f21ba9d3b 100644 --- a/tests/storage-xml/pool-gluster.xml +++ b/tests/storage-xml/pool-gluster.xml @@ -3,6 +3,7 @@ 10310811-7115-1161-0111-410310811711 - pool-gluster + + gv0 diff --git a/tests/storage-xml/pool-netfs-list0.xml b/tests/storage-xml/pool-netfs-list0.xml index 7d4043966..7c10f2dbd 100644 --- a/tests/storage-xml/pool-netfs-list0.xml +++ b/tests/storage-xml/pool-netfs-list0.xml @@ -1,11 +1,11 @@ - pool-netfs-list0 - 11010111-6102-1154-5108-105115116481 - + + pool-netfs-list0 + 11010111-6102-1154-5108-105115116481 /var/lib/libvirt/images/pool-netfs-list0 diff --git a/tests/storage.py b/tests/storage.py index 60422ddb9..86b9a3f07 100644 --- a/tests/storage.py +++ b/tests/storage.py @@ -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)) diff --git a/tests/xmlparse-xml/pool-dir-vol-out.xml b/tests/xmlparse-xml/pool-dir-vol-out.xml index b23af50a8..35595ec6a 100644 --- a/tests/xmlparse-xml/pool-dir-vol-out.xml +++ b/tests/xmlparse-xml/pool-dir-vol-out.xml @@ -1,4 +1,4 @@ - + pool-dir-vol 2000 1000 diff --git a/tests/xmlparse-xml/pool-gluster-out.xml b/tests/xmlparse-xml/pool-gluster-out.xml index b38999a0b..68f6972d1 100644 --- a/tests/xmlparse-xml/pool-gluster-out.xml +++ b/tests/xmlparse-xml/pool-gluster-out.xml @@ -3,7 +3,7 @@ 10310811-7115-1161-0111-410310811711 - pool-gluster + gv0 diff --git a/tests/xmlparse-xml/pool-gluster.xml b/tests/xmlparse-xml/pool-gluster.xml deleted file mode 100644 index 1fb127611..000000000 --- a/tests/xmlparse-xml/pool-gluster.xml +++ /dev/null @@ -1,8 +0,0 @@ - - pool-gluster - 10310811-7115-1161-0111-410310811711 - - - pool-gluster - - diff --git a/tests/xmlparse-xml/pool-rbd-out.xml b/tests/xmlparse-xml/pool-rbd-out.xml new file mode 100644 index 000000000..2ed561543 --- /dev/null +++ b/tests/xmlparse-xml/pool-rbd-out.xml @@ -0,0 +1,14 @@ + + rbd-pool + 4bcd023e-990e-fcf6-d95c-52dd0cd938c8 + 47256127143936 + 5537792235090 + 35978000121856 + + + + + rbd + + + diff --git a/tests/xmlparse-xml/pool-rbd.xml b/tests/xmlparse-xml/pool-rbd.xml new file mode 100644 index 000000000..293f679ff --- /dev/null +++ b/tests/xmlparse-xml/pool-rbd.xml @@ -0,0 +1,13 @@ + + rbd-pool + 4bcd023e-990e-fcf6-d95c-52dd0cd938c8 + 47256127143936 + 5537792235090 + 35978000121856 + + + + + rbd + + diff --git a/tests/xmlparse.py b/tests/xmlparse.py index 307e0f51a..140fd5c21 100644 --- a/tests/xmlparse.py +++ b/tests/xmlparse.py @@ -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) diff --git a/virtManager/createpool.py b/virtManager/createpool.py index 8036a976c..6641ce98c 100644 --- a/virtManager/createpool.py +++ b/virtManager/createpool.py @@ -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: diff --git a/virtinst/storage.py b/virtinst/storage.py index 7ef58491b..f580cf295 100644 --- a/virtinst/storage.py +++ b/virtinst/storage.py @@ -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)))