device: disk: Move <source> XML handling to its own class

Makes DeviceDisk less complicated, helps with readability

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2020-11-11 09:41:47 -05:00
parent 9f930bd329
commit b3ff59c75c
4 changed files with 183 additions and 160 deletions

View File

@ -339,22 +339,24 @@ def testAlterDisk():
disk = _get_disk("vdb")
check = _make_checker(disk)
check("source_pool", "defaultPool", "anotherPool")
check("source_volume", "foobar", "newvol")
check("source.pool", "defaultPool", "anotherPool")
check("source.volume", "foobar", "newvol")
disk = _get_disk("vdc")
check = _make_checker(disk)
check("source_protocol", "rbd", "gluster")
check("source_name", "pool/image", "new-val/vol")
check("source_host_name", "mon1.example.org", "diff.example.org")
check("source_host_port", 6321, 1234)
check("source.protocol", "rbd", "gluster")
check("source.name", "pool/image", "new-val/vol")
hcheck = _make_checker(disk.source.hosts[0])
hcheck("name", "mon1.example.org", "diff.example.org")
hcheck("port", 6321, 1234)
check("path", "gluster://diff.example.org:1234/new-val/vol")
disk = _get_disk("vdd")
check = _make_checker(disk)
check("source_protocol", "nbd")
check("source_host_transport", "unix")
check("source_host_socket", "/var/run/nbdsock")
hcheck = _make_checker(disk.source.hosts[0])
check("source.protocol", "nbd")
hcheck("transport", "unix")
hcheck("socket", "/var/run/nbdsock")
check("path", "nbd+unix:///var/run/nbdsock")
_alter_compare(conn, guest.get_xml(), outfile)

View File

@ -3355,8 +3355,8 @@ class ParserDisk(VirtCLIParser):
###################
def host_find_inst_cb(self, *args, **kwargs):
cliarg = "listens" # host[0-9]*
list_propname = "hosts" # disk.hosts
cliarg = "hosts" # host[0-9]*
list_propname = "source.hosts" # disk.hosts
cb = self._make_find_inst_cb(cliarg, list_propname)
return cb(*args, **kwargs)
@ -3381,22 +3381,22 @@ class ParserDisk(VirtCLIParser):
cls.add_arg("cache", "driver_cache", cb=cls.noset_cb)
# More standard XML props
cls.add_arg("source.dir", "source_dir")
cls.add_arg("source.file", "source_file")
cls.add_arg("source.dev", "source_dev")
cls.add_arg("source.pool", "source_pool")
cls.add_arg("source.volume", "source_volume")
cls.add_arg("source.name", "source_name")
cls.add_arg("source.protocol", "source_protocol")
cls.add_arg("source.dir", "source.dir")
cls.add_arg("source.file", "source.file")
cls.add_arg("source.dev", "source.dev")
cls.add_arg("source.pool", "source.pool")
cls.add_arg("source.volume", "source.volume")
cls.add_arg("source.name", "source.name")
cls.add_arg("source.protocol", "source.protocol")
cls.add_arg("source.startupPolicy", "startup_policy")
# type=nvme source props
cls.add_arg("source.type", "source_type")
cls.add_arg("source.namespace", "source_namespace")
cls.add_arg("source.managed", "source_managed", is_onoff=True)
cls.add_arg("source.address.domain", "source_address.domain")
cls.add_arg("source.address.bus", "source_address.bus")
cls.add_arg("source.address.slot", "source_address.slot")
cls.add_arg("source.address.function", "source_address.function")
cls.add_arg("source.type", "source.type")
cls.add_arg("source.namespace", "source.namespace")
cls.add_arg("source.managed", "source.managed", is_onoff=True)
cls.add_arg("source.address.domain", "source.address.domain")
cls.add_arg("source.address.bus", "source.address.bus")
cls.add_arg("source.address.slot", "source.address.slot")
cls.add_arg("source.address.function", "source.address.function")
cls.add_arg("source.host[0-9]*.name", "name",
find_inst_cb=cls.host_find_inst_cb)

View File

