mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	Fix resize volume and migrate volume to update volume path if DRS is applied on volume in datastore cluster (#5539)
* Fix resize volume and migrate volume to update volume path if DRS is applied on volume in datastore cluster * Change in constructors * Naming changes * Remove commented code * Refactor code for more readability * Addressed review comments on code refactor
This commit is contained in:
		
							parent
							
								
									6e216dd0d1
								
							
						
					
					
						commit
						cd4e7e031a
					
				| @ -30,6 +30,7 @@ import com.cloud.storage.Volume; | |||||||
| public class MigrateVolumeCommand extends Command { | public class MigrateVolumeCommand extends Command { | ||||||
|     long volumeId; |     long volumeId; | ||||||
|     String volumePath; |     String volumePath; | ||||||
|  |     String chainInfo; | ||||||
|     StorageFilerTO pool; |     StorageFilerTO pool; | ||||||
|     StorageFilerTO sourcePool; |     StorageFilerTO sourcePool; | ||||||
|     String attachedVmName; |     String attachedVmName; | ||||||
| @ -49,14 +50,22 @@ public class MigrateVolumeCommand extends Command { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public MigrateVolumeCommand(long volumeId, String volumePath, StoragePool pool, String attachedVmName, Volume.Type volumeType, int timeout) { |     public MigrateVolumeCommand(long volumeId, String volumePath, StoragePool pool, String attachedVmName, Volume.Type volumeType, int timeout) { | ||||||
|         this(volumeId,volumePath,pool,timeout); |         this(volumeId, volumePath, pool, timeout); | ||||||
|         this.attachedVmName = attachedVmName; |         this.attachedVmName = attachedVmName; | ||||||
|         this.volumeType = volumeType; |         this.volumeType = volumeType; | ||||||
|         this.setWait(timeout); |         this.setWait(timeout); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public MigrateVolumeCommand(long volumeId, String volumePath, String attachedVmName, StoragePool sourcePool, StoragePool targetPool, String hostGuidInTargetCluster) { |     public MigrateVolumeCommand(long volumeId, String volumePath, StoragePool pool, String attachedVmName, Volume.Type volumeType, int timeout, String chainInfo) { | ||||||
|         this(volumeId,volumePath,targetPool, attachedVmName, Volume.Type.UNKNOWN, -1); |         this(volumeId, volumePath, pool, timeout); | ||||||
|  |         this.attachedVmName = attachedVmName; | ||||||
|  |         this.volumeType = volumeType; | ||||||
|  |         this.chainInfo = chainInfo; | ||||||
|  |         this.setWait(timeout); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public MigrateVolumeCommand(long volumeId, String volumePath, String attachedVmName, StoragePool sourcePool, StoragePool targetPool, String hostGuidInTargetCluster, String chainInfo) { | ||||||
|  |         this(volumeId,volumePath,targetPool, attachedVmName, Volume.Type.UNKNOWN, -1, chainInfo); | ||||||
|         this.sourcePool = new StorageFilerTO(sourcePool); |         this.sourcePool = new StorageFilerTO(sourcePool); | ||||||
|         this.hostGuidInTargetCluster = hostGuidInTargetCluster; |         this.hostGuidInTargetCluster = hostGuidInTargetCluster; | ||||||
|     } |     } | ||||||
| @ -134,4 +143,6 @@ public class MigrateVolumeCommand extends Command { | |||||||
|     public int getWaitInMillSeconds() { |     public int getWaitInMillSeconds() { | ||||||
|         return getWait() * 1000; |         return getWait() * 1000; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public String getChainInfo() { return chainInfo; } | ||||||
| } | } | ||||||
|  | |||||||
| @ -29,6 +29,7 @@ public class ResizeVolumeCommand extends Command { | |||||||
|     private Long newSize; |     private Long newSize; | ||||||
|     private boolean shrinkOk; |     private boolean shrinkOk; | ||||||
|     private String vmInstance; |     private String vmInstance; | ||||||
|  |     private String chainInfo; | ||||||
| 
 | 
 | ||||||
|     /* For managed storage */ |     /* For managed storage */ | ||||||
|     private boolean managed; |     private boolean managed; | ||||||
| @ -47,6 +48,11 @@ public class ResizeVolumeCommand extends Command { | |||||||
|         this.managed = false; |         this.managed = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public ResizeVolumeCommand(String path, StorageFilerTO pool, Long currentSize, Long newSize, boolean shrinkOk, String vmInstance, String chainInfo) { | ||||||
|  |         this(path, pool, currentSize, newSize, shrinkOk, vmInstance); | ||||||
|  |         this.chainInfo = chainInfo; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public ResizeVolumeCommand(String path, StorageFilerTO pool, Long currentSize, Long newSize, boolean shrinkOk, String vmInstance, |     public ResizeVolumeCommand(String path, StorageFilerTO pool, Long currentSize, Long newSize, boolean shrinkOk, String vmInstance, | ||||||
|                                boolean isManaged, String iScsiName) { |                                boolean isManaged, String iScsiName) { | ||||||
|         this(path, pool, currentSize, newSize, shrinkOk, vmInstance); |         this(path, pool, currentSize, newSize, shrinkOk, vmInstance); | ||||||
| @ -81,6 +87,8 @@ public class ResizeVolumeCommand extends Command { | |||||||
| 
 | 
 | ||||||
|     public String get_iScsiName() {return iScsiName; } |     public String get_iScsiName() {return iScsiName; } | ||||||
| 
 | 
 | ||||||
|  |     public String getChainInfo() {return chainInfo; } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * {@inheritDoc} |      * {@inheritDoc} | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -24,6 +24,8 @@ import java.util.Map; | |||||||
| 
 | 
 | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| 
 | 
 | ||||||
|  | import com.cloud.agent.api.to.DiskTO; | ||||||
|  | import com.cloud.storage.Storage; | ||||||
| import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; | import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; | ||||||
| import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; | import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; | ||||||
| import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy; | import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy; | ||||||
| @ -447,8 +449,13 @@ public class AncientDataMotionStrategy implements DataMotionStrategy { | |||||||
|         int waitInterval = NumbersUtil.parseInt(value, Integer.parseInt(Config.MigrateWait.getDefaultValue())); |         int waitInterval = NumbersUtil.parseInt(value, Integer.parseInt(Config.MigrateWait.getDefaultValue())); | ||||||
| 
 | 
 | ||||||
|         VolumeInfo volume = (VolumeInfo)srcData; |         VolumeInfo volume = (VolumeInfo)srcData; | ||||||
|  |         StoragePool srcPool = (StoragePool)dataStoreMgr.getDataStore(srcData.getDataStore().getId(), DataStoreRole.Primary); | ||||||
|         StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(destData.getDataStore().getId(), DataStoreRole.Primary); |         StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(destData.getDataStore().getId(), DataStoreRole.Primary); | ||||||
|         MigrateVolumeCommand command = new MigrateVolumeCommand(volume.getId(), volume.getPath(), destPool, volume.getAttachedVmName(), volume.getVolumeType(), waitInterval); |         MigrateVolumeCommand command = new MigrateVolumeCommand(volume.getId(), volume.getPath(), destPool, volume.getAttachedVmName(), volume.getVolumeType(), waitInterval, volume.getChainInfo()); | ||||||
|  |         if (srcPool.getParent() != 0) { | ||||||
|  |             command.setContextParam(DiskTO.PROTOCOL_TYPE, Storage.StoragePoolType.DatastoreCluster.toString()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         EndPoint ep = selector.select(srcData, StorageAction.MIGRATEVOLUME); |         EndPoint ep = selector.select(srcData, StorageAction.MIGRATEVOLUME); | ||||||
|         Answer answer = null; |         Answer answer = null; | ||||||
|         if (ep == null) { |         if (ep == null) { | ||||||
|  | |||||||
| @ -775,9 +775,13 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|         String vmName = cmd.getInstanceName(); |         String vmName = cmd.getInstanceName(); | ||||||
|         long newSize = cmd.getNewSize() / ResourceType.bytesToKiB; |         long newSize = cmd.getNewSize() / ResourceType.bytesToKiB; | ||||||
|         long oldSize = cmd.getCurrentSize() / ResourceType.bytesToKiB; |         long oldSize = cmd.getCurrentSize() / ResourceType.bytesToKiB; | ||||||
|  |         boolean managed = cmd.isManaged(); | ||||||
|  |         String poolUUID = cmd.getPoolUuid(); | ||||||
|  |         String chainInfo = cmd.getChainInfo(); | ||||||
|         boolean useWorkerVm = false; |         boolean useWorkerVm = false; | ||||||
| 
 | 
 | ||||||
|         VmwareHypervisorHost hyperHost = getHyperHost(getServiceContext()); |         VmwareContext context = getServiceContext(); | ||||||
|  |         VmwareHypervisorHost hyperHost = getHyperHost(context); | ||||||
|         VirtualMachineMO vmMo = null; |         VirtualMachineMO vmMo = null; | ||||||
| 
 | 
 | ||||||
|         String vmdkDataStorePath = null; |         String vmdkDataStorePath = null; | ||||||
| @ -789,24 +793,6 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|             } else if (newSize == oldSize) { |             } else if (newSize == oldSize) { | ||||||
|                 return new ResizeVolumeAnswer(cmd, true, "success", newSize * ResourceType.bytesToKiB); |                 return new ResizeVolumeAnswer(cmd, true, "success", newSize * ResourceType.bytesToKiB); | ||||||
|             } |             } | ||||||
|             /* |  | ||||||
|             // FR41 this is yet to fix |  | ||||||
|             ManagedObjectReference morDS1 = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, cmd.getPoolUuid()); |  | ||||||
|             DatastoreMO dsMo1 = new DatastoreMO(hyperHost.getContext(), morDS1); |  | ||||||
|             vmdkDataStorePath = VmwareStorageLayoutHelper.getLegacyDatastorePathFromVmdkFileName(dsMo1, path + VMDK_EXTENSION); |  | ||||||
|             DatastoreFile dsFile1 = new DatastoreFile(vmdkDataStorePath); |  | ||||||
| 
 |  | ||||||
|             s_logger.debug("vDiskid does not exist for volume " + vmdkDataStorePath + " registering the disk now"); |  | ||||||
|             VirtualStorageObjectManagerMO vStorageObjectManagerMO = new VirtualStorageObjectManagerMO(getServiceContext()); |  | ||||||
|             try { |  | ||||||
|                 VStorageObject vStorageObject = vStorageObjectManagerMO.registerVirtualDisk(dsFile1, null, dsMo1.getOwnerDatacenter().second()); |  | ||||||
|                 VStorageObjectConfigInfo diskConfigInfo = vStorageObject.getConfig(); |  | ||||||
|                 ID vdiskId = diskConfigInfo.getId(); |  | ||||||
|             } catch (Throwable e) { |  | ||||||
|                 if (e instanceof AlreadyExistsFaultMsg) { |  | ||||||
| 
 |  | ||||||
|                 } |  | ||||||
|             }*/ |  | ||||||
| 
 | 
 | ||||||
|             if (vmName.equalsIgnoreCase("none")) { |             if (vmName.equalsIgnoreCase("none")) { | ||||||
|                 // OfflineVmwareMigration: we need to refactor the worker vm creation out for use in migration methods as well as here |                 // OfflineVmwareMigration: we need to refactor the worker vm creation out for use in migration methods as well as here | ||||||
| @ -852,26 +838,8 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|                 throw new Exception(msg); |                 throw new Exception(msg); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // OfflineVmwareMigration: 5. ignore/replace the rest of the try-block; It is the functional bit |  | ||||||
|             Pair<VirtualDisk, String> vdisk = vmMo.getDiskDevice(path); |  | ||||||
| 
 |  | ||||||
|             if (vdisk == null) { |  | ||||||
|                 if (s_logger.isTraceEnabled()) { |  | ||||||
|                     s_logger.trace("resize volume done (failed)"); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 throw new Exception("No such disk device: " + path); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // IDE virtual disk cannot be re-sized if VM is running |  | ||||||
|             if (vdisk.second() != null && vdisk.second().contains("ide")) { |  | ||||||
|                 throw new Exception("Re-sizing a virtual disk over an IDE controller is not supported in the VMware hypervisor. " + |  | ||||||
|                         "Please re-try when virtual disk is attached to a VM using a SCSI controller."); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (cmd.isManaged()) { |  | ||||||
|                 VmwareContext context = getServiceContext(); |  | ||||||
| 
 | 
 | ||||||
|  |             if (managed) { | ||||||
|                 ManagedObjectReference morCluster = hyperHost.getHyperHostCluster(); |                 ManagedObjectReference morCluster = hyperHost.getHyperHostCluster(); | ||||||
|                 ClusterMO clusterMO = new ClusterMO(context, morCluster); |                 ClusterMO clusterMO = new ClusterMO(context, morCluster); | ||||||
| 
 | 
 | ||||||
| @ -892,15 +860,26 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|                 _storageProcessor.expandDatastore(hostDatastoreSystem, dsMo); |                 _storageProcessor.expandDatastore(hostDatastoreSystem, dsMo); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (vdisk.second() != null && !vdisk.second().toLowerCase().startsWith("scsi")) { |             boolean volumePathChangeObserved = false; | ||||||
|                 s_logger.error("Unsupported disk device bus " + vdisk.second()); |             boolean datastoreChangeObserved = false; | ||||||
|                 throw new Exception("Unsupported disk device bus " + vdisk.second()); | 
 | ||||||
|  |             Pair<String, String> pathAndChainInfo = getNewPathAndChainInfoInDatastoreCluster(vmMo, path, chainInfo, managed, cmd.get_iScsiName(), poolUUID, cmd.getContextParam(DiskTO.PROTOCOL_TYPE)); | ||||||
|  |             Pair<String, String> poolUUIDandChainInfo = getNewPoolUUIDAndChainInfoInDatastoreCluster(vmMo, path, chainInfo, managed, cmd.get_iScsiName(), poolUUID, cmd.getContextParam(DiskTO.PROTOCOL_TYPE)); | ||||||
|  | 
 | ||||||
|  |             if (pathAndChainInfo != null) { | ||||||
|  |                 volumePathChangeObserved = true; | ||||||
|  |                 path = pathAndChainInfo.first(); | ||||||
|  |                 chainInfo = pathAndChainInfo.second(); | ||||||
|             } |             } | ||||||
|             VirtualDisk disk = vdisk.first(); | 
 | ||||||
|             if ((VirtualDiskFlatVer2BackingInfo) disk.getBacking() != null && ((VirtualDiskFlatVer2BackingInfo) disk.getBacking()).getParent() != null) { |             if (poolUUIDandChainInfo != null) { | ||||||
|                 s_logger.error("Resize is not supported because Disk device has Parent " + ((VirtualDiskFlatVer2BackingInfo) disk.getBacking()).getParent().getUuid()); |                 datastoreChangeObserved = true; | ||||||
|                 throw new Exception("Resize is not supported because Disk device has Parent " + ((VirtualDiskFlatVer2BackingInfo) disk.getBacking()).getParent().getUuid()); |                 poolUUID = poolUUIDandChainInfo.first(); | ||||||
|  |                 chainInfo = poolUUIDandChainInfo.second(); | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             // OfflineVmwareMigration: 5. ignore/replace the rest of the try-block; It is the functional bit | ||||||
|  |             VirtualDisk disk = getDiskAfterResizeDiskValidations(vmMo, path); | ||||||
|             String vmdkAbsFile = getAbsoluteVmdkFile(disk); |             String vmdkAbsFile = getAbsoluteVmdkFile(disk); | ||||||
| 
 | 
 | ||||||
|             if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) { |             if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) { | ||||||
| @ -922,7 +901,17 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|                 throw new Exception("Failed to configure VM to resize disk. vmName: " + vmName); |                 throw new Exception("Failed to configure VM to resize disk. vmName: " + vmName); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return new ResizeVolumeAnswer(cmd, true, "success", newSize * 1024); |             ResizeVolumeAnswer answer = new ResizeVolumeAnswer(cmd, true, "success", newSize * 1024); | ||||||
|  |             if (datastoreChangeObserved) { | ||||||
|  |                 answer.setContextParam("datastoreUUID", poolUUID); | ||||||
|  |                 answer.setContextParam("chainInfo", chainInfo); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (volumePathChangeObserved) { | ||||||
|  |                 answer.setContextParam("volumePath", path); | ||||||
|  |                 answer.setContextParam("chainInfo", chainInfo); | ||||||
|  |             } | ||||||
|  |             return answer; | ||||||
|         } catch (Exception e) { |         } catch (Exception e) { | ||||||
|             s_logger.error("Unable to resize volume", e); |             s_logger.error("Unable to resize volume", e); | ||||||
| 
 | 
 | ||||||
| @ -944,6 +933,79 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private VirtualDisk getDiskAfterResizeDiskValidations(VirtualMachineMO vmMo, String volumePath) throws Exception { | ||||||
|  |         Pair<VirtualDisk, String> vdisk = vmMo.getDiskDevice(volumePath); | ||||||
|  |         if (vdisk == null) { | ||||||
|  |             if (s_logger.isTraceEnabled()) { | ||||||
|  |                 s_logger.trace("resize volume done (failed)"); | ||||||
|  |             } | ||||||
|  |             throw new Exception("No such disk device: " + volumePath); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // IDE virtual disk cannot be re-sized if VM is running | ||||||
|  |         if (vdisk.second() != null && vdisk.second().contains("ide")) { | ||||||
|  |             throw new Exception("Re-sizing a virtual disk over an IDE controller is not supported in the VMware hypervisor. " + | ||||||
|  |                     "Please re-try when virtual disk is attached to a VM using a SCSI controller."); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (vdisk.second() != null && !vdisk.second().toLowerCase().startsWith("scsi")) { | ||||||
|  |             s_logger.error("Unsupported disk device bus " + vdisk.second()); | ||||||
|  |             throw new Exception("Unsupported disk device bus " + vdisk.second()); | ||||||
|  |         } | ||||||
|  |         VirtualDisk disk = vdisk.first(); | ||||||
|  |         if ((VirtualDiskFlatVer2BackingInfo) disk.getBacking() != null && ((VirtualDiskFlatVer2BackingInfo) disk.getBacking()).getParent() != null) { | ||||||
|  |             s_logger.error("Resize is not supported because Disk device has Parent " + ((VirtualDiskFlatVer2BackingInfo) disk.getBacking()).getParent().getUuid()); | ||||||
|  |             throw new Exception("Resize is not supported because Disk device has Parent " + ((VirtualDiskFlatVer2BackingInfo) disk.getBacking()).getParent().getUuid()); | ||||||
|  |         } | ||||||
|  |         return disk; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Pair<String, String> getNewPathAndChainInfoInDatastoreCluster(VirtualMachineMO vmMo, String path, String chainInfo, boolean managed, String iscsiName, String poolUUID, String poolType) throws Exception { | ||||||
|  |         VmwareContext context = getServiceContext(); | ||||||
|  |         VmwareHypervisorHost hyperHost = getHyperHost(context); | ||||||
|  |         if (poolType != null && poolType.equalsIgnoreCase(Storage.StoragePoolType.DatastoreCluster.toString())) { | ||||||
|  |             VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder(); | ||||||
|  |             VirtualMachineDiskInfo matchingExistingDisk = getMatchingExistingDiskWithVolumeDetails(diskInfoBuilder, path, chainInfo, managed, iscsiName, poolUUID, hyperHost, context); | ||||||
|  |             if (diskInfoBuilder != null && matchingExistingDisk != null) { | ||||||
|  |                 String[] diskChain = matchingExistingDisk.getDiskChain(); | ||||||
|  |                 DatastoreFile file = new DatastoreFile(diskChain[0]); | ||||||
|  |                 if (!file.getFileBaseName().equalsIgnoreCase(path)) { | ||||||
|  |                     if (s_logger.isInfoEnabled()) | ||||||
|  |                         s_logger.info("Detected disk-chain top file change on volume: " + path + " -> " + file.getFileBaseName()); | ||||||
|  |                     path = file.getFileBaseName(); | ||||||
|  |                     chainInfo = _gson.toJson(matchingExistingDisk); | ||||||
|  |                     return new Pair<>(path, chainInfo); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Pair<String, String> getNewPoolUUIDAndChainInfoInDatastoreCluster(VirtualMachineMO vmMo, String path, String chainInfo, boolean managed, String iscsiName, String poolUUID, String poolType) throws Exception { | ||||||
|  |         VmwareContext context = getServiceContext(); | ||||||
|  |         VmwareHypervisorHost hyperHost = getHyperHost(context); | ||||||
|  |         if (poolType != null && poolType.equalsIgnoreCase(Storage.StoragePoolType.DatastoreCluster.toString())) { | ||||||
|  |             VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder(); | ||||||
|  |             VirtualMachineDiskInfo matchingExistingDisk = getMatchingExistingDiskWithVolumeDetails(diskInfoBuilder, path, chainInfo, managed, iscsiName, poolUUID, hyperHost, context); | ||||||
|  |             if (diskInfoBuilder != null && matchingExistingDisk != null) { | ||||||
|  |                 String[] diskChain = matchingExistingDisk.getDiskChain(); | ||||||
|  |                 DatastoreFile file = new DatastoreFile(diskChain[0]); | ||||||
|  |                 DatacenterMO dcMo = new DatacenterMO(hyperHost.getContext(), hyperHost.getHyperHostDatacenter()); | ||||||
|  |                 DatastoreMO diskDatastoreMofromVM = new DatastoreMO(context, dcMo.findDatastore(file.getDatastoreName())); | ||||||
|  |                 if (diskDatastoreMofromVM != null) { | ||||||
|  |                     String actualPoolUuid = diskDatastoreMofromVM.getCustomFieldValue(CustomFieldConstants.CLOUD_UUID); | ||||||
|  |                     if (!actualPoolUuid.equalsIgnoreCase(poolUUID)) { | ||||||
|  |                         s_logger.warn(String.format("Volume %s found to be in a different storage pool %s", path, actualPoolUuid)); | ||||||
|  |                         poolUUID = actualPoolUuid; | ||||||
|  |                         chainInfo = _gson.toJson(matchingExistingDisk); | ||||||
|  |                         return new Pair<>(poolUUID, chainInfo); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     protected Answer execute(CheckNetworkCommand cmd) { |     protected Answer execute(CheckNetworkCommand cmd) { | ||||||
|         if (s_logger.isInfoEnabled()) { |         if (s_logger.isInfoEnabled()) { | ||||||
|             s_logger.info("Executing resource CheckNetworkCommand " + _gson.toJson(cmd)); |             s_logger.info("Executing resource CheckNetworkCommand " + _gson.toJson(cmd)); | ||||||
| @ -1911,7 +1973,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|                                 s_logger.error(msg); |                                 s_logger.error(msg); | ||||||
|                                 throw new Exception(msg); |                                 throw new Exception(msg); | ||||||
|                             } |                             } | ||||||
|                         if (rootDisk.getDetails().get(DiskTO.PROTOCOL_TYPE) != null && rootDisk.getDetails().get(DiskTO.PROTOCOL_TYPE).equalsIgnoreCase("DatastoreCluster")) { |                         if (rootDisk.getDetails().get(DiskTO.PROTOCOL_TYPE) != null && rootDisk.getDetails().get(DiskTO.PROTOCOL_TYPE).equalsIgnoreCase(Storage.StoragePoolType.DatastoreCluster.toString())) { | ||||||
|                             if (diskInfoBuilder != null) { |                             if (diskInfoBuilder != null) { | ||||||
|                                 DatastoreMO diskDatastoreMofromVM = getDataStoreWhereDiskExists(hyperHost, context, diskInfoBuilder, rootDisk, diskDatastores); |                                 DatastoreMO diskDatastoreMofromVM = getDataStoreWhereDiskExists(hyperHost, context, diskInfoBuilder, rootDisk, diskDatastores); | ||||||
|                                 if (diskDatastoreMofromVM != null) { |                                 if (diskDatastoreMofromVM != null) { | ||||||
| @ -3254,70 +3316,82 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|             nicIndex++; |             nicIndex++; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     private VirtualMachineDiskInfo getMatchingExistingDiskWithVolumeDetails(VirtualMachineDiskInfoBuilder diskInfoBuilder, String volumePath, | ||||||
|  |                                                                              String chainInfo, boolean isManaged, String iScsiName, String datastoreUUID, | ||||||
|  |                                                                              VmwareHypervisorHost hyperHost, VmwareContext context) throws Exception { | ||||||
| 
 | 
 | ||||||
|     private VirtualMachineDiskInfo getMatchingExistingDisk(VirtualMachineDiskInfoBuilder diskInfoBuilder, DiskTO vol, VmwareHypervisorHost hyperHost, VmwareContext context) |         Pair<String, String> dsNameAndFileName = getVMDiskInfo(volumePath, isManaged, iScsiName, datastoreUUID, hyperHost, context); | ||||||
|             throws Exception { |         String dsName = dsNameAndFileName.first(); | ||||||
|         if (diskInfoBuilder != null) { |         String diskBackingFileBaseName = dsNameAndFileName.second(); | ||||||
|             VolumeObjectTO volume = (VolumeObjectTO) vol.getData(); |  | ||||||
| 
 | 
 | ||||||
|             String dsName = null; |         VirtualMachineDiskInfo diskInfo = diskInfoBuilder.getDiskInfoByBackingFileBaseName(diskBackingFileBaseName, dsName); | ||||||
|             String diskBackingFileBaseName = null; |         if (diskInfo != null) { | ||||||
| 
 |             s_logger.info("Found existing disk info from volume path: " + volumePath); | ||||||
|             Map<String, String> details = vol.getDetails(); |             return diskInfo; | ||||||
|             boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED)); |         } else { | ||||||
| 
 |             if (chainInfo != null) { | ||||||
|             if (isManaged) { |                 VirtualMachineDiskInfo infoInChain = _gson.fromJson(chainInfo, VirtualMachineDiskInfo.class); | ||||||
|                 String iScsiName = details.get(DiskTO.IQN); |                 if (infoInChain != null) { | ||||||
| 
 |                     String[] disks = infoInChain.getDiskChain(); | ||||||
|                 // if the storage is managed, iScsiName should not be null |                     if (disks.length > 0) { | ||||||
|                 dsName = VmwareResource.getDatastoreName(iScsiName); |                         for (String diskPath : disks) { | ||||||
| 
 |                             DatastoreFile file = new DatastoreFile(diskPath); | ||||||
|                 diskBackingFileBaseName = new DatastoreFile(volume.getPath()).getFileBaseName(); |                             diskInfo = diskInfoBuilder.getDiskInfoByBackingFileBaseName(file.getFileBaseName(), dsName); | ||||||
|             } else { |  | ||||||
|                 ManagedObjectReference morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, volume.getDataStore().getUuid()); |  | ||||||
|                 DatastoreMO dsMo = new DatastoreMO(context, morDs); |  | ||||||
| 
 |  | ||||||
|                 dsName = dsMo.getName(); |  | ||||||
| 
 |  | ||||||
|                 diskBackingFileBaseName = volume.getPath(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             VirtualMachineDiskInfo diskInfo = diskInfoBuilder.getDiskInfoByBackingFileBaseName(diskBackingFileBaseName, dsName); |  | ||||||
|             if (diskInfo != null) { |  | ||||||
|                 s_logger.info("Found existing disk info from volume path: " + volume.getPath()); |  | ||||||
|                 return diskInfo; |  | ||||||
|             } else { |  | ||||||
|                 String chainInfo = volume.getChainInfo(); |  | ||||||
|                 if (chainInfo != null) { |  | ||||||
|                     VirtualMachineDiskInfo infoInChain = _gson.fromJson(chainInfo, VirtualMachineDiskInfo.class); |  | ||||||
|                     if (infoInChain != null) { |  | ||||||
|                         String[] disks = infoInChain.getDiskChain(); |  | ||||||
|                         if (disks.length > 0) { |  | ||||||
|                             for (String diskPath : disks) { |  | ||||||
|                                 DatastoreFile file = new DatastoreFile(diskPath); |  | ||||||
|                                 diskInfo = diskInfoBuilder.getDiskInfoByBackingFileBaseName(file.getFileBaseName(), dsName); |  | ||||||
|                                 if (diskInfo != null) { |  | ||||||
|                                     s_logger.info("Found existing disk from chain info: " + diskPath); |  | ||||||
|                                     return diskInfo; |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         if (diskInfo == null) { |  | ||||||
|                             diskInfo = diskInfoBuilder.getDiskInfoByDeviceBusName(infoInChain.getDiskDeviceBusName()); |  | ||||||
|                             if (diskInfo != null) { |                             if (diskInfo != null) { | ||||||
|                                 s_logger.info("Found existing disk from from chain device bus information: " + infoInChain.getDiskDeviceBusName()); |                                 s_logger.info("Found existing disk from chain info: " + diskPath); | ||||||
|                                 return diskInfo; |                                 return diskInfo; | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  | 
 | ||||||
|  |                     if (diskInfo == null) { | ||||||
|  |                         diskInfo = diskInfoBuilder.getDiskInfoByDeviceBusName(infoInChain.getDiskDeviceBusName()); | ||||||
|  |                         if (diskInfo != null) { | ||||||
|  |                             s_logger.info("Found existing disk from from chain device bus information: " + infoInChain.getDiskDeviceBusName()); | ||||||
|  |                             return diskInfo; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private Pair<String, String> getVMDiskInfo(String volumePath, boolean isManaged, String iScsiName, String datastoreUUID, | ||||||
|  |                                                VmwareHypervisorHost hyperHost, VmwareContext context) throws Exception { | ||||||
|  |         String dsName = null; | ||||||
|  |         String diskBackingFileBaseName = null; | ||||||
|  | 
 | ||||||
|  |         if (isManaged) { | ||||||
|  |             // if the storage is managed, iScsiName should not be null | ||||||
|  |             dsName = VmwareResource.getDatastoreName(iScsiName); | ||||||
|  |             diskBackingFileBaseName = new DatastoreFile(volumePath).getFileBaseName(); | ||||||
|  |         } else { | ||||||
|  |             ManagedObjectReference morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, datastoreUUID); | ||||||
|  |             DatastoreMO dsMo = new DatastoreMO(context, morDs); | ||||||
|  |             dsName = dsMo.getName(); | ||||||
|  |             diskBackingFileBaseName = volumePath; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return new Pair<>(dsName, diskBackingFileBaseName); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private VirtualMachineDiskInfo getMatchingExistingDisk(VirtualMachineDiskInfoBuilder diskInfoBuilder, DiskTO vol, VmwareHypervisorHost hyperHost, VmwareContext context) | ||||||
|  |             throws Exception { | ||||||
|  |         if (diskInfoBuilder != null) { | ||||||
|  |             VolumeObjectTO volume = (VolumeObjectTO) vol.getData(); | ||||||
|  |             String chainInfo = volume.getChainInfo(); | ||||||
|  |             Map<String, String> details = vol.getDetails(); | ||||||
|  |             boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED)); | ||||||
|  |             String iScsiName = details.get(DiskTO.IQN); | ||||||
|  |             String datastoreUUID = volume.getDataStore().getUuid(); | ||||||
|  | 
 | ||||||
|  |             return getMatchingExistingDiskWithVolumeDetails(diskInfoBuilder, volume.getPath(), chainInfo, isManaged, iScsiName, datastoreUUID, hyperHost, context); | ||||||
|  |         } else { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private String getDiskController(VirtualMachineMO vmMo, VirtualMachineDiskInfo matchingExistingDisk, DiskTO vol, Pair<String, String> controllerInfo, boolean deployAsIs) throws Exception { |     private String getDiskController(VirtualMachineMO vmMo, VirtualMachineDiskInfo matchingExistingDisk, DiskTO vol, Pair<String, String> controllerInfo, boolean deployAsIs) throws Exception { | ||||||
|         DiskControllerType controllerType = DiskControllerType.none; |         DiskControllerType controllerType = DiskControllerType.none; | ||||||
|         if (deployAsIs && matchingExistingDisk != null) { |         if (deployAsIs && matchingExistingDisk != null) { | ||||||
| @ -4791,7 +4865,9 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|     // OfflineVmwareMigration: refactor to be able to handle a detached volume |     // OfflineVmwareMigration: refactor to be able to handle a detached volume | ||||||
|     private Answer execute(MigrateVolumeCommand cmd) { |     private Answer execute(MigrateVolumeCommand cmd) { | ||||||
|         String volumePath = cmd.getVolumePath(); |         String volumePath = cmd.getVolumePath(); | ||||||
|  |         String chainInfo = cmd.getChainInfo(); | ||||||
|         StorageFilerTO poolTo = cmd.getPool(); |         StorageFilerTO poolTo = cmd.getPool(); | ||||||
|  |         VolumeObjectTO volumeObjectTO = (VolumeObjectTO)cmd.getSrcData(); | ||||||
| 
 | 
 | ||||||
|         if (s_logger.isInfoEnabled()) { |         if (s_logger.isInfoEnabled()) { | ||||||
|             s_logger.info("Executing resource MigrateVolumeCommand: " + _gson.toJson(cmd)); |             s_logger.info("Executing resource MigrateVolumeCommand: " + _gson.toJson(cmd)); | ||||||
| @ -4838,6 +4914,22 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             DatastoreMO targetDsMo = new DatastoreMO(srcHyperHost.getContext(), morDs); |             DatastoreMO targetDsMo = new DatastoreMO(srcHyperHost.getContext(), morDs); | ||||||
|  |             if (cmd.getContextParam(DiskTO.PROTOCOL_TYPE) != null && cmd.getContextParam(DiskTO.PROTOCOL_TYPE).equalsIgnoreCase("DatastoreCluster")) { | ||||||
|  |                 VmwareContext context = getServiceContext(); | ||||||
|  |                 VmwareHypervisorHost hyperHost = getHyperHost(context); | ||||||
|  |                 VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder(); | ||||||
|  |                 VirtualMachineDiskInfo matchingExistingDisk = getMatchingExistingDiskWithVolumeDetails(diskInfoBuilder, volumePath, chainInfo, false, null, poolTo.getUuid(), hyperHost, context); | ||||||
|  |                 if (diskInfoBuilder != null && matchingExistingDisk != null) { | ||||||
|  |                     String[] diskChain = matchingExistingDisk.getDiskChain(); | ||||||
|  |                     DatastoreFile file = new DatastoreFile(diskChain[0]); | ||||||
|  |                     if (!file.getFileBaseName().equalsIgnoreCase(volumePath)) { | ||||||
|  |                         if (s_logger.isInfoEnabled()) | ||||||
|  |                             s_logger.info("Detected disk-chain top file change on volume: " + volumePath + " -> " + file.getFileBaseName()); | ||||||
|  |                         volumePath = file.getFileBaseName(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             String fullVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(targetDsMo, vmName, volumePath + VMDK_EXTENSION); |             String fullVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(targetDsMo, vmName, volumePath + VMDK_EXTENSION); | ||||||
|             Pair<VirtualDisk, String> diskInfo = getVirtualDiskInfo(vmMo, appendFileType(volumePath, VMDK_EXTENSION)); |             Pair<VirtualDisk, String> diskInfo = getVirtualDiskInfo(vmMo, appendFileType(volumePath, VMDK_EXTENSION)); | ||||||
|             String vmdkAbsFile = getAbsoluteVmdkFile(diskInfo.first()); |             String vmdkAbsFile = getAbsoluteVmdkFile(diskInfo.first()); | ||||||
| @ -4892,7 +4984,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|                     } |                     } | ||||||
|             } |             } | ||||||
|             VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder(); |             VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder(); | ||||||
|             String chainInfo = _gson.toJson(diskInfoBuilder.getDiskInfoByBackingFileBaseName(volumePath, targetDsMo.getName())); |             chainInfo = _gson.toJson(diskInfoBuilder.getDiskInfoByBackingFileBaseName(volumePath, targetDsMo.getName())); | ||||||
|             MigrateVolumeAnswer answer = new MigrateVolumeAnswer(cmd, true, null, volumePath); |             MigrateVolumeAnswer answer = new MigrateVolumeAnswer(cmd, true, null, volumePath); | ||||||
|             answer.setVolumeChainInfo(chainInfo); |             answer.setVolumeChainInfo(chainInfo); | ||||||
|             return answer; |             return answer; | ||||||
|  | |||||||
| @ -26,6 +26,8 @@ import java.util.Map; | |||||||
| 
 | 
 | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| 
 | 
 | ||||||
|  | import com.cloud.agent.api.to.DiskTO; | ||||||
|  | import com.cloud.storage.Storage; | ||||||
| import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; | import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; | ||||||
| import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy; | import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy; | ||||||
| import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; | import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; | ||||||
| @ -247,7 +249,10 @@ public class VmwareStorageMotionStrategy implements DataMotionStrategy { | |||||||
|                 , vm != null ? vm.getInstanceName() : null |                 , vm != null ? vm.getInstanceName() : null | ||||||
|                 , sourcePool |                 , sourcePool | ||||||
|                 , targetPool |                 , targetPool | ||||||
|                 , hostIdForVmAndHostGuidInTargetCluster.second()); |                 , hostIdForVmAndHostGuidInTargetCluster.second(), ((VolumeObjectTO) srcData.getTO()).getChainInfo()); | ||||||
|  |         if (sourcePool.getParent() != 0) { | ||||||
|  |             cmd.setContextParam(DiskTO.PROTOCOL_TYPE, Storage.StoragePoolType.DatastoreCluster.toString()); | ||||||
|  |         } | ||||||
|         Answer answer; |         Answer answer; | ||||||
|         if (hostId != null) { |         if (hostId != null) { | ||||||
|             answer = agentMgr.easySend(hostId, cmd); |             answer = agentMgr.easySend(hostId, cmd); | ||||||
|  | |||||||
| @ -26,6 +26,8 @@ import java.util.UUID; | |||||||
| 
 | 
 | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| 
 | 
 | ||||||
|  | import com.cloud.agent.api.to.DiskTO; | ||||||
|  | import com.cloud.storage.VolumeVO; | ||||||
| import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; | import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; | ||||||
| import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; | import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; | ||||||
| import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; | import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; | ||||||
| @ -408,7 +410,10 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri | |||||||
| 
 | 
 | ||||||
|         ResizeVolumeCommand resizeCmd = |         ResizeVolumeCommand resizeCmd = | ||||||
|                 new ResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(), resizeParameter.newSize, resizeParameter.shrinkOk, |                 new ResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(), resizeParameter.newSize, resizeParameter.shrinkOk, | ||||||
|                         resizeParameter.instanceName); |                         resizeParameter.instanceName, vol.getChainInfo()); | ||||||
|  |         if (pool.getParent() != 0) { | ||||||
|  |             resizeCmd.setContextParam(DiskTO.PROTOCOL_TYPE, Storage.StoragePoolType.DatastoreCluster.toString()); | ||||||
|  |         } | ||||||
|         CreateCmdResult result = new CreateCmdResult(null, null); |         CreateCmdResult result = new CreateCmdResult(null, null); | ||||||
|         try { |         try { | ||||||
|             ResizeVolumeAnswer answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, resizeParameter.hosts, resizeCmd); |             ResizeVolumeAnswer answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, resizeParameter.hosts, resizeCmd); | ||||||
| @ -418,6 +423,8 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri | |||||||
| 
 | 
 | ||||||
|                 vol.setSize(finalSize); |                 vol.setSize(finalSize); | ||||||
|                 vol.update(); |                 vol.update(); | ||||||
|  | 
 | ||||||
|  |                 updateVolumePathDetails(vol, answer); | ||||||
|             } else if (answer != null) { |             } else if (answer != null) { | ||||||
|                 result.setResult(answer.getDetails()); |                 result.setResult(answer.getDetails()); | ||||||
|             } else { |             } else { | ||||||
| @ -433,6 +440,31 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri | |||||||
|         callback.complete(result); |         callback.complete(result); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private void updateVolumePathDetails(VolumeObject vol, ResizeVolumeAnswer answer) { | ||||||
|  |         VolumeVO volumeVO = volumeDao.findById(vol.getId()); | ||||||
|  |         String datastoreUUID = answer.getContextParam("datastoreUUID"); | ||||||
|  |         if (datastoreUUID != null) { | ||||||
|  |             StoragePoolVO storagePoolVO = primaryStoreDao.findByUuid(datastoreUUID); | ||||||
|  |             if (storagePoolVO != null) { | ||||||
|  |                 volumeVO.setPoolId(storagePoolVO.getId()); | ||||||
|  |             } else { | ||||||
|  |                 s_logger.warn(String.format("Unable to find datastore %s while updating the new datastore of the volume %d", datastoreUUID, vol.getId())); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         String volumePath = answer.getContextParam("volumePath"); | ||||||
|  |         if (volumePath != null) { | ||||||
|  |             volumeVO.setPath(volumePath); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         String chainInfo = answer.getContextParam("chainInfo"); | ||||||
|  |         if (chainInfo != null) { | ||||||
|  |             volumeVO.setChainInfo(chainInfo); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         volumeDao.update(volumeVO.getId(), volumeVO); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {} |     public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {} | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user