Timeout config to copy the disks of remote KVM instance while importing the instance from an external host (#9213)

* Added timeout config to copy the disks of remote KVM instance while importing the instance from an external host

* Updated copy config units to mins

* Cleanup remote converted file and local file when copy failed
This commit is contained in:
Suresh Kumar Anaparti 2024-06-21 10:28:18 +05:30 committed by GitHub
parent 097359bef9
commit 5ab23cd9c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 58 additions and 29 deletions

View File

@ -30,6 +30,15 @@ public interface UnmanagedVMsManager extends VmImportService, UnmanageVMService,
"If set to true, do not remove VM nics (and its MAC addresses) when unmanaging a VM, leaving them allocated but not reserved. " + "If set to true, do not remove VM nics (and its MAC addresses) when unmanaging a VM, leaving them allocated but not reserved. " +
"If set to false, nics are removed and MAC addresses can be reassigned", true, ConfigKey.Scope.Zone); "If set to false, nics are removed and MAC addresses can be reassigned", true, ConfigKey.Scope.Zone);
ConfigKey<Integer> RemoteKvmInstanceDisksCopyTimeout = new ConfigKey<>(Integer.class,
"remote.kvm.instance.disks.copy.timeout",
"Advanced",
"30",
"Timeout (in mins) to prepare and copy the disks of remote KVM instance while importing the instance from an external host",
true,
ConfigKey.Scope.Global,
null);
static boolean isSupported(Hypervisor.HypervisorType hypervisorType) { static boolean isSupported(Hypervisor.HypervisorType hypervisorType) {
return hypervisorType == VMware || hypervisorType == KVM; return hypervisorType == VMware || hypervisorType == KVM;
} }

View File