@ -44,6 +44,136 @@ class _DiskSourceAddress(DeviceAddress):
pass
class _DiskSource(XMLBuilder):
"""
Class representing disk <source> block, and various helpers
that only operate on <source> contents
"""
_XML_PROP_ORDER = [
"file", "dev", "dir",
"volume", "pool", "protocol", "name", "hosts",
"type", "managed", "namespace", "address"]
XML_NAME = "source"
file = XMLProperty("./@file")
dev = XMLProperty("./@dev")
dir = XMLProperty("./@dir")
pool = XMLProperty("./@pool")
volume = XMLProperty("./@volume")
hosts = XMLChildProperty(_Host)
name = XMLProperty("./@name")
protocol = XMLProperty("./@protocol")
type = XMLProperty("./@type")
managed = XMLProperty("./@managed", is_yesno=True)
namespace = XMLProperty("./@namespace", is_int=True)
address = XMLChildProperty(_DiskSourceAddress, is_single=True)
def set_from_url(self, uri):
"""
For a passed in path URI like gluster:// or https://, split it
up and set the <disk> properties directly
"""
from ..uri import URI
uriobj = URI(uri)
if uriobj.scheme:
self.protocol = uriobj.scheme
if ((uriobj.hostname or uriobj.port or uriobj.transport) and
not self.hosts):
self.hosts.add_new()
if uriobj.transport:
self.hosts[0].transport = uriobj.transport
if uriobj.hostname:
self.hosts[0].name = uriobj.hostname
if uriobj.port:
self.hosts[0].port = uriobj.port
if uriobj.path:
if self.hosts and self.hosts[0].transport:
self.hosts[0].socket = uriobj.path
else:
self.name = uriobj.path
if self.name.startswith("/"):
self.name = self.name[1:]
def build_url_from_network(self):
"""
Build a URL from network contents of <source>
"""
host = _Host(self.conn)
if self.hosts:
host = self.hosts[0]
ret = self.protocol or "unknown"
if host.transport:
ret += "+%s" % host.transport
ret += "://"
if host.name:
ret += host.name
if host.port:
ret += ":" + str(host.port)
if self.name:
if not self.name.startswith("/"):
ret += "/"
ret += self.name
elif host.socket:
if not host.socket.startswith("/"):
ret += "/"
ret += host.socket
return ret
def clear_source(self):
"""
Unset all XML properties that describe the actual source media
"""
self.file = None
self.dev = None
self.dir = None
self.volume = None
self.pool = None
self.name = None
self.protocol = None
for h in self.hosts[:]:
self.remove_child(h)
def set_network_from_storage(self, volxml, poolxml):
"""
For the passed pool + vol object combo representing a network
volume, set the <source> elements directly
"""
is_iscsi_direct = poolxml.type == "iscsi-direct"
protocol = poolxml.type
if is_iscsi_direct:
protocol = "iscsi"
self.protocol = protocol
for idx, poolhost in enumerate(poolxml.hosts):
if len(self.hosts) < (idx + 1):
self.hosts.add_new()
self.hosts[idx].name = poolhost.name
self.hosts[idx].port = poolhost.port
path = ""
if is_iscsi_direct:
# Vol path is like this:
# ip-10.66.144.87:3260-iscsi-iqn.2017-12.com.virttest:emulated-iscsi-noauth.target2-lun-1
# Always seems to have -iscsi- embedded in it
if "-iscsi-iqn." in volxml.target_path:
path = volxml.target_path.split("-iscsi-", 1)[-1]
else:
if poolxml.source_name:
path += poolxml.source_name
if poolxml.source_path:
path += poolxml.source_path
if not path.endswith('/'):
path += "/"
path += volxml.name
self.name = path or None
class DeviceDisk(Device):
XML_NAME = "disk"
@ -299,12 +429,8 @@ class DeviceDisk(Device):
"driver_name", "driver_type",
"driver_cache", "driver_discard", "driver_detect_zeroes",
"driver_io", "error_policy",
"source_file", "source_dev", "source_dir",
"auth_username", "auth_secret_type", "auth_secret_uuid",
"source_volume", "source_pool", "source_protocol", "source_name",
"source_host_name", "source_host_port",
"source_host_transport", "source_host_socket",
"source_type", "source_managed", "source_namespace", "source_address",
"source",
"target", "bus",
]
@ -412,90 +538,16 @@ class DeviceDisk(Device):
# XML source media handling #
#############################
source_file = XMLProperty("./source/@file")
source_dev = XMLProperty("./source/@dev")
source_dir = XMLProperty("./source/@dir")
source_pool = XMLProperty("./source/@pool")
source_volume = XMLProperty("./source/@volume")
auth_username = XMLProperty("./auth/@username")
auth_secret_type = XMLProperty("./auth/secret/@type")
auth_secret_uuid = XMLProperty("./auth/secret/@uuid")
hosts = XMLChildProperty(_Host, relative_xpath="./source")
source_name = XMLProperty("./source/@name")
source_protocol = XMLProperty("./source/@protocol")
# Technically multiple host lines can be listed
source_host_name = XMLProperty("./source/host/@name")
source_host_port = XMLProperty("./source/host/@port", is_int=True)
source_host_transport = XMLProperty("./source/host/@transport")
source_host_socket = XMLProperty("./source/host/@socket")
source_type = XMLProperty("./source/@type")
source_managed = XMLProperty("./source/@managed", is_yesno=True)
source_namespace = XMLProperty("./source/@namespace", is_int=True)
source_address = XMLChildProperty(
_DiskSourceAddress, is_single=True, relative_xpath="./source")
def _set_source_network_from_url(self, uri):
from ..uri import URI
uriobj = URI(uri)
if uriobj.scheme:
self.source_protocol = uriobj.scheme
if uriobj.transport:
self.source_host_transport = uriobj.transport
if uriobj.hostname:
self.source_host_name = uriobj.hostname
if uriobj.port:
self.source_host_port = uriobj.port
if uriobj.path:
if self.source_host_transport:
self.source_host_socket = uriobj.path
else:
self.source_name = uriobj.path
if self.source_name.startswith("/"):
self.source_name = self.source_name[1:]
source = XMLChildProperty(_DiskSource, is_single=True)
def _set_source_network_from_storage(self, volxml, poolxml):
is_iscsi_direct = poolxml.type == "iscsi-direct"
protocol = poolxml.type
if is_iscsi_direct:
protocol = "iscsi"
self.source_protocol = protocol
self.type = "network"
if poolxml.auth_type:
self.auth_username = poolxml.auth_username
self.auth_secret_type = poolxml.auth_type
self.auth_secret_uuid = poolxml.auth_secret_uuid
if poolxml.hosts:
self.source_host_name = poolxml.hosts[0].name
self.source_host_port = poolxml.hosts[0].port
for host in poolxml.hosts:
obj = self.hosts.add_new()
obj.name = host.name
obj.port = host.port
path = ""
if is_iscsi_direct:
# Vol path is like this:
# ip-10.66.144.87:3260-iscsi-iqn.2017-12.com.virttest:emulated-iscsi-noauth.target2-lun-1
# Always seems to have -iscsi- embedded in it
if "-iscsi-iqn." in volxml.target_path:
path = volxml.target_path.split("-iscsi-", 1)[-1]
else:
if poolxml.source_name:
path += poolxml.source_name
if poolxml.source_path:
path += poolxml.source_path
if not path.endswith('/'):
path += "/"
path += volxml.name
self.source_name = path or None
self.type = "network"
self.source.set_network_from_storage(volxml, poolxml)
def _set_network_source_from_backend(self):
if (self._storage_backend.get_vol_object() or
@ -504,60 +556,26 @@ class DeviceDisk(Device):
poolxml = self._storage_backend.get_parent_pool_xml()
self._set_source_network_from_storage(volxml, poolxml)
elif self._storage_backend.get_path():
self._set_source_network_from_url(self._storage_backend.get_path())
def _build_url_from_network_source(self):
ret = self.source_protocol or "unknown"
if self.source_host_transport:
ret += "+%s" % self.source_host_transport
ret += "://"
if self.source_host_name:
ret += self.source_host_name
if self.source_host_port:
ret += ":" + str(self.source_host_port)
if self.source_name:
if not self.source_name.startswith("/"):
ret += "/"
ret += self.source_name
elif self.source_host_socket:
if not self.source_host_socket.startswith("/"):
ret += "/"
ret += self.source_host_socket
return ret
self.source.set_from_url(self._storage_backend.get_path())
def _get_default_type(self):
if self.source_pool or self.source_volume:
if self.source.pool or self.source.volume:
return DeviceDisk.TYPE_VOLUME
if not self._storage_backend.is_stub():
return self._storage_backend.get_dev_type()
if self.source_protocol:
if self.source.protocol:
return DeviceDisk.TYPE_NETWORK
return self.TYPE_FILE
def _clear_source_xml(self):
"""
Unset all XML properties that describe the actual source media
"""
self.source_file = None
self.source_dev = None
self.source_dir = None
self.source_volume = None
self.source_pool = None
self.source_name = None
self.source_protocol = None
self.source_host_name = None
self.source_host_port = None
self.source_host_transport = None
self.source_host_socket = None
def _disk_type_to_object_prop_name(self):
disk_type = self.type
if disk_type == DeviceDisk.TYPE_BLOCK:
return "source_dev"
return "dev"
elif disk_type == DeviceDisk.TYPE_DIR:
return "source_dir"
return "dir"
elif disk_type == DeviceDisk.TYPE_FILE:
return "source_file"
return "file"
return None
@ -565,15 +583,15 @@ class DeviceDisk(Device):
# they don't have any special properties aside from needing to match
# 'type' value with the source property used.
def _get_xmlpath(self):
if self.source_file:
return self.source_file
if self.source_dev:
return self.source_dev
if self.source_dir:
return self.source_dir
if self.source.file:
return self.source.file
if self.source.dev:
return self.source.dev
if self.source.dir:
return self.source.dir
return None
def _set_xmlpath(self, val):
self._clear_source_xml()
self.source.clear_source()
if self._storage_backend.get_dev_type() == "network":
self._set_network_source_from_backend()
@ -582,7 +600,7 @@ class DeviceDisk(Device):
propname = self._disk_type_to_object_prop_name()
if not propname:
return
return setattr(self, propname, val)
return setattr(self.source, propname, val)
##################
@ -611,6 +629,9 @@ class DeviceDisk(Device):
driver_name = XMLProperty("./driver/@name")
driver_type = XMLProperty("./driver/@type")
auth_username = XMLProperty("./auth/@username")
auth_secret_type = XMLProperty("./auth/secret/@type")
auth_secret_uuid = XMLProperty("./auth/secret/@uuid")
snapshot_policy = XMLProperty("./@snapshot")
@ -671,18 +692,18 @@ class DeviceDisk(Device):
if self.type == DeviceDisk.TYPE_NETWORK:
# Fill in a completed URL for virt-manager UI, path comparison, etc
path = self._build_url_from_network_source()
path = self.source.build_url_from_network()
if typ == DeviceDisk.TYPE_VOLUME:
try:
parent_pool = self.conn.storagePoolLookupByName(
self.source_pool)
self.source.pool)
vol_object = parent_pool.storageVolLookupByName(
self.source_volume)
self.source.volume)
except Exception as e:
self._source_volume_err = str(e)
log.debug("Error fetching source pool=%s vol=%s",
self.source_pool, self.source_volume, exc_info=True)
self.source.pool, self.source.volume, exc_info=True)
if vol_object is None and path is None:
path = self._get_xmlpath()

View File

@ -187,7 +187,7 @@ class Installer(object):
if self.conn.in_testsuite():
# Hack to set just the XML path differently for the test suite.
# Setting this via regular 'path' will error that it doesn't exist
dev.source_file = _make_testsuite_path(location)
dev.source.file = _make_testsuite_path(location)
def _remove_unattended_install_cdrom_device(self, guest):
dummy = guest