XenAPI: Add Direct PCI Device (DPCI) Assignment Support

Signed-off-by: Yosuke Iwamatsu <y-iwamatsu@ab.jp.nec.com>
This commit is contained in:
Keir Fraser committed 2008-07-03 10:27:12 +01:00
1 parent 5a940c0997
commit 8e65d32968
7 files changed
+434 -83

No files matched your search

+9
View File
@@ -41,6 +41,7 @@ from XendVMMetrics import XendVMMetrics
from XendPIF import XendPIF
from XendPBD import XendPBD
from XendPPCI import XendPPCI
from XendDPCI import XendDPCI
from XendXSPolicy import XendXSPolicy, XendACMPolicy
from XendAPIConstants import *
@@ -479,6 +480,7 @@ classes = {
'PBD' : valid_object("PBD"),
'PIF_metrics' : valid_object("PIF_metrics"),
'PPCI' : valid_object("PPCI"),
'DPCI' : valid_object("DPCI")
}
autoplug_classes = {
@@ -488,6 +490,7 @@ autoplug_classes = {
'PBD' : XendPBD,
'PIF_metrics' : XendPIFMetrics,
'PPCI' : XendPPCI,
'DPCI' : XendDPCI,
'XSPolicy' : XendXSPolicy,
'ACMPolicy' : XendACMPolicy,
}
@@ -1154,6 +1157,7 @@ class XendAPI(object):
'VIFs',
'VBDs',
'VTPMs',
'DPCIs',
'tools_version',
'domid',
'is_control_domain',
@@ -1296,6 +1300,10 @@ class XendAPI(object):
dom = XendDomain.instance().get_vm_by_uuid(vm_ref)
return xen_api_success(dom.get_consoles())
def VM_get_DPCIs(self, session, vm_ref):
dom = XendDomain.instance().get_vm_by_uuid(vm_ref)
return xen_api_success(dom.get_dpcis())
def VM_get_tools_version(self, session, vm_ref):
dom = XendDomain.instance().get_vm_by_uuid(vm_ref)
return dom.get_tools_version()
@@ -1675,6 +1683,7 @@ class XendAPI(object):
'VIFs': xeninfo.get_vifs(),
'VBDs': xeninfo.get_vbds(),
'VTPMs': xeninfo.get_vtpms(),
'DPCIs': xeninfo.get_dpcis(),
'PV_bootloader': xeninfo.info.get('PV_bootloader'),
'PV_kernel': xeninfo.info.get('PV_kernel'),
'PV_ramdisk': xeninfo.info.get('PV_ramdisk'),
+121 -38
View File
@@ -24,6 +24,8 @@ from xen.xend import sxp
from xen.xend import uuid
from xen.xend import XendOptions
from xen.xend import XendAPIStore
from xen.xend.XendPPCI import XendPPCI
from xen.xend.XendDPCI import XendDPCI
from xen.xend.XendError import VmError
from xen.xend.XendDevices import XendDevices
from xen.xend.PrettyPrint import prettyprintstring
@@ -773,6 +775,11 @@ class XendConfig(dict):
"""
log.debug('_sxp_to_xapi(%s)' % scrub_password(sxp_cfg))
# _parse_sxp() below will call device_add() and construct devices.
# Some devices (currently only pci) may require VM's uuid, so
# setup self['uuid'] beforehand.
self['uuid'] = sxp.child_value(sxp_cfg, 'uuid', uuid.createString())
cfg = self._parse_sxp(sxp_cfg)
for key, typ in XENAPI_CFG_TYPES.items():
@@ -1209,42 +1216,35 @@ class XendConfig(dict):
dev_type = sxp.name(config)
dev_info = {}
# Parsing the device SXP's. In most cases, the SXP looks
# like this:
#
# [device, [vif, [mac, xx:xx:xx:xx:xx:xx], [ip 1.3.4.5]]]
#
# However, for PCI devices it looks like this:
#
# [device, [pci, [dev, [domain, 0], [bus, 0], [slot, 1]]]]
#
# It seems the reasoning for this difference is because
# pciif.py needs all the PCI device configurations at
# the same time when creating the devices.
#
# To further complicate matters, Xen 2.0 configuration format
# uses the following for pci device configuration:
#
# [device, [pci, [domain, 0], [bus, 0], [dev, 1], [func, 2]]]
if dev_type == 'pci':
pci_devs_uuid = sxp.child_value(config, 'uuid',
uuid.createString())
pci_devs = []
for pci_dev in sxp.children(config, 'dev'):
pci_dev_info = {}
for opt_val in pci_dev[1:]:
try:
opt, val = opt_val
pci_dev_info[opt] = val
except TypeError:
pass
pci_devs.append(pci_dev_info)
pci_dict = self.pci_convert_sxp_to_dict(config)
pci_devs = pci_dict['devs']
# create XenAPI DPCI objects.
for pci_dev in pci_devs:
dpci_uuid = pci_dev.get('uuid')
ppci_uuid = XendPPCI.get_by_sbdf(pci_dev['domain'],
pci_dev['bus'],
pci_dev['slot'],
pci_dev['func'])
if ppci_uuid is None:
continue
dpci_record = {
'VM': self['uuid'],
'PPCI': ppci_uuid,
'hotplug_slot': pci_dev.get('vslot', 0)
}
XendDPCI(dpci_uuid, dpci_record)
target['devices'][pci_devs_uuid] = (dev_type,
{'devs': pci_devs,
'uuid': pci_devs_uuid})
log.debug("XendConfig: reading device: %s" % pci_devs)
return pci_devs_uuid
for opt_val in config[1:]:
@@ -1482,6 +1482,76 @@ class XendConfig(dict):
return ''
def pci_convert_sxp_to_dict(self, dev_sxp):
"""Convert pci device sxp to dict
@param dev_sxp: device configuration
@type dev_sxp: SXP object (parsed config)
@return: dev_config
@rtype: dictionary
"""
# Parsing the device SXP's. In most cases, the SXP looks
# like this:
#
# [device, [vif, [mac, xx:xx:xx:xx:xx:xx], [ip 1.3.4.5]]]
#
# However, for PCI devices it looks like this:
#
# [device, [pci, [dev, [domain, 0], [bus, 0], [slot, 1], [func, 2]]]
#
# It seems the reasoning for this difference is because
# pciif.py needs all the PCI device configurations at
# the same time when creating the devices.
#
# To further complicate matters, Xen 2.0 configuration format
# uses the following for pci device configuration:
#
# [device, [pci, [domain, 0], [bus, 0], [dev, 1], [func, 2]]]
# For PCI device hotplug support, the SXP of PCI devices is
# extendend like this:
#
# [device, [pci, [dev, [domain, 0], [bus, 0], [slot, 1], [func, 2],
# [vslt, 0]],
# [state, 'Initialising']]]
#
# 'vslt' shows the virtual hotplug slot number which the PCI device
# is inserted in. This is only effective for HVM domains.
#
# state 'Initialising' indicates that the device is being attached,
# while state 'Closing' indicates that the device is being detached.
#
# The Dict looks like this:
#
# { devs: [{domain: 0, bus: 0, slot: 1, func: 2, vslt: 0}],
# states: ['Initialising'] }
dev_config = {}
pci_devs = []
for pci_dev in sxp.children(dev_sxp, 'dev'):
pci_dev_info = {}
for opt_val in pci_dev[1:]:
try:
opt, val = opt_val
pci_dev_info[opt] = val
except TypeError:
pass
# append uuid for each pci device.
dpci_uuid = pci_dev_info.get('uuid', uuid.createString())
pci_dev_info['uuid'] = dpci_uuid
pci_devs.append(pci_dev_info)
dev_config['devs'] = pci_devs
pci_states = []
for pci_state in sxp.children(dev_sxp, 'state'):
try:
pci_states.append(pci_state[1])
except IndexError:
raise XendError("Error reading state while parsing pci sxp")
dev_config['states'] = pci_states
return dev_config
def console_add(self, protocol, location, other_config = {}):
dev_uuid = uuid.createString()
if protocol == 'vt100':
@@ -1556,16 +1626,29 @@ class XendConfig(dict):
dev_type, dev_info = self['devices'][dev_uuid]
if dev_type == 'pci': # Special case for pci
pci_devs = []
for pci_dev in sxp.children(config, 'dev'):
pci_dev_info = {}
for opt_val in pci_dev[1:]:
try:
opt, val = opt_val
pci_dev_info[opt] = val
except TypeError:
pass
pci_devs.append(pci_dev_info)
pci_dict = self.pci_convert_sxp_to_dict(config)
pci_devs = pci_dict['devs']
# destroy existing XenAPI DPCI objects
for dpci_uuid in XendDPCI.get_by_VM(self['uuid']):
XendAPIStore.deregister(dpci_uuid, "DPCI")
# create XenAPI DPCI objects.
for pci_dev in pci_devs:
dpci_uuid = pci_dev.get('uuid')
ppci_uuid = XendPPCI.get_by_sbdf(pci_dev['domain'],
pci_dev['bus'],
pci_dev['slot'],
pci_dev['func'])
if ppci_uuid is None:
continue
dpci_record = {
'VM': self['uuid'],
'PPCI': ppci_uuid,
'hotplug_slot': pci_dev.get('vslot', 0)
}
XendDPCI(dpci_uuid, dpci_record)
self['devices'][dev_uuid] = (dev_type,
{'devs': pci_devs,
'uuid': dev_uuid})
+154
View File
@@ -0,0 +1,154 @@
#============================================================================
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#============================================================================
# Copyright (c) 2008 NEC Corporation
# Yosuke Iwamatsu <y-iwamatsu at ab jp nec com>
#============================================================================
from xen.xend.XendBase import XendBase
from xen.xend.XendPPCI import XendPPCI
from xen.xend import XendAPIStore
from xen.xend import uuid as genuuid
import XendDomain, XendNode
from XendError import *
from XendTask import XendTask
from XendLogging import log
class XendDPCI(XendBase):
"""Representation of a passthrough PCI device."""
def getClass(self):
return "DPCI"
def getAttrRO(self):
attrRO = ['virtual_domain',
'virtual_bus',
'virtual_slot',
'virtual_func',
'virtual_name',
'VM',
'PPCI',
'hotplug_slot']
return XendBase.getAttrRO() + attrRO
def getAttrRW(self):
attrRW = []
return XendBase.getAttrRW() + attrRW
def getAttrInst(self):
attrInst = ['VM',
'PPCI',
'hotplug_slot']
return XendBase.getAttrInst() + attrInst
def getMethods(self):
methods = ['destroy']
return XendBase.getMethods() + methods
def getFuncs(self):
funcs = ['create']
return XendBase.getFuncs() + funcs
getClass = classmethod(getClass)
getAttrRO = classmethod(getAttrRO)
getAttrRW = classmethod(getAttrRW)
getAttrInst = classmethod(getAttrInst)
getMethods = classmethod(getMethods)
getFuncs = classmethod(getFuncs)
def create(self, dpci_struct):
# Check if VM is valid
xendom = XendDomain.instance()
if not xendom.is_valid_vm(dpci_struct['VM']):
raise InvalidHandleError('VM', dpci_struct['VM'])
dom = xendom.get_vm_by_uuid(dpci_struct['VM'])
# Check if PPCI is valid
xennode = XendNode.instance()
ppci_uuid = xennode.get_ppci_by_uuid(dpci_struct['PPCI'])
if not ppci_uuid:
raise InvalidHandleError('PPCI', dpci_struct['PPCI'])
for existing_dpci in XendAPIStore.get_all('DPCI'):
if ppci_uuid == existing_dpci.get_PPCI():
raise DirectPCIError("Device is in use")
# Assign PPCI to VM
try:
dpci_ref = XendTask.log_progress(0, 100, dom.create_dpci,
dpci_struct)
except XendError, e:
raise DirectPCIError("Failed to assign device")
# TODO: Retrive virtual pci device infomation.
return dpci_ref
create = classmethod(create)
def get_by_VM(cls, VM_ref):
result = []
for dpci in XendAPIStore.get_all("DPCI"):
if dpci.get_VM() == VM_ref:
result.append(dpci.get_uuid())
return result
get_by_VM = classmethod(get_by_VM)
def __init__(self, uuid, record):
XendBase.__init__(self, uuid, record)
self.virtual_domain = -1
self.virtual_bus = -1
self.virtual_slot = -1
self.virtual_func = -1
self.VM = record['VM']
self.PPCI = record['PPCI']
self.hotplug_slot = record['hotplug_slot']
def destroy(self):
xendom = XendDomain.instance()
dom = xendom.get_vm_by_uuid(self.get_VM())
if not dom:
raise InvalidHandleError("VM", self.get_VM())
XendTask.log_progress(0, 100, dom.destroy_dpci, self.get_uuid())
def get_virtual_domain(self):
return self.virtual_domain
def get_virtual_bus(self):
return self.virtual_bus
def get_virtual_slot(self):
return self.virtual_slot
def get_virtual_func(self):
return self.virtual_func
def get_virtual_name(self):
return "%04x:%02x:%02x.%01x" % (self.virtual_domain, self.virtual_bus,
self.virtual_slot, self.virtual_func)
def get_VM(self):
return self.VM
def get_PPCI(self):
return self.PPCI
def get_hotplug_slot(self):
return self.hotplug_slot
+3
View File
@@ -478,6 +478,8 @@ class XendDomain:
if domid in self.domains:
del self.domains[domid]
info.destroy_xapi_device_instances()
else:
log.warning("Attempted to remove non-existent domain.")
@@ -1091,6 +1093,7 @@ class XendDomain:
self._managed_domain_unregister(dominfo)
self._remove_domain(dominfo)
XendDevices.destroy_device_state(dominfo)
dominfo.destroy_xapi_device_instances()
def domain_configure(self, config):
+125 -44
View File
@@ -55,6 +55,10 @@ from xen.xend.XendAPIConstants import *
from xen.xend.XendVMMetrics import XendVMMetrics
from xen.xend.XendPPCI import XendPPCI
from xen.xend.XendDPCI import XendDPCI
from xen.xend import XendAPIStore
MIGRATE_TIMEOUT = 30.0
BOOTLOADER_LOOPBACK_DEVICE = '/dev/xvdp'
@@ -642,50 +646,7 @@ class XendDomainInfo:
xen.xend.XendDomain.instance().managed_config_save(self)
return self.getDeviceController(dev_type).sxpr(devid)
def pci_convert_sxp_to_dict(self, dev_sxp):
"""Convert pci device sxp to dict
@param dev_sxp: device configuration
@type dev_sxp: SXP object (parsed config)
@return: dev_config
@rtype: dictionary
"""
# In reconfigure phase, config of PCI device looks like below:
#
# sxp:
# [device, [pci, [dev, [domain, '0x0'], [bus, '0x0'], [slot, '0x0'],
# [func, '0x0'], [vslt, '0x0']],
# [state, 'Initialising']]]
#
# dict:
# {devs: [{domain: '0x0', bus: '0x0', slot: '0x0', func: '0x0',
# vslt: '0x0'}],
# states: ['Initialising']}
#
# state 'Initialising' means the device is being attached.
# state 'Closing' means the device is being detached.
dev_config = {}
pci_devs = []
for pci_dev in sxp.children(dev_sxp, 'dev'):
pci_dev_info = {}
for opt_val in pci_dev[1:]:
try:
opt, val = opt_val
pci_dev_info[opt] = val
except TypeError:
pass
pci_devs.append(pci_dev_info)
dev_config['devs'] = pci_devs
pci_states = []
for pci_state in sxp.children(dev_sxp, 'state'):
try:
pci_states.append(pci_state[1])
except IndexError:
raise XendError("Error reading state while parsing pci sxp")
dev_config['states'] = pci_states
return dev_config
def pci_device_configure(self, dev_sxp, devid = 0):
"""Configure an existing pci device.
@@ -711,7 +672,7 @@ class XendDomainInfo:
raise XendError("Cannot detach when pci platform does not exist")
pci_dev = sxp.children(dev_sxp, 'dev')[0]
dev_config = self.pci_convert_sxp_to_dict(dev_sxp)
dev_config = self.info.pci_convert_sxp_to_dict(dev_sxp)
dev = dev_config['devs'][0]
# Do HVM specific processing
@@ -785,6 +746,8 @@ class XendDomainInfo:
self.destroyDevice('pci', devid)
del self.info['devices'][dev_uuid]
xen.xend.XendDomain.instance().managed_config_save(self)
return True
def device_configure(self, dev_sxp, devid = None):
@@ -3169,6 +3132,9 @@ class XendDomainInfo:
def get_vtpms(self):
return self.info.get('vtpm_refs', [])
def get_dpcis(self):
return XendDPCI.get_by_VM(self.info.get('uuid'))
def create_vbd(self, xenapi_vbd, vdi_image_path):
"""Create a VBD using a VDI from XendStorageRepository.
@@ -3292,6 +3258,64 @@ class XendDomainInfo:
def set_console_other_config(self, console_uuid, other_config):
self.info.console_update(console_uuid, 'other_config', other_config)
def create_dpci(self, xenapi_pci):
"""Create pci device from the passed struct in Xen API format.
@param xenapi_pci: DPCI struct from Xen API
@rtype: bool
#@rtype: string
@return: True if successfully created device
#@return: UUID
"""
dpci_uuid = uuid.createString()
# Convert xenapi to sxp
ppci = XendAPIStore.get(xenapi_pci.get('PPCI'), 'PPCI')
target_pci_sxp = \
['pci',
['dev',
['domain', '0x%02x' % ppci.get_domain()],
['bus', '0x%02x' % ppci.get_bus()],
['slot', '0x%02x' % ppci.get_slot()],
['func', '0x%1x' % ppci.get_func()],
['vslt', '0x%02x' % xenapi_pci.get('hotplug_slot')],
['uuid', dpci_uuid]
],
['state', 'Initialising']
]
if self._stateGet() != XEN_API_VM_POWER_STATE_RUNNING:
old_pci_sxp = self._getDeviceInfo_pci(0)
if old_pci_sxp is None:
dev_uuid = self.info.device_add('pci', cfg_sxp = target_pci_sxp)
if not dev_uuid:
raise XendError('Failed to create device')
else:
new_pci_sxp = ['pci']
for existing_dev in sxp.children(old_pci_sxp, 'dev'):
new_pci_sxp.append(existing_dev)
new_pci_sxp.append(sxp.child0(target_pci_sxp, 'dev'))
dev_uuid = sxp.child_value(old_pci_sxp, 'uuid')
self.info.device_update(dev_uuid, new_pci_sxp)
xen.xend.XendDomain.instance().managed_config_save(self)
else:
try:
self.device_configure(target_pci_sxp)
except Exception, exn:
raise XendError('Failed to create device')
return dpci_uuid
def destroy_device_by_uuid(self, dev_type, dev_uuid):
if dev_uuid not in self.info['devices']:
raise XendError('Device does not exist')
@@ -3318,6 +3342,63 @@ class XendDomainInfo:
def destroy_vtpm(self, dev_uuid):
self.destroy_device_by_uuid('vtpm', dev_uuid)
def destroy_dpci(self, dev_uuid):
dpci = XendAPIStore.get(dev_uuid, 'DPCI')
ppci = XendAPIStore.get(dpci.get_PPCI(), 'PPCI')
old_pci_sxp = self._getDeviceInfo_pci(0)
dev_uuid = sxp.child_value(old_pci_sxp, 'uuid')
target_dev = None
new_pci_sxp = ['pci']
for dev in sxp.children(old_pci_sxp, 'dev'):
domain = int(sxp.child_value(dev, 'domain'), 16)
bus = int(sxp.child_value(dev, 'bus'), 16)
slot = int(sxp.child_value(dev, 'slot'), 16)
func = int(sxp.child_value(dev, 'func'), 16)
name = "%04x:%02x:%02x.%01x" % (domain, bus, slot, func)
if ppci.get_name() == name:
target_dev = dev
else:
new_pci_sxp.append(dev)
if target_dev is None:
raise XendError('Failed to destroy device')
target_pci_sxp = ['pci', target_dev, ['state', 'Closing']]
if self._stateGet() != XEN_API_VM_POWER_STATE_RUNNING:
self.info.device_update(dev_uuid, new_pci_sxp)
if len(sxp.children(new_pci_sxp, 'dev')) == 0:
del self.info['devices'][dev_uuid]
xen.xend.XendDomain.instance().managed_config_save(self)
else:
try:
self.device_configure(target_pci_sxp)
except Exception, exn:
raise XendError('Failed to destroy device')
def destroy_xapi_device_instances(self):
"""Destroy Xen-API device instances stored in XendAPIStore.
"""
# Xen-API classes based on XendBase have their instances stored
# in XendAPIStore. Cleanup these virtual device instances here
# if they are supposed to be destroyed when the parent domain is dead.
#
# Most of the virtual devices (vif, vbd, vfb, etc) are not based on
# XendBase and there's no need to remove them from XendAPIStore.
from xen.xend import XendDomain
if XendDomain.instance().is_valid_vm(self.info.get('uuid')):
# domain still exists.
return
for dpci_uuid in XendDPCI.get_by_VM(self.info.get('uuid')):
XendAPIStore.deregister(dpci_uuid, "DPCI")
def has_device(self, dev_class, dev_uuid):
return (dev_uuid in self.info['%s_refs' % dev_class.lower()])
+11
View File
@@ -175,6 +175,17 @@ class NetworkError(XendAPIError):
def __str__(self):
return 'NETWORK_ERROR: %s %s' % (self.error, self.network)
class DirectPCIError(XendAPIError):
def __init__(self, error):
XendAPIError.__init__(self)
self.error = error
def get_api_error(self):
return ['DIRECT_PCI_ERROR', self.error]
def __str__(self):
return 'DIRECT_PCI_ERROR: %s' % self.error
from xen.util.xsconstants import xserr2string
class SecurityError(XendAPIError):
+11 -1
View File
@@ -76,6 +76,7 @@ class PciController(DevController):
back['dev-%i' % pcidevid] = "%04x:%02x:%02x.%02x" % \
(domain, bus, slot, func)
back['uuid-%i' % pcidevid] = pci_config.get('uuid', '')
pcidevid += 1
if vslots != "":
@@ -101,6 +102,7 @@ class PciController(DevController):
try:
dev = back['dev-%i' % i]
state = states[i]
uuid = back['uuid-%i' %i]
except:
raise XendError('Error reading config')
@@ -121,6 +123,7 @@ class PciController(DevController):
self.writeBackend(devid, 'dev-%i' % (num_olddevs + i), dev)
self.writeBackend(devid, 'state-%i' % (num_olddevs + i),
str(xenbusState['Initialising']))
self.writeBackend(devid, 'uuid-%i' % (num_olddevs + i), uuid)
self.writeBackend(devid, 'num_devs', str(num_olddevs + i + 1))
# Update vslots
@@ -141,7 +144,7 @@ class PciController(DevController):
raise XendError('Device %s is not connected' % dev)
# Update vslots
if back['vslots'] is not None:
if back.get('vslots') is not None:
vslots = old_vslots
for vslt in back['vslots'].split(';'):
if vslt != '':
@@ -186,6 +189,9 @@ class PciController(DevController):
'slot': '0x%(slot)s' % pci_dev_info,
'func': '0x%(func)s' % pci_dev_info}
# Per device uuid info
dev_dict['uuid'] = self.readBackend(devid, 'uuid-%d' % i)
#append vslot info
if vslots is not None:
try:
@@ -442,6 +448,7 @@ class PciController(DevController):
self.removeBackend(devid, 'dev-%i' % i)
self.removeBackend(devid, 'vdev-%i' % i)
self.removeBackend(devid, 'state-%i' % i)
self.removeBackend(devid, 'uuid-%i' % i)
else:
if new_num_devs != i:
tmpdev = self.readBackend(devid, 'dev-%i' % i)
@@ -455,6 +462,9 @@ class PciController(DevController):
tmpstate = self.readBackend(devid, 'state-%i' % i)
self.writeBackend(devid, 'state-%i' % new_num_devs, tmpstate)
self.removeBackend(devid, 'state-%i' % i)
tmpuuid = self.readBackend(devid, 'uuid-%i' % i)
self.writeBackend(devid, 'uuid-%i' % new_num_devs, tmpuuid)
self.removeBackend(devid, 'uuid-%i' % i)
new_num_devs = new_num_devs + 1
self.writeBackend(devid, 'num_devs', str(new_num_devs))
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.