@ -23,14 +23,11 @@ import com.cloud.agent.api.to.StorageFilerTO;
@LogLevel(LogLevel.Log4jLevel.Trace) @LogLevel(LogLevel.Log4jLevel.Trace)
public class CopyRemoteVolumeCommand extends Command { public class CopyRemoteVolumeCommand extends Command {
String remoteIp; String remoteIp;
String username; String username;
String password; String password;
String srcFile; String srcFile;
String tmpPath; String tmpPath;
StorageFilerTO storageFilerTO; StorageFilerTO storageFilerTO;
public CopyRemoteVolumeCommand(String remoteIp, String username, String password) { public CopyRemoteVolumeCommand(String remoteIp, String username, String password) {

View File

@ -5336,20 +5336,31 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
/* /*
Scp volume from remote host to local directory Scp volume from remote host to local directory
*/ */
public String copyVolume(String srcIp, String username, String password, String localDir, String remoteFile, String tmpPath) { public String copyVolume(String srcIp, String username, String password, String localDir, String remoteFile, String tmpPath, int timeoutInSecs) {
String outputFile = UUID.randomUUID().toString();
try { try {
String outputFile = UUID.randomUUID().toString();
StringBuilder command = new StringBuilder("qemu-img convert -O qcow2 "); StringBuilder command = new StringBuilder("qemu-img convert -O qcow2 ");
command.append(remoteFile); command.append(remoteFile);
command.append(" "+tmpPath); command.append(" " + tmpPath);
command.append(outputFile); command.append(outputFile);
s_logger.debug("Converting remoteFile: "+remoteFile); s_logger.debug(String.format("Converting remote disk file: %s, output file: %s%s (timeout: %d secs)", remoteFile, tmpPath, outputFile, timeoutInSecs));
SshHelper.sshExecute(srcIp, 22, username, null, password, command.toString()); SshHelper.sshExecute(srcIp, 22, username, null, password, command.toString(), timeoutInSecs * 1000);
s_logger.debug("Copying remoteFile to: "+localDir); s_logger.debug("Copying converted remote disk file " + outputFile + " to: " + localDir);
SshHelper.scpFrom(srcIp, 22, username, null, password, localDir, tmpPath+outputFile); SshHelper.scpFrom(srcIp, 22, username, null, password, localDir, tmpPath + outputFile);
s_logger.debug("Successfully copyied remoteFile to: "+localDir+"/"+outputFile); s_logger.debug("Successfully copied converted remote disk file to: " + localDir + "/" + outputFile);
return outputFile; return outputFile;
} catch (Exception e) { } catch (Exception e) {
try {
String deleteRemoteConvertedFileCmd = String.format("rm -f %s%s", tmpPath, outputFile);
SshHelper.sshExecute(srcIp, 22, username, null, password, deleteRemoteConvertedFileCmd);
} catch (Exception ignored) {
}
try {
FileUtils.deleteQuietly(new File(localDir + "/" + outputFile));
} catch (Exception ignored) {
}
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }

View File

@ -46,7 +46,6 @@ public final class LibvirtCopyRemoteVolumeCommandWrapper extends CommandWrapper<
@Override @Override
public Answer execute(final CopyRemoteVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { public Answer execute(final CopyRemoteVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
String result = null;
String srcIp = command.getRemoteIp(); String srcIp = command.getRemoteIp();
String username = command.getUsername(); String username = command.getUsername();
String password = command.getPassword(); String password = command.getPassword();
@ -56,23 +55,25 @@ public final class LibvirtCopyRemoteVolumeCommandWrapper extends CommandWrapper<
KVMStoragePoolManager poolMgr = libvirtComputingResource.getStoragePoolMgr(); KVMStoragePoolManager poolMgr = libvirtComputingResource.getStoragePoolMgr();
KVMStoragePool pool = poolMgr.getStoragePool(storageFilerTO.getType(), storageFilerTO.getUuid()); KVMStoragePool pool = poolMgr.getStoragePool(storageFilerTO.getType(), storageFilerTO.getUuid());
String dstPath = pool.getLocalPath(); String dstPath = pool.getLocalPath();
int timeoutInSecs = command.getWait();
try { try {
if (storageFilerTO.getType() == Storage.StoragePoolType.Filesystem || if (storageFilerTO.getType() == Storage.StoragePoolType.Filesystem ||
storageFilerTO.getType() == Storage.StoragePoolType.NetworkFilesystem) { storageFilerTO.getType() == Storage.StoragePoolType.NetworkFilesystem) {
String filename = libvirtComputingResource.copyVolume(srcIp, username, password, dstPath, srcFile, tmpPath); String filename = libvirtComputingResource.copyVolume(srcIp, username, password, dstPath, srcFile, tmpPath, timeoutInSecs);
s_logger.debug("Volume Copy Successful"); s_logger.debug("Volume " + srcFile + " copy successful, copied to file: " + filename);
final KVMPhysicalDisk vol = pool.getPhysicalDisk(filename); final KVMPhysicalDisk vol = pool.getPhysicalDisk(filename);
final String path = vol.getPath(); final String path = vol.getPath();
long size = getVirtualSizeFromFile(path); long size = getVirtualSizeFromFile(path);
return new CopyRemoteVolumeAnswer(command, "", filename, size); return new CopyRemoteVolumeAnswer(command, "", filename, size);
} else { } else {
return new Answer(command, false, "Unsupported Storage Pool"); String msg = "Unsupported storage pool type: " + storageFilerTO.getType().toString() + ", only local and NFS pools are supported";
return new Answer(command, false, msg);
} }
} catch (final Exception e) { } catch (final Exception e) {
s_logger.error("Error while copying file from remote host: "+ e.getMessage()); s_logger.error("Error while copying volume file from remote host: " + e.getMessage(), e);
return new Answer(command, false, result); String msg = "Failed to copy volume due to: " + e.getMessage();
return new Answer(command, false, msg);
} }
} }

View File

@ -135,6 +135,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.userdata.UserDataManager; import org.apache.cloudstack.userdata.UserDataManager;
import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper; import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.cloudstack.vm.UnmanagedVMsManager;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
@ -557,6 +558,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.key()); configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.key());
configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_MAX_CONNECTIONS.key()); configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_MAX_CONNECTIONS.key());
configValuesForValidation.add(UserDataManager.VM_USERDATA_MAX_LENGTH_STRING); configValuesForValidation.add(UserDataManager.VM_USERDATA_MAX_LENGTH_STRING);
configValuesForValidation.add(UnmanagedVMsManager.RemoteKvmInstanceDisksCopyTimeout.key());
} }
private void weightBasedParametersForValidation() { private void weightBasedParametersForValidation() {

View File

@ -794,13 +794,19 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
} }
} }
copyRemoteVolumeCommand.setTempPath(tmpPath); copyRemoteVolumeCommand.setTempPath(tmpPath);
int copyTimeout = UnmanagedVMsManager.RemoteKvmInstanceDisksCopyTimeout.value();
if (copyTimeout <= 0) {
copyTimeout = Integer.valueOf(UnmanagedVMsManager.RemoteKvmInstanceDisksCopyTimeout.defaultValue());
}
int copyTimeoutInSecs = copyTimeout * 60;
copyRemoteVolumeCommand.setWait(copyTimeoutInSecs);
Answer answer = agentManager.easySend(dest.getHost().getId(), copyRemoteVolumeCommand); Answer answer = agentManager.easySend(dest.getHost().getId(), copyRemoteVolumeCommand);
if (!(answer instanceof CopyRemoteVolumeAnswer)) { if (!(answer instanceof CopyRemoteVolumeAnswer)) {
throw new CloudRuntimeException("Error while copying volume"); throw new CloudRuntimeException("Error while copying volume of remote instance: " + answer.getDetails());
} }
CopyRemoteVolumeAnswer copyRemoteVolumeAnswer = (CopyRemoteVolumeAnswer) answer; CopyRemoteVolumeAnswer copyRemoteVolumeAnswer = (CopyRemoteVolumeAnswer) answer;
if(!copyRemoteVolumeAnswer.getResult()) { if(!copyRemoteVolumeAnswer.getResult()) {
throw new CloudRuntimeException("Error while copying volume"); throw new CloudRuntimeException("Unable to copy volume of remote instance");
} }
diskProfile.setSize(copyRemoteVolumeAnswer.getSize()); diskProfile.setSize(copyRemoteVolumeAnswer.getSize());
DiskProfile profile = volumeManager.updateImportedVolume(type, diskOffering, vm, template, deviceId, DiskProfile profile = volumeManager.updateImportedVolume(type, diskOffering, vm, template, deviceId,
@ -813,7 +819,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
Volume.Type type, VirtualMachineTemplate template, Volume.Type type, VirtualMachineTemplate template,
Long deviceId, Long hostId, String diskPath, DiskProfile diskProfile) { Long deviceId, Long hostId, String diskPath, DiskProfile diskProfile) {
List<StoragePoolVO> storagePools = primaryDataStoreDao.findLocalStoragePoolsByHostAndTags(hostId, null); List<StoragePoolVO> storagePools = primaryDataStoreDao.findLocalStoragePoolsByHostAndTags(hostId, null);
if(storagePools.size() < 1) { if(storagePools.size() < 1) {
throw new CloudRuntimeException("Local Storage not found for host"); throw new CloudRuntimeException("Local Storage not found for host");
} }
@ -826,7 +831,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
return new Pair<>(profile, storagePool); return new Pair<>(profile, storagePool);
} }
private Pair<DiskProfile, StoragePool> importKVMSharedDisk(VirtualMachine vm, DiskOffering diskOffering, private Pair<DiskProfile, StoragePool> importKVMSharedDisk(VirtualMachine vm, DiskOffering diskOffering,
Volume.Type type, VirtualMachineTemplate template, Volume.Type type, VirtualMachineTemplate template,
Long deviceId, Long poolId, String diskPath, DiskProfile diskProfile) { Long deviceId, Long poolId, String diskPath, DiskProfile diskProfile) {
@ -838,7 +842,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
return new Pair<>(profile, storagePool); return new Pair<>(profile, storagePool);
} }
private Pair<DiskProfile, StoragePool> importDisk(UnmanagedInstanceTO.Disk disk, VirtualMachine vm, Cluster cluster, DiskOffering diskOffering, private Pair<DiskProfile, StoragePool> importDisk(UnmanagedInstanceTO.Disk disk, VirtualMachine vm, Cluster cluster, DiskOffering diskOffering,
Volume.Type type, String name, Long diskSize, Long minIops, Long maxIops, VirtualMachineTemplate template, Volume.Type type, String name, Long diskSize, Long minIops, Long maxIops, VirtualMachineTemplate template,
Account owner, Long deviceId) { Account owner, Long deviceId) {
@ -2335,7 +2338,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s. Suitable deployment destination not found", userVm.getInstanceName())); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s. Suitable deployment destination not found", userVm.getInstanceName()));
} }
Map<Volume, StoragePool> storage = dest.getStorageForDisks(); Map<Volume, StoragePool> storage = dest.getStorageForDisks();
Volume volume = volumeDao.findById(diskProfile.getVolumeId()); Volume volume = volumeDao.findById(diskProfile.getVolumeId());
StoragePool storagePool = storage.get(volume); StoragePool storagePool = storage.get(volume);
@ -2355,7 +2357,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
} }
diskProfile.setSize(checkVolumeAnswer.getSize()); diskProfile.setSize(checkVolumeAnswer.getSize());
List<Pair<DiskProfile, StoragePool>> diskProfileStoragePoolList = new ArrayList<>(); List<Pair<DiskProfile, StoragePool>> diskProfileStoragePoolList = new ArrayList<>();
try { try {
long deviceId = 1L; long deviceId = 1L;
@ -2376,7 +2377,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
return userVm; return userVm;
} }
private NetworkVO getDefaultNetwork(DataCenter zone, Account owner, boolean selectAny) throws InsufficientCapacityException, ResourceAllocationException { private NetworkVO getDefaultNetwork(DataCenter zone, Account owner, boolean selectAny) throws InsufficientCapacityException, ResourceAllocationException {
NetworkVO defaultNetwork = null; NetworkVO defaultNetwork = null;
@ -2503,6 +2503,9 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
@Override @Override
public ConfigKey<?>[] getConfigKeys() { public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[]{UnmanageVMPreserveNic}; return new ConfigKey<?>[]{
UnmanageVMPreserveNic,
RemoteKvmInstanceDisksCopyTimeout
};
} }
} }

View File

@ -38,12 +38,18 @@ import com.cloud.utils.Pair;
public class SshHelper { public class SshHelper {
private static final int DEFAULT_CONNECT_TIMEOUT = 180000; private static final int DEFAULT_CONNECT_TIMEOUT = 180000;
private static final int DEFAULT_KEX_TIMEOUT = 60000; private static final int DEFAULT_KEX_TIMEOUT = 60000;
private static final int DEFAULT_WAIT_RESULT_TIMEOUT = 120000;
private static final Logger s_logger = Logger.getLogger(SshHelper.class); private static final Logger s_logger = Logger.getLogger(SshHelper.class);
public static Pair<Boolean, String> sshExecute(String host, int port, String user, File pemKeyFile, String password, String command) throws Exception { public static Pair<Boolean, String> sshExecute(String host, int port, String user, File pemKeyFile, String password, String command) throws Exception {
return sshExecute(host, port, user, pemKeyFile, password, command, DEFAULT_CONNECT_TIMEOUT, DEFAULT_KEX_TIMEOUT, 120000); return sshExecute(host, port, user, pemKeyFile, password, command, DEFAULT_CONNECT_TIMEOUT, DEFAULT_KEX_TIMEOUT, DEFAULT_WAIT_RESULT_TIMEOUT);
}
public static Pair<Boolean, String> sshExecute(String host, int port, String user, File pemKeyFile, String password, String command, int waitResultTimeoutInMs) throws Exception {
return sshExecute(host, port, user, pemKeyFile, password, command, DEFAULT_CONNECT_TIMEOUT, DEFAULT_KEX_TIMEOUT, waitResultTimeoutInMs);
} }
public static void scpTo(String host, int port, String user, File pemKeyFile, String password, String remoteTargetDirectory, String localFile, String fileMode) public static void scpTo(String host, int port, String user, File pemKeyFile, String password, String remoteTargetDirectory, String localFile, String fileMode)