mirror of
https://github.com/virt-manager/virt-manager.git
synced 2024-12-22 13:34:07 +03:00
urldetect: Drop GenericDistro, non-treeinfo redhat bits
non-treeinfo redhat only applies to pre RHEL5.4 and very old Fedora. It's not worth it anymore to slow down all URL lookups and maintain code complexity to handle such long out of date distros. GenericDistro doesn't actually apply to any public trees that I can find, except for some with TreeInfo. So turn it into GenericTreeinfoDistro. If random URL trees want to work with virt-install, add a treeinfo file
This commit is contained in:
parent
a8522032eb
commit
5aedc0aff7
@ -8,7 +8,7 @@
|
|||||||
<type arch="x86_64">hvm</type>
|
<type arch="x86_64">hvm</type>
|
||||||
<kernel>/tmp/virtinst-vmlinuz.</kernel>
|
<kernel>/tmp/virtinst-vmlinuz.</kernel>
|
||||||
<initrd>/tmp/virtinst-initrd.img.</initrd>
|
<initrd>/tmp/virtinst-initrd.img.</initrd>
|
||||||
<cmdline>console=ttyS0 method=tests/cli-test-xml/faketree</cmdline>
|
<cmdline>console=ttyS0 method=tests/cli-test-xml/fakefedoratree</cmdline>
|
||||||
</os>
|
</os>
|
||||||
<features>
|
<features>
|
||||||
<acpi/>
|
<acpi/>
|
||||||
|
@ -1 +1 @@
|
|||||||
None
|
fedora17
|
@ -8,7 +8,7 @@
|
|||||||
<type arch="x86_64" machine="xenpv">xen</type>
|
<type arch="x86_64" machine="xenpv">xen</type>
|
||||||
<kernel>/tmp/virtinst-vmlinuz.</kernel>
|
<kernel>/tmp/virtinst-vmlinuz.</kernel>
|
||||||
<initrd>/tmp/virtinst-initrd.img.</initrd>
|
<initrd>/tmp/virtinst-initrd.img.</initrd>
|
||||||
<cmdline>method=tests/cli-test-xml/faketree</cmdline>
|
<cmdline>method=tests/cli-test-xml/fakefedoratree</cmdline>
|
||||||
</os>
|
</os>
|
||||||
<on_reboot>destroy</on_reboot>
|
<on_reboot>destroy</on_reboot>
|
||||||
<devices>
|
<devices>
|
||||||
@ -22,6 +22,7 @@
|
|||||||
<mac address="00:11:22:33:44:55"/>
|
<mac address="00:11:22:33:44:55"/>
|
||||||
</interface>
|
</interface>
|
||||||
<console type="pty"/>
|
<console type="pty"/>
|
||||||
|
<input type="tablet" bus="usb"/>
|
||||||
<graphics type="vnc" port="-1" keymap="en-us"/>
|
<graphics type="vnc" port="-1" keymap="en-us"/>
|
||||||
</devices>
|
</devices>
|
||||||
</domain>
|
</domain>
|
||||||
@ -43,6 +44,7 @@
|
|||||||
<mac address="00:11:22:33:44:55"/>
|
<mac address="00:11:22:33:44:55"/>
|
||||||
</interface>
|
</interface>
|
||||||
<console type="pty"/>
|
<console type="pty"/>
|
||||||
|
<input type="tablet" bus="usb"/>
|
||||||
<graphics type="vnc" port="-1" keymap="en-us"/>
|
<graphics type="vnc" port="-1" keymap="en-us"/>
|
||||||
</devices>
|
</devices>
|
||||||
</domain>
|
</domain>
|
||||||
|
@ -1 +0,0 @@
|
|||||||
testiso
|
|
@ -1 +0,0 @@
|
|||||||
testinitrd
|
|
@ -1 +0,0 @@
|
|||||||
testvmlinuz
|
|
@ -28,7 +28,7 @@ os.environ["DISPLAY"] = ":3.4"
|
|||||||
# Location
|
# Location
|
||||||
image_prefix = "/tmp/__virtinst_cli_"
|
image_prefix = "/tmp/__virtinst_cli_"
|
||||||
xmldir = "tests/cli-test-xml"
|
xmldir = "tests/cli-test-xml"
|
||||||
treedir = "%s/faketree" % xmldir
|
treedir = "%s/fakefedoratree" % xmldir
|
||||||
fakeiso = "%s/fakefedora.iso" % xmldir
|
fakeiso = "%s/fakefedora.iso" % xmldir
|
||||||
vcdir = "%s/virtconv" % xmldir
|
vcdir = "%s/virtconv" % xmldir
|
||||||
compare_xmldir = "%s/compare" % xmldir
|
compare_xmldir = "%s/compare" % xmldir
|
||||||
|
@ -40,10 +40,6 @@ testbootiso = 1
|
|||||||
# CentOS #
|
# CentOS #
|
||||||
##########
|
##########
|
||||||
|
|
||||||
# Final centos4 trees. No distro detection, doesn't work here
|
|
||||||
[centos-4.9]
|
|
||||||
url = http://vault.centos.org/4.9/os/x86_64
|
|
||||||
|
|
||||||
# Final centos5 trees
|
# Final centos5 trees
|
||||||
[centos-5.11]
|
[centos-5.11]
|
||||||
url = http://vault.centos.org/5.11/os/x86_64
|
url = http://vault.centos.org/5.11/os/x86_64
|
||||||
|
@ -22,7 +22,7 @@ from virtinst.urldetect import ALTLinuxDistro
|
|||||||
from virtinst.urldetect import CentOSDistro
|
from virtinst.urldetect import CentOSDistro
|
||||||
from virtinst.urldetect import DebianDistro
|
from virtinst.urldetect import DebianDistro
|
||||||
from virtinst.urldetect import FedoraDistro
|
from virtinst.urldetect import FedoraDistro
|
||||||
from virtinst.urldetect import GenericDistro
|
from virtinst.urldetect import GenericTreeinfoDistro
|
||||||
from virtinst.urldetect import MandrivaDistro
|
from virtinst.urldetect import MandrivaDistro
|
||||||
from virtinst.urldetect import RHELDistro
|
from virtinst.urldetect import RHELDistro
|
||||||
from virtinst.urldetect import SLDistro
|
from virtinst.urldetect import SLDistro
|
||||||
@ -73,7 +73,7 @@ class _URLTestData(object):
|
|||||||
if "altlinux" in name:
|
if "altlinux" in name:
|
||||||
return ALTLinuxDistro
|
return ALTLinuxDistro
|
||||||
if "generic" in name:
|
if "generic" in name:
|
||||||
return GenericDistro
|
return GenericTreeinfoDistro
|
||||||
raise RuntimeError("name=%s didn't map to any distro class. Extend "
|
raise RuntimeError("name=%s didn't map to any distro class. Extend "
|
||||||
"_distroclass_for_name" % name)
|
"_distroclass_for_name" % name)
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ def _distroFromSUSEContent(fetcher, arch, vmtype):
|
|||||||
sle_version = sle_version + '.' + d[1].strip().rsplit(' ')[5][2]
|
sle_version = sle_version + '.' + d[1].strip().rsplit(' ')[5][2]
|
||||||
return ['VERSION', sle_version]
|
return ['VERSION', sle_version]
|
||||||
|
|
||||||
dclass = GenericDistro
|
dclass = OpensuseDistro
|
||||||
if distribution:
|
if distribution:
|
||||||
if re.match(".*SUSE Linux Enterprise Server*", distribution[1]) or \
|
if re.match(".*SUSE Linux Enterprise Server*", distribution[1]) or \
|
||||||
re.match(".*SUSE SLES*", distribution[1]):
|
re.match(".*SUSE SLES*", distribution[1]):
|
||||||
@ -146,8 +146,7 @@ def _distroFromSUSEContent(fetcher, arch, vmtype):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
ob = dclass(fetcher, tree_arch or arch, vmtype)
|
ob = dclass(fetcher, tree_arch or arch, vmtype)
|
||||||
if dclass != GenericDistro:
|
ob.version_from_content = distro_version
|
||||||
ob.version_from_content = distro_version
|
|
||||||
|
|
||||||
# Explictly call this, so we populate os_type/variant info
|
# Explictly call this, so we populate os_type/variant info
|
||||||
ob.isValidStore()
|
ob.isValidStore()
|
||||||
@ -352,113 +351,34 @@ class Distro(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
class GenericDistro(Distro):
|
class GenericTreeinfoDistro(Distro):
|
||||||
"""
|
name = "Generic Treeinfo"
|
||||||
Generic distro store. Check well known paths for kernel locations
|
|
||||||
as a last resort if we can't recognize any actual distro
|
|
||||||
"""
|
|
||||||
name = "Generic"
|
|
||||||
uses_treeinfo = True
|
uses_treeinfo = True
|
||||||
|
urldistro = None
|
||||||
_xen_paths = [("images/xen/vmlinuz",
|
treeinfo_version = None
|
||||||
"images/xen/initrd.img"), # Fedora
|
|
||||||
]
|
|
||||||
_hvm_paths = [("images/pxeboot/vmlinuz",
|
|
||||||
"images/pxeboot/initrd.img"), # Fedora
|
|
||||||
("ppc/ppc64/vmlinuz",
|
|
||||||
"ppc/ppc64/initrd.img"), # CenOS 7 ppc64le
|
|
||||||
]
|
|
||||||
_iso_paths = ["images/boot.iso", # RH/Fedora
|
|
||||||
"boot/boot.iso", # Suse
|
|
||||||
"current/images/netboot/mini.iso", # Debian
|
|
||||||
"install/images/boot.iso", # Mandriva
|
|
||||||
]
|
|
||||||
|
|
||||||
# Holds values to use when actually pulling down media
|
|
||||||
_valid_kernel_path = None
|
|
||||||
_valid_iso_path = None
|
|
||||||
|
|
||||||
def isValidStore(self):
|
def isValidStore(self):
|
||||||
if self.treeinfo:
|
return bool(self.treeinfo)
|
||||||
# Use treeinfo to pull down media paths
|
|
||||||
if self.type == "xen":
|
|
||||||
typ = "xen"
|
|
||||||
else:
|
|
||||||
typ = self.treeinfo.get("general", "arch")
|
|
||||||
|
|
||||||
kernelSection = "images-%s" % typ
|
def _hasTreeinfoFamily(self, famregex):
|
||||||
isoSection = "images-%s" % self.treeinfo.get("general", "arch")
|
if not self.treeinfo:
|
||||||
|
return False
|
||||||
|
|
||||||
if self.treeinfo.has_section(kernelSection):
|
treeinfo_family = self.treeinfo.get("general", "family")
|
||||||
try:
|
if self.treeinfo.has_option("general", "version"):
|
||||||
self._valid_kernel_path = (
|
self.treeinfo_version = self.treeinfo.get("general", "version")
|
||||||
self._getTreeinfoMedia("kernel"),
|
|
||||||
self._getTreeinfoMedia("initrd"))
|
|
||||||
except (configparser.NoSectionError,
|
|
||||||
configparser.NoOptionError) as e:
|
|
||||||
logging.debug(e)
|
|
||||||
|
|
||||||
if self.treeinfo.has_section(isoSection):
|
return bool(re.match(famregex, treeinfo_family))
|
||||||
try:
|
|
||||||
self._valid_iso_path = self.treeinfo.get(isoSection,
|
|
||||||
"boot.iso")
|
|
||||||
except configparser.NoOptionError as e:
|
|
||||||
logging.debug(e)
|
|
||||||
|
|
||||||
if self.type == "xen":
|
|
||||||
kern_list = self._xen_paths
|
|
||||||
else:
|
|
||||||
kern_list = self._hvm_paths
|
|
||||||
|
|
||||||
# If validated media paths weren't found (no treeinfo), check against
|
|
||||||
# list of media location paths.
|
|
||||||
for kern, init in kern_list:
|
|
||||||
if (self._valid_kernel_path is None and
|
|
||||||
self.fetcher.hasFile(kern) and
|
|
||||||
self.fetcher.hasFile(init)):
|
|
||||||
self._valid_kernel_path = (kern, init)
|
|
||||||
break
|
|
||||||
|
|
||||||
for iso in self._iso_paths:
|
|
||||||
if (self._valid_iso_path is None and
|
|
||||||
self.fetcher.hasFile(iso)):
|
|
||||||
self._valid_iso_path = iso
|
|
||||||
break
|
|
||||||
|
|
||||||
if self._valid_kernel_path or self._valid_iso_path:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def acquireKernel(self, guest):
|
|
||||||
if self._valid_kernel_path is None:
|
|
||||||
raise ValueError(_("Could not find a kernel path for virt type "
|
|
||||||
"'%s'" % self.type))
|
|
||||||
|
|
||||||
return self._kernelFetchHelper(guest,
|
|
||||||
self._valid_kernel_path[0],
|
|
||||||
self._valid_kernel_path[1])
|
|
||||||
|
|
||||||
def acquireBootDisk(self, guest):
|
|
||||||
if self._valid_iso_path is None:
|
|
||||||
raise ValueError(_("Could not find a boot iso path for this tree."))
|
|
||||||
|
|
||||||
return self.fetcher.acquireFile(self._valid_iso_path)
|
|
||||||
|
|
||||||
|
|
||||||
class RedHatDistro(Distro):
|
class RedHatDistro(GenericTreeinfoDistro):
|
||||||
"""
|
"""
|
||||||
Base image store for any Red Hat related distros which have
|
Base image store for any Red Hat related distros which have
|
||||||
a common layout
|
a common layout
|
||||||
"""
|
"""
|
||||||
uses_treeinfo = True
|
name = None
|
||||||
_version_number = None
|
_version_number = None
|
||||||
|
|
||||||
_boot_iso_paths = ["images/boot.iso"]
|
|
||||||
_hvm_kernel_paths = [("images/pxeboot/vmlinuz",
|
|
||||||
"images/pxeboot/initrd.img")]
|
|
||||||
_xen_kernel_paths = [("images/xen/vmlinuz",
|
|
||||||
"images/xen/initrd.img")]
|
|
||||||
|
|
||||||
def isValidStore(self):
|
def isValidStore(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -475,19 +395,8 @@ class FedoraDistro(RedHatDistro):
|
|||||||
name = "Fedora"
|
name = "Fedora"
|
||||||
urldistro = "fedora"
|
urldistro = "fedora"
|
||||||
|
|
||||||
def isValidStore(self):
|
def _parse_fedora_version(self):
|
||||||
if not self.treeinfo:
|
ver = self.treeinfo_version
|
||||||
return self.fetcher.hasFile("Fedora")
|
|
||||||
|
|
||||||
if not re.match(".*Fedora.*", self.treeinfo.get("general", "family")):
|
|
||||||
return False
|
|
||||||
|
|
||||||
ver = self.treeinfo.get("general", "version")
|
|
||||||
if not ver:
|
|
||||||
logging.debug("No version found in .treeinfo")
|
|
||||||
return False
|
|
||||||
logging.debug("Found treeinfo version=%s", ver)
|
|
||||||
|
|
||||||
latest_variant = OSDB.latest_fedora_version()
|
latest_variant = OSDB.latest_fedora_version()
|
||||||
if re.match("fedora[0-9]+", latest_variant):
|
if re.match("fedora[0-9]+", latest_variant):
|
||||||
latest_vernum = int(latest_variant[6:])
|
latest_vernum = int(latest_variant[6:])
|
||||||
@ -498,9 +407,7 @@ class FedoraDistro(RedHatDistro):
|
|||||||
|
|
||||||
# rawhide trees changed to use version=Rawhide in Apr 2016
|
# rawhide trees changed to use version=Rawhide in Apr 2016
|
||||||
if ver in ["development", "rawhide", "Rawhide"]:
|
if ver in ["development", "rawhide", "Rawhide"]:
|
||||||
self._version_number = latest_vernum
|
return latest_vernum, latest_variant
|
||||||
self.os_variant = latest_variant
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Dev versions can be like '23_Alpha'
|
# Dev versions can be like '23_Alpha'
|
||||||
if "_" in ver:
|
if "_" in ver:
|
||||||
@ -516,11 +423,23 @@ class FedoraDistro(RedHatDistro):
|
|||||||
vernum = latest_vernum
|
vernum = latest_vernum
|
||||||
|
|
||||||
if vernum > latest_vernum:
|
if vernum > latest_vernum:
|
||||||
self.os_variant = latest_variant
|
os_variant = latest_variant
|
||||||
else:
|
else:
|
||||||
self.os_variant = "fedora" + str(vernum)
|
os_variant = "fedora" + str(vernum)
|
||||||
|
|
||||||
self._version_number = vernum
|
return vernum, os_variant
|
||||||
|
|
||||||
|
def isValidStore(self):
|
||||||
|
famregex = ".*Fedora.*"
|
||||||
|
if not self._hasTreeinfoFamily(famregex):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.treeinfo_version:
|
||||||
|
logging.debug("No version found in .treeinfo")
|
||||||
|
return False
|
||||||
|
logging.debug("Found treeinfo version=%s", self.treeinfo_version)
|
||||||
|
|
||||||
|
self._version_number, self.os_variant = self._parse_fedora_version()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -529,29 +448,6 @@ class RHELDistro(RedHatDistro):
|
|||||||
name = "Red Hat Enterprise Linux"
|
name = "Red Hat Enterprise Linux"
|
||||||
urldistro = "rhel"
|
urldistro = "rhel"
|
||||||
|
|
||||||
def isValidStore(self):
|
|
||||||
if self.treeinfo:
|
|
||||||
# Matches:
|
|
||||||
# Red Hat Enterprise Linux
|
|
||||||
# RHEL Atomic Host
|
|
||||||
m = re.match(".*(Red Hat Enterprise Linux|RHEL).*",
|
|
||||||
self.treeinfo.get("general", "family"))
|
|
||||||
ret = (m is not None)
|
|
||||||
|
|
||||||
if ret:
|
|
||||||
self._variantFromVersion()
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if (self.fetcher.hasFile("Server") or
|
|
||||||
self.fetcher.hasFile("Client")):
|
|
||||||
self.os_variant = "rhel5"
|
|
||||||
return True
|
|
||||||
return self.fetcher.hasFile("RedHat")
|
|
||||||
|
|
||||||
|
|
||||||
################################
|
|
||||||
# osdict autodetection helpers #
|
|
||||||
################################
|
|
||||||
|
|
||||||
def _parseTreeinfoVersion(self, verstr):
|
def _parseTreeinfoVersion(self, verstr):
|
||||||
def _safeint(c):
|
def _safeint(c):
|
||||||
@ -573,24 +469,6 @@ class RHELDistro(RedHatDistro):
|
|||||||
|
|
||||||
return version, update
|
return version, update
|
||||||
|
|
||||||
def _variantFromVersion(self):
|
|
||||||
ver = self.treeinfo.get("general", "version")
|
|
||||||
name = None
|
|
||||||
if self.treeinfo.has_option("general", "name"):
|
|
||||||
name = self.treeinfo.get("general", "name")
|
|
||||||
if not ver:
|
|
||||||
return
|
|
||||||
|
|
||||||
if name and name.startswith("Red Hat Enterprise Linux Server for ARM"):
|
|
||||||
# Kind of a hack, but good enough for the time being
|
|
||||||
version = 7
|
|
||||||
update = 0
|
|
||||||
else:
|
|
||||||
version, update = self._parseTreeinfoVersion(ver)
|
|
||||||
|
|
||||||
self._version_number = version
|
|
||||||
self._setRHELVariant(version, update)
|
|
||||||
|
|
||||||
def _setRHELVariant(self, version, update):
|
def _setRHELVariant(self, version, update):
|
||||||
base = "rhel" + str(version)
|
base = "rhel" + str(version)
|
||||||
if update < 0:
|
if update < 0:
|
||||||
@ -614,6 +492,26 @@ class RHELDistro(RedHatDistro):
|
|||||||
if ret:
|
if ret:
|
||||||
self.os_variant = ret
|
self.os_variant = ret
|
||||||
|
|
||||||
|
def _variantFromVersion(self):
|
||||||
|
if not self.treeinfo_version:
|
||||||
|
return
|
||||||
|
|
||||||
|
version, update = self._parseTreeinfoVersion(self.treeinfo_version)
|
||||||
|
self._version_number = version
|
||||||
|
self._setRHELVariant(version, update)
|
||||||
|
|
||||||
|
|
||||||
|
def isValidStore(self):
|
||||||
|
# Matches:
|
||||||
|
# Red Hat Enterprise Linux
|
||||||
|
# RHEL Atomic Host
|
||||||
|
famregex = ".*(Red Hat Enterprise Linux|RHEL).*"
|
||||||
|
if not self._hasTreeinfoFamily(famregex):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._variantFromVersion()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# CentOS distro check
|
# CentOS distro check
|
||||||
class CentOSDistro(RHELDistro):
|
class CentOSDistro(RHELDistro):
|
||||||
@ -621,18 +519,16 @@ class CentOSDistro(RHELDistro):
|
|||||||
urldistro = "centos"
|
urldistro = "centos"
|
||||||
|
|
||||||
def isValidStore(self):
|
def isValidStore(self):
|
||||||
if not self.treeinfo:
|
famregex = ".*CentOS.*"
|
||||||
return self.fetcher.hasFile("CentOS")
|
if not self._hasTreeinfoFamily(famregex):
|
||||||
|
return False
|
||||||
|
|
||||||
m = re.match(".*CentOS.*", self.treeinfo.get("general", "family"))
|
self._variantFromVersion()
|
||||||
ret = (m is not None)
|
if self.os_variant:
|
||||||
if ret:
|
new_variant = self.os_variant.replace("rhel", "centos")
|
||||||
self._variantFromVersion()
|
if self._check_osvariant_valid(new_variant):
|
||||||
if self.os_variant:
|
self.os_variant = new_variant
|
||||||
new_variant = self.os_variant.replace("rhel", "centos")
|
return True
|
||||||
if self._check_osvariant_valid(new_variant):
|
|
||||||
self.os_variant = new_variant
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
# Scientific Linux distro check
|
# Scientific Linux distro check
|
||||||
@ -640,21 +536,13 @@ class SLDistro(RHELDistro):
|
|||||||
name = "Scientific Linux"
|
name = "Scientific Linux"
|
||||||
urldistro = None
|
urldistro = None
|
||||||
|
|
||||||
_boot_iso_paths = RHELDistro._boot_iso_paths + ["images/SL/boot.iso"]
|
|
||||||
_hvm_kernel_paths = RHELDistro._hvm_kernel_paths + [
|
|
||||||
("images/SL/pxeboot/vmlinuz", "images/SL/pxeboot/initrd.img")]
|
|
||||||
|
|
||||||
def isValidStore(self):
|
def isValidStore(self):
|
||||||
if self.treeinfo:
|
famregex = ".*Scientific.*"
|
||||||
m = re.match(".*Scientific.*",
|
if not self._hasTreeinfoFamily(famregex):
|
||||||
self.treeinfo.get("general", "family"))
|
return False
|
||||||
ret = (m is not None)
|
|
||||||
|
|
||||||
if ret:
|
self._variantFromVersion()
|
||||||
self._variantFromVersion()
|
return True
|
||||||
return ret
|
|
||||||
|
|
||||||
return self.fetcher.hasFile("SL")
|
|
||||||
|
|
||||||
|
|
||||||
class SuseDistro(Distro):
|
class SuseDistro(Distro):
|
||||||
@ -1051,8 +939,8 @@ def _build_distro_list():
|
|||||||
seen_urldistro.append(obj.urldistro)
|
seen_urldistro.append(obj.urldistro)
|
||||||
|
|
||||||
# Always stick GenericDistro at the end, since it's a catchall
|
# Always stick GenericDistro at the end, since it's a catchall
|
||||||
allstores.remove(GenericDistro)
|
allstores.remove(GenericTreeinfoDistro)
|
||||||
allstores.append(GenericDistro)
|
allstores.append(GenericTreeinfoDistro)
|
||||||
|
|
||||||
return allstores
|
return allstores
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user