mirror of https://gitee.com/openkylin/libvirt.git
esx: Add autodetection for the SCSI controller model
This works for file-backed SCSI disk device with a datastore related source path.
This commit is contained in:
parent
afb85c5889
commit
cf8cf8a59f
|
@ -292,6 +292,15 @@ ethernet0.checkMACAddress = "false"
|
|||
|
||||
<h4>SCSI controller models</h4>
|
||||
<dl>
|
||||
<dt><code>auto</code></dt>
|
||||
<dd>
|
||||
This isn't a actual controller model. If specified the ESX driver
|
||||
tries to detect the SCSI controller model referenced in the
|
||||
<code>.vmdk</code> file and use it. Autodetection fails when a
|
||||
SCSI controller has multiple disks attached and the SCSI controller
|
||||
models referenced in the <code>.vmdk</code> files are inconsistent.
|
||||
<span class="since">Since 0.8.3</span>
|
||||
</dd>
|
||||
<dt><code>buslogic</code></dt>
|
||||
<dd>
|
||||
BusLogic SCSI controller for older guests.
|
||||
|
|
|
@ -676,6 +676,7 @@
|
|||
<optional>
|
||||
<attribute name="model">
|
||||
<choice>
|
||||
<value>auto</value>
|
||||
<value>buslogic</value>
|
||||
<value>lsilogic</value>
|
||||
<value>lsisas1068</value>
|
||||
|
|
|
@ -141,6 +141,7 @@ VIR_ENUM_IMPL(virDomainController, VIR_DOMAIN_CONTROLLER_TYPE_LAST,
|
|||
"virtio-serial")
|
||||
|
||||
VIR_ENUM_IMPL(virDomainControllerModel, VIR_DOMAIN_CONTROLLER_MODEL_LAST,
|
||||
"auto",
|
||||
"buslogic",
|
||||
"lsilogic",
|
||||
"lsisas1068",
|
||||
|
|
|
@ -196,6 +196,7 @@ enum virDomainControllerType {
|
|||
|
||||
|
||||
enum virDomainControllerModel {
|
||||
VIR_DOMAIN_CONTROLLER_MODEL_AUTO,
|
||||
VIR_DOMAIN_CONTROLLER_MODEL_BUSLOGIC,
|
||||
VIR_DOMAIN_CONTROLLER_MODEL_LSILOGIC,
|
||||
VIR_DOMAIN_CONTROLLER_MODEL_LSISAS1068,
|
||||
|
|
|
@ -184,6 +184,40 @@ object Event
|
|||
end
|
||||
|
||||
|
||||
object FileInfo
|
||||
String path r
|
||||
Long fileSize o
|
||||
DateTime modification o
|
||||
end
|
||||
|
||||
|
||||
object FileQuery
|
||||
end
|
||||
|
||||
|
||||
object FileQueryFlags
|
||||
Boolean fileType r
|
||||
Boolean fileSize r
|
||||
Boolean modification r
|
||||
end
|
||||
|
||||
|
||||
object FloppyImageFileInfo extends FileInfo
|
||||
end
|
||||
|
||||
|
||||
object FloppyImageFileQuery extends FileQuery
|
||||
end
|
||||
|
||||
|
||||
object FolderFileInfo extends FileInfo
|
||||
end
|
||||
|
||||
|
||||
object FolderFileQuery extends FileQuery
|
||||
end
|
||||
|
||||
|
||||
object HostCpuIdInfo
|
||||
Int level r
|
||||
String vendor o
|
||||
|
@ -194,6 +228,22 @@ object HostCpuIdInfo
|
|||
end
|
||||
|
||||
|
||||
object HostDatastoreBrowserSearchResults
|
||||
ManagedObjectReference datastore o
|
||||
String folderPath o
|
||||
FileInfo file ol
|
||||
end
|
||||
|
||||
|
||||
object HostDatastoreBrowserSearchSpec
|
||||
FileQuery query ol
|
||||
FileQueryFlags details o
|
||||
Boolean searchCaseInsensitive o
|
||||
String matchPattern ol
|
||||
Boolean sortFoldersFirst o
|
||||
end
|
||||
|
||||
|
||||
object HostFileSystemVolume
|
||||
String type r
|
||||
String name r
|
||||
|
@ -225,6 +275,14 @@ object HostVmfsVolume extends HostFileSystemVolume
|
|||
end
|
||||
|
||||
|
||||
object IsoImageFileInfo extends FileInfo
|
||||
end
|
||||
|
||||
|
||||
object IsoImageFileQuery extends FileQuery
|
||||
end
|
||||
|
||||
|
||||
object LocalDatastoreInfo extends DatastoreInfo
|
||||
String path o
|
||||
end
|
||||
|
@ -424,6 +482,14 @@ object TaskInfo
|
|||
end
|
||||
|
||||
|
||||
object TemplateConfigFileInfo extends VmConfigFileInfo
|
||||
end
|
||||
|
||||
|
||||
object TemplateConfigFileQuery extends VmConfigFileQuery
|
||||
end
|
||||
|
||||
|
||||
object TraversalSpec extends SelectionSpec
|
||||
String type r
|
||||
String path r
|
||||
|
@ -502,6 +568,82 @@ object VirtualMachineSnapshotTree
|
|||
end
|
||||
|
||||
|
||||
object VmConfigFileInfo extends FileInfo
|
||||
Int configVersion o
|
||||
end
|
||||
|
||||
|
||||
object VmConfigFileQuery extends FileQuery
|
||||
VmConfigFileQueryFilter filter o
|
||||
VmConfigFileQueryFlags details o
|
||||
end
|
||||
|
||||
|
||||
object VmConfigFileQueryFilter
|
||||
Int matchConfigVersion ol
|
||||
end
|
||||
|
||||
|
||||
object VmConfigFileQueryFlags
|
||||
Boolean configVersion r
|
||||
end
|
||||
|
||||
|
||||
object VmDiskFileInfo extends FileInfo
|
||||
String diskType o
|
||||
Long capacityKb o
|
||||
Int hardwareVersion o
|
||||
String controllerType o
|
||||
String diskExtents ol
|
||||
end
|
||||
|
||||
|
||||
object VmDiskFileQuery extends FileQuery
|
||||
VmDiskFileQueryFilter filter o
|
||||
VmDiskFileQueryFlags details o
|
||||
end
|
||||
|
||||
|
||||
object VmDiskFileQueryFilter
|
||||
String diskType ol
|
||||
Int matchHardwareVersion ol
|
||||
String controllerType ol
|
||||
end
|
||||
|
||||
|
||||
object VmDiskFileQueryFlags
|
||||
Boolean diskType r
|
||||
Boolean capacityKb r
|
||||
Boolean hardwareVersion r
|
||||
Boolean controllerType o
|
||||
Boolean diskExtents o
|
||||
end
|
||||
|
||||
|
||||
object VmLogFileInfo extends FileInfo
|
||||
end
|
||||
|
||||
|
||||
object VmLogFileQuery extends FileQuery
|
||||
end
|
||||
|
||||
|
||||
object VmNvramFileInfo extends FileInfo
|
||||
end
|
||||
|
||||
|
||||
object VmNvramFileQuery extends FileQuery
|
||||
end
|
||||
|
||||
|
||||
object VmSnapshotFileInfo extends FileInfo
|
||||
end
|
||||
|
||||
|
||||
object VmSnapshotFileQuery extends FileQuery
|
||||
end
|
||||
|
||||
|
||||
object VmfsDatastoreInfo extends DatastoreInfo
|
||||
HostVmfsVolume vmfs o
|
||||
end
|
||||
|
@ -658,6 +800,13 @@ method RevertToSnapshot_Task returns ManagedObjectReference r
|
|||
end
|
||||
|
||||
|
||||
method SearchDatastore_Task returns ManagedObjectReference r
|
||||
ManagedObjectReference _this r
|
||||
String datastorePath r
|
||||
HostDatastoreBrowserSearchSpec searchSpec o
|
||||
end
|
||||
|
||||
|
||||
method SessionIsActive returns Boolean r
|
||||
ManagedObjectReference _this:SessionManager r
|
||||
String sessionID r
|
||||
|
|
|
@ -1123,7 +1123,10 @@ additional_enum_features = { "ManagedEntityStatus" : Enum.FEATURE__ANY_TYPE
|
|||
|
||||
additional_object_features = { "DatastoreInfo" : Object.FEATURE__ANY_TYPE | Object.FEATURE__DYNAMIC_CAST,
|
||||
"Event" : Object.FEATURE__LIST,
|
||||
"FileInfo" : Object.FEATURE__DYNAMIC_CAST,
|
||||
"FileQuery" : Object.FEATURE__DYNAMIC_CAST,
|
||||
"HostCpuIdInfo" : Object.FEATURE__ANY_TYPE | Object.FEATURE__LIST,
|
||||
"HostDatastoreBrowserSearchResults" : Object.FEATURE__ANY_TYPE,
|
||||
"ManagedObjectReference" : Object.FEATURE__ANY_TYPE,
|
||||
"ObjectContent" : Object.FEATURE__DEEP_COPY | Object.FEATURE__LIST,
|
||||
"PerfCounterInfo" : Object.FEATURE__LIST,
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "virterror_internal.h"
|
||||
#include "memory.h"
|
||||
#include "logging.h"
|
||||
#include "esx_vi_methods.h"
|
||||
#include "esx_private.h"
|
||||
#include "esx_util.h"
|
||||
#include "esx_vmx.h"
|
||||
|
@ -433,6 +434,7 @@ def->parallels[0]...
|
|||
* are actually SCSI controller models in the ESX case */
|
||||
VIR_ENUM_DECL(esxVMX_SCSIControllerModel)
|
||||
VIR_ENUM_IMPL(esxVMX_SCSIControllerModel, VIR_DOMAIN_CONTROLLER_MODEL_LAST,
|
||||
"auto", /* just to match virDomainControllerModel, will never be used */
|
||||
"buslogic",
|
||||
"lsilogic",
|
||||
"lsisas1068",
|
||||
|
@ -716,34 +718,244 @@ esxVMX_HandleLegacySCSIDiskDriverName(virDomainDefPtr def,
|
|||
|
||||
|
||||
int
|
||||
esxVMX_GatherSCSIControllers(virDomainDefPtr def, int virtualDev[4],
|
||||
bool present[4])
|
||||
esxVMX_AutodetectSCSIControllerModel(esxVI_Context *ctx,
|
||||
virDomainDiskDefPtr def, int *model)
|
||||
{
|
||||
int i;
|
||||
int result = -1;
|
||||
char *datastoreName = NULL;
|
||||
char *directoryName = NULL;
|
||||
char *fileName = NULL;
|
||||
char *datastorePath = NULL;
|
||||
esxVI_String *propertyNameList = NULL;
|
||||
esxVI_ObjectContent *datastore = NULL;
|
||||
esxVI_ManagedObjectReference *hostDatastoreBrowser = NULL;
|
||||
esxVI_HostDatastoreBrowserSearchSpec *searchSpec = NULL;
|
||||
esxVI_VmDiskFileQuery *vmDiskFileQuery = NULL;
|
||||
esxVI_ManagedObjectReference *task = NULL;
|
||||
esxVI_TaskInfoState taskInfoState;
|
||||
esxVI_TaskInfo *taskInfo = NULL;
|
||||
esxVI_HostDatastoreBrowserSearchResults *searchResults = NULL;
|
||||
esxVI_VmDiskFileInfo *vmDiskFileInfo = NULL;
|
||||
|
||||
if (def->device != VIR_DOMAIN_DISK_DEVICE_DISK ||
|
||||
def->bus != VIR_DOMAIN_DISK_BUS_SCSI ||
|
||||
def->type != VIR_DOMAIN_DISK_TYPE_FILE ||
|
||||
def->src == NULL ||
|
||||
! STRPREFIX(def->src, "[")) {
|
||||
/*
|
||||
* This isn't a file-based SCSI disk device with a datastore related
|
||||
* source path => do nothing.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (esxUtil_ParseDatastoreRelatedPath(def->src, &datastoreName,
|
||||
&directoryName, &fileName) < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (directoryName == NULL) {
|
||||
if (virAsprintf(&datastorePath, "[%s]", datastoreName) < 0) {
|
||||
virReportOOMError();
|
||||
goto cleanup;
|
||||
}
|
||||
} else {
|
||||
if (virAsprintf(&datastorePath, "[%s] %s", datastoreName,
|
||||
directoryName) < 0) {
|
||||
virReportOOMError();
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lookup HostDatastoreBrowser */
|
||||
if (esxVI_String_AppendValueToList(&propertyNameList, "browser") < 0 ||
|
||||
esxVI_LookupDatastoreByName(ctx, datastoreName, propertyNameList,
|
||||
&datastore,
|
||||
esxVI_Occurrence_RequiredItem) < 0 ||
|
||||
esxVI_GetManagedObjectReference(datastore, "browser",
|
||||
&hostDatastoreBrowser,
|
||||
esxVI_Occurrence_RequiredItem) < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Build HostDatastoreBrowserSearchSpec */
|
||||
if (esxVI_HostDatastoreBrowserSearchSpec_Alloc(&searchSpec) < 0 ||
|
||||
esxVI_FileQueryFlags_Alloc(&searchSpec->details) < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
searchSpec->details->fileType = esxVI_Boolean_True;
|
||||
searchSpec->details->fileSize = esxVI_Boolean_False;
|
||||
searchSpec->details->modification = esxVI_Boolean_False;
|
||||
|
||||
if (esxVI_VmDiskFileQuery_Alloc(&vmDiskFileQuery) < 0 ||
|
||||
esxVI_VmDiskFileQueryFlags_Alloc(&vmDiskFileQuery->details) < 0 ||
|
||||
esxVI_FileQuery_AppendToList
|
||||
(&searchSpec->query,
|
||||
esxVI_FileQuery_DynamicCast(vmDiskFileQuery)) < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
vmDiskFileQuery->details->diskType = esxVI_Boolean_False;
|
||||
vmDiskFileQuery->details->capacityKb = esxVI_Boolean_False;
|
||||
vmDiskFileQuery->details->hardwareVersion = esxVI_Boolean_False;
|
||||
vmDiskFileQuery->details->controllerType = esxVI_Boolean_True;
|
||||
vmDiskFileQuery->details->diskExtents = esxVI_Boolean_False;
|
||||
|
||||
if (esxVI_String_Alloc(&searchSpec->matchPattern) < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
searchSpec->matchPattern->value = fileName;
|
||||
|
||||
/* Search datastore for file */
|
||||
if (esxVI_SearchDatastore_Task(ctx, hostDatastoreBrowser, datastorePath,
|
||||
searchSpec, &task) < 0 ||
|
||||
esxVI_WaitForTaskCompletion(ctx, task, NULL, esxVI_Boolean_False,
|
||||
&taskInfoState) < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (taskInfoState != esxVI_TaskInfoState_Success) {
|
||||
ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
|
||||
_("Could not serach in datastore '%s'"), datastoreName);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (esxVI_LookupTaskInfoByTask(ctx, task, &taskInfo) < 0 ||
|
||||
esxVI_HostDatastoreBrowserSearchResults_CastFromAnyType
|
||||
(taskInfo->result, &searchResults) < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Interpret search result */
|
||||
vmDiskFileInfo = esxVI_VmDiskFileInfo_DynamicCast(searchResults->file);
|
||||
|
||||
if (vmDiskFileInfo == NULL || vmDiskFileInfo->controllerType == NULL) {
|
||||
ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
|
||||
_("Could not lookup controller model for '%s'"), def->src);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (STRCASEEQ(vmDiskFileInfo->controllerType,
|
||||
"VirtualBusLogicController")) {
|
||||
*model = VIR_DOMAIN_CONTROLLER_MODEL_BUSLOGIC;
|
||||
} else if (STRCASEEQ(vmDiskFileInfo->controllerType,
|
||||
"VirtualLsiLogicController")) {
|
||||
*model = VIR_DOMAIN_CONTROLLER_MODEL_LSILOGIC;
|
||||
} else if (STRCASEEQ(vmDiskFileInfo->controllerType,
|
||||
"VirtualLsiLogicSASController")) {
|
||||
*model = VIR_DOMAIN_CONTROLLER_MODEL_LSISAS1068;
|
||||
} else if (STRCASEEQ(vmDiskFileInfo->controllerType,
|
||||
"ParaVirtualSCSIController")) {
|
||||
*model = VIR_DOMAIN_CONTROLLER_MODEL_VMPVSCSI;
|
||||
} else {
|
||||
ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
|
||||
_("Found unexpected controller model '%s' for disk '%s'"),
|
||||
vmDiskFileInfo->controllerType, def->src);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
result = 0;
|
||||
|
||||
cleanup:
|
||||
/* Don't double free fileName */
|
||||
if (searchSpec != NULL && searchSpec->matchPattern != NULL) {
|
||||
searchSpec->matchPattern->value = NULL;
|
||||
}
|
||||
|
||||
VIR_FREE(datastoreName);
|
||||
VIR_FREE(directoryName);
|
||||
VIR_FREE(fileName);
|
||||
VIR_FREE(datastorePath);
|
||||
esxVI_String_Free(&propertyNameList);
|
||||
esxVI_ObjectContent_Free(&datastore);
|
||||
esxVI_ManagedObjectReference_Free(&hostDatastoreBrowser);
|
||||
esxVI_HostDatastoreBrowserSearchSpec_Free(&searchSpec);
|
||||
esxVI_ManagedObjectReference_Free(&task);
|
||||
esxVI_TaskInfo_Free(&taskInfo);
|
||||
esxVI_HostDatastoreBrowserSearchResults_Free(&searchResults);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int
|
||||
esxVMX_GatherSCSIControllers(esxVI_Context *ctx, virDomainDefPtr def,
|
||||
int virtualDev[4], bool present[4])
|
||||
{
|
||||
int result = -1;
|
||||
int i, k;
|
||||
virDomainDiskDefPtr disk;
|
||||
virDomainControllerDefPtr controller = NULL;
|
||||
virDomainControllerDefPtr controller;
|
||||
bool controllerHasDisksAttached;
|
||||
int count = 0;
|
||||
int *autodetectedModels;
|
||||
|
||||
for (i = 0; i < def->ndisks; ++i) {
|
||||
disk = def->disks[i];
|
||||
if (VIR_ALLOC_N(autodetectedModels, def->ndisks) < 0) {
|
||||
virReportOOMError();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (disk->bus != VIR_DOMAIN_DISK_BUS_SCSI) {
|
||||
for (i = 0; i < def->ncontrollers; ++i) {
|
||||
controller = def->controllers[i];
|
||||
|
||||
if (controller->type != VIR_DOMAIN_CONTROLLER_TYPE_SCSI) {
|
||||
// skip non-SCSI controllers
|
||||
continue;
|
||||
}
|
||||
|
||||
controller = NULL;
|
||||
controllerHasDisksAttached = false;
|
||||
|
||||
for (i = 0; i < def->ncontrollers; ++i) {
|
||||
if (def->controllers[i]->idx == disk->info.addr.drive.controller) {
|
||||
controller = def->controllers[i];
|
||||
for (k = 0; k < def->ndisks; ++k) {
|
||||
disk = def->disks[k];
|
||||
|
||||
if (disk->bus == VIR_DOMAIN_DISK_BUS_SCSI &&
|
||||
disk->info.addr.drive.controller == controller->idx) {
|
||||
controllerHasDisksAttached = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller == NULL) {
|
||||
ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
|
||||
_("Missing SCSI controller for index %d"),
|
||||
disk->info.addr.drive.controller);
|
||||
return -1;
|
||||
if (! controllerHasDisksAttached) {
|
||||
// skip SCSI controllers without attached disks
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctx != NULL &&
|
||||
controller->model == VIR_DOMAIN_CONTROLLER_MODEL_AUTO) {
|
||||
count = 0;
|
||||
|
||||
// try to autodetect the SCSI controller model by collecting
|
||||
// SCSI controller model of all disks attached to this controller
|
||||
for (k = 0; k < def->ndisks; ++k) {
|
||||
disk = def->disks[k];
|
||||
|
||||
if (disk->bus == VIR_DOMAIN_DISK_BUS_SCSI &&
|
||||
disk->info.addr.drive.controller == controller->idx) {
|
||||
if (esxVMX_AutodetectSCSIControllerModel
|
||||
(ctx, disk, &autodetectedModels[count]) < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
// autodetection fails when the disks attached to one controller
|
||||
// have inconsistent SCSI controller models
|
||||
for (k = 0; k < count; ++k) {
|
||||
if (autodetectedModels[k] != autodetectedModels[0]) {
|
||||
ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
|
||||
_("Disks on SCSI controller %d have inconsistent "
|
||||
"controller models, cannot autodetect model"),
|
||||
controller->idx);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
controller->model = autodetectedModels[0];
|
||||
}
|
||||
|
||||
if (controller->model != -1 &&
|
||||
|
@ -756,14 +968,19 @@ esxVMX_GatherSCSIControllers(virDomainDefPtr def, int virtualDev[4],
|
|||
"'controller' to be 'buslogic' or 'lsilogic' or "
|
||||
"'lsisas1068' or 'vmpvscsi' but found '%s'"),
|
||||
virDomainControllerModelTypeToString(controller->model));
|
||||
return -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
present[controller->idx] = true;
|
||||
virtualDev[controller->idx] = controller->model;
|
||||
}
|
||||
|
||||
return 0;
|
||||
result = 0;
|
||||
|
||||
cleanup:
|
||||
VIR_FREE(autodetectedModels);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2620,7 +2837,8 @@ esxVMX_FormatConfig(esxVI_Context *ctx, virCapsPtr caps, virDomainDefPtr def,
|
|||
}
|
||||
}
|
||||
|
||||
if (esxVMX_GatherSCSIControllers(def, scsi_virtualDev, scsi_present) < 0) {
|
||||
if (esxVMX_GatherSCSIControllers(ctx, def, scsi_virtualDev,
|
||||
scsi_present) < 0) {
|
||||
goto failure;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,8 +48,12 @@ esxVMX_HandleLegacySCSIDiskDriverName(virDomainDefPtr def,
|
|||
virDomainDiskDefPtr disk);
|
||||
|
||||
int
|
||||
esxVMX_GatherSCSIControllers(virDomainDefPtr def, int virtualDev[4],
|
||||
bool present[4]);
|
||||
esxVMX_AutodetectSCSIControllerModel(esxVI_Context *ctx,
|
||||
virDomainDiskDefPtr def, int *model);
|
||||
|
||||
int
|
||||
esxVMX_GatherSCSIControllers(esxVI_Context *ctx, virDomainDefPtr def,
|
||||
int virtualDev[4], bool present[4]);
|
||||
|
||||
char *
|
||||
esxVMX_AbsolutePathToDatastoreRelatedPath(esxVI_Context *ctx,
|
||||
|
|
Loading…
Reference in New Issue