From 96cd1d4e47fe65fe5e168de2f62ecacec7cddfa5 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 9 Jan 2026 11:51:28 +0900 Subject: [PATCH 01/35] Update Agent.java --- agent/src/main/java/com/cloud/agent/Agent.java | 1 - 1 file changed, 1 deletion(-) diff --git a/agent/src/main/java/com/cloud/agent/Agent.java b/agent/src/main/java/com/cloud/agent/Agent.java index 49c9db012007..938a215a7196 100644 --- a/agent/src/main/java/com/cloud/agent/Agent.java +++ b/agent/src/main/java/com/cloud/agent/Agent.java @@ -37,7 +37,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.SynchronousQueue; From e85ce1e0a231c700538d9bb01dee76ac051c874b Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 29 Jan 2026 14:14:43 +0900 Subject: [PATCH 02/35] =?UTF-8?q?Zone=EC=97=90=20=EB=94=B0=EB=9D=BC=20Comm?= =?UTF-8?q?vault=20=EB=B0=B1=EC=97=85=20=EC=98=A4=ED=8D=BC=EB=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=8B=9C=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=B3=80=EA=B2=BD,=20rbd=EB=8F=84=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EB=8F=84=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cloudstack/backup/BackupManagerImpl.java | 14 --------- .../views/offering/ImportBackupOffering.vue | 31 ++++++++++++------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index 80809c1d64c5..b47667a7dde8 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -331,20 +331,6 @@ public BackupOffering importBackupOffering(final ImportBackupOfferingCmd cmd) { } final BackupProvider provider = getBackupProvider(providerName); - if ("commvault".equals(providerName)) { - List pools = primaryDataStoreDao.listByDataCenterId(cmd.getZoneId()); - boolean validPool = false; - for (StoragePoolVO pool : pools) { - if (pool.getStatus() == StoragePoolStatus.Up && pool.getPoolType() == StoragePoolType.SharedMountPoint) { - validPool = true; - break; - } - } - if (!validPool) { - throw new CloudRuntimeException("The backup offering cannot be imported because storage of type SharedMountPoint with storage status Up does not exist."); - } - } - if (!provider.isValidProviderOffering(cmd.getZoneId(), cmd.getExternalId())) { throw new CloudRuntimeException("Backup offering '" + cmd.getExternalId() + "' does not exist on provider " + provider.getName() + " on zone " + cmd.getZoneId()); } diff --git a/ui/src/views/offering/ImportBackupOffering.vue b/ui/src/views/offering/ImportBackupOffering.vue index 90166821f1cc..98ba447b4b50 100644 --- a/ui/src/views/offering/ImportBackupOffering.vue +++ b/ui/src/views/offering/ImportBackupOffering.vue @@ -278,15 +278,21 @@ export default { this.zones.loading = false }) }, - checkBackupOffering () { - getAPI('listBackupOfferings').then(json => { - var backupOff = json.listbackupofferingsresponse.backupoffering || [] - for (const off of backupOff) { - if (off.provider === 'commvault') { - this.useCommvault = true - return - } - } + async checkBackupOffering () { + if (!this.selectedZoneId || !this.selectedProviderName) { + this.useCommvault = false + return + } + const json = await getAPI('listBackupOfferings') + const backupOff = json.listbackupofferingsresponse.backupoffering || [] + + const selProvider = (this.selectedProviderName || '').toLowerCase() + const selZoneId = this.selectedZoneId + + this.useCommvault = backupOff.some(off => { + const offProvider = (off.provider || '').toLowerCase() + const offZoneId = off.zoneid || off.zoneId + return offProvider === selProvider && offZoneId === selZoneId }) }, fetchProvider (zoneId) { @@ -339,8 +345,9 @@ export default { handleSubmit (e) { e.preventDefault() if (this.loading) return - this.formRef.value.validate().then(() => { - if (this.useCommvault && this.selectedProviderName === 'commvault') { + this.formRef.value.validate().then(async () => { + await this.checkBackupOffering() + if (this.useCommvault) { this.$notification.error({ message: this.$t('message.request.failed'), description: this.$t('message.error.import.backup.offering') @@ -414,6 +421,7 @@ export default { this.form.externalid = undefined this.externals.opts = [] this.fetchProvider(this.selectedZoneId) + this.checkBackupOffering() }, onChangeProvider (value) { if (!value) { @@ -429,6 +437,7 @@ export default { if (this.selectedZoneId && this.selectedProviderName) { this.fetchExternal(this.selectedZoneId, this.selectedProviderName) } + this.checkBackupOffering() }, closeAction () { this.$emit('close-action') From 5dff30bccce22193cb00f518ab971de3b5844142 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 29 Jan 2026 14:17:24 +0900 Subject: [PATCH 03/35] =?UTF-8?q?=EB=B9=8C=EB=93=9C=EC=98=A4=EB=A5=98?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index b47667a7dde8..a3b8a88583d2 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -128,8 +128,6 @@ import com.cloud.storage.GuestOSVO; import com.cloud.storage.ScopeType; import com.cloud.storage.Storage; -import com.cloud.storage.StoragePoolStatus; -import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeVO; From 7ee311e3342322f48bc07710db6ffdb4ccf197b5 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 6 Feb 2026 13:28:56 +0900 Subject: [PATCH 04/35] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resource/LibvirtComputingResource.java | 8 ++-- .../kvm/storage/KVMStorageProcessor.java | 44 ++++++++++++++++--- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index d8313d7c4ad1..35271838c7e9 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -5877,11 +5877,12 @@ public void removeCheckpointsOnVm(String vmName, String volumeUuid, List } public boolean recreateCheckpointsOnVm(List volumes, String vmName, Connect conn) { - logger.debug("Trying to recreate checkpoints on VM [{}] with volumes [{}].", vmName, volumes); + LOGGER.info("정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfStoppedVm recreateCheckpointsOnVm LibvirtComputingResource.java start"); + LOGGER.debug("Trying to recreate checkpoints on VM [{}] with volumes [{}].", vmName, volumes); try { validateLibvirtAndQemuVersionForIncrementalSnapshots(); } catch (CloudRuntimeException e) { - logger.warn("Will not recreate the checkpoints on VM as {}", e.getMessage(), e); + LOGGER.warn("Will not recreate the checkpoints on VM as {}", e.getMessage(), e); return false; } List diskDefs = getDisks(conn, vmName); @@ -5895,7 +5896,8 @@ public boolean recreateCheckpointsOnVm(List volumes, String vmNa recreateCheckpointsOfDisk(vmName, volume, mapDiskToDiskDef); disconnectAllVolumeSnapshotSecondaryStorages(storagePoolSet); } - logger.debug("Successfully recreated all checkpoints on VM [{}].", vmName); + LOGGER.info("정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfStoppedVm recreateCheckpointsOnVm LibvirtComputingResource.java end"); + LOGGER.debug("Successfully recreated all checkpoints on VM [{}].", vmName); return true; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java index 2f9df1d4c7a2..af7cc4342f72 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java @@ -1920,25 +1920,37 @@ public Answer createSnapshot(final CreateObjectCommand cmd) { String diskPath = disk.getPath(); String snapshotPath = diskPath + File.separator + snapshotName; + logger.info("KVMStorageProcessor.java createSnapshot"); + logger.info("diskPath : " + diskPath); + logger.info("snapshotPath : " + snapshotPath); SnapshotObjectTO newSnapshot = new SnapshotObjectTO(); if (DomainInfo.DomainState.VIR_DOMAIN_RUNNING.equals(state) && !primaryPool.isExternalSnapshot()) { + logger.info("if 1:::::::::::::실행중인 가상머신"); if (snapshotTO.isKvmIncrementalSnapshot()) { + logger.info("if 1-1::::::::::::::::::::::::::::::::::실행중인 가상머신 증분 스냅샷"); newSnapshot = takeIncrementalVolumeSnapshotOfRunningVm(snapshotTO, primaryPool, secondaryPool, imageStoreTo != null ? imageStoreTo.getUrl() : null, snapshotName, volume, vm, conn, cmd.getWait()); } else { + logger.info("if 1-2::::::::::::::::::::::::::::::::::"); newSnapshot = takeFullVolumeSnapshotOfRunningVm(cmd, primaryPool, secondaryPool, disk, snapshotName, conn, vmName, diskPath, vm, volume, snapshotPath); } } else { + logger.info("else 1::::::::::::::::::::::::::::::::::정지중인 가상머신"); if (primaryPool.getType() == StoragePoolType.RBD) { + logger.info("else 1-1::::::::::::::::::::::::::::::::::정지중인 가상머신 rbd"); takeRbdVolumeSnapshotOfStoppedVm(primaryPool, disk, snapshotName); newSnapshot.setPath(snapshotPath); } else if (primaryPool.getType() == StoragePoolType.CLVM) { + logger.info("else 1-2::::::::::::::::::::::::::::::::::"); CreateObjectAnswer result = takeClvmVolumeSnapshotOfStoppedVm(disk, snapshotName); if (result != null) return result; newSnapshot.setPath(snapshotPath); } else { + logger.info("else 1-3::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs"); if (snapshotTO.isKvmIncrementalSnapshot()) { + logger.info("else 1-3-1::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs 증분 스냅샷"); newSnapshot = takeIncrementalVolumeSnapshotOfStoppedVm(snapshotTO, primaryPool, secondaryPool, imageStoreTo != null ? imageStoreTo.getUrl() : null, snapshotName, volume, conn, cmd.getWait()); } else { + logger.info("else 1-3-2::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs FULL 스냅샷"); newSnapshot = takeFullVolumeSnapshotOfStoppedVm(cmd, primaryPool, secondaryPool, snapshotName, disk, volume); } } @@ -1978,6 +1990,7 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfStoppedVm(SnapshotObject String secondaryPoolUrl, String snapshotName, VolumeObjectTO volumeObjectTo, Connect conn, int wait) throws LibvirtException { resource.validateLibvirtAndQemuVersionForIncrementalSnapshots(); Domain vm = null; + logger.info("else 1-3-1::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfStoppedVm"); logger.debug("Taking incremental volume snapshot of volume [{}]. Snapshot will be copied to [{}].", volumeObjectTo, ObjectUtils.defaultIfNull(secondaryPool, primaryPool)); try { @@ -1991,7 +2004,7 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfStoppedVm(SnapshotObject vm = resource.getDomain(conn, vmName); resource.recreateCheckpointsOnVm(List.of(volumeObjectTo), vmName, conn); - + logger.info("정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfStoppedVm recreateCheckpointsOnVm"); return takeIncrementalVolumeSnapshotOfRunningVm(snapshotObjectTO, primaryPool, secondaryPool, secondaryPoolUrl, snapshotName, volumeObjectTo, vm, conn, wait); } catch (InternalErrorException | LibvirtException | CloudRuntimeException e) { logger.error("Failed to take incremental volume snapshot of volume [{}] due to {}.", volumeObjectTo, e.getMessage(), e); @@ -2011,11 +2024,10 @@ private String getVmXml(KVMStoragePool primaryPool, VolumeObjectTO volumeObjectT } private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObjectTO snapshotObjectTO, KVMStoragePool primaryPool, KVMStoragePool secondaryPool, - String secondaryPoolUrl, String snapshotName, VolumeObjectTO volumeObjectTo, Domain vm, Connect conn, int wait) { - logger.debug("Taking incremental volume snapshot of volume [{}] attached to running VM [{}]. Snapshot will be copied to [{}].", volumeObjectTo, volumeObjectTo.getVmName(), + String secondaryPoolUrl, String snapshotName, VolumeObjectTO volumeObjectTo, Domain vm, Connect conn, int wait) { logger.debug("Taking incremental volume snapshot of volume [{}] attached to running VM [{}]. Snapshot will be copied to [{}].", volumeObjectTo, volumeObjectTo.getVmName(), ObjectUtils.defaultIfNull(secondaryPool, primaryPool)); resource.validateLibvirtAndQemuVersionForIncrementalSnapshots(); - + logger.info("else 1-3-1::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfRunningVm"); Pair fullSnapshotPathAndDirPath = getFullSnapshotOrCheckpointPathAndDirPathOnCorrectStorage(primaryPool, secondaryPool, snapshotName, volumeObjectTo, false); String diskLabel; @@ -2032,20 +2044,28 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObject String[] parents = snapshotObjectTO.getParents(); String fullSnapshotPath = fullSnapshotPathAndDirPath.first(); + logger.info("takeIncrementalVolumeSnapshotOfRunningVm"); + logger.info("fullSnapshotPath : " + fullSnapshotPath); String backupXml = generateBackupXml(volumeObjectTo, parents, diskLabel, fullSnapshotPath); String checkpointXml = String.format(CHECKPOINT_XML, snapshotName, diskLabel); - + logger.info("backupXml : " + backupXml); + logger.info("checkpointXml : " + checkpointXml); Path backupXmlPath = createFileAndWrite(backupXml, BACKUP_XML_TEMP_DIR, snapshotName); + logger.info("backupXmlPath.toString() : " + backupXmlPath.toString()); Path checkpointXmlPath = createFileAndWrite(checkpointXml, CHECKPOINT_XML_TEMP_DIR, snapshotName); + logger.info("checkpointXmlPath.toString() : " + checkpointXmlPath.toString()); String backupCommand = String.format(BACKUP_BEGIN_COMMAND, vmName, backupXmlPath.toString(), checkpointXmlPath.toString()); + logger.info("takeIncrementalVolumeSnapshotOfRunningVm createFolderOnCorrectStorage start"); createFolderOnCorrectStorage(primaryPool, secondaryPool, fullSnapshotPathAndDirPath); - + logger.info("takeIncrementalVolumeSnapshotOfRunningVm createFolderOnCorrectStorage end"); if (Script.runSimpleBashScript(backupCommand) == null) { + logger.info("Script.runSimpleBashScript(backupCommand) == null"); throw new CloudRuntimeException(String.format("Error backing up using backupXML [%s], checkpointXML [%s] for volume [%s].", backupXml, checkpointXml, volumeObjectTo)); } + logger.info("waitForBackup"); try { waitForBackup(vmName); @@ -2054,6 +2074,7 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObject throw ex; } + logger.info("rebaseSnapshot"); rebaseSnapshot(snapshotObjectTO, secondaryPool, secondaryPoolUrl, fullSnapshotPath, snapshotName, parents, wait); try { @@ -2063,7 +2084,7 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObject } String checkpointPath = dumpCheckpoint(primaryPool, secondaryPool, snapshotName, volumeObjectTo, vmName, parents); - + logger.info("checkpointPath"); SnapshotObjectTO result = createSnapshotToAndUpdatePathAndSize(secondaryPool == null ? fullSnapshotPath : fullSnapshotPathAndDirPath.second() + File.separator + snapshotName, fullSnapshotPath); @@ -2074,9 +2095,13 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObject protected void createFolderOnCorrectStorage(KVMStoragePool primaryPool, KVMStoragePool secondaryPool, Pair fullSnapshotPathAndDirPath) { if (secondaryPool == null) { + logger.info("createFolderOnCorrectStorage : 1"); primaryPool.createFolder(fullSnapshotPathAndDirPath.second()); + logger.info("createFolderOnCorrectStorage : 111"); } else { + logger.info("createFolderOnCorrectStorage : 2"); secondaryPool.createFolder(fullSnapshotPathAndDirPath.second()); + logger.info("createFolderOnCorrectStorage : 222"); } } @@ -2444,15 +2469,20 @@ private Pair getFullSnapshotOrCheckpointPathAndDirPathOnCorrectS String dirPath; if (secondaryPool == null) { + logger.info("getFullSnapshotOrCheckpointPathAndDirPathOnCorrectStorage :::: 1"); fullSnapshotPath = getSnapshotOrCheckpointPathInPrimaryStorage(primaryPool.getLocalPath(), snapshotName, checkpoint); dirPath = checkpoint ? TemplateConstants.DEFAULT_CHECKPOINT_ROOT_DIR : TemplateConstants.DEFAULT_SNAPSHOT_ROOT_DIR; } else { + logger.info("getFullSnapshotOrCheckpointPathAndDirPathOnCorrectStorage :::: 2"); Pair fullPathAndDirectoryPath = getSnapshotOrCheckpointPathAndDirectoryPathInSecondaryStorage(secondaryPool.getLocalPath(), snapshotName, volume.getAccountId(), volume.getVolumeId(), checkpoint); fullSnapshotPath = fullPathAndDirectoryPath.first(); dirPath = fullPathAndDirectoryPath.second(); } + logger.info("else 1-3-1::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs 증분 스냅샷 getFullSnapshotOrCheckpointPathAndDirPathOnCorrectStorage"); + logger.info("fullSnapshotPath : " + fullSnapshotPath); + logger.info("dirPath : " + dirPath); return new Pair<>(fullSnapshotPath, dirPath); } From 8bd6d101a683cc45e7545aee7a3fbc885670aab4 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 6 Feb 2026 13:39:23 +0900 Subject: [PATCH 05/35] Update LibvirtStorageAdaptor.java --- .../cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java index 8f0987504263..4ad21a8be75e 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java @@ -122,15 +122,19 @@ public boolean createFolder(String uuid, String path) { @Override public boolean createFolder(String uuid, String path, String localPath) { + logger.info("LibvirtStorageAdaptor.java createFolder"); String mountPoint = _mountPoint + File.separator + uuid; - + logger.info("mountPoint : " + mountPoint); if (localPath != null) { + logger.info("createFolder if"); logger.debug(String.format("Pool [%s] is of type local or shared mount point; therefore, we will use the local path [%s] to create the folder [%s] (if it does not" + " exist).", uuid, localPath, path)); mountPoint = localPath; + logger.info("mountPoint"); } + logger.info("file : " + mountPoint + File.separator + path); File f = new File(mountPoint + File.separator + path); if (!f.exists()) { f.mkdirs(); From 46a049bfdca6023d74b211190c29c9dba840a874 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 6 Feb 2026 14:20:58 +0900 Subject: [PATCH 06/35] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java index 923a45d6c6ca..d219eda60614 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java @@ -298,7 +298,7 @@ public boolean delete() { @Override public boolean createFolder(String path) { - return this._storageAdaptor.createFolder(this.uuid, path, this.type == StoragePoolType.Filesystem ? this.localPath : null); + return this._storageAdaptor.createFolder(this.uuid, path, this.type == StoragePoolType.Filesystem || this.type == StoragePoolType.SharedMountPoint ? this.localPath : null); } @Override From 542b666a04ff8a7e716d2eed129f453f4235bf1a Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 6 Feb 2026 14:54:37 +0900 Subject: [PATCH 07/35] Revert "Update LibvirtStorageAdaptor.java" This reverts commit 8bd6d101a683cc45e7545aee7a3fbc885670aab4. --- .../cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java index 4ad21a8be75e..8f0987504263 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java @@ -122,19 +122,15 @@ public boolean createFolder(String uuid, String path) { @Override public boolean createFolder(String uuid, String path, String localPath) { - logger.info("LibvirtStorageAdaptor.java createFolder"); String mountPoint = _mountPoint + File.separator + uuid; - logger.info("mountPoint : " + mountPoint); + if (localPath != null) { - logger.info("createFolder if"); logger.debug(String.format("Pool [%s] is of type local or shared mount point; therefore, we will use the local path [%s] to create the folder [%s] (if it does not" + " exist).", uuid, localPath, path)); mountPoint = localPath; - logger.info("mountPoint"); } - logger.info("file : " + mountPoint + File.separator + path); File f = new File(mountPoint + File.separator + path); if (!f.exists()) { f.mkdirs(); From 4fc42eedda24e7da86a0709a848ec152763f9d48 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 6 Feb 2026 14:54:46 +0900 Subject: [PATCH 08/35] =?UTF-8?q?Revert=20"=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7ee311e3342322f48bc07710db6ffdb4ccf197b5. --- .../resource/LibvirtComputingResource.java | 8 ++-- .../kvm/storage/KVMStorageProcessor.java | 44 +++---------------- 2 files changed, 10 insertions(+), 42 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 35271838c7e9..d8313d7c4ad1 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -5877,12 +5877,11 @@ public void removeCheckpointsOnVm(String vmName, String volumeUuid, List } public boolean recreateCheckpointsOnVm(List volumes, String vmName, Connect conn) { - LOGGER.info("정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfStoppedVm recreateCheckpointsOnVm LibvirtComputingResource.java start"); - LOGGER.debug("Trying to recreate checkpoints on VM [{}] with volumes [{}].", vmName, volumes); + logger.debug("Trying to recreate checkpoints on VM [{}] with volumes [{}].", vmName, volumes); try { validateLibvirtAndQemuVersionForIncrementalSnapshots(); } catch (CloudRuntimeException e) { - LOGGER.warn("Will not recreate the checkpoints on VM as {}", e.getMessage(), e); + logger.warn("Will not recreate the checkpoints on VM as {}", e.getMessage(), e); return false; } List diskDefs = getDisks(conn, vmName); @@ -5896,8 +5895,7 @@ public boolean recreateCheckpointsOnVm(List volumes, String vmNa recreateCheckpointsOfDisk(vmName, volume, mapDiskToDiskDef); disconnectAllVolumeSnapshotSecondaryStorages(storagePoolSet); } - LOGGER.info("정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfStoppedVm recreateCheckpointsOnVm LibvirtComputingResource.java end"); - LOGGER.debug("Successfully recreated all checkpoints on VM [{}].", vmName); + logger.debug("Successfully recreated all checkpoints on VM [{}].", vmName); return true; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java index af7cc4342f72..2f9df1d4c7a2 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java @@ -1920,37 +1920,25 @@ public Answer createSnapshot(final CreateObjectCommand cmd) { String diskPath = disk.getPath(); String snapshotPath = diskPath + File.separator + snapshotName; - logger.info("KVMStorageProcessor.java createSnapshot"); - logger.info("diskPath : " + diskPath); - logger.info("snapshotPath : " + snapshotPath); SnapshotObjectTO newSnapshot = new SnapshotObjectTO(); if (DomainInfo.DomainState.VIR_DOMAIN_RUNNING.equals(state) && !primaryPool.isExternalSnapshot()) { - logger.info("if 1:::::::::::::실행중인 가상머신"); if (snapshotTO.isKvmIncrementalSnapshot()) { - logger.info("if 1-1::::::::::::::::::::::::::::::::::실행중인 가상머신 증분 스냅샷"); newSnapshot = takeIncrementalVolumeSnapshotOfRunningVm(snapshotTO, primaryPool, secondaryPool, imageStoreTo != null ? imageStoreTo.getUrl() : null, snapshotName, volume, vm, conn, cmd.getWait()); } else { - logger.info("if 1-2::::::::::::::::::::::::::::::::::"); newSnapshot = takeFullVolumeSnapshotOfRunningVm(cmd, primaryPool, secondaryPool, disk, snapshotName, conn, vmName, diskPath, vm, volume, snapshotPath); } } else { - logger.info("else 1::::::::::::::::::::::::::::::::::정지중인 가상머신"); if (primaryPool.getType() == StoragePoolType.RBD) { - logger.info("else 1-1::::::::::::::::::::::::::::::::::정지중인 가상머신 rbd"); takeRbdVolumeSnapshotOfStoppedVm(primaryPool, disk, snapshotName); newSnapshot.setPath(snapshotPath); } else if (primaryPool.getType() == StoragePoolType.CLVM) { - logger.info("else 1-2::::::::::::::::::::::::::::::::::"); CreateObjectAnswer result = takeClvmVolumeSnapshotOfStoppedVm(disk, snapshotName); if (result != null) return result; newSnapshot.setPath(snapshotPath); } else { - logger.info("else 1-3::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs"); if (snapshotTO.isKvmIncrementalSnapshot()) { - logger.info("else 1-3-1::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs 증분 스냅샷"); newSnapshot = takeIncrementalVolumeSnapshotOfStoppedVm(snapshotTO, primaryPool, secondaryPool, imageStoreTo != null ? imageStoreTo.getUrl() : null, snapshotName, volume, conn, cmd.getWait()); } else { - logger.info("else 1-3-2::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs FULL 스냅샷"); newSnapshot = takeFullVolumeSnapshotOfStoppedVm(cmd, primaryPool, secondaryPool, snapshotName, disk, volume); } } @@ -1990,7 +1978,6 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfStoppedVm(SnapshotObject String secondaryPoolUrl, String snapshotName, VolumeObjectTO volumeObjectTo, Connect conn, int wait) throws LibvirtException { resource.validateLibvirtAndQemuVersionForIncrementalSnapshots(); Domain vm = null; - logger.info("else 1-3-1::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfStoppedVm"); logger.debug("Taking incremental volume snapshot of volume [{}]. Snapshot will be copied to [{}].", volumeObjectTo, ObjectUtils.defaultIfNull(secondaryPool, primaryPool)); try { @@ -2004,7 +1991,7 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfStoppedVm(SnapshotObject vm = resource.getDomain(conn, vmName); resource.recreateCheckpointsOnVm(List.of(volumeObjectTo), vmName, conn); - logger.info("정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfStoppedVm recreateCheckpointsOnVm"); + return takeIncrementalVolumeSnapshotOfRunningVm(snapshotObjectTO, primaryPool, secondaryPool, secondaryPoolUrl, snapshotName, volumeObjectTo, vm, conn, wait); } catch (InternalErrorException | LibvirtException | CloudRuntimeException e) { logger.error("Failed to take incremental volume snapshot of volume [{}] due to {}.", volumeObjectTo, e.getMessage(), e); @@ -2024,10 +2011,11 @@ private String getVmXml(KVMStoragePool primaryPool, VolumeObjectTO volumeObjectT } private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObjectTO snapshotObjectTO, KVMStoragePool primaryPool, KVMStoragePool secondaryPool, - String secondaryPoolUrl, String snapshotName, VolumeObjectTO volumeObjectTo, Domain vm, Connect conn, int wait) { logger.debug("Taking incremental volume snapshot of volume [{}] attached to running VM [{}]. Snapshot will be copied to [{}].", volumeObjectTo, volumeObjectTo.getVmName(), + String secondaryPoolUrl, String snapshotName, VolumeObjectTO volumeObjectTo, Domain vm, Connect conn, int wait) { + logger.debug("Taking incremental volume snapshot of volume [{}] attached to running VM [{}]. Snapshot will be copied to [{}].", volumeObjectTo, volumeObjectTo.getVmName(), ObjectUtils.defaultIfNull(secondaryPool, primaryPool)); resource.validateLibvirtAndQemuVersionForIncrementalSnapshots(); - logger.info("else 1-3-1::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs 증분 스냅샷 takeIncrementalVolumeSnapshotOfRunningVm"); + Pair fullSnapshotPathAndDirPath = getFullSnapshotOrCheckpointPathAndDirPathOnCorrectStorage(primaryPool, secondaryPool, snapshotName, volumeObjectTo, false); String diskLabel; @@ -2044,28 +2032,20 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObject String[] parents = snapshotObjectTO.getParents(); String fullSnapshotPath = fullSnapshotPathAndDirPath.first(); - logger.info("takeIncrementalVolumeSnapshotOfRunningVm"); - logger.info("fullSnapshotPath : " + fullSnapshotPath); String backupXml = generateBackupXml(volumeObjectTo, parents, diskLabel, fullSnapshotPath); String checkpointXml = String.format(CHECKPOINT_XML, snapshotName, diskLabel); - logger.info("backupXml : " + backupXml); - logger.info("checkpointXml : " + checkpointXml); + Path backupXmlPath = createFileAndWrite(backupXml, BACKUP_XML_TEMP_DIR, snapshotName); - logger.info("backupXmlPath.toString() : " + backupXmlPath.toString()); Path checkpointXmlPath = createFileAndWrite(checkpointXml, CHECKPOINT_XML_TEMP_DIR, snapshotName); - logger.info("checkpointXmlPath.toString() : " + checkpointXmlPath.toString()); String backupCommand = String.format(BACKUP_BEGIN_COMMAND, vmName, backupXmlPath.toString(), checkpointXmlPath.toString()); - logger.info("takeIncrementalVolumeSnapshotOfRunningVm createFolderOnCorrectStorage start"); createFolderOnCorrectStorage(primaryPool, secondaryPool, fullSnapshotPathAndDirPath); - logger.info("takeIncrementalVolumeSnapshotOfRunningVm createFolderOnCorrectStorage end"); + if (Script.runSimpleBashScript(backupCommand) == null) { - logger.info("Script.runSimpleBashScript(backupCommand) == null"); throw new CloudRuntimeException(String.format("Error backing up using backupXML [%s], checkpointXML [%s] for volume [%s].", backupXml, checkpointXml, volumeObjectTo)); } - logger.info("waitForBackup"); try { waitForBackup(vmName); @@ -2074,7 +2054,6 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObject throw ex; } - logger.info("rebaseSnapshot"); rebaseSnapshot(snapshotObjectTO, secondaryPool, secondaryPoolUrl, fullSnapshotPath, snapshotName, parents, wait); try { @@ -2084,7 +2063,7 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObject } String checkpointPath = dumpCheckpoint(primaryPool, secondaryPool, snapshotName, volumeObjectTo, vmName, parents); - logger.info("checkpointPath"); + SnapshotObjectTO result = createSnapshotToAndUpdatePathAndSize(secondaryPool == null ? fullSnapshotPath : fullSnapshotPathAndDirPath.second() + File.separator + snapshotName, fullSnapshotPath); @@ -2095,13 +2074,9 @@ private SnapshotObjectTO takeIncrementalVolumeSnapshotOfRunningVm(SnapshotObject protected void createFolderOnCorrectStorage(KVMStoragePool primaryPool, KVMStoragePool secondaryPool, Pair fullSnapshotPathAndDirPath) { if (secondaryPool == null) { - logger.info("createFolderOnCorrectStorage : 1"); primaryPool.createFolder(fullSnapshotPathAndDirPath.second()); - logger.info("createFolderOnCorrectStorage : 111"); } else { - logger.info("createFolderOnCorrectStorage : 2"); secondaryPool.createFolder(fullSnapshotPathAndDirPath.second()); - logger.info("createFolderOnCorrectStorage : 222"); } } @@ -2469,20 +2444,15 @@ private Pair getFullSnapshotOrCheckpointPathAndDirPathOnCorrectS String dirPath; if (secondaryPool == null) { - logger.info("getFullSnapshotOrCheckpointPathAndDirPathOnCorrectStorage :::: 1"); fullSnapshotPath = getSnapshotOrCheckpointPathInPrimaryStorage(primaryPool.getLocalPath(), snapshotName, checkpoint); dirPath = checkpoint ? TemplateConstants.DEFAULT_CHECKPOINT_ROOT_DIR : TemplateConstants.DEFAULT_SNAPSHOT_ROOT_DIR; } else { - logger.info("getFullSnapshotOrCheckpointPathAndDirPathOnCorrectStorage :::: 2"); Pair fullPathAndDirectoryPath = getSnapshotOrCheckpointPathAndDirectoryPathInSecondaryStorage(secondaryPool.getLocalPath(), snapshotName, volume.getAccountId(), volume.getVolumeId(), checkpoint); fullSnapshotPath = fullPathAndDirectoryPath.first(); dirPath = fullPathAndDirectoryPath.second(); } - logger.info("else 1-3-1::::::::::::::::::::::::::::::::::정지중인 가상머신 gfs 증분 스냅샷 getFullSnapshotOrCheckpointPathAndDirPathOnCorrectStorage"); - logger.info("fullSnapshotPath : " + fullSnapshotPath); - logger.info("dirPath : " + dirPath); return new Pair<>(fullSnapshotPath, dirPath); } From 4166e81a4517c8f821460b079f6a8d985a914aec Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Wed, 11 Feb 2026 08:57:57 +0900 Subject: [PATCH 09/35] test --- .../backup/CommvaultRestoreBackupCommand.java | 141 ++++++ .../backup/CommvaultTakeBackupCommand.java | 86 ++++ .../backup/CommvaultBackupProvider.java | 445 +++++++----------- ...tCommvaultRestoreBackupCommandWrapper.java | 298 ++++++++++++ ...virtCommvaultTakeBackupCommandWrapper.java | 107 +++++ scripts/vm/hypervisor/kvm/cvtbackup.sh | 0 6 files changed, 805 insertions(+), 272 deletions(-) create mode 100644 core/src/main/java/org/apache/cloudstack/backup/CommvaultRestoreBackupCommand.java create mode 100644 core/src/main/java/org/apache/cloudstack/backup/CommvaultTakeBackupCommand.java create mode 100644 plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java create mode 100644 plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java create mode 100644 scripts/vm/hypervisor/kvm/cvtbackup.sh diff --git a/core/src/main/java/org/apache/cloudstack/backup/CommvaultRestoreBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CommvaultRestoreBackupCommand.java new file mode 100644 index 000000000000..2f766c16098f --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CommvaultRestoreBackupCommand.java @@ -0,0 +1,141 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; + +import java.util.List; + +public class CommvaultRestoreBackupCommand extends Command { + private String vmName; + private String backupPath; + private List backupVolumesUUIDs; + private List restoreVolumePools; + private List restoreVolumePaths; + private String diskType; + private Boolean vmExists; + private String restoreVolumeUUID; + private VirtualMachine.State vmState; + private Integer timeout; + private String cacheMode; + + protected CommvaultRestoreBackupCommand() { + super(); + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public String getBackupPath() { + return backupPath; + } + + public void setBackupPath(String backupPath) { + this.backupPath = backupPath; + } + + public List getRestoreVolumePools() { + return restoreVolumePools; + } + + public void setRestoreVolumePools(List restoreVolumePools) { + this.restoreVolumePools = restoreVolumePools; + } + + public List getRestoreVolumePaths() { + return restoreVolumePaths; + } + + public void setRestoreVolumePaths(List restoreVolumePaths) { + this.restoreVolumePaths = restoreVolumePaths; + } + + public Boolean isVmExists() { + return vmExists; + } + + public void setVmExists(Boolean vmExists) { + this.vmExists = vmExists; + } + + public String getDiskType() { + return diskType; + } + + public void setDiskType(String diskType) { + this.diskType = diskType; + } + + public String getRestoreVolumeUUID() { + return restoreVolumeUUID; + } + + public void setRestoreVolumeUUID(String restoreVolumeUUID) { + this.restoreVolumeUUID = restoreVolumeUUID; + } + + public VirtualMachine.State getVmState() { + return vmState; + } + + public void setVmState(VirtualMachine.State vmState) { + this.vmState = vmState; + } + + @LogLevel(LogLevel.Log4jLevel.Off) + private String mountOptions; + @Override + + public boolean executeInSequence() { + return true; + } + + public List getBackupVolumesUUIDs() { + return backupVolumesUUIDs; + } + + public void setBackupVolumesUUIDs(List backupVolumesUUIDs) { + this.backupVolumesUUIDs = backupVolumesUUIDs; + } + + public Integer getTimeout() { + return this.timeout == null ? 0 : this.timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + public String getCacheMode() { + return cacheMode; + } + + public void setCacheMode(String cacheMode) { + this.cacheMode = cacheMode; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/CommvaultTakeBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CommvaultTakeBackupCommand.java new file mode 100644 index 000000000000..004b7a87915a --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CommvaultTakeBackupCommand.java @@ -0,0 +1,86 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; + +import java.util.List; + +public class CommvaultTakeBackupCommand extends Command { + private String vmName; + private String backupPath; + private List volumePools; + private List volumePaths; + private Boolean quiesce; + @LogLevel(LogLevel.Log4jLevel.Off) + + public CommvaultTakeBackupCommand(String vmName, String backupPath) { + super(); + this.vmName = vmName; + this.backupPath = backupPath; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public String getBackupPath() { + return backupPath; + } + + public void setBackupPath(String backupPath) { + this.backupPath = backupPath; + } + + public List getVolumePools() { + return volumePools; + } + + public void setVolumePools(List volumePools) { + this.volumePools = volumePools; + } + + public List getVolumePaths() { + return volumePaths; + } + + public void setVolumePaths(List volumePaths) { + this.volumePaths = volumePaths; + } + + public Boolean getQuiesce() { + return quiesce; + } + + public void setQuiesce(Boolean quiesce) { + this.quiesce = quiesce; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index d024ef1d8292..c7c7fdc2e15f 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -767,8 +767,59 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI return new Pair<>(false,null); } + protected Host getVMHypervisorHostForBackup(VirtualMachine vm) { + Long hostId = vm.getHostId(); + if (hostId == null && VirtualMachine.State.Running.equals(vm.getState())) { + throw new CloudRuntimeException(String.format("Unable to find the hypervisor host for %s. Make sure the virtual machine is running", vm.getName())); + } + if (VirtualMachine.State.Stopped.equals(vm.getState())) { + hostId = vm.getLastHostId(); + } + if (hostId == null) { + throw new CloudRuntimeException(String.format("Unable to find the hypervisor host for stopped VM: %s", vm)); + } + final Host host = hostDao.findById(hostId); + if (host == null || !Status.Up.equals(host.getStatus()) || !Hypervisor.HypervisorType.KVM.equals(host.getHypervisorType())) { + throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); + } + return host; + } + + private BackupVO createBackupObject(VirtualMachine vm, String backupPath) { + BackupVO backup = new BackupVO(); + backup.setVmId(vm.getId()); + backup.setExternalId(backupPath); + backup.setType("FULL"); + backup.setDate(new Date()); + long virtualSize = 0L; + for (final Volume volume: volumeDao.findByInstance(vm.getId())) { + if (Volume.State.Ready.equals(volume.getState())) { + virtualSize += volume.getSize(); + } + } + backup.setProtectedSize(virtualSize); + backup.setStatus(Backup.Status.BackingUp); + backup.setBackupOfferingId(vm.getBackupOfferingId()); + backup.setAccountId(vm.getAccountId()); + backup.setDomainId(vm.getDomainId()); + backup.setZoneId(vm.getDataCenterId()); + backup.setName(backupManager.getBackupNameFromVM(vm)); + Map details = backupManager.getBackupDetailsFromVM(vm); + backup.setDetails(details); + + return backupDao.persist(backup); + } + @Override public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { + final Host vmHost = getVMHypervisorHostForBackup(vm); + + if (CollectionUtils.isNotEmpty(vmSnapshotDao.findByVmAndByType(vm.getId(), VMSnapshot.Type.DiskAndMemory))) { + logger.debug("Commvault backup provider cannot take backups of a VM [{}] with disk-and-memory VM snapshots. Restoring the backup will corrupt any newer disk-and-memory " + + "VM snapshots.", vm); + throw new CloudRuntimeException(String.format("Cannot take backup of VM [%s] as it has disk-and-memory VM snapshots.", vm.getUuid())); + } + String hostName = null; try { String commvaultServer = getUrlDomain(CommvaultUrl.value()); @@ -793,295 +844,145 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { } BackupOfferingVO vmBackupOffering = new BackupOfferingDaoImpl().findById(vm.getBackupOfferingId()); String planId = vmBackupOffering.getExternalId(); - // 스냅샷 생성 mold-API 호출 - String[] properties = getServerProperties(); - ManagementServerHostVO msHost = msHostDao.findByMsid(ManagementServerNode.getManagementServerId()); - String moldUrl = properties[1] + "://" + msHost.getServiceIP() + ":" + properties[0] + "/client/api/"; - String moldMethod = "POST"; - String moldCommand = "createSnapshotBackup"; - UserAccount user = accountService.getActiveUserAccount("admin", 1L); - String apiKey = user.getApiKey(); - String secretKey = user.getSecretKey(); - if (apiKey == null || secretKey == null) { - throw new CloudRuntimeException("Failed because the API key and Secret key for the admin account do not exist."); - } - UserVmJoinVO userVM = userVmJoinDao.findById(vm.getId()); - List volumes = volumeDao.findByInstance(userVM.getId()); - volumes.sort(Comparator.comparing(Volume::getDeviceId)); - StringJoiner joiner = new StringJoiner(","); - Map checkResult = new HashMap<>(); - for (VolumeVO vol : volumes) { - Map snapParams = new HashMap<>(); - snapParams.put("volumeid", Long.toString(vol.getId())); - snapParams.put("backup", "true"); - // snapParams.put("quiescevm", String.valueOf(quiesceVM)); - String createSnapResult = moldCreateSnapshotBackupAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapParams); - if (createSnapResult == null) { - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - Map snapshotParams = new HashMap<>(); - snapshotParams.put("id", value); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - } - } - LOG.error("Failed to request createSnapshot Mold-API."); - return new Pair<>(false, null); - } else { - JSONObject jsonObject = new JSONObject(createSnapResult); - String jobId = jsonObject.get("jobid").toString(); - String snapId = jsonObject.get("id").toString(); - int jobStatus = getAsyncJobResult(moldUrl, apiKey, secretKey, jobId); - if (jobStatus == 2) { - Map snapshotParams = new HashMap<>(); - snapshotParams.put("id", snapId); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - snapshotParams = new HashMap<>(); - snapshotParams.put("id", value); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - } - } - LOG.error("createSnapshot Mold-API async job resulted in failure."); - return new Pair<>(false, null); - } - checkResult.put(vol.getId(), snapId); - SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findLatestSnapshotForVolume(vol.getId(), DataStoreRole.Primary); - joiner.add(snapshotStore.getInstallPath()); - } + + final Date creationDate = new Date(); + final String backupPath = String.format("%s/%s", vm.getInstanceName(), + new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(creationDate)); + + BackupVO backupVO = createBackupObject(vm, backupPath); + CommvaultTakeBackupCommand command = new CommvaultTakeBackupCommand(vm.getInstanceName(), backupPath); + command.setQuiesce(quiesceVM); + + if (VirtualMachine.State.Stopped.equals(vm.getState())) { + List vmVolumes = volumeDao.findByInstance(vm.getId()); + vmVolumes.sort(Comparator.comparing(Volume::getDeviceId)); + Pair, List> volumePoolsAndPaths = getVolumePoolsAndPaths(vmVolumes); + command.setVolumePools(volumePoolsAndPaths.first()); + command.setVolumePaths(volumePoolsAndPaths.second()); } - String path = joiner.toString(); - String backupPath = path; - // 가상머신이 실행중인 경우 가상머신 xml 파일 함께 백업 - HostVO hostVO = null; - StoragePoolVO storagePool = null; - String storagePath = null; - String command = null; - Ternary credentials = null; - int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); - if (vm.getState() == VirtualMachine.State.Running) { - hostVO = getRunningVMHypervisorHost(vm); - credentials = getKVMHyperisorCredentials(hostVO); - List rootVolumesOfVm = volumeDao.findByInstanceAndType(userVM.getId(), Volume.Type.ROOT); - if (!rootVolumesOfVm.isEmpty()) { - storagePool = primaryDataStoreDao.findById(rootVolumesOfVm.get(0).getPoolId()); - storagePath = storagePool.getPath(); - command = String.format( - "mkdir -p %1$s && " + - "virsh -c qemu:///system dumpxml '%2$s' > %1$s/domain-config.xml && " + - "virsh -c qemu:///system dominfo '%2$s' > %1$s/dominfo.xml && " + - "virsh -c qemu:///system domiflist '%2$s' > %1$s/domiflist.xml && " + - "virsh -c qemu:///system domblklist '%2$s' > %1$s/domblklist.xml", - storagePath + "/" + vm.getInstanceName(), vm.getInstanceName() - ); - if (!executeTakeBackupCommand(hostVO, credentials.first(), credentials.second(), sshPort, command)) { - command = String.format(RM_COMMAND, storagePath + "/" + vm.getInstanceName()); - executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } else { - joiner.add(storagePath + "/" + vm.getInstanceName()); - path = joiner.toString(); - } - } + + BackupAnswer answer; + try { + answer = (BackupAnswer) agentManager.send(vmHost.getId(), command); + } catch (AgentUnavailableException e) { + logger.error("Unable to contact backend control plane to initiate backup for VM {}", vm.getInstanceName()); + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); + } catch (OperationTimedoutException e) { + logger.error("Operation to initiate backup timed out for VM {}", vm.getInstanceName()); + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + throw new CloudRuntimeException("Operation to initiate backup timed out, please try again"); } - // 생성된 스냅샷의 경로로 해당 백업 세트의 백업 콘텐츠 경로 업데이트 - String clientId = client.getClientId(hostName); - String subClientEntity = client.getSubclient(clientId, vm.getInstanceName()); - if (subClientEntity == null) { - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - Map snapshotParams = new HashMap<>(); - snapshotParams.put("id", value); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - } - if (vm.getState() == VirtualMachine.State.Running) { - command = String.format(RM_COMMAND, storagePath + "/" + vm.getInstanceName()); - executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } + + if (answer != null && answer.getResult()) { + // 생성된 백업 폴더 경로로 해당 백업 세트의 백업 콘텐츠 경로 업데이트 + String clientId = client.getClientId(hostName); + String subClientEntity = client.getSubclient(clientId, vm.getInstanceName()); + if (subClientEntity == null) { + // 백업 폴더 삭제 명령 전송 필요* + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + throw new CloudRuntimeException("Failed to take backup for VM " + vm.getInstanceName() + " to get subclient info commvault api"); } - throw new CloudRuntimeException("Failed to get subclient info commvault api"); - } - JSONObject jsonObject = new JSONObject(subClientEntity); - String subclientId = String.valueOf(jsonObject.get("subclientId")); - String applicationId = String.valueOf(jsonObject.get("applicationId")); - String backupsetId = String.valueOf(jsonObject.get("backupsetId")); - String instanceId = String.valueOf(jsonObject.get("instanceId")); - String backupsetName = String.valueOf(jsonObject.get("backupsetName")); - String displayName = String.valueOf(jsonObject.get("displayName")); - String commCellName = String.valueOf(jsonObject.get("commCellName")); - String companyId = String.valueOf(jsonObject.getJSONObject("entityInfo").get("companyId")); - String companyName = String.valueOf(jsonObject.getJSONObject("entityInfo").get("companyName")); - String instanceName = String.valueOf(jsonObject.get("instanceName")); - String appName = String.valueOf(jsonObject.get("appName")); - String clientName = String.valueOf(jsonObject.get("clientName")); - String subclientGUID = String.valueOf(jsonObject.get("subclientGUID")); - String subclientName = String.valueOf(jsonObject.get("subclientName")); - String csGUID = String.valueOf(jsonObject.get("csGUID")); - boolean upResult = client.updateBackupSet(path, subclientId, clientId, planId, applicationId, backupsetId, instanceId, subclientName, backupsetName); - if (upResult) { - String planName = client.getPlanName(planId); - String storagePolicyId = client.getStoragePolicyId(planName); - if (planName == null || storagePolicyId == null) { - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - Map snapshotParams = new HashMap<>(); - snapshotParams.put("id", value); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - } - if (vm.getState() == VirtualMachine.State.Running) { - command = String.format(RM_COMMAND, storagePath + "/" + vm.getInstanceName()); - executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } + JSONObject jsonObject = new JSONObject(subClientEntity); + String subclientId = String.valueOf(jsonObject.get("subclientId")); + String applicationId = String.valueOf(jsonObject.get("applicationId")); + String backupsetId = String.valueOf(jsonObject.get("backupsetId")); + String instanceId = String.valueOf(jsonObject.get("instanceId")); + String backupsetName = String.valueOf(jsonObject.get("backupsetName")); + String displayName = String.valueOf(jsonObject.get("displayName")); + String commCellName = String.valueOf(jsonObject.get("commCellName")); + String companyId = String.valueOf(jsonObject.getJSONObject("entityInfo").get("companyId")); + String companyName = String.valueOf(jsonObject.getJSONObject("entityInfo").get("companyName")); + String instanceName = String.valueOf(jsonObject.get("instanceName")); + String appName = String.valueOf(jsonObject.get("appName")); + String clientName = String.valueOf(jsonObject.get("clientName")); + String subclientGUID = String.valueOf(jsonObject.get("subclientGUID")); + String subclientName = String.valueOf(jsonObject.get("subclientName")); + String csGUID = String.valueOf(jsonObject.get("csGUID")); + boolean upResult = client.updateBackupSet(backupPath, subclientId, clientId, planId, applicationId, backupsetId, instanceId, subclientName, backupsetName); + if (upResult) { + String planName = client.getPlanName(planId); + String storagePolicyId = client.getStoragePolicyId(planName); + if (planName == null || storagePolicyId == null) { + // 백업 폴더 삭제 명령 전송 필요* + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + throw new CloudRuntimeException("Failed to take backup for VM " + vm.getInstanceName() + " to get storage policy id commvault api"); } - throw new CloudRuntimeException("Failed to get storage Policy id commvault api"); - } - // 백업 실행 - String jobId = client.createBackup(subclientId, storagePolicyId, displayName, commCellName, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, subclientGUID, subclientName, csGUID, backupsetName); - if (jobId != null) { - String jobStatus = client.getJobStatus(jobId); - if (jobStatus.equalsIgnoreCase("Completed")) { - String jobDetails = client.getJobDetails(jobId); - if (jobDetails != null) { - JSONObject jsonObject2 = new JSONObject(jobDetails); - String endTime = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("detailInfo").get("endTime")); - long timestamp = Long.parseLong(endTime) * 1000L; - Date endDate = new Date(timestamp); - SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - String formattedString = formatterDateTime.format(endDate); - String size = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("detailInfo").get("sizeOfApplication")); - String type = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").get("backupType")); - String externalId = backupPath + "," + jobId; - BackupVO backup = new BackupVO(); - backup.setVmId(vm.getId()); - backup.setExternalId(externalId); - backup.setType(type.toUpperCase()); - try { - backup.setDate(formatterDateTime.parse(formattedString)); - } catch (ParseException e) { - String msg = String.format("Unable to parse date [%s].", endTime); - LOG.error(msg, e); - throw new CloudRuntimeException(msg, e); - } - backup.setSize(Long.parseLong(size)); - long virtualSize = 0L; - for (final Volume volume: volumeDao.findByInstance(vm.getId())) { - if (Volume.State.Ready.equals(volume.getState())) { - virtualSize += volume.getSize(); + // 백업 실행 + String jobId = client.createBackup(subclientId, storagePolicyId, displayName, commCellName, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, subclientGUID, subclientName, csGUID, backupsetName); + if (jobId != null) { + String jobStatus = client.getJobStatus(jobId); + if (jobStatus.equalsIgnoreCase("Completed")) { + String jobDetails = client.getJobDetails(jobId); + if (jobDetails != null) { + JSONObject jsonObject2 = new JSONObject(jobDetails); + String endTime = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("detailInfo").get("endTime")); + long timestamp = Long.parseLong(endTime) * 1000L; + Date endDate = new Date(timestamp); + SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + String formattedString = formatterDateTime.format(endDate); + String size = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("detailInfo").get("sizeOfApplication")); + String type = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").get("backupType")); + String externalId = backupPath + "," + jobId; + backupVO.setExternalId(externalId); + backupVO.setType(type.toUpperCase()); + try { + backupVO.setDate(formatterDateTime.parse(formattedString)); + } catch (ParseException e) { + String msg = String.format("Unable to parse date [%s].", endTime); + LOG.error(msg, e); + throw new CloudRuntimeException(msg, e); } + backupVO.setSize(Long.parseLong(size)); + backupVO.setStatus(Backup.Status.BackedUp); + List vols = new ArrayList<>(volumeDao.findByInstance(vm.getId())); + backupVO.setBackedUpVolumes(backupManager.createVolumeInfoFromVolumes(vols)); + if (backupDao.update(backupVO.getId(), backupVO)) { + return new Pair<>(true, backupVO); + } else { + throw new CloudRuntimeException("Failed to update backup"); + } + } else { + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + logger.error("Failed to take backup for VM " + vm.getInstanceName() + " to get details job commvault api"); + return new Pair<>(false, null); } - backup.setProtectedSize(Long.valueOf(virtualSize)); - backup.setStatus(org.apache.cloudstack.backup.Backup.Status.BackedUp); - backup.setBackupOfferingId(vm.getBackupOfferingId()); - backup.setAccountId(vm.getAccountId()); - backup.setDomainId(vm.getDomainId()); - backup.setZoneId(vm.getDataCenterId()); - backup.setName(backupManager.getBackupNameFromVM(vm)); - List vols = new ArrayList<>(volumeDao.findByInstance(vm.getId())); - backup.setBackedUpVolumes(backupManager.createVolumeInfoFromVolumes(vols)); - Map details = backupManager.getBackupDetailsFromVM(vm); - backup.setDetails(details); - StringJoiner snapshots = new StringJoiner(","); - for (String value : checkResult.values()) { - snapshots.add(value); - } - backup.setSnapshotId(snapshots.toString()); - backupDao.persist(backup); - // 백업 오퍼링 할당 시의 볼륨 정보만 담겨있어서 이후 추가된 볼륨에 대해 복원 시 오류로 백업 시 볼륨 정보 업데이트 - VMInstanceVO vmInstance = vmInstanceDao.findByIdIncludingRemoved(vm.getId()); - vmInstance.setBackupVolumes(backupManager.createVolumeInfoFromVolumes(vols)); - vmInstanceDao.update(vm.getId(), vmInstance); - // 백업 성공 후 스냅샷 삭제 - for (String value : checkResult.values()) { - Map snapshotParams = new HashMap<>(); - snapshotParams.put("id", value); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - } - if (vm.getState() == VirtualMachine.State.Running) { - command = String.format(RM_COMMAND, storagePath + "/" + vm.getInstanceName()); - executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } - return new Pair<>(true, backup); } else { - // 백업 실패 - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - Map snapshotParams = new HashMap<>(); - snapshotParams.put("id", value); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - } - } - if (vm.getState() == VirtualMachine.State.Running) { - command = String.format(RM_COMMAND, storagePath + "/" + vm.getInstanceName()); - executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } - LOG.error("createBackup commvault api resulted in " + jobStatus); + // 백업 폴더 삭제 명령 전송 필요* + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + logger.error("Failed to take backup for VM " + vm.getInstanceName() + " to create backup job status is " + jobStatus); return new Pair<>(false, null); } } else { - // 백업 실패 - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - Map snapshotParams = new HashMap<>(); - snapshotParams.put("id", value); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - } - } - if (vm.getState() == VirtualMachine.State.Running) { - command = String.format(RM_COMMAND, storagePath + "/" + vm.getInstanceName()); - executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } - LOG.error("createBackup commvault api resulted in " + jobStatus); + // 백업 폴더 삭제 명령 전송 필요* + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + logger.error("Failed to take backup for VM " + vm.getInstanceName() + " to create backup job commvault api"); return new Pair<>(false, null); } } else { - // 백업 실패 - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - Map snapshotParams = new HashMap<>(); - snapshotParams.put("id", value); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - } - } - if (vm.getState() == VirtualMachine.State.Running) { - command = String.format(RM_COMMAND, storagePath + "/" + vm.getInstanceName()); - executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } - LOG.error("failed request createBackup commvault api"); + // 백업 폴더 삭제 명령 전송 필요* + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + logger.error("Failed to take backup for VM " + vm.getInstanceName() + " to update backupset content path commvault api"); return new Pair<>(false, null); } } else { - // 백업 경로 업데이트 실패 - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - Map snapshotParams = new HashMap<>(); - snapshotParams.put("id", value); - moldMethod = "GET"; - moldCommand = "deleteSnapshot"; - moldDeleteSnapshotAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, snapshotParams); - } - } - if (vm.getState() == VirtualMachine.State.Running) { - command = String.format(RM_COMMAND, storagePath + "/" + vm.getInstanceName()); - executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); + logger.error("Failed to take backup for VM {}: {}", vm.getInstanceName(), answer != null ? answer.getDetails() : "No answer received"); + if (answer.getNeedsCleanup()) { + logger.error("Backup cleanup failed for VM {}. Leaving the backup in Error state.", vm.getInstanceName()); + backupVO.setStatus(Backup.Status.Error); + backupDao.update(backupVO.getId(), backupVO); + } else { + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); } - LOG.error("updateBackupSet commvault api resulted in failure."); return new Pair<>(false, null); } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java new file mode 100644 index 000000000000..8ce978ae0a33 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java @@ -0,0 +1,298 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.storage.Storage; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.backup.BackupAnswer; +import org.apache.cloudstack.backup.CommvaultRestoreBackupCommand; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; +import org.apache.cloudstack.utils.qemu.QemuImg; +import org.apache.cloudstack.utils.qemu.QemuImgException; +import org.apache.cloudstack.utils.qemu.QemuImgFile; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.libvirt.LibvirtException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +@ResourceWrapper(handles = CommvaultRestoreBackupCommand.class) +public class LibvirtCommvaultRestoreBackupCommandWrapper extends CommandWrapper { + private static final String FILE_PATH_PLACEHOLDER = "%s/%s"; + private static final String ATTACH_QCOW2_DISK_COMMAND = " virsh attach-disk %s %s %s --driver qemu --subdriver qcow2 --cache none"; + private static final String ATTACH_RBD_DISK_XML_COMMAND = " virsh attach-device %s /dev/stdin < backedVolumeUUIDs = command.getBackupVolumesUUIDs(); + List restoreVolumePools = command.getRestoreVolumePools(); + List restoreVolumePaths = command.getRestoreVolumePaths(); + String restoreVolumeUuid = command.getRestoreVolumeUUID(); + int timeout = command.getWait(); + String cacheMode = command.getCacheMode(); + KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr(); + + String newVolumeId = null; + try { + if (Objects.isNull(vmExists)) { + PrimaryDataStoreTO volumePool = restoreVolumePools.get(0); + String volumePath = restoreVolumePaths.get(0); + int lastIndex = volumePath.lastIndexOf("/"); + newVolumeId = volumePath.substring(lastIndex + 1); + restoreVolume(storagePoolMgr, backupPath, volumePool, volumePath, diskType, restoreVolumeUuid, + new Pair<>(vmName, command.getVmState()), timeout, cacheMode); + } else if (Boolean.TRUE.equals(vmExists)) { + restoreVolumesOfExistingVM(storagePoolMgr, restoreVolumePools, restoreVolumePaths, backedVolumeUUIDs, backupPath, timeout); + } else { + restoreVolumesOfDestroyedVMs(storagePoolMgr, restoreVolumePools, restoreVolumePaths, vmName, backupPath, timeout); + } + } catch (CloudRuntimeException e) { + String errorMessage = e.getMessage() != null ? e.getMessage() : ""; + return new BackupAnswer(command, false, errorMessage); + } + + return new BackupAnswer(command, true, newVolumeId); + } + + private void verifyBackupFile(String backupPath, String volUuid) { + if (!checkBackupPathExists(backupPath)) { + throw new CloudRuntimeException(String.format("Backup file for the volume [%s] does not exist.", volUuid)); + } + if (!checkBackupFileImage(backupPath)) { + throw new CloudRuntimeException(String.format("Backup qcow2 file for the volume [%s] is corrupt.", volUuid)); + } + } + + private void restoreVolumesOfExistingVM(KVMStoragePoolManager storagePoolMgr, List restoreVolumePools, List restoreVolumePaths, List backedVolumesUUIDs, + String backupPath, String mountDirectory, int timeout) { + String diskType = "root"; + try { + for (int idx = 0; idx < restoreVolumePaths.size(); idx++) { + PrimaryDataStoreTO restoreVolumePool = restoreVolumePools.get(idx); + String restoreVolumePath = restoreVolumePaths.get(idx); + String backupVolumeUuid = backedVolumesUUIDs.get(idx); + Pair bkpPathAndVolUuid = getBackupPath(null, backupPath, diskType, backupVolumeUuid); + diskType = "datadisk"; + verifyBackupFile(bkpPathAndVolUuid.first(), bkpPathAndVolUuid.second()); + if (!replaceVolumeWithBackup(storagePoolMgr, restoreVolumePool, restoreVolumePath, bkpPathAndVolUuid.first(), timeout)) { + throw new CloudRuntimeException(String.format("Unable to restore contents from the backup volume [%s].", bkpPathAndVolUuid.second())); + } + } + } finally { + deleteBackupDirectory(backupPath); + } + } + + private void restoreVolumesOfDestroyedVMs(KVMStoragePoolManager storagePoolMgr, List volumePools, List volumePaths, String vmName, String backupPath, String mountDirectory, int timeout) { + String diskType = "root"; + try { + for (int i = 0; i < volumePaths.size(); i++) { + PrimaryDataStoreTO volumePool = volumePools.get(i); + String volumePath = volumePaths.get(i); + Pair bkpPathAndVolUuid = getBackupPath(volumePath, backupPath, diskType, null); + diskType = "datadisk"; + verifyBackupFile(bkpPathAndVolUuid.first(), bkpPathAndVolUuid.second()); + if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, bkpPathAndVolUuid.first(), timeout)) { + throw new CloudRuntimeException(String.format("Unable to restore contents from the backup volume [%s].", bkpPathAndVolUuid.second())); + } + } + } finally { + deleteBackupDirectory(backupPath); + } + } + + private void restoreVolume(KVMStoragePoolManager storagePoolMgr, String backupPath, PrimaryDataStoreTO volumePool, String volumePath, String diskType, String volumeUUID, + Pair vmNameAndState, int timeout, String cacheMode) { + Pair bkpPathAndVolUuid; + try { + bkpPathAndVolUuid = getBackupPath(volumePath, backupPath, diskType, volumeUUID); + verifyBackupFile(bkpPathAndVolUuid.first(), bkpPathAndVolUuid.second()); + if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, bkpPathAndVolUuid.first(), timeout, true)) { + throw new CloudRuntimeException(String.format("Unable to restore contents from the backup volume [%s].", bkpPathAndVolUuid.second())); + } + if (VirtualMachine.State.Running.equals(vmNameAndState.second())) { + if (!attachVolumeToVm(storagePoolMgr, vmNameAndState.first(), volumePool, volumePath, cacheMode)) { + throw new CloudRuntimeException(String.format("Failed to attach volume to VM: %s", vmNameAndState.first())); + } + } + } finally { + deleteBackupDirectory(backupPath); + } + } + + private void deleteBackupDirectory(String backupDirectory) { + try { + Files.deleteIfExists(Paths.get(backupDirectory)); + } catch (IOException e) { + logger.error(String.format("Failed to delete backup directory: %s", backupDirectory), e); + throw new CloudRuntimeException("Failed to delete the backup directory"); + } + } + + private Pair getBackupPath(String volumePath, String backupPath, String diskType, String volumeUuid) { + String volUuid = Objects.isNull(volumeUuid) ? volumePath.substring(volumePath.lastIndexOf(File.separator) + 1) : volumeUuid; + String backupFileName = String.format("%s.%s.qcow2", diskType.toLowerCase(Locale.ROOT), volUuid); + backupPath = String.format(FILE_PATH_PLACEHOLDER, backupPath, backupFileName); + return new Pair<>(backupPath, volUuid); + } + + private boolean checkBackupFileImage(String backupPath) { + int exitValue = Script.runSimpleBashScriptForExitValue(String.format("qemu-img check %s", backupPath)); + return exitValue == 0; + } + + private boolean checkBackupPathExists(String backupPath) { + int exitValue = Script.runSimpleBashScriptForExitValue(String.format("ls %s", backupPath)); + return exitValue == 0; + } + + private boolean replaceVolumeWithBackup(KVMStoragePoolManager storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String backupPath, int timeout) { + return replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, backupPath, timeout, false); + } + + private boolean replaceVolumeWithBackup(KVMStoragePoolManager storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String backupPath, int timeout, boolean createTargetVolume) { + if (volumePool.getPoolType() != Storage.StoragePoolType.RBD) { + int exitValue = Script.runSimpleBashScriptForExitValue(String.format(RSYNC_COMMAND, backupPath, volumePath)); + return exitValue == 0; + } + + return replaceRbdVolumeWithBackup(storagePoolMgr, volumePool, volumePath, backupPath, timeout, createTargetVolume); + } + + private boolean replaceRbdVolumeWithBackup(KVMStoragePoolManager storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String backupPath, int timeout, boolean createTargetVolume) { + KVMStoragePool volumeStoragePool = storagePoolMgr.getStoragePool(volumePool.getPoolType(), volumePool.getUuid()); + QemuImg qemu; + try { + qemu = new QemuImg(timeout * 1000, true, false); + if (!createTargetVolume) { + KVMPhysicalDisk rdbDisk = volumeStoragePool.getPhysicalDisk(volumePath); + logger.debug("Restoring RBD volume: {}", rdbDisk.toString()); + qemu.setSkipTargetVolumeCreation(true); + } + } catch (LibvirtException ex) { + throw new CloudRuntimeException("Failed to create qemu-img command to restore RBD volume with backup", ex); + } + + QemuImgFile srcBackupFile = null; + QemuImgFile destVolumeFile = null; + try { + srcBackupFile = new QemuImgFile(backupPath, QemuImg.PhysicalDiskFormat.QCOW2); + String rbdDestVolumeFile = KVMPhysicalDisk.RBDStringBuilder(volumeStoragePool, volumePath); + destVolumeFile = new QemuImgFile(rbdDestVolumeFile, QemuImg.PhysicalDiskFormat.RAW); + + logger.debug("Starting convert backup {} to RBD volume {}", backupPath, volumePath); + qemu.convert(srcBackupFile, destVolumeFile); + logger.debug("Successfully converted backup {} to RBD volume {}", backupPath, volumePath); + } catch (QemuImgException | LibvirtException e) { + String srcFilename = srcBackupFile != null ? srcBackupFile.getFileName() : null; + String destFilename = destVolumeFile != null ? destVolumeFile.getFileName() : null; + logger.error("Failed to convert backup {} to volume {}, the error was: {}", srcFilename, destFilename, e.getMessage()); + return false; + } + + return true; + } + + private boolean attachVolumeToVm(KVMStoragePoolManager storagePoolMgr, String vmName, PrimaryDataStoreTO volumePool, String volumePath, String cacheMode) { + String deviceToAttachDiskTo = getDeviceToAttachDisk(vmName); + int exitValue; + if (volumePool.getPoolType() != Storage.StoragePoolType.RBD) { + exitValue = Script.runSimpleBashScriptForExitValue(String.format(ATTACH_QCOW2_DISK_COMMAND, vmName, volumePath, deviceToAttachDiskTo)); + } else { + String xmlForRbdDisk = getXmlForRbdDisk(storagePoolMgr, volumePool, volumePath, deviceToAttachDiskTo, cacheMode); + logger.debug("RBD disk xml to attach: {}", xmlForRbdDisk); + exitValue = Script.runSimpleBashScriptForExitValue(String.format(ATTACH_RBD_DISK_XML_COMMAND, vmName, xmlForRbdDisk)); + } + return exitValue == 0; + } + + private String getDeviceToAttachDisk(String vmName) { + String currentDevice = Script.runSimpleBashScript(String.format(CURRRENT_DEVICE, vmName)); + char lastChar = currentDevice.charAt(currentDevice.length() - 1); + char incrementedChar = (char) (lastChar + 1); + return currentDevice.substring(0, currentDevice.length() - 1) + incrementedChar; + } + + private String getXmlForRbdDisk(KVMStoragePoolManager storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String deviceToAttachDiskTo, String cacheMode) { + StringBuilder diskBuilder = new StringBuilder(); + diskBuilder.append("\n\n"); + + diskBuilder.append(" \n"); + + diskBuilder.append("\n"); + for (String sourceHost : volumePool.getHost().split(",")) { + diskBuilder.append("\n"); + } + diskBuilder.append("\n"); + String authUserName = null; + final KVMStoragePool primaryPool = storagePoolMgr.getStoragePool(volumePool.getPoolType(), volumePool.getUuid()); + if (primaryPool != null) { + authUserName = primaryPool.getAuthUserName(); + } + if (StringUtils.isNotBlank(authUserName)) { + diskBuilder.append("\n"); + diskBuilder.append("\n"); + diskBuilder.append("\n"); + } + diskBuilder.append("\n"); + diskBuilder.append("\n"); + return diskBuilder.toString(); + } +} \ No newline at end of file diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java new file mode 100644 index 000000000000..22816be3e6d8 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java @@ -0,0 +1,107 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.hypervisor.kvm.resource.wrapper; + +import com.amazonaws.util.CollectionUtils; +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.storage.Storage; +import com.cloud.utils.Pair; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.backup.CommvaultBackupAnswer; +import org.apache.cloudstack.backup.CommvaultTakeBackupCommand; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@ResourceWrapper(handles = CommvaultTakeBackupCommand.class) +public class LibvirtCommvaultTakeBackupCommandWrapper extends CommandWrapper { + private static final Integer EXIT_CLEANUP_FAILED = 20; + @Override + public Answer execute(CommvaultTakeBackupCommand command, LibvirtComputingResource libvirtComputingResource) { + final String vmName = command.getVmName(); + final String backupPath = command.getBackupPath(); + List volumePools = command.getVolumePools(); + final List volumePaths = command.getVolumePaths(); + KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + + List diskPaths = new ArrayList<>(); + if (Objects.nonNull(volumePaths)) { + for (int idx = 0; idx < volumePaths.size(); idx++) { + PrimaryDataStoreTO volumePool = volumePools.get(idx); + String volumePath = volumePaths.get(idx); + if (volumePool.getPoolType() != Storage.StoragePoolType.RBD) { + diskPaths.add(volumePath); + } else { + KVMStoragePool volumeStoragePool = storagePoolMgr.getStoragePool(volumePool.getPoolType(), volumePool.getUuid()); + String rbdDestVolumeFile = KVMPhysicalDisk.RBDStringBuilder(volumeStoragePool, volumePath); + diskPaths.add(rbdDestVolumeFile); + } + } + } + + List commands = new ArrayList<>(); + commands.add(new String[]{ + libvirtComputingResource.getNasBackupPath(), + "-o", "backup", + "-v", vmName, + "-p", backupPath, + "-q", command.getQuiesce() != null && command.getQuiesce() ? "true" : "false", + "-d", diskPaths.isEmpty() ? "" : String.join(",", diskPaths) + }); + + Pair result = Script.executePipedCommands(commands, libvirtComputingResource.getCmdsTimeout()); + + if (result.first() != 0) { + logger.debug("Failed to take VM backup: " + result.second()); + BackupAnswer answer = new BackupAnswer(command, false, result.second().trim()); + if (result.first() == EXIT_CLEANUP_FAILED) { + logger.debug("Backup cleanup failed"); + answer.setNeedsCleanup(true); + } + return answer; + } + + long backupSize = 0L; + if (CollectionUtils.isNullOrEmpty(diskPaths)) { + List outputLines = Arrays.asList(result.second().trim().split("\n")); + if (!outputLines.isEmpty()) { + backupSize = Long.parseLong(outputLines.get(outputLines.size() - 1).trim()); + } + } else { + String[] outputLines = result.second().trim().split("\n"); + for(String line : outputLines) { + backupSize = backupSize + Long.parseLong(line.split(" ")[0].trim()); + } + } + + BackupAnswer answer = new BackupAnswer(command, true, result.second().trim()); + answer.setSize(backupSize); + return answer; + } +} diff --git a/scripts/vm/hypervisor/kvm/cvtbackup.sh b/scripts/vm/hypervisor/kvm/cvtbackup.sh new file mode 100644 index 000000000000..e69de29bb2d1 From ceec807c29ceb7ef10e23045da448249fad90a28 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 13 Feb 2026 14:57:39 +0900 Subject: [PATCH 10/35] =?UTF-8?q?commvault=20rbd=20=EB=B0=B1=EC=97=85=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=20=EB=B0=8F=20=EA=B8=B0=EC=A1=B4=20=EB=A9=94?= =?UTF-8?q?=EC=BB=A4=EB=8B=88=EC=A6=98=20=EB=B3=80=EA=B2=BD=201=EC=B0=A8?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backup/CommvaultRestoreBackupCommand.java | 9 + .../backup/CommvaultBackupProvider.java | 1668 +++++++---------- ...virtCommvaultTakeBackupCommandWrapper.java | 20 +- scripts/vm/hypervisor/kvm/cvtbackup.sh | 254 +++ 4 files changed, 945 insertions(+), 1006 deletions(-) diff --git a/core/src/main/java/org/apache/cloudstack/backup/CommvaultRestoreBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CommvaultRestoreBackupCommand.java index 2f766c16098f..fbcff2070801 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/CommvaultRestoreBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/CommvaultRestoreBackupCommand.java @@ -38,6 +38,7 @@ public class CommvaultRestoreBackupCommand extends Command { private VirtualMachine.State vmState; private Integer timeout; private String cacheMode; + private String hostName; protected CommvaultRestoreBackupCommand() { super(); @@ -138,4 +139,12 @@ public String getCacheMode() { public void setCacheMode(String cacheMode) { this.cacheMode = cacheMode; } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } } diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index c7c7fdc2e15f..eab121cd6e55 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -22,6 +22,7 @@ import com.cloud.cluster.dao.ManagementServerHostDao; import com.cloud.dc.dao.ClusterDao; import com.cloud.domain.Domain; +import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; @@ -116,24 +117,31 @@ public class CommvaultBackupProvider extends AdapterBase implements BackupProvider, Configurable { private static final Logger LOG = LogManager.getLogger(CommvaultBackupProvider.class); + private static final String RM_COMMAND = "rm -rf %s"; + private static final int BASE_MAJOR = 11; + private static final int BASE_FR = 32; + private static final int BASE_MT = 89; + private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\s*SP\\s*(\\d+)(?:\\.(\\d+))?$", Pattern.CASE_INSENSITIVE); + private static final String COMMVAULT_DIRECTORY = "/tmp/mold/backup"; + public ConfigKey CommvaultUrl = new ConfigKey<>("Advanced", String.class, "backup.plugin.commvault.url", "https://localhost/commandcenter/api", "Commvault Command Center API URL.", true, ConfigKey.Scope.Zone); - private final ConfigKey CommvaultUsername = new ConfigKey<>("Advanced", String.class, + private ConfigKey CommvaultUsername = new ConfigKey<>("Advanced", String.class, "backup.plugin.commvault.username", "admin", "Commvault Command Center API username.", true, ConfigKey.Scope.Zone); - private final ConfigKey CommvaultPassword = new ConfigKey<>("Secure", String.class, + private ConfigKey CommvaultPassword = new ConfigKey<>("Secure", String.class, "backup.plugin.commvault.password", "password", "Commvault Command Center API password.", true, ConfigKey.Scope.Zone); - private final ConfigKey CommvaultValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, + private ConfigKey CommvaultValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, "backup.plugin.commvault.validate.ssl", "false", "Validate the SSL certificate when connecting to Commvault Command Center API service.", true, ConfigKey.Scope.Zone); - private final ConfigKey CommvaultApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, + private ConfigKey CommvaultApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.commvault.request.timeout", "300", "Commvault Command Center API request timeout in seconds.", true, ConfigKey.Scope.Zone); @@ -149,18 +157,16 @@ public class CommvaultBackupProvider extends AdapterBase implements BackupProvid "backup.plugin.commvault.task.poll.max.retry", "120", "The max number of retrying times when the management server polls for Commvault task status.", true, ConfigKey.Scope.Zone); - private final ConfigKey CommvaultClientVerboseLogs = new ConfigKey<>("Advanced", Boolean.class, + private ConfigKey CommvaultClientVerboseLogs = new ConfigKey<>("Advanced", Boolean.class, "backup.plugin.commvault.client.verbosity", "false", "Produce Verbose logs in Hypervisor", true, ConfigKey.Scope.Zone); - private static final String RSYNC_COMMAND = "rsync -az %s %s"; - private static final String RM_COMMAND = "rm -rf %s"; - private static final String CURRRENT_DEVICE = "virsh domblklist --domain %s | tail -n 3 | head -n 1 | awk '{print $1}'"; - private static final String ATTACH_DISK_COMMAND = " virsh attach-disk %s %s %s --driver qemu --subdriver qcow2 --cache none"; - private static final int BASE_MAJOR = 11; - private static final int BASE_FR = 32; - private static final int BASE_MT = 89; - private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\s*SP\\s*(\\d+)(?:\\.(\\d+))?$", Pattern.CASE_INSENSITIVE); + private ConfigKey CommvaultBackupRestoreTimeout = new ConfigKey<>("Advanced", Integer.class, + "commvault.backup.restore.timeout", + "30", + "Timeout in seconds after which qemu-img execute when restoring", + true, + BackupFrameworkEnabled.key()); @Inject private BackupDao backupDao; @@ -210,335 +216,267 @@ public class CommvaultBackupProvider extends AdapterBase implements BackupProvid @Inject private DiskOfferingDao diskOfferingDao; - private static String getUrlDomain(String url) throws URISyntaxException { - URI uri; - try { - uri = new URI(url); - } catch (URI.MalformedURIException e) { - throw new CloudRuntimeException("Failed to cast URI"); - } - - return uri.getHost(); - } - - @Override - public ConfigKey[] getConfigKeys() { - return new ConfigKey[]{ - CommvaultUrl, - CommvaultUsername, - CommvaultPassword, - CommvaultValidateSSLSecurity, - CommvaultApiRequestTimeout, - CommvaultClientVerboseLogs - }; - } - - @Override - public String getName() { - return "commvault"; - } - - @Override - public String getDescription() { - return "Commvault Backup Plugin"; - } - @Override - public String getConfigComponentName() { - return BackupService.class.getSimpleName(); + private Long getClusterIdFromRootVolume(VirtualMachine vm) { + VolumeVO rootVolume = volumeDao.getInstanceRootVolume(vm.getId()); + StoragePoolVO rootDiskPool = primaryDataStoreDao.findById(rootVolume.getPoolId()); + if (rootDiskPool == null) { + return null; + } + return rootDiskPool.getClusterId(); } - protected HostVO getLastVMHypervisorHost(VirtualMachine vm) { - HostVO host; + protected Host getVMHypervisorHost(VirtualMachine vm) { Long hostId = vm.getLastHostId(); + Long clusterId = null; - if (hostId == null) { - LOG.debug("Cannot find last host for vm. This should never happen, please check your database."); - return null; + if (hostId != null) { + Host host = hostDao.findById(hostId); + if (host.getStatus() == Status.Up) { + return host; + } + // Try to find any Up host in the same cluster + clusterId = host.getClusterId(); + } else { + // Try to find any Up host in the same cluster as the root volume + clusterId = getClusterIdFromRootVolume(vm); } - host = hostDao.findById(hostId); - if (host.getStatus() == Status.Up) { - return host; - } else { - // Try to find a host in the same cluster - List altClusterHosts = hostDao.findHypervisorHostInCluster(host.getClusterId()); - for (final HostVO candidateClusterHost : altClusterHosts) { - if ( candidateClusterHost.getStatus() == Status.Up ) { - LOG.debug(String.format("Found Host %s", candidateClusterHost)); - return candidateClusterHost; + if (clusterId != null) { + for (final Host hostInCluster : hostDao.findHypervisorHostInCluster(clusterId)) { + if (hostInCluster.getStatus() == Status.Up) { + LOG.debug("Found Host {} in cluster {}", hostInCluster, clusterId); + return hostInCluster; } } } - // Try to find a Host in the zone - List altZoneHosts = hostDao.findByDataCenterId(host.getDataCenterId()); - for (final HostVO candidateZoneHost : altZoneHosts) { - if ( candidateZoneHost.getStatus() == Status.Up && candidateZoneHost.getHypervisorType() == Hypervisor.HypervisorType.KVM ) { - LOG.debug("Found Host " + candidateZoneHost); - return candidateZoneHost; - } - } - return null; - } - protected HostVO getRunningVMHypervisorHost(VirtualMachine vm) { + // Try to find any Host in the zone + return resourceManager.findOneRandomRunningHostByHypervisor(Hypervisor.HypervisorType.KVM, vm.getDataCenterId()); + } - HostVO host; + protected Host getVMHypervisorHostForBackup(VirtualMachine vm) { Long hostId = vm.getHostId(); - + if (hostId == null && VirtualMachine.State.Running.equals(vm.getState())) { + throw new CloudRuntimeException(String.format("Unable to find the hypervisor host for %s. Make sure the virtual machine is running", vm.getName())); + } + if (VirtualMachine.State.Stopped.equals(vm.getState())) { + hostId = vm.getLastHostId(); + } if (hostId == null) { - throw new CloudRuntimeException("Unable to find the HYPERVISOR for " + vm.getName() + ". Make sure the virtual machine is running"); + throw new CloudRuntimeException(String.format("Unable to find the hypervisor host for stopped VM: %s", vm)); + } + final Host host = hostDao.findById(hostId); + if (host == null || !Status.Up.equals(host.getStatus()) || !Hypervisor.HypervisorType.KVM.equals(host.getHypervisorType())) { + throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); } - - host = hostDao.findById(hostId); - return host; } - protected String getVMHypervisorCluster(HostVO host) { - - return clusterDao.findById(host.getClusterId()).getName(); - } + // 백업 + @Override + public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { + final Host vmHost = getVMHypervisorHostForBackup(vm); + if (CollectionUtils.isNotEmpty(vmSnapshotDao.findByVmAndByType(vm.getId(), VMSnapshot.Type.DiskAndMemory))) { + LOG.debug("Commvault backup provider cannot take backups of a VM [{}] with disk-and-memory VM snapshots. Restoring the backup will corrupt any newer disk-and-memory " + + "VM snapshots.", vm); + throw new CloudRuntimeException(String.format("Cannot take backup of VM [%s] as it has disk-and-memory VM snapshots.", vm.getUuid())); + } - protected Ternary getKVMHyperisorCredentials(HostVO host) { + try { + String commvaultServer = getUrlDomain(CommvaultUrl.value()); + } catch (URISyntaxException e) { + throw new CloudRuntimeException(String.format("Failed to convert API to HOST : %s", e)); + } + // 백업 중인 작업 조회 + final CommvaultClient client = getClient(vm.getDataCenterId()); + boolean activeJob = client.getActiveJob(vm.getInstanceName()); + if (activeJob) { + throw new CloudRuntimeException("There are backup jobs running on the virtual machine. Please try again later."); + } - String username = null; - String password = null; + BackupOfferingVO vmBackupOffering = new BackupOfferingDaoImpl().findById(vm.getBackupOfferingId()); + String planId = vmBackupOffering.getExternalId(); - if (host != null && host.getHypervisorType() == Hypervisor.HypervisorType.KVM) { - hostDao.loadDetails(host); - password = host.getDetail("password"); - username = host.getDetail("username"); - } - if ( password == null || username == null) { - throw new CloudRuntimeException("Cannot find login credentials for HYPERVISOR " + Objects.requireNonNull(host).getUuid()); + // 클라이언트의 백업세트 조회하여 호스트 정의 + String checkVm = client.getVmBackupSetId(vmHost.getName(), vm.getInstanceName()); + if (checkVm == null) { + String clientId = client.getClientId(vmHost.getName()); + String applicationId = client.getApplicationId(clientId); + boolean result = client.createBackupSet(vm.getInstanceName(), applicationId, clientId, planId); + if (!result) { + throw new CloudRuntimeException("Execution of the API that creates a backup set of a virtual machine on the host failed."); + } } - return new Ternary<>(username, password, null); - } + final Date creationDate = new Date(); + final String backupPath = String.format("%s/%s/%s", COMMVAULT_DIRECTORY, vm.getInstanceName(), + new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(creationDate)); - private CommvaultClient getClient(final Long zoneId) { + BackupVO backupVO = createBackupObject(vm, backupPath); + CommvaultTakeBackupCommand command = new CommvaultTakeBackupCommand(vm.getInstanceName(), backupPath); + command.setQuiesce(quiesceVM); + + if (VirtualMachine.State.Stopped.equals(vm.getState())) { + List vmVolumes = volumeDao.findByInstance(vm.getId()); + vmVolumes.sort(Comparator.comparing(Volume::getDeviceId)); + Pair, List> volumePoolsAndPaths = getVolumePoolsAndPaths(vmVolumes); + command.setVolumePools(volumePoolsAndPaths.first()); + command.setVolumePaths(volumePoolsAndPaths.second()); + } + + BackupAnswer answer; try { - return new CommvaultClient(CommvaultUrl.valueIn(zoneId), CommvaultUsername.valueIn(zoneId), CommvaultPassword.valueIn(zoneId), - CommvaultValidateSSLSecurity.valueIn(zoneId), CommvaultApiRequestTimeout.valueIn(zoneId)); - } catch (URISyntaxException e) { - throw new CloudRuntimeException("Failed to parse Commvault API URL: " + e.getMessage()); - } catch (NoSuchAlgorithmException | KeyManagementException e) { - LOG.error("Failed to build Commvault API client due to: ", e); + answer = (BackupAnswer) agentManager.send(vmHost.getId(), command); + } catch (AgentUnavailableException e) { + LOG.error("Unable to contact backend control plane to initiate backup for VM {}", vm.getInstanceName()); + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); + } catch (OperationTimedoutException e) { + LOG.error("Operation to initiate backup timed out for VM {}", vm.getInstanceName()); + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + throw new CloudRuntimeException("Operation to initiate backup timed out, please try again"); } - throw new CloudRuntimeException("Failed to build Commvault API client"); - } - @Override - public boolean checkBackupAgent(final Long zoneId) { - Map checkResult = new HashMap<>(); - final CommvaultClient client = getClient(zoneId); - String csVersionInfo = client.getCvtVersion(); - boolean version = versionCheck(csVersionInfo); - if (version) { - List Hosts = hostDao.findByDataCenterId(zoneId); - for (final HostVO host : Hosts) { - if (host.getStatus() == Status.Up && host.getHypervisorType() == Hypervisor.HypervisorType.KVM) { - String checkHost = client.getClientId(host.getName()); - if (checkHost == null) { - return false; + if (answer != null && answer.getResult()) { + // 생성된 백업 폴더 경로로 해당 백업 세트의 백업 콘텐츠 경로 업데이트 + String clientId = client.getClientId(vmHost.getName()); + String subClientEntity = client.getSubclient(clientId, vm.getInstanceName()); + if (subClientEntity == null) { + LOG.error("Failed to take backup for VM " + vm.getInstanceName() + " to get subclient info commvault api"); + } else { + JSONObject jsonObject = new JSONObject(subClientEntity); + String subclientId = String.valueOf(jsonObject.get("subclientId")); + String applicationId = String.valueOf(jsonObject.get("applicationId")); + String backupsetId = String.valueOf(jsonObject.get("backupsetId")); + String instanceId = String.valueOf(jsonObject.get("instanceId")); + String backupsetName = String.valueOf(jsonObject.get("backupsetName")); + String displayName = String.valueOf(jsonObject.get("displayName")); + String commCellName = String.valueOf(jsonObject.get("commCellName")); + String companyId = String.valueOf(jsonObject.getJSONObject("entityInfo").get("companyId")); + String companyName = String.valueOf(jsonObject.getJSONObject("entityInfo").get("companyName")); + String instanceName = String.valueOf(jsonObject.get("instanceName")); + String appName = String.valueOf(jsonObject.get("appName")); + String clientName = String.valueOf(jsonObject.get("clientName")); + String subclientGUID = String.valueOf(jsonObject.get("subclientGUID")); + String subclientName = String.valueOf(jsonObject.get("subclientName")); + String csGUID = String.valueOf(jsonObject.get("csGUID")); + boolean upResult = client.updateBackupSet(backupPath, subclientId, clientId, planId, applicationId, backupsetId, instanceId, subclientName, backupsetName); + if (upResult) { + String planName = client.getPlanName(planId); + String storagePolicyId = client.getStoragePolicyId(planName); + if (planName == null || storagePolicyId == null) { + LOG.error("Failed to take backup for VM " + vm.getInstanceName() + " to get storage policy id commvault api"); } else { - boolean installJob = client.getInstallActiveJob(host.getPrivateIpAddress()); - boolean checkInstall = client.getClientProps(checkHost); - if (installJob || !checkInstall) { - if (!checkInstall) { - LOG.error("The host is registered with the client, but the readiness status is not normal and you must manually check the client status."); + // 백업 실행 + String jobId = client.createBackup(subclientId, storagePolicyId, displayName, commCellName, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, subclientGUID, subclientName, csGUID, backupsetName); + if (jobId != null) { + String jobStatus = client.getJobStatus(jobId); + if (jobStatus.equalsIgnoreCase("Completed")) { + String jobDetails = client.getJobDetails(jobId); + if (jobDetails != null) { + JSONObject jsonObject2 = new JSONObject(jobDetails); + String endTime = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("detailInfo").get("endTime")); + long timestamp = Long.parseLong(endTime) * 1000L; + Date endDate = new Date(timestamp); + SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + String formattedString = formatterDateTime.format(endDate); + String size = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("detailInfo").get("sizeOfApplication")); + String type = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").get("backupType")); + String externalId = backupPath + "," + jobId; + backupVO.setExternalId(externalId); + backupVO.setType(type.toUpperCase()); + try { + backupVO.setDate(formatterDateTime.parse(formattedString)); + } catch (ParseException e) { + String msg = String.format("Unable to parse date [%s].", endTime); + LOG.error(msg, e); + throw new CloudRuntimeException(msg, e); + } + backupVO.setSize(Long.parseLong(size)); + backupVO.setStatus(Backup.Status.BackedUp); + List vols = new ArrayList<>(volumeDao.findByInstance(vm.getId())); + backupVO.setBackedUpVolumes(backupManager.createVolumeInfoFromVolumes(vols)); + if (backupDao.update(backupVO.getId(), backupVO)) { + return new Pair<>(true, backupVO); + } else { + throw new CloudRuntimeException("Failed to update backup"); + } + } else { + LOG.error("Failed to take backup for VM " + vm.getInstanceName() + " to get details job commvault api"); + } + } else { + LOG.error("Failed to take backup for VM " + vm.getInstanceName() + " to create backup job status is " + jobStatus); } - return false; + } else { + LOG.error("Failed to take backup for VM " + vm.getInstanceName() + " to create backup job commvault api"); } } + } else { + LOG.error("Failed to take backup for VM " + vm.getInstanceName() + " to update backupset content path commvault api"); } } - return true; + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); + Ternary credentials = getKVMHyperisorCredentials(vmHost); + String cmd = String.format(RM_COMMAND, backupPath); + executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, cmd); + return new Pair<>(false, null); + } else { + LOG.error("Failed to take backup for VM {}: {}", vm.getInstanceName(), answer != null ? answer.getDetails() : "No answer received"); + if (answer.getNeedsCleanup()) { + LOG.error("Backup cleanup failed for VM {}. Leaving the backup in Error state.", vm.getInstanceName()); + backupVO.setStatus(Backup.Status.Error); + backupDao.update(backupVO.getId(), backupVO); + } else { + backupVO.setStatus(Backup.Status.Failed); + backupDao.remove(backupVO.getId()); + } + return new Pair<>(false, null); } - return false; } - @Override - public boolean installBackupAgent(final Long zoneId) { - Map failResult = new HashMap<>(); - final CommvaultClient client = getClient(zoneId); - List Hosts = hostDao.findByDataCenterId(zoneId); - for (final HostVO host : Hosts) { - if (host.getStatus() == Status.Up && host.getHypervisorType() == Hypervisor.HypervisorType.KVM) { - String commCell = client.getCommcell(); - JSONObject jsonObject = new JSONObject(commCell); - String commCellId = String.valueOf(jsonObject.get("commCellId")); - String commServeHostName = String.valueOf(jsonObject.get("commCellName")); - Ternary credentials = getKVMHyperisorCredentials(host); - boolean installJob = true; - LOG.info("checking for install agent on the Commvault Backup Provider in host " + host.getPrivateIpAddress()); - // 설치가 진행중인 호스트가 있는지 확인 - while (installJob) { - installJob = client.getInstallActiveJob(host.getName()); - try { - Thread.sleep(30000); - } catch (InterruptedException e) { - LOG.error("checkBackupAgent get install active job result sleep interrupted error"); - } - } - String checkHost = client.getClientId(host.getName()); - // 호스트가 클라이언트에 등록되지 않은 경우 - if (checkHost == null) { - String jobId = client.installAgent(host.getPrivateIpAddress(), commCellId, commServeHostName, credentials.first(), credentials.second()); - if (jobId != null) { - String jobStatus = client.getJobStatus(jobId); - if (!jobStatus.equalsIgnoreCase("Completed")) { - LOG.error("installing agent on the Commvault Backup Provider failed jogId : " + jobId + " , jobStatus : " + jobStatus); - ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, EventTypes.EVENT_HOST_AGENT_INSTALL, - "Failed install the commvault client agent on the host : " + host.getPrivateIpAddress(), User.UID_SYSTEM, ApiCommandResourceType.Host.toString()); - failResult.put(host.getPrivateIpAddress(), jobId); - } - } else { - return false; - } - } else { - // 호스트가 클라이언트에는 등록되었지만 구성이 정상적으로 되지 않은 경우 준비 상태 체크 - boolean checkInstall = client.getClientCheckReadiness(checkHost); - if (!checkInstall) { - LOG.error("The host is registered with the client, but the readiness status is not normal and you must manually check the client status."); - ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, EventTypes.EVENT_HOST_AGENT_INSTALL, - "Failed check readiness the commvault client agent on the host : " + host.getPrivateIpAddress(), User.UID_SYSTEM, ApiCommandResourceType.Host.toString()); - return false; - } - } + private BackupVO createBackupObject(VirtualMachine vm, String backupPath) { + BackupVO backup = new BackupVO(); + backup.setVmId(vm.getId()); + backup.setExternalId(backupPath); + backup.setType("FULL"); + backup.setDate(new Date()); + long virtualSize = 0L; + for (final Volume volume: volumeDao.findByInstance(vm.getId())) { + if (Volume.State.Ready.equals(volume.getState())) { + virtualSize += volume.getSize(); } } - if (!failResult.isEmpty()) { - return false; - } - return true; + backup.setProtectedSize(virtualSize); + backup.setStatus(Backup.Status.BackingUp); + backup.setBackupOfferingId(vm.getBackupOfferingId()); + backup.setAccountId(vm.getAccountId()); + backup.setDomainId(vm.getDomainId()); + backup.setZoneId(vm.getDataCenterId()); + backup.setName(backupManager.getBackupNameFromVM(vm)); + Map details = backupManager.getBackupDetailsFromVM(vm); + backup.setDetails(details); + + return backupDao.persist(backup); } + // 백업에서 새 인스턴스 생성 @Override - public boolean importBackupPlan(final Long zoneId, final String retentionPeriod, final String externalId) { - final CommvaultClient client = getClient(zoneId); - // 선택한 백업 정책의 RPO 편집 Commvault API 호출 - String type = "deleteRpo"; - String taskId = client.getScheduleTaskId(type, externalId); - if (taskId != null) { - String subTaskId = client.getSubTaskId(taskId); - if (subTaskId != null) { - boolean result = client.deleteSchedulePolicy(taskId, subTaskId); - if (!result) { - throw new CloudRuntimeException("Failed to delete schedule policy commvault api"); - } - } - } else { - throw new CloudRuntimeException("Failed to get plan details schedule task id commvault api"); - } - // 선택한 백업 정책의 보존 기간 변경 Commvault API 호출 - type = "updateRpo"; - String planEntity = client.getScheduleTaskId(type, externalId); - JSONObject jsonObject = new JSONObject(planEntity); - String planType = String.valueOf(jsonObject.get("planType")); - String planName = String.valueOf(jsonObject.get("planName")); - String planSubtype = String.valueOf(jsonObject.get("planSubtype")); - String planId = String.valueOf(jsonObject.get("planId")); - JSONObject entityInfo = jsonObject.getJSONObject("entityInfo"); - String companyId = String.valueOf(entityInfo.get("companyId")); - String storagePolicyId = client.getStoragePolicyId(planName); - if (storagePolicyId == null) { - throw new CloudRuntimeException("Failed to get plan storage policy id commvault api"); - } - boolean result = client.getStoragePolicyDetails(planId, storagePolicyId, retentionPeriod); - if (result) { - // 호스트에 선택한 백업 정책 설정 Commvault API 호출 - String path = "/"; - List Hosts = hostDao.findByDataCenterId(zoneId); - for (final HostVO host : Hosts) { - String backupSetId = client.getDefaultBackupSetId(host.getName()); - if (backupSetId != null) { - if (!client.setBackupSet(path, planType, planName, planSubtype, planId, companyId, backupSetId)) { - throw new CloudRuntimeException("Failed to setting backup plan for client commvault api"); - } - } - } - return true; - } else { - throw new CloudRuntimeException("Failed to edit plan schedule retention period commvault api"); - } - } - - @Override - public boolean updateBackupPlan(final Long zoneId, final String retentionPeriod, final String externalId) { - final CommvaultClient client = getClient(zoneId); - String type = "updateRpo"; - String planEntity = client.getScheduleTaskId(type, externalId); - JSONObject jsonObject = new JSONObject(planEntity); - String planType = String.valueOf(jsonObject.get("planType")); - String planName = String.valueOf(jsonObject.get("planName")); - String planSubtype = String.valueOf(jsonObject.get("planSubtype")); - String planId = String.valueOf(jsonObject.get("planId")); - JSONObject entityInfo = jsonObject.getJSONObject("entityInfo"); - String companyId = String.valueOf(entityInfo.get("companyId")); - String storagePolicyId = client.getStoragePolicyId(planName); - if (storagePolicyId == null) { - throw new CloudRuntimeException("Failed to get plan storage policy id commvault api"); - } - return client.getStoragePolicyDetails(planId, storagePolicyId, retentionPeriod); - } - - @Override - public List listBackupOfferings(Long zoneId) { - return getClient(zoneId).listPlans(); - } - - @Override - public boolean isValidProviderOffering(Long zoneId, String uuid) { - List policies = listBackupOfferings(zoneId); - if (CollectionUtils.isEmpty(policies)) { - return false; - } - for (final BackupOffering policy : policies) { - if (policy.getExternalId().equals(uuid)) { - return true; - } - } - return false; - } - - @Override - public boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backupOffering) { - HostVO hostVO; - final CommvaultClient client = getClient(vm.getDataCenterId()); - if (vm.getState() == VirtualMachine.State.Running) { - hostVO = getRunningVMHypervisorHost(vm); - } else { - hostVO = getLastVMHypervisorHost(vm); - } - String clientId = client.getClientId(hostVO.getName()); - String applicationId = client.getApplicationId(clientId); - return client.createBackupSet(vm.getInstanceName(), applicationId, clientId, backupOffering.getExternalId()); + public Pair restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid) { + return restoreVMBackup(vm, backup); } + // 가상머신 백업 복원 @Override - public boolean removeVMFromBackupOffering(VirtualMachine vm) { - final CommvaultClient client = getClient(vm.getDataCenterId()); - List Hosts = hostDao.findByDataCenterId(vm.getDataCenterId()); - for (final HostVO host : Hosts) { - if (host.getHypervisorType() == Hypervisor.HypervisorType.KVM) { - String backupSetId = client.getVmBackupSetId(host.getName(), vm.getInstanceName()); - if (backupSetId != null) { - return client.deleteBackupSet(backupSetId); - } - } - } - return false; + public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { + return restoreVMBackup(vm, backup).first(); } - @Override - public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { - List backedVolumes = backup.getBackedUpVolumes(); - List volumes = backedVolumes.stream() - .map(volume -> volumeDao.findByUuid(volume.getUuid())) - .sorted((v1, v2) -> Long.compare(v1.getDeviceId(), v2.getDeviceId())) - .collect(Collectors.toList()); + private Pair restoreVMBackup(VirtualMachine vm, Backup backup) { try { String commvaultServer = getUrlDomain(CommvaultUrl.value()); } catch (URISyntaxException e) { @@ -571,68 +509,101 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { if (backupsetGUID == null) { throw new CloudRuntimeException("Failed to get vm backup set guid commvault api"); } + // 복원된 호스트 정의 + final HostVO restoreHost = hostDao.findByName(clientName); LOG.info(String.format("Restoring vm %s from backup %s on the Commvault Backup Provider", vm, backup)); // 복원 실행 - int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); - HostVO hostVO = hostDao.findByName(clientName); - Ternary credentials = getKVMHyperisorCredentials(hostVO); String jobId2 = client.restoreFullVM(subclientId, displayName, backupsetGUID, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, backupsetName, commCellId, endTime, path); if (jobId2 != null) { String jobStatus = client.getJobStatus(jobId2); if (jobStatus.equalsIgnoreCase("Completed")) { - String snapshotId = backup.getSnapshotId(); - Map checkResult = new HashMap<>(); - if (snapshotId != null || !snapshotId.isEmpty()) { - String[] snapshots = snapshotId.split(","); - for (int i=0; i < snapshots.length; i++) { - SnapshotVO snapshot = snapshotDao.findByUuidIncludingRemoved(snapshots[i]); - VolumeVO volume = volumeDao.findById(snapshot.getVolumeId()); - StoragePoolVO storagePool = primaryDataStoreDao.findById(volume.getPoolId()); - String volumePath = String.format("%s/%s", storagePool.getPath(), volume.getPath()); - SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findDestroyedReferenceBySnapshot(snapshot.getSnapshotId(), DataStoreRole.Primary); - String snapshotPath = snapshotStore.getInstallPath(); - String command = String.format(RSYNC_COMMAND, snapshotPath, volumePath); - if (executeRestoreCommand(hostVO, credentials.first(), credentials.second(), sshPort, command)) { - Date restoreJobEnd = new Date(); - if (snapshots.length > 1) { - String[] paths = path.split(","); - checkResult.put(snapshots[i], paths[i]); - } else { - checkResult.put(snapshots[i], path); - } - } else { - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - command = String.format(RM_COMMAND, value); - executeDeleteSnapshotCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } - } - return false; - } - } - if (!checkResult.isEmpty()) { - for (String value : checkResult.values()) { - String command = String.format(RM_COMMAND, value); - executeDeleteSnapshotCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } + List backedVolumesUUIDs = backup.getBackedUpVolumes().stream() + .sorted(Comparator.comparingLong(Backup.VolumeInfo::getDeviceId)) + .map(Backup.VolumeInfo::getUuid) + .collect(Collectors.toList()); + + List restoreVolumes = volumeDao.findByInstance(vm.getId()).stream() + .sorted(Comparator.comparingLong(VolumeVO::getDeviceId)) + .collect(Collectors.toList()); + + LOG.debug("Restoring vm {} from backup {} on the Commvault Backup Provider", vm, backup); + // 가상머신이 실행중인 호스트 정의 + final Host vmHost = getVMHypervisorHost(vm); + CommvaultRestoreBackupCommand restoreCommand = new RestoreBackupCommand(); + restoreCommand.setBackupPath(backup.getExternalId()); + restoreCommand.setVmName(vm.getName()); + restoreCommand.setBackupVolumesUUIDs(backedVolumesUUIDs); + Pair, List> volumePoolsAndPaths = getVolumePoolsAndPaths(restoreVolumes); + restoreCommand.setRestoreVolumePools(volumePoolsAndPaths.first()); + restoreCommand.setRestoreVolumePaths(volumePoolsAndPaths.second()); + restoreCommand.setVmExists(vm.getRemoved() == null); + restoreCommand.setVmState(vm.getState()); + restoreCommand.setTimeout(CommvaultBackupRestoreTimeout.value()); + // 복원된 호스트와 가상머신이 실행중인 호스트가 같은 경우 null, 다른 경우 추가 + restoreCommand.setHostName(restoreHost.getId() == vmHost.getId() ? null : restoreHost.getName()); + + BackupAnswer answer; + try { + answer = (BackupAnswer) agentManager.send(vmHost.getId(), restoreCommand); + } catch (AgentUnavailableException e) { + throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); + } catch (OperationTimedoutException e) { + throw new CloudRuntimeException("Operation to restore backup timed out, please try again"); + } + if (!answer.getResult()) { + int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); + Ternary credentials = getKVMHyperisorCredentials(vmHost); + String command = String.format(RM_COMMAND, path); + executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, command); + if (restoreHost.getId() != vmHost.getId()) { + credentials = getKVMHyperisorCredentials(restoreHost); + command = String.format(RM_COMMAND, path); + executeDeleteBackupPathCommand(restoreHost, credentials.first(), credentials.second(), sshPort, command); } - return true; } + return new Pair<>(answer.getResult(), answer.getDetails()); } else { - // 복원 실패 - LOG.error("restoreBackup commvault api resulted in " + jobStatus); - return false; + throw new CloudRuntimeException("Failed to restore Full VM commvault api resulted in " + jobStatus); } + } else { + throw new CloudRuntimeException("Failed to restore Full VM commvault api"); } - return false; } + private Pair, List> getVolumePoolsAndPaths(List volumes) { + List volumePools = new ArrayList<>(); + List volumePaths = new ArrayList<>(); + for (VolumeVO volume : volumes) { + StoragePoolVO storagePool = primaryDataStoreDao.findById(volume.getPoolId()); + if (Objects.isNull(storagePool)) { + throw new CloudRuntimeException("Unable to find storage pool associated to the volume"); + } + + DataStore dataStore = dataStoreMgr.getDataStore(storagePool.getId(), DataStoreRole.Primary); + volumePools.add(dataStore != null ? (PrimaryDataStoreTO)dataStore.getTO() : null); + + String volumePathPrefix = getVolumePathPrefix(storagePool); + volumePaths.add(String.format("%s/%s", volumePathPrefix, volume.getPath())); + } + return new Pair<>(volumePools, volumePaths); + } + + private String getVolumePathPrefix(StoragePoolVO storagePool) { + String volumePathPrefix; + if (ScopeType.HOST.equals(storagePool.getScope()) || + Storage.StoragePoolType.SharedMountPoint.equals(storagePool.getPoolType()) || + Storage.StoragePoolType.RBD.equals(storagePool.getPoolType())) { + volumePathPrefix = storagePool.getPath(); + } else { + // Should be Storage.StoragePoolType.NetworkFilesystem + volumePathPrefix = String.format("/mnt/%s", storagePool.getUuid()); + } + return volumePathPrefix; + } + + // 백업 볼륨 복원 및 연결 @Override public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, Pair vmNameAndState) { - final VolumeVO volume = volumeDao.findByUuid(backupVolumeInfo.getUuid()); - List backedVolumes = backup.getBackedUpVolumes(); - final DiskOffering diskOffering = diskOfferingDao.findByUuid(backupVolumeInfo.getDiskOfferingId()); - final StoragePoolVO pool = primaryDataStoreDao.findByUuid(dataStoreUuid); try { String commvaultServer = getUrlDomain(CommvaultUrl.value()); } catch (URISyntaxException e) { @@ -668,345 +639,247 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI } LOG.info(String.format("Restoring volume %s from backup %s on the Commvault Backup Provider", volume.getUuid(), backup)); // 복원 실행 - int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); - HostVO hostVO = hostDao.findByName(clientName); - Ternary credentials = getKVMHyperisorCredentials(hostVO); String jobId2 = client.restoreFullVM(subclientId, displayName, backupsetGUID, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, backupsetName, commCellId, endTime, path); if (jobId2 != null) { String jobStatus = client.getJobStatus(jobId2); if (jobStatus.equalsIgnoreCase("Completed")) { - String snapshotId = backup.getSnapshotId(); - Map checkResult = new HashMap<>(); - String restoreVolume = null; - if (snapshotId != null || !snapshotId.isEmpty()) { - String[] snapshots = snapshotId.split(","); - for (int i=0; i < snapshots.length; i++) { - SnapshotVO snapshot = snapshotDao.findByUuidIncludingRemoved(snapshots[i]); - VolumeVO volumes = volumeDao.findById(snapshot.getVolumeId()); - StoragePoolVO storagePool = primaryDataStoreDao.findById(volumes.getPoolId()); - String volumePath = String.format("%s/%s", storagePool.getPath(), volume.getPath()); - SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findDestroyedReferenceBySnapshot(snapshot.getSnapshotId(), DataStoreRole.Primary); - String snapshotPath = snapshotStore.getInstallPath(); - if (volumes.getPath().equalsIgnoreCase(volume.getPath())) { - VMInstanceVO backupSourceVm = vmInstanceDao.findById(backup.getVmId()); - VolumeVO restoredVolume = new VolumeVO(Volume.Type.DATADISK, null, backup.getZoneId(), - backup.getDomainId(), backup.getAccountId(), 0, null, - backup.getSize(), null, null, null); - String volumeName = volume != null ? volume.getName() : backupVolumeInfo.getUuid(); - restoredVolume.setName("RV-"+volumeName); - restoredVolume.setProvisioningType(diskOffering.getProvisioningType()); - restoredVolume.setUpdated(new Date()); - restoredVolume.setUuid(UUID.randomUUID().toString()); - restoredVolume.setRemoved(null); - restoredVolume.setDisplayVolume(true); - restoredVolume.setPoolId(pool.getId()); - restoredVolume.setPath(restoredVolume.getUuid()); - restoredVolume.setState(Volume.State.Copying); - restoredVolume.setSize(backupVolumeInfo.getSize()); - restoredVolume.setDiskOfferingId(diskOffering.getId()); - try { - volumeDao.persist(restoredVolume); - } catch (Exception e) { - throw new CloudRuntimeException("Unable to craft restored volume due to: "+e); - } - String reVolumePath = String.format("%s/%s", storagePool.getPath(), restoredVolume.getUuid()); - String command = String.format(RSYNC_COMMAND, snapshotPath, reVolumePath); - if (executeRestoreCommand(hostVO, credentials.first(), credentials.second(), sshPort, command)) { - Date restoreJobEnd = new Date(); - LOG.info("Restore Job for jobID " + jobId2 + " completed successfully at " + restoreJobEnd); - if (VirtualMachine.State.Running.equals(vmNameAndState.second())) { - final VMInstanceVO vm = vmInstanceDao.findVMByInstanceName(vmNameAndState.first()); - HostVO rvHostVO = hostDao.findById(vm.getHostId()); - Ternary rvCredentials = getKVMHyperisorCredentials(rvHostVO); - command = String.format(CURRRENT_DEVICE, vmNameAndState.first()); - String currentDevice = executeDeviceCommand(rvHostVO, rvCredentials.first(), rvCredentials.second(), sshPort, command); - if (currentDevice == null || currentDevice.contains("error")) { - volumeDao.expunge(restoredVolume.getId()); - command = String.format(RM_COMMAND, snapshotPath); - executeDeleteSnapshotCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - throw new CloudRuntimeException("Failed to get current device execute command VM to location " + volume.getPath()); - } else { - currentDevice = currentDevice.replaceAll("\\s", ""); - char lastChar = currentDevice.charAt(currentDevice.length() - 1); - char incrementedChar = (char) (lastChar + 1); - String rvDevice = currentDevice.substring(0, currentDevice.length() - 1) + incrementedChar; - command = String.format(ATTACH_DISK_COMMAND, vmNameAndState.first(), reVolumePath, rvDevice); - if (!executeAttachCommand(rvHostVO, rvCredentials.first(), rvCredentials.second(), sshPort, command)) { - volumeDao.expunge(restoredVolume.getId()); - command = String.format(RM_COMMAND, snapshotPath); - executeDeleteSnapshotCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - throw new CloudRuntimeException(String.format("Failed to attach volume to VM: %s", vmNameAndState.first())); - } - } - } - checkResult.put(snapshots[i], volumePath); - restoreVolume = restoredVolume.getUuid(); - } else { - volumeDao.expunge(restoredVolume.getId()); - LOG.info("Restore Job for jobID " + jobId2 + " completed failed."); - command = String.format(RM_COMMAND, snapshotPath); - executeDeleteSnapshotCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } - } else { - String command = String.format(RM_COMMAND, snapshotPath); - executeDeleteSnapshotCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); - } + final VolumeVO volume = volumeDao.findByUuid(backupVolumeInfo.getUuid()); + final DiskOffering diskOffering = diskOfferingDao.findByUuid(backupVolumeInfo.getDiskOfferingId()); + String cacheMode = null; + final VMInstanceVO vm = vmInstanceDao.findVMByInstanceName(vmNameAndState.first()); + List listVolumes = volumeDao.findByInstanceAndType(vm.getId(), Type.ROOT); + if(CollectionUtils.isNotEmpty(listVolumes)) { + VolumeVO rootDisk = listVolumes.get(0); + DiskOffering baseDiskOffering = diskOfferingDao.findById(rootDisk.getDiskOfferingId()); + if (baseDiskOffering.getCacheMode() != null) { + cacheMode = baseDiskOffering.getCacheMode().toString(); } - if (!checkResult.isEmpty()) { - return new Pair<>(true,restoreVolume); - } else { - throw new CloudRuntimeException("Failed to restore VM to location " + volume.getPath()); + } + final StoragePoolVO pool = primaryDataStoreDao.findByUuid(dataStoreUuid); + // 백업 볼륨 복원 및 연결 시 연결할 가상머신이 실행중인 경우 해당 호스트, 정지중인 경우 랜덤 호스트 정의백업 + final HostVO vmHost = hostDao.findByIp(hostIp); + // 복원된 호스트 정의 + final HostVO restoreHost = hostDao.findByName(clientName); + LOG.debug("Restoring vm volume {} from backup {} on the Commvault Backup Provider", backupVolumeInfo, backup); + VolumeVO restoredVolume = new VolumeVO(Volume.Type.DATADISK, null, backup.getZoneId(), + backup.getDomainId(), backup.getAccountId(), 0, null, + backup.getSize(), null, null, null); + String volumeUUID = UUID.randomUUID().toString(); + String volumeName = volume != null ? volume.getName() : backupVolumeInfo.getUuid(); + restoredVolume.setName("RestoredVol-" + volumeName); + restoredVolume.setProvisioningType(diskOffering.getProvisioningType()); + restoredVolume.setUpdated(new Date()); + restoredVolume.setUuid(volumeUUID); + restoredVolume.setRemoved(null); + restoredVolume.setDisplayVolume(true); + restoredVolume.setPoolId(pool.getId()); + restoredVolume.setPoolType(pool.getPoolType()); + restoredVolume.setPath(restoredVolume.getUuid()); + restoredVolume.setState(Volume.State.Copying); + restoredVolume.setSize(backupVolumeInfo.getSize()); + restoredVolume.setDiskOfferingId(diskOffering.getId()); + if (pool.getPoolType() != Storage.StoragePoolType.RBD) { + restoredVolume.setFormat(Storage.ImageFormat.QCOW2); + } else { + restoredVolume.setFormat(Storage.ImageFormat.RAW); + } + + CommvaultRestoreBackupCommand restoreCommand = new CommvaultRestoreBackupCommand(); + restoreCommand.setBackupPath(path); + restoreCommand.setVmName(vmNameAndState.first()); + restoreCommand.setRestoreVolumePaths(Collections.singletonList(String.format("%s/%s", getVolumePathPrefix(pool), volumeUUID))); + DataStore dataStore = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); + restoreCommand.setRestoreVolumePools(Collections.singletonList(dataStore != null ? (PrimaryDataStoreTO)dataStore.getTO() : null)); + restoreCommand.setDiskType(backupVolumeInfo.getType().name().toLowerCase(Locale.ROOT)); + restoreCommand.setVmExists(null); + restoreCommand.setVmState(vmNameAndState.second()); + restoreCommand.setRestoreVolumeUUID(backupVolumeInfo.getUuid()); + restoreCommand.setTimeout(CommvaultBackupRestoreTimeout.value()); + restoreCommand.setCacheMode(cacheMode); + // 복원된 호스트와 가상머신이 실행중인 호스트가 같은 경우 null, 다른 경우 추가 + restoreCommand.setHostName(restoreHost.getId() == vmHost.getId() ? null : restoreHost.getName()); + + BackupAnswer answer; + try { + answer = (BackupAnswer) agentManager.send(hostVO.getId(), restoreCommand); + } catch (AgentUnavailableException e) { + throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); + } catch (OperationTimedoutException e) { + throw new CloudRuntimeException("Operation to restore backed up volume timed out, please try again"); + } + + if (answer.getResult()) { + try { + volumeDao.persist(restoredVolume); + } catch (Exception e) { + throw new CloudRuntimeException("Unable to create restored volume due to: " + e); + } + return new Pair<>(answer.getResult(), answer.getDetails()); + } else { + int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); + Ternary credentials = getKVMHyperisorCredentials(vmHost); + String command = String.format(RM_COMMAND, path); + executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, command); + if (restoreHost.getId() != vmHost.getId()) { + credentials = getKVMHyperisorCredentials(restoreHost); + command = String.format(RM_COMMAND, path); + executeDeleteBackupPathCommand(restoreHost, credentials.first(), credentials.second(), sshPort, command); } } } else { - // 복원 실패 - LOG.error("restoreBackup commvault api resulted in " + jobStatus); - throw new CloudRuntimeException("Failed to restore VM to location " + volume.getPath() + " commvault api resulted in " + jobStatus); + LOG.error("Failed to restore backup for VM " + vmNameAndState.first() + " to restore backup job status is " + jobStatus); } + } else { + LOG.error("Failed to restore backup for VM " + vmNameAndState.first() + " to restore backup job commvault api"); } - return new Pair<>(false,null); + return new Pair<>(false, null); } - protected Host getVMHypervisorHostForBackup(VirtualMachine vm) { - Long hostId = vm.getHostId(); - if (hostId == null && VirtualMachine.State.Running.equals(vm.getState())) { - throw new CloudRuntimeException(String.format("Unable to find the hypervisor host for %s. Make sure the virtual machine is running", vm.getName())); - } - if (VirtualMachine.State.Stopped.equals(vm.getState())) { - hostId = vm.getLastHostId(); - } - if (hostId == null) { - throw new CloudRuntimeException(String.format("Unable to find the hypervisor host for stopped VM: %s", vm)); - } - final Host host = hostDao.findById(hostId); - if (host == null || !Status.Up.equals(host.getStatus()) || !Hypervisor.HypervisorType.KVM.equals(host.getHypervisorType())) { - throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); - } - return host; + private Optional getBackedUpVolumeInfo(List backedUpVolumes, String volumeUuid) { + return backedUpVolumes.stream() + .filter(v -> v.getUuid().equals(volumeUuid)) + .findFirst(); } - private BackupVO createBackupObject(VirtualMachine vm, String backupPath) { - BackupVO backup = new BackupVO(); - backup.setVmId(vm.getId()); - backup.setExternalId(backupPath); - backup.setType("FULL"); - backup.setDate(new Date()); - long virtualSize = 0L; - for (final Volume volume: volumeDao.findByInstance(vm.getId())) { - if (Volume.State.Ready.equals(volume.getState())) { - virtualSize += volume.getSize(); - } + @Override + public boolean deleteBackup(Backup backup, boolean forced) { + final Long zoneId = backup.getZoneId(); + final String externalId = backup.getExternalId(); + String jobId = externalId.substring(externalId.lastIndexOf(',') + 1).trim(); + String path = externalId.substring(0, externalId.lastIndexOf(',')); + final CommvaultClient client = getClient(zoneId); + String jobDetails = client.getJobDetails(jobId); + if (jobDetails != null) { + JSONObject jsonObject = new JSONObject(jobDetails); + String subclientId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("subclientId")); + String applicationId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("applicationId")); + String instanceId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("instanceId")); + String clientId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("clientId")); + String clientName = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("clientName")); + String backupsetId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("backupsetId")); + return client.deleteBackup(subclientId, applicationId, applicationId, clientId, clientName, backupsetId, path); + } else { + throw new CloudRuntimeException("Failed to request backup job detail commvault api"); } - backup.setProtectedSize(virtualSize); - backup.setStatus(Backup.Status.BackingUp); - backup.setBackupOfferingId(vm.getBackupOfferingId()); - backup.setAccountId(vm.getAccountId()); - backup.setDomainId(vm.getDomainId()); - backup.setZoneId(vm.getDataCenterId()); - backup.setName(backupManager.getBackupNameFromVM(vm)); - Map details = backupManager.getBackupDetailsFromVM(vm); - backup.setDetails(details); + } - return backupDao.persist(backup); + public void syncBackupMetrics(Long zoneId) { + } + + @Override + public List listRestorePoints(VirtualMachine vm) { + return null; } @Override - public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { - final Host vmHost = getVMHypervisorHostForBackup(vm); + public Backup createNewBackupEntryForRestorePoint(Backup.RestorePoint restorePoint, VirtualMachine vm) { + return null; + } - if (CollectionUtils.isNotEmpty(vmSnapshotDao.findByVmAndByType(vm.getId(), VMSnapshot.Type.DiskAndMemory))) { - logger.debug("Commvault backup provider cannot take backups of a VM [{}] with disk-and-memory VM snapshots. Restoring the backup will corrupt any newer disk-and-memory " + - "VM snapshots.", vm); - throw new CloudRuntimeException(String.format("Cannot take backup of VM [%s] as it has disk-and-memory VM snapshots.", vm.getUuid())); - } + @Override + public boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backupOffering) { + final CommvaultClient client = getClient(vm.getDataCenterId()); + final Host host = getVMHypervisorHostForBackup(vm); + String clientId = client.getClientId(hostVO.getName()); + String applicationId = client.getApplicationId(clientId); + return client.createBackupSet(vm.getInstanceName(), applicationId, clientId, backupOffering.getExternalId()); + } - String hostName = null; - try { - String commvaultServer = getUrlDomain(CommvaultUrl.value()); - } catch (URISyntaxException e) { - throw new CloudRuntimeException(String.format("Failed to convert API to HOST : %s", e)); - } - // 백업 중인 작업 조회 + @Override + public boolean removeVMFromBackupOffering(VirtualMachine vm) { final CommvaultClient client = getClient(vm.getDataCenterId()); - boolean activeJob = client.getActiveJob(vm.getInstanceName()); - if (activeJob) { - throw new CloudRuntimeException("There are backup jobs running on the virtual machine. Please try again later."); - } - // 클라이언트의 백업세트 조회하여 호스트 정의 List Hosts = hostDao.findByDataCenterId(vm.getDataCenterId()); + boolean allDeleted = true; for (final HostVO host : Hosts) { if (host.getHypervisorType() == Hypervisor.HypervisorType.KVM) { - String checkVm = client.getVmBackupSetId(host.getName(), vm.getInstanceName()); - if (checkVm != null) { - hostName = host.getName(); + String backupSetId = client.getVmBackupSetId(host.getName(), vm.getInstanceName()); + if (backupSetId != null) { + boolean deleted = client.deleteBackupSet(backupSetId); + if (!deleted) { + allDeleted = false; + LOG.error("Failed to delete backupSetId: " + backupSetId +" for VM: " + vm.getInstanceName()); + } } } } - BackupOfferingVO vmBackupOffering = new BackupOfferingDaoImpl().findById(vm.getBackupOfferingId()); - String planId = vmBackupOffering.getExternalId(); - - final Date creationDate = new Date(); - final String backupPath = String.format("%s/%s", vm.getInstanceName(), - new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(creationDate)); + return allDeleted; + } - BackupVO backupVO = createBackupObject(vm, backupPath); - CommvaultTakeBackupCommand command = new CommvaultTakeBackupCommand(vm.getInstanceName(), backupPath); - command.setQuiesce(quiesceVM); + // 하위 클라이언트 삭제 시 백업본 데이터는 그대로 남아있지만, 해당 하위 클라이언트가 삭제되었기 때문에 스케줄도 삭제시켜야하며 + // 남아있는 백업본 데이터는 mold에서 관리하지 않고, commvault 의 plan 보존기간에 따라 데이터 에이징 됨. + @Override + public boolean willDeleteBackupsOnOfferingRemoval() { + return true; + } - if (VirtualMachine.State.Stopped.equals(vm.getState())) { - List vmVolumes = volumeDao.findByInstance(vm.getId()); - vmVolumes.sort(Comparator.comparing(Volume::getDeviceId)); - Pair, List> volumePoolsAndPaths = getVolumePoolsAndPaths(vmVolumes); - command.setVolumePools(volumePoolsAndPaths.first()); - command.setVolumePaths(volumePoolsAndPaths.second()); - } + @Override + public boolean supportsInstanceFromBackup() { + return true; + } - BackupAnswer answer; - try { - answer = (BackupAnswer) agentManager.send(vmHost.getId(), command); - } catch (AgentUnavailableException e) { - logger.error("Unable to contact backend control plane to initiate backup for VM {}", vm.getInstanceName()); - backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); - throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); - } catch (OperationTimedoutException e) { - logger.error("Operation to initiate backup timed out for VM {}", vm.getInstanceName()); - backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); - throw new CloudRuntimeException("Operation to initiate backup timed out, please try again"); - } + @Override + public boolean supportsMemoryVmSnapshot() { + return false; + } - if (answer != null && answer.getResult()) { - // 생성된 백업 폴더 경로로 해당 백업 세트의 백업 콘텐츠 경로 업데이트 - String clientId = client.getClientId(hostName); - String subClientEntity = client.getSubclient(clientId, vm.getInstanceName()); - if (subClientEntity == null) { - // 백업 폴더 삭제 명령 전송 필요* - backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); - throw new CloudRuntimeException("Failed to take backup for VM " + vm.getInstanceName() + " to get subclient info commvault api"); - } - JSONObject jsonObject = new JSONObject(subClientEntity); - String subclientId = String.valueOf(jsonObject.get("subclientId")); - String applicationId = String.valueOf(jsonObject.get("applicationId")); - String backupsetId = String.valueOf(jsonObject.get("backupsetId")); - String instanceId = String.valueOf(jsonObject.get("instanceId")); - String backupsetName = String.valueOf(jsonObject.get("backupsetName")); - String displayName = String.valueOf(jsonObject.get("displayName")); - String commCellName = String.valueOf(jsonObject.get("commCellName")); - String companyId = String.valueOf(jsonObject.getJSONObject("entityInfo").get("companyId")); - String companyName = String.valueOf(jsonObject.getJSONObject("entityInfo").get("companyName")); - String instanceName = String.valueOf(jsonObject.get("instanceName")); - String appName = String.valueOf(jsonObject.get("appName")); - String clientName = String.valueOf(jsonObject.get("clientName")); - String subclientGUID = String.valueOf(jsonObject.get("subclientGUID")); - String subclientName = String.valueOf(jsonObject.get("subclientName")); - String csGUID = String.valueOf(jsonObject.get("csGUID")); - boolean upResult = client.updateBackupSet(backupPath, subclientId, clientId, planId, applicationId, backupsetId, instanceId, subclientName, backupsetName); - if (upResult) { - String planName = client.getPlanName(planId); - String storagePolicyId = client.getStoragePolicyId(planName); - if (planName == null || storagePolicyId == null) { - // 백업 폴더 삭제 명령 전송 필요* - backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); - throw new CloudRuntimeException("Failed to take backup for VM " + vm.getInstanceName() + " to get storage policy id commvault api"); - } - // 백업 실행 - String jobId = client.createBackup(subclientId, storagePolicyId, displayName, commCellName, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, subclientGUID, subclientName, csGUID, backupsetName); - if (jobId != null) { - String jobStatus = client.getJobStatus(jobId); - if (jobStatus.equalsIgnoreCase("Completed")) { - String jobDetails = client.getJobDetails(jobId); - if (jobDetails != null) { - JSONObject jsonObject2 = new JSONObject(jobDetails); - String endTime = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("detailInfo").get("endTime")); - long timestamp = Long.parseLong(endTime) * 1000L; - Date endDate = new Date(timestamp); - SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - String formattedString = formatterDateTime.format(endDate); - String size = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("detailInfo").get("sizeOfApplication")); - String type = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").get("backupType")); - String externalId = backupPath + "," + jobId; - backupVO.setExternalId(externalId); - backupVO.setType(type.toUpperCase()); - try { - backupVO.setDate(formatterDateTime.parse(formattedString)); - } catch (ParseException e) { - String msg = String.format("Unable to parse date [%s].", endTime); - LOG.error(msg, e); - throw new CloudRuntimeException(msg, e); - } - backupVO.setSize(Long.parseLong(size)); - backupVO.setStatus(Backup.Status.BackedUp); - List vols = new ArrayList<>(volumeDao.findByInstance(vm.getId())); - backupVO.setBackedUpVolumes(backupManager.createVolumeInfoFromVolumes(vols)); - if (backupDao.update(backupVO.getId(), backupVO)) { - return new Pair<>(true, backupVO); - } else { - throw new CloudRuntimeException("Failed to update backup"); - } - } else { - backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); - logger.error("Failed to take backup for VM " + vm.getInstanceName() + " to get details job commvault api"); - return new Pair<>(false, null); - } - } else { - // 백업 폴더 삭제 명령 전송 필요* - backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); - logger.error("Failed to take backup for VM " + vm.getInstanceName() + " to create backup job status is " + jobStatus); - return new Pair<>(false, null); - } - } else { - // 백업 폴더 삭제 명령 전송 필요* - backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); - logger.error("Failed to take backup for VM " + vm.getInstanceName() + " to create backup job commvault api"); - return new Pair<>(false, null); - } - } else { - // 백업 폴더 삭제 명령 전송 필요* - backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); - logger.error("Failed to take backup for VM " + vm.getInstanceName() + " to update backupset content path commvault api"); - return new Pair<>(false, null); - } - } else { - logger.error("Failed to take backup for VM {}: {}", vm.getInstanceName(), answer != null ? answer.getDetails() : "No answer received"); - if (answer.getNeedsCleanup()) { - logger.error("Backup cleanup failed for VM {}. Leaving the backup in Error state.", vm.getInstanceName()); - backupVO.setStatus(Backup.Status.Error); - backupDao.update(backupVO.getId(), backupVO); - } else { - backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); + @Override + public Pair getBackupStorageStats(Long zoneId) { + return new Pair<>(0L, 0L); + } + + @Override + public void syncBackupStorageStats(Long zoneId) { + } + + @Override + public List listBackupOfferings(Long zoneId) { + return getClient(zoneId).listPlans(); + } + + @Override + public boolean isValidProviderOffering(Long zoneId, String uuid) { + List policies = listBackupOfferings(zoneId); + if (CollectionUtils.isEmpty(policies)) { + return false; + } + for (final BackupOffering policy : policies) { + if (policy.getExternalId().equals(uuid)) { + return true; } - return new Pair<>(false, null); } + return false; } @Override - public boolean deleteBackup(Backup backup, boolean forced) { - final Long zoneId = backup.getZoneId(); - final String externalId = backup.getExternalId(); - String jobId = externalId.substring(externalId.lastIndexOf(',') + 1).trim(); - String path = externalId.substring(0, externalId.lastIndexOf(',')); - final CommvaultClient client = getClient(zoneId); - String jobDetails = client.getJobDetails(jobId); - if (jobDetails != null) { - JSONObject jsonObject = new JSONObject(jobDetails); - String subclientId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("subclientId")); - String applicationId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("applicationId")); - String instanceId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("instanceId")); - String clientId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("clientId")); - String clientName = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("clientName")); - String backupsetId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("subclient").get("backupsetId")); - return client.deleteBackup(subclientId, applicationId, applicationId, clientId, clientName, backupsetId, path); - } else { - throw new CloudRuntimeException("Failed to request backup job detail commvault api"); - } + public Boolean crossZoneInstanceCreationEnabled(BackupOffering backupOffering) { + return false; + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{ + CommvaultUrl, + CommvaultUsername, + CommvaultPassword, + CommvaultValidateSSLSecurity, + CommvaultApiRequestTimeout, + CommvaultClientVerboseLogs + }; + } + + @Override + public String getName() { + return "commvault"; + } + + @Override + public String getDescription() { + return "Commvault Backup Plugin"; + } + + @Override + public String getConfigComponentName() { + return BackupService.class.getSimpleName(); } @Override @@ -1026,7 +899,6 @@ public void syncBackups(VirtualMachine vm) { JSONObject jsonObject = new JSONObject(jobDetails); String retainedUntil = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").get("retainedUntil")); String storagePolicyId = String.valueOf(jsonObject.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").getJSONObject("storagePolicy").get("storagePolicyId")); - // 보존 기간 sync BackupOfferingVO vmBackupOffering = new BackupOfferingDaoImpl().findById(vm.getBackupOfferingId()); BackupOfferingVO offering = backupOfferingDao.createForUpdate(vmBackupOffering.getId()); String retentionDay = client.getRetentionPeriod(storagePolicyId); @@ -1051,352 +923,214 @@ public void syncBackups(VirtualMachine vm) { return; } - protected static String moldCreateSnapshotBackupAPI(String region, String command, String method, String apiKey, String secretKey, Map params) { - try { - String readLine = null; - StringBuffer sb = null; - String apiParams = buildParamsMold(command, params); - String urlFinal = buildUrl(apiParams, region, apiKey, secretKey); - URL url = new URL(urlFinal); - HttpURLConnection connection = null; - if (region.contains("https")) { - final SSLContext sslContext = SSLUtils.getSSLContext(); - sslContext.init(null, new TrustManager[]{new TrustAllManager()}, new SecureRandom()); - HttpsURLConnection httpsConnection = (HttpsURLConnection) url.openConnection(); - httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection = httpsConnection; - } else { - connection = (HttpURLConnection) url.openConnection(); - } - connection.setDoOutput(true); - connection.setRequestMethod(method); - connection.setConnectTimeout(10000); - connection.setReadTimeout(180000); - connection.setRequestProperty("Accept", "application/json"); - connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); - if (connection.getResponseCode() == 200) { - BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); - sb = new StringBuffer(); - while ((readLine = br.readLine()) != null) { - sb.append(readLine); + @Override + public boolean checkBackupAgent(final Long zoneId) { + Map checkResult = new HashMap<>(); + final CommvaultClient client = getClient(zoneId); + String csVersionInfo = client.getCvtVersion(); + boolean version = versionCheck(csVersionInfo); + if (version) { + List Hosts = hostDao.findByDataCenterId(zoneId); + for (final HostVO host : Hosts) { + if (host.getStatus() == Status.Up && host.getHypervisorType() == Hypervisor.HypervisorType.KVM) { + String checkHost = client.getClientId(host.getName()); + if (checkHost == null) { + return false; + } else { + boolean installJob = client.getInstallActiveJob(host.getPrivateIpAddress()); + boolean checkInstall = client.getClientProps(checkHost); + if (installJob || !checkInstall) { + if (!checkInstall) { + LOG.error("The host is registered with the client, but the readiness status is not normal and you must manually check the client status."); + } + return false; + } + } } - } else { - String msg = "Failed to request mold API. response code : " + connection.getResponseCode(); - LOG.error(msg); - return null; } - JSONObject jObject = XML.toJSONObject(sb.toString()); - JSONObject response = (JSONObject) jObject.get("createsnapshotbackupresponse"); - return response.toString(); - } catch (Exception e) { - LOG.error(String.format("Mold API endpoint not available"), e); - return null; + return true; } + return false; } - protected static String moldDeleteSnapshotAPI(String region, String command, String method, String apiKey, String secretKey, Map params) { - try { - String readLine = null; - StringBuffer sb = null; - String apiParams = buildParamsMold(command, params); - String urlFinal = buildUrl(apiParams, region, apiKey, secretKey); - URL url = new URL(urlFinal); - HttpURLConnection connection = null; - if (region.contains("https")) { - final SSLContext sslContext = SSLUtils.getSSLContext(); - sslContext.init(null, new TrustManager[]{new TrustAllManager()}, new SecureRandom()); - HttpsURLConnection httpsConnection = (HttpsURLConnection) url.openConnection(); - httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection = httpsConnection; - } else { - connection = (HttpURLConnection) url.openConnection(); - } - connection.setDoOutput(true); - connection.setRequestMethod(method); - connection.setConnectTimeout(10000); - connection.setReadTimeout(180000); - connection.setRequestProperty("Accept", "application/json"); - connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); - if (connection.getResponseCode() == 200) { - BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); - sb = new StringBuffer(); - while ((readLine = br.readLine()) != null) { - sb.append(readLine); + @Override + public boolean installBackupAgent(final Long zoneId) { + Map failResult = new HashMap<>(); + final CommvaultClient client = getClient(zoneId); + List Hosts = hostDao.findByDataCenterId(zoneId); + for (final HostVO host : Hosts) { + if (host.getStatus() == Status.Up && host.getHypervisorType() == Hypervisor.HypervisorType.KVM) { + String commCell = client.getCommcell(); + JSONObject jsonObject = new JSONObject(commCell); + String commCellId = String.valueOf(jsonObject.get("commCellId")); + String commServeHostName = String.valueOf(jsonObject.get("commCellName")); + Ternary credentials = getKVMHyperisorCredentials(host); + boolean installJob = true; + LOG.info("checking for install agent on the Commvault Backup Provider in host " + host.getPrivateIpAddress()); + // 설치가 진행중인 호스트가 있는지 확인 + while (installJob) { + installJob = client.getInstallActiveJob(host.getName()); + try { + Thread.sleep(30000); + } catch (InterruptedException e) { + LOG.error("checkBackupAgent get install active job result sleep interrupted error"); + } } - } else { - String msg = "Failed to request mold API. response code : " + connection.getResponseCode(); - LOG.error(msg); - return null; - } - JSONObject jObject = XML.toJSONObject(sb.toString()); - JSONObject response = (JSONObject) jObject.get("deletesnapshotresponse"); - return response.toString(); - } catch (Exception e) { - LOG.error(String.format("Mold API endpoint not available"), e); - return null; - } - } - - protected static String moldQueryAsyncJobResultAPI(String region, String command, String method, String apiKey, String secretKey, Map params) { - try { - String readLine = null; - StringBuffer sb = null; - String apiParams = buildParamsMold(command, params); - String urlFinal = buildUrl(apiParams, region, apiKey, secretKey); - URL url = new URL(urlFinal); - HttpURLConnection connection = null; - if (region.contains("https")) { - final SSLContext sslContext = SSLUtils.getSSLContext(); - sslContext.init(null, new TrustManager[]{new TrustAllManager()}, new SecureRandom()); - HttpsURLConnection httpsConnection = (HttpsURLConnection) url.openConnection(); - httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection = httpsConnection; - } else { - connection = (HttpURLConnection) url.openConnection(); - } - connection.setDoOutput(true); - connection.setRequestMethod(method); - connection.setConnectTimeout(10000); - connection.setReadTimeout(180000); - connection.setRequestProperty("Accept", "application/json"); - connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); - if (connection.getResponseCode() == 200) { - BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); - sb = new StringBuffer(); - while ((readLine = br.readLine()) != null) { - sb.append(readLine); + String checkHost = client.getClientId(host.getName()); + // 호스트가 클라이언트에 등록되지 않은 경우 + if (checkHost == null) { + String jobId = client.installAgent(host.getPrivateIpAddress(), commCellId, commServeHostName, credentials.first(), credentials.second()); + if (jobId != null) { + String jobStatus = client.getJobStatus(jobId); + if (!jobStatus.equalsIgnoreCase("Completed")) { + LOG.error("installing agent on the Commvault Backup Provider failed jogId : " + jobId + " , jobStatus : " + jobStatus); + ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, EventTypes.EVENT_HOST_AGENT_INSTALL, + "Failed install the commvault client agent on the host : " + host.getPrivateIpAddress(), User.UID_SYSTEM, ApiCommandResourceType.Host.toString()); + failResult.put(host.getPrivateIpAddress(), jobId); + } + } else { + return false; + } + } else { + // 호스트가 클라이언트에는 등록되었지만 구성이 정상적으로 되지 않은 경우 준비 상태 체크 + boolean checkInstall = client.getClientCheckReadiness(checkHost); + if (!checkInstall) { + LOG.error("The host is registered with the client, but the readiness status is not normal and you must manually check the client status."); + ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, EventTypes.EVENT_HOST_AGENT_INSTALL, + "Failed check readiness the commvault client agent on the host : " + host.getPrivateIpAddress(), User.UID_SYSTEM, ApiCommandResourceType.Host.toString()); + return false; + } } - } else { - String msg = "Failed to request mold API. response code : " + connection.getResponseCode(); - LOG.error(msg); - return null; } - JSONObject jObject = XML.toJSONObject(sb.toString()); - JSONObject response = (JSONObject) jObject.get("queryasyncjobresultresponse"); - return response.get("jobstatus").toString(); - } catch (Exception e) { - LOG.error(String.format("Mold API endpoint not available"), e); - return null; } - } - - protected static String buildParamsMold(String command, Map params) { - StringBuffer paramString = new StringBuffer("command=" + command); - if (params != null) { - try { - for(Map.Entry param : params.entrySet() ){ - String key = param.getKey(); - String value = param.getValue(); - paramString.append("&" + param.getKey() + "=" + URLEncoder.encode(param.getValue(), "UTF-8")); - } - } catch (UnsupportedEncodingException e) { - LOG.error(e.getMessage()); - return null; - } + if (!failResult.isEmpty()) { + return false; } - return paramString.toString(); + return true; } - private static String buildUrl(String apiParams, String region, String apiKey, String secretKey) { - String encodedApiKey; - try { - encodedApiKey = URLEncoder.encode(apiKey, "UTF-8"); - List sortedParams = new ArrayList(); - sortedParams.add("apikey=" + encodedApiKey.toLowerCase()); - StringTokenizer st = new StringTokenizer(apiParams, "&"); - String url = null; - boolean first = true; - while (st.hasMoreTokens()) { - String paramValue = st.nextToken(); - String param = paramValue.substring(0, paramValue.indexOf("=")); - String value = paramValue.substring(paramValue.indexOf("=") + 1, paramValue.length()); - if (first) { - url = param + "=" + value; - first = false; - } else { - url = url + "&" + param + "=" + value; - } - sortedParams.add(param.toLowerCase() + "=" + value.toLowerCase()); - } - Collections.sort(sortedParams); - String sortedUrl = null; - first = true; - for (String param : sortedParams) { - if (first) { - sortedUrl = param; - first = false; - } else { - sortedUrl = sortedUrl + "&" + param; + @Override + public boolean importBackupPlan(final Long zoneId, final String retentionPeriod, final String externalId) { + final CommvaultClient client = getClient(zoneId); + // 선택한 백업 정책의 RPO 편집 Commvault API 호출 + String type = "deleteRpo"; + String taskId = client.getScheduleTaskId(type, externalId); + if (taskId != null) { + String subTaskId = client.getSubTaskId(taskId); + if (subTaskId != null) { + boolean result = client.deleteSchedulePolicy(taskId, subTaskId); + if (!result) { + throw new CloudRuntimeException("Failed to delete schedule policy commvault api"); } } - String encodedSignature = signRequest(sortedUrl, secretKey); - String finalUrl = region + "?" + apiParams + "&apiKey=" + apiKey + "&signature=" + encodedSignature; - return finalUrl; - } catch (UnsupportedEncodingException e) { - LOG.error(e.getMessage()); - return null; + } else { + throw new CloudRuntimeException("Failed to get plan details schedule task id commvault api"); } - } - - private static String signRequest(String request, String key) { - try { - Mac mac = Mac.getInstance("HmacSHA256"); - SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacSHA256"); - mac.init(keySpec); - mac.update(request.getBytes()); - byte[] encryptedBytes = mac.doFinal(); - return URLEncoder.encode(Base64.encodeBase64String(encryptedBytes), "UTF-8"); - } catch (Exception ex) { - LOG.error(ex.getMessage()); - return null; + // 선택한 백업 정책의 보존 기간 변경 Commvault API 호출 + type = "updateRpo"; + String planEntity = client.getScheduleTaskId(type, externalId); + JSONObject jsonObject = new JSONObject(planEntity); + String planType = String.valueOf(jsonObject.get("planType")); + String planName = String.valueOf(jsonObject.get("planName")); + String planSubtype = String.valueOf(jsonObject.get("planSubtype")); + String planId = String.valueOf(jsonObject.get("planId")); + JSONObject entityInfo = jsonObject.getJSONObject("entityInfo"); + String companyId = String.valueOf(entityInfo.get("companyId")); + String storagePolicyId = client.getStoragePolicyId(planName); + if (storagePolicyId == null) { + throw new CloudRuntimeException("Failed to get plan storage policy id commvault api"); } - } - - private int getAsyncJobResult(String moldUrl, String apiKey, String secretKey, String jobId) throws CloudRuntimeException { - int jobStatus = 0; - String moldCommand = "queryAsyncJobResult"; - String moldMethod = "GET"; - Map params = new HashMap<>(); - params.put("jobid", jobId); - while (jobStatus == 0) { - String result = moldQueryAsyncJobResultAPI(moldUrl, moldCommand, moldMethod, apiKey, secretKey, params); - if (result != null) { - jobStatus = Integer.parseInt(result); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - LOG.error("create snapshot get asyncjob result sleep interrupted error"); + boolean result = client.getStoragePolicyDetails(planId, storagePolicyId, retentionPeriod); + if (result) { + // 호스트에 선택한 백업 정책 설정 Commvault API 호출 + String path = "/"; + List Hosts = hostDao.findByDataCenterId(zoneId); + for (final HostVO host : Hosts) { + String backupSetId = client.getDefaultBackupSetId(host.getName()); + if (backupSetId != null) { + if (!client.setBackupSet(path, planType, planName, planSubtype, planId, companyId, backupSetId)) { + throw new CloudRuntimeException("Failed to setting backup plan for client commvault api"); + } } - } else { - throw new CloudRuntimeException("Failed to request queryAsyncJobResult Mold-API."); } + return true; + } else { + throw new CloudRuntimeException("Failed to edit plan schedule retention period commvault api"); } - return jobStatus; - } - - private Optional getBackedUpVolumeInfo(List backedUpVolumes, String volumeUuid) { - return backedUpVolumes.stream() - .filter(v -> v.getUuid().equals(volumeUuid)) - .findFirst(); } - private String[] getServerProperties() { - String[] serverInfo = null; - final String HTTP_PORT = "http.port"; - final String HTTPS_ENABLE = "https.enable"; - final String HTTPS_PORT = "https.port"; - final File confFile = PropertiesUtil.findConfigFile("server.properties"); - try { - InputStream is = new FileInputStream(confFile); - String port = null; - String protocol = null; - final Properties properties = ServerProperties.getServerProperties(is); - if (properties.getProperty(HTTPS_ENABLE).equals("true")){ - port = properties.getProperty(HTTPS_PORT); - protocol = "https"; - } else { - port = properties.getProperty(HTTP_PORT); - protocol = "http"; - } - serverInfo = new String[]{port, protocol}; - } catch (final IOException e) { - LOG.debug("Failed to read configuration from server.properties file", e); + @Override + public boolean updateBackupPlan(final Long zoneId, final String retentionPeriod, final String externalId) { + final CommvaultClient client = getClient(zoneId); + String type = "updateRpo"; + String planEntity = client.getScheduleTaskId(type, externalId); + JSONObject jsonObject = new JSONObject(planEntity); + String planType = String.valueOf(jsonObject.get("planType")); + String planName = String.valueOf(jsonObject.get("planName")); + String planSubtype = String.valueOf(jsonObject.get("planSubtype")); + String planId = String.valueOf(jsonObject.get("planId")); + JSONObject entityInfo = jsonObject.getJSONObject("entityInfo"); + String companyId = String.valueOf(entityInfo.get("companyId")); + String storagePolicyId = client.getStoragePolicyId(planName); + if (storagePolicyId == null) { + throw new CloudRuntimeException("Failed to get plan storage policy id commvault api"); } - return serverInfo; + return client.getStoragePolicyDetails(planId, storagePolicyId, retentionPeriod); } - private boolean executeTakeBackupCommand(HostVO host, String username, String password, int port, String command) { + private static String getUrlDomain(String url) throws URISyntaxException { + URI uri; try { - Pair response = SshHelper.sshExecute(host.getPrivateIpAddress(), port, - username, null, password, command, 120000, 120000, 3600000); - - if (!response.first()) { - LOG.error(String.format("take backup vm xml file failed on HYPERVISOR %s due to: %s", host, response.second())); - } else { - return true; - } - } catch (final Exception e) { - throw new CloudRuntimeException(String.format("Failed to take backup vm xml file on host %s due to: %s", host.getName(), e.getMessage())); + uri = new URI(url); + } catch (URI.MalformedURIException e) { + throw new CloudRuntimeException("Failed to cast URI"); } - return false; - } - private boolean executeDeleteXmlCommand(HostVO host, String username, String password, int port, String command) { - try { - Pair response = SshHelper.sshExecute(host.getPrivateIpAddress(), port, - username, null, password, command, 120000, 120000, 3600000); - - if (!response.first()) { - LOG.error(String.format("Delete xml file failed on HYPERVISOR %s due to: %s", host, response.second())); - } else { - return true; - } - } catch (final Exception e) { - throw new CloudRuntimeException(String.format("Failed to delete xml file on host %s due to: %s", host.getName(), e.getMessage())); - } - return false; + return uri.getHost(); } - private String executeDeviceCommand(HostVO host, String username, String password, int port, String command) { + private CommvaultClient getClient(final Long zoneId) { try { - Pair response = SshHelper.sshExecute(host.getPrivateIpAddress(), port, - username, null, password, command, 120000, 120000, 3600000); - - if (!response.first()) { - LOG.error(String.format("get current device failed on HYPERVISOR %s due to: %s", host, response.second())); - } else { - return response.second(); - } - } catch (final Exception e) { - throw new CloudRuntimeException(String.format("Failed to get current device backup on host %s due to: %s", host.getName(), e.getMessage())); + return new CommvaultClient(CommvaultUrl.valueIn(zoneId), CommvaultUsername.valueIn(zoneId), CommvaultPassword.valueIn(zoneId), + CommvaultValidateSSLSecurity.valueIn(zoneId), CommvaultApiRequestTimeout.valueIn(zoneId)); + } catch (URISyntaxException e) { + throw new CloudRuntimeException("Failed to parse Commvault API URL: " + e.getMessage()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + LOG.error("Failed to build Commvault API client due to: ", e); } - return null; + throw new CloudRuntimeException("Failed to build Commvault API client"); } - private boolean executeAttachCommand(HostVO host, String username, String password, int port, String command) { - try { - Pair response = SshHelper.sshExecute(host.getPrivateIpAddress(), port, - username, null, password, command, 120000, 120000, 3600000); - - if (!response.first()) { - LOG.error(String.format("Attach voulme failed on HYPERVISOR %s due to: %s", host, response.second())); - } else { - return true; - } - } catch (final Exception e) { - throw new CloudRuntimeException(String.format("Failed to attach volume backup on host %s due to: %s", host.getName(), e.getMessage())); - } - return false; - } + protected Ternary getKVMHyperisorCredentials(Host host) { - private boolean executeRestoreCommand(HostVO host, String username, String password, int port, String command) { - try { - Pair response = SshHelper.sshExecute(host.getPrivateIpAddress(), port, - username, null, password, command, 120000, 120000, 3600000); + String username = null; + String password = null; + HostVO hostVO = hostDao.findById(host.getId()); - if (!response.first()) { - LOG.error(String.format("Restore failed on HYPERVISOR %s due to: %s", host, response.second())); - } else { - return true; - } - } catch (final Exception e) { - throw new CloudRuntimeException(String.format("Failed to restore backup on host %s due to: %s", host.getName(), e.getMessage())); + if (hostVO != null && hostVO.getHypervisorType() == Hypervisor.HypervisorType.KVM) { + hostDao.loadDetails(hostVO); + password = hostVO.getDetail("password"); + username = hostVO.getDetail("username"); } - return false; + if ( password == null || username == null) { + throw new CloudRuntimeException("Cannot find login credentials for HYPERVISOR " + Objects.requireNonNull(hostVO).getUuid()); + } + + return new Ternary<>(username, password, null); } - private boolean executeDeleteSnapshotCommand(HostVO host, String username, String password, int port, String command) { + private boolean executeDeleteBackupPathCommand(Host host, String username, String password, int port, String command) { try { Pair response = SshHelper.sshExecute(host.getPrivateIpAddress(), port, username, null, password, command, 120000, 120000, 3600000); if (!response.first()) { - LOG.error(String.format("Restore failed on HYPERVISOR %s due to: %s", host, response.second())); + LOG.error(String.format("failed on HYPERVISOR %s due to: %s", host, response.second())); } else { return true; } } catch (final Exception e) { - throw new CloudRuntimeException(String.format("Failed to restore backup on host %s due to: %s", host.getName(), e.getMessage())); + throw new CloudRuntimeException(String.format("Failed to delete backup path on host %s due to: %s", host.getName(), e.getMessage())); } return false; } @@ -1442,48 +1176,4 @@ public static boolean versionCheck(String csVersionInfo) { return true; } - @Override - public boolean supportsInstanceFromBackup() { - return false; - } - - @Override - public Pair getBackupStorageStats(Long zoneId) { - return new Pair<>(0L, 0L); - } - - @Override - public void syncBackupStorageStats(Long zoneId) { - } - - // 하위 클라이언트 삭제 시 백업본 데이터는 그대로 남아있지만, 해당 하위 클라이언트가 삭제되었기 때문에 스케줄도 삭제시켜야하며 - // 남아있는 백업본 데이터는 mold에서 관리하지 않고, commvault 의 plan 보존기간에 따라 데이터 에이징 됨. - @Override - public boolean willDeleteBackupsOnOfferingRemoval() { - return true; - } - - @Override - public Pair restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid) { - return new Pair<>(true, null); - } - - @Override - public List listRestorePoints(VirtualMachine vm) { - return null; - } - - @Override - public Backup createNewBackupEntryForRestorePoint(Backup.RestorePoint restorePoint, VirtualMachine vm) { - return null; - } - - public void syncBackupMetrics(Long zoneId) { - } - - @Override - public Boolean crossZoneInstanceCreationEnabled(BackupOffering backupOffering) { - return false; - } - } \ No newline at end of file diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java index 22816be3e6d8..13c9fa1b76f4 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java @@ -78,8 +78,8 @@ public Answer execute(CommvaultTakeBackupCommand command, LibvirtComputingResour Pair result = Script.executePipedCommands(commands, libvirtComputingResource.getCmdsTimeout()); if (result.first() != 0) { - logger.debug("Failed to take VM backup: " + result.second()); - BackupAnswer answer = new BackupAnswer(command, false, result.second().trim()); + logger.debug("Failed to take VM backup"); + BackupAnswer answer = new BackupAnswer(command, false, null); if (result.first() == EXIT_CLEANUP_FAILED) { logger.debug("Backup cleanup failed"); answer.setNeedsCleanup(true); @@ -87,21 +87,7 @@ public Answer execute(CommvaultTakeBackupCommand command, LibvirtComputingResour return answer; } - long backupSize = 0L; - if (CollectionUtils.isNullOrEmpty(diskPaths)) { - List outputLines = Arrays.asList(result.second().trim().split("\n")); - if (!outputLines.isEmpty()) { - backupSize = Long.parseLong(outputLines.get(outputLines.size() - 1).trim()); - } - } else { - String[] outputLines = result.second().trim().split("\n"); - for(String line : outputLines) { - backupSize = backupSize + Long.parseLong(line.split(" ")[0].trim()); - } - } - - BackupAnswer answer = new BackupAnswer(command, true, result.second().trim()); - answer.setSize(backupSize); + BackupAnswer answer = new BackupAnswer(command, true, "success"); return answer; } } diff --git a/scripts/vm/hypervisor/kvm/cvtbackup.sh b/scripts/vm/hypervisor/kvm/cvtbackup.sh index e69de29bb2d1..4251a83a93e3 100644 --- a/scripts/vm/hypervisor/kvm/cvtbackup.sh +++ b/scripts/vm/hypervisor/kvm/cvtbackup.sh @@ -0,0 +1,254 @@ +#!/usr/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -eo pipefail + +# CloudStack B&R Commvault Backup and Recovery Tool for KVM + +# TODO: do libvirt/logging etc checks + +### Declare variables ### + +OP="" +VM="" +BACKUP_DIR="" +DISK_PATHS="" +QUIESCE="" +logFile="/var/log/cloudstack/agent/agent.log" + +EXIT_CLEANUP_FAILED=20 + +log() { + [[ "$verb" -eq 1 ]] && builtin echo "$@" + if [[ "$1" == "-ne" || "$1" == "-e" || "$1" == "-n" ]]; then + builtin echo -e "$(date '+%Y-%m-%d %H-%M-%S>')" "${@: 2}" >> "$logFile" + else + builtin echo "$(date '+%Y-%m-%d %H-%M-%S>')" "$@" >> "$logFile" + fi +} + +vercomp() { + local IFS=. + local i ver1=($1) ver2=($3) + + # Compare each segment of the version numbers + for ((i=0; i<${#ver1[@]}; i++)); do + if [[ -z ${ver2[i]} ]]; then + ver2[i]=0 + fi + + if ((10#${ver1[i]} > 10#${ver2[i]})); then + return 0 # Version 1 is greater + elif ((10#${ver1[i]} < 10#${ver2[i]})); then + return 2 # Version 2 is greater + fi + done + return 0 # Versions are equal +} + +sanity_checks() { + hvVersion=$(virsh version | grep hypervisor | awk '{print $(NF)}') + libvVersion=$(virsh version | grep libvirt | awk '{print $(NF)}' | tail -n 1) + apiVersion=$(virsh version | grep API | awk '{print $(NF)}') + + # Compare qemu version (hvVersion >= 4.2.0) + vercomp "$hvVersion" ">=" "4.2.0" + hvStatus=$? + + # Compare libvirt version (libvVersion >= 7.2.0) + vercomp "$libvVersion" ">=" "7.2.0" + libvStatus=$? + + if [[ $hvStatus -eq 0 && $libvStatus -eq 0 ]]; then + log -ne "Success... [ QEMU: $hvVersion Libvirt: $libvVersion apiVersion: $apiVersion ]" + else + echo "Failure... Your QEMU version $hvVersion or libvirt version $libvVersion is unsupported. Consider upgrading to the required minimum version of QEMU: 4.2.0 and Libvirt: 7.2.0" + exit 1 + fi + + log -ne "Environment Sanity Checks successfully passed" +} + +### Operation methods ### + +backup_running_vm() { + mkdir -p "$dest" || { echo "Failed to create backup directory $dest"; exit 1; } + + name="root" + echo "" > $dest/backup.xml + for disk in $(virsh -c qemu:///system domblklist $VM --details 2>/dev/null | awk '/disk/{print$3}'); do + volpath=$(virsh -c qemu:///system domblklist $VM --details | awk "/$disk/{print $4}" | sed 's/.*\///') + echo "" >> $dest/backup.xml + name="datadisk" + done + echo "" >> $dest/backup.xml + + local thaw=0 + if [[ ${QUIESCE} == "true" ]]; then + if virsh -c qemu:///system qemu-agent-command "$VM" '{"execute":"guest-fsfreeze-freeze"}' > /dev/null 2>/dev/null; then + thaw=1 + fi + fi + + # Start push backup + local backup_begin=0 + if virsh -c qemu:///system backup-begin --domain $VM --backupxml $dest/backup.xml 2>&1 > /dev/null; then + backup_begin=1; + fi + + if [[ $thaw -eq 1 ]]; then + if ! response=$(virsh -c qemu:///system qemu-agent-command "$VM" '{"execute":"guest-fsfreeze-thaw"}' 2>&1 > /dev/null); then + echo "Failed to thaw the filesystem for vm $VM: $response" + cleanup + exit 1 + fi + fi + + if [[ $backup_begin -ne 1 ]]; then + cleanup + exit 1 + fi + + # Backup domain information + virsh -c qemu:///system dumpxml $VM > $dest/domain-config.xml 2>/dev/null + virsh -c qemu:///system dominfo $VM > $dest/dominfo.xml 2>/dev/null + virsh -c qemu:///system domiflist $VM > $dest/domiflist.xml 2>/dev/null + virsh -c qemu:///system domblklist $VM > $dest/domblklist.xml 2>/dev/null + + while true; do + status=$(virsh -c qemu:///system domjobinfo $VM --completed --keep-completed | awk '/Job type:/ {print $3}') + case "$status" in + Completed) + break ;; + Failed) + echo "Virsh backup job failed" + cleanup ;; + esac + sleep 5 + done + sync + +} + +backup_stopped_vm() { + mkdir -p "$dest" || { echo "Failed to create backup directory $dest"; exit 1; } + + IFS="," + + name="root" + for disk in $DISK_PATHS; do + if [[ "$disk" == rbd:* ]]; then + # disk for rbd => rbd:/:mon_host=... + # sample: rbd:cloudstack/53d5c355-d726-4d3e-9422-046a503a0b12:mon_host=10.0.1.2... + beforeUuid="${disk#*/}" # Remove up to first slash after rbd: + volUuid="${beforeUuid%%:*}" # Remove everything after colon to get the uuid + else + volUuid="${disk##*/}" + fi + output="$dest/$name.$volUuid.qcow2" + if ! qemu-img convert -O qcow2 "$disk" "$output" > "$logFile" 2> >(cat >&2); then + echo "qemu-img convert failed for $disk $output" + cleanup + fi + name="datadisk" + done + sync + +} + +cleanup() { + local status=0 + + rm -rf "$dest" || { echo "Failed to delete $dest"; status=1; } + + if [[ $status -ne 0 ]]; then + echo "Backup cleanup failed" + exit $EXIT_CLEANUP_FAILED + fi +} + +function usage { + echo "" + echo "Usage: $0 -o -v|--vm -p -d -q|--quiesce " + echo "" + exit 1 +} + +while [[ $# -gt 0 ]]; do + case $1 in + -o|--operation) + OP="$2" + shift + shift + ;; + -v|--vm) + VM="$2" + shift + shift + ;; + -p|--path) + BACKUP_DIR="$2" + shift + shift + ;; + -q|--quiesce) + QUIESCE="$2" + shift + shift + ;; + -d|--diskpaths) + DISK_PATHS="$2" + shift + shift + ;; + -h|--help) + usage + shift + ;; + *) + echo "Invalid option: $1" + usage + ;; + esac +done + +if [[ -z "$BACKUP_DIR" ]]; then + echo "Backup path (-p|--path) is required" + exit 1 +fi + +dest="$BACKUP_DIR" + +# Perform Initial sanity checks +sanity_checks + +if [[ "$OP" != "backup" ]]; then + echo "Unsupported operation: $OP" + exit 1 +fi + +STATE=$(virsh -c qemu:///system list | awk -v vm="$VM" '$2 == vm {print $3}') + +if [[ -n "$STATE" && "$STATE" == "running" ]]; then + backup_running_vm +else + backup_stopped_vm +fi + +exit 0 From 4cf495e68c0d6ca00539bb6f9bd1620e88078e93 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 13 Feb 2026 15:02:23 +0900 Subject: [PATCH 11/35] Update CommvaultTakeBackupCommand.java --- .../apache/cloudstack/backup/CommvaultTakeBackupCommand.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/main/java/org/apache/cloudstack/backup/CommvaultTakeBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CommvaultTakeBackupCommand.java index 004b7a87915a..f24f41d98675 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/CommvaultTakeBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/CommvaultTakeBackupCommand.java @@ -20,7 +20,6 @@ package org.apache.cloudstack.backup; import com.cloud.agent.api.Command; -import com.cloud.agent.api.LogLevel; import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import java.util.List; @@ -31,7 +30,6 @@ public class CommvaultTakeBackupCommand extends Command { private List volumePools; private List volumePaths; private Boolean quiesce; - @LogLevel(LogLevel.Log4jLevel.Off) public CommvaultTakeBackupCommand(String vmName, String backupPath) { super(); From 908b2dd962e644a01c12ba7bdad0997bbb616e4d Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 13 Feb 2026 15:38:44 +0900 Subject: [PATCH 12/35] =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backup/CommvaultBackupProvider.java | 85 +++++++------------ ...tCommvaultRestoreBackupCommandWrapper.java | 28 +++++- ...virtCommvaultTakeBackupCommandWrapper.java | 4 +- 3 files changed, 59 insertions(+), 58 deletions(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index eab121cd6e55..a195d08a0ba7 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -16,10 +16,9 @@ // under the License. package org.apache.cloudstack.backup; -import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.api.query.dao.UserVmJoinDao; -import com.cloud.cluster.ManagementServerHostVO; -import com.cloud.cluster.dao.ManagementServerHostDao; +import com.cloud.agent.AgentManager; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; import com.cloud.dc.dao.ClusterDao; import com.cloud.domain.Domain; import com.cloud.host.Host; @@ -28,64 +27,59 @@ import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.offering.DiskOffering; +import com.cloud.resource.ResourceManager; import com.cloud.storage.DataStoreRole; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage; import com.cloud.storage.Volume; +import com.cloud.storage.Volume.Type; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.DiskOfferingDao; -import com.cloud.storage.SnapshotVO; -import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.User; -import com.cloud.user.UserAccount; import com.cloud.user.Account; import com.cloud.user.AccountService; import com.cloud.utils.NumbersUtil; -import com.cloud.utils.PropertiesUtil; -import com.cloud.utils.server.ServerProperties; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; import com.cloud.utils.ssh.SshHelper; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.nio.TrustAllManager; import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import org.apache.cloudstack.backup.commvault.CommvaultClient; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupOfferingDaoImpl; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.utils.security.SSLUtils; -import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.codec.binary.Base64; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import org.apache.xml.utils.URI; import org.json.JSONObject; -import org.json.XML; -import java.net.HttpURLConnection; import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLEncoder; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.HashMap; import java.util.Date; @@ -93,26 +87,13 @@ import java.util.UUID; import java.util.Optional; import java.util.stream.Collectors; -import java.util.StringTokenizer; -import java.util.StringJoiner; -import java.util.Properties; import java.util.Collections; import java.util.Comparator; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.io.File; -import java.io.InputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import javax.inject.Inject; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.HttpsURLConnection; -import javax.crypto.spec.SecretKeySpec; -import javax.crypto.Mac; + +import static org.apache.cloudstack.backup.BackupManager.BackupFrameworkEnabled; public class CommvaultBackupProvider extends AdapterBase implements BackupProvider, Configurable { @@ -124,7 +105,6 @@ public class CommvaultBackupProvider extends AdapterBase implements BackupProvid private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\s*SP\\s*(\\d+)(?:\\.(\\d+))?$", Pattern.CASE_INSENSITIVE); private static final String COMMVAULT_DIRECTORY = "/tmp/mold/backup"; - public ConfigKey CommvaultUrl = new ConfigKey<>("Advanced", String.class, "backup.plugin.commvault.url", "https://localhost/commandcenter/api", "Commvault Command Center API URL.", true, ConfigKey.Scope.Zone); @@ -193,16 +173,16 @@ public class CommvaultBackupProvider extends AdapterBase implements BackupProvid private VMInstanceDao vmInstanceDao; @Inject - private ManagementServerHostDao msHostDao; + private AccountService accountService; @Inject - private AccountService accountService; + DataStoreManager dataStoreMgr; @Inject - private UserVmJoinDao userVmJoinDao; + private AgentManager agentManager; @Inject - private SnapshotDao snapshotDao; + private VMSnapshotDao vmSnapshotDao; @Inject private PrimaryDataStoreDao primaryDataStoreDao; @@ -214,8 +194,10 @@ public class CommvaultBackupProvider extends AdapterBase implements BackupProvid private BackupManager backupManager; @Inject - private DiskOfferingDao diskOfferingDao; + ResourceManager resourceManager; + @Inject + private DiskOfferingDao diskOfferingDao; private Long getClusterIdFromRootVolume(VirtualMachine vm) { VolumeVO rootVolume = volumeDao.getInstanceRootVolume(vm.getId()); @@ -273,7 +255,6 @@ protected Host getVMHypervisorHostForBackup(VirtualMachine vm) { return host; } - // 백업 @Override public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { final Host vmHost = getVMHypervisorHostForBackup(vm); @@ -403,7 +384,7 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { return new Pair<>(true, backupVO); } else { throw new CloudRuntimeException("Failed to update backup"); - } + } } else { LOG.error("Failed to take backup for VM " + vm.getInstanceName() + " to get details job commvault api"); } @@ -422,7 +403,7 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { backupDao.remove(backupVO.getId()); int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); Ternary credentials = getKVMHyperisorCredentials(vmHost); - String cmd = String.format(RM_COMMAND, backupPath); + String cmd = String.format(RM_COMMAND, backupPath); executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, cmd); return new Pair<>(false, null); } else { @@ -529,7 +510,7 @@ private Pair restoreVMBackup(VirtualMachine vm, Backup backup) LOG.debug("Restoring vm {} from backup {} on the Commvault Backup Provider", vm, backup); // 가상머신이 실행중인 호스트 정의 final Host vmHost = getVMHypervisorHost(vm); - CommvaultRestoreBackupCommand restoreCommand = new RestoreBackupCommand(); + CommvaultRestoreBackupCommand restoreCommand = new CommvaultRestoreBackupCommand(); restoreCommand.setBackupPath(backup.getExternalId()); restoreCommand.setVmName(vm.getName()); restoreCommand.setBackupVolumesUUIDs(backedVolumesUUIDs); @@ -553,11 +534,11 @@ private Pair restoreVMBackup(VirtualMachine vm, Backup backup) if (!answer.getResult()) { int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); Ternary credentials = getKVMHyperisorCredentials(vmHost); - String command = String.format(RM_COMMAND, path); + String command = String.format(RM_COMMAND, path); executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, command); if (restoreHost.getId() != vmHost.getId()) { credentials = getKVMHyperisorCredentials(restoreHost); - command = String.format(RM_COMMAND, path); + command = String.format(RM_COMMAND, path); executeDeleteBackupPathCommand(restoreHost, credentials.first(), credentials.second(), sshPort, command); } } @@ -637,7 +618,6 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI if (backupsetGUID == null) { throw new CloudRuntimeException("Failed to get vm backup set guid commvault api"); } - LOG.info(String.format("Restoring volume %s from backup %s on the Commvault Backup Provider", volume.getUuid(), backup)); // 복원 실행 String jobId2 = client.restoreFullVM(subclientId, displayName, backupsetGUID, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, backupsetName, commCellId, endTime, path); if (jobId2 != null) { @@ -660,6 +640,7 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI final HostVO vmHost = hostDao.findByIp(hostIp); // 복원된 호스트 정의 final HostVO restoreHost = hostDao.findByName(clientName); + LOG.info(String.format("Restoring volume %s from backup %s on the Commvault Backup Provider", volume.getUuid(), backup)); LOG.debug("Restoring vm volume {} from backup {} on the Commvault Backup Provider", backupVolumeInfo, backup); VolumeVO restoredVolume = new VolumeVO(Volume.Type.DATADISK, null, backup.getZoneId(), backup.getDomainId(), backup.getAccountId(), 0, null, @@ -701,7 +682,7 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI BackupAnswer answer; try { - answer = (BackupAnswer) agentManager.send(hostVO.getId(), restoreCommand); + answer = (BackupAnswer) agentManager.send(vmHost.getId(), restoreCommand); } catch (AgentUnavailableException e) { throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); } catch (OperationTimedoutException e) { @@ -718,11 +699,11 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI } else { int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); Ternary credentials = getKVMHyperisorCredentials(vmHost); - String command = String.format(RM_COMMAND, path); + String command = String.format(RM_COMMAND, path); executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, command); if (restoreHost.getId() != vmHost.getId()) { credentials = getKVMHyperisorCredentials(restoreHost); - command = String.format(RM_COMMAND, path); + command = String.format(RM_COMMAND, path); executeDeleteBackupPathCommand(restoreHost, credentials.first(), credentials.second(), sshPort, command); } } @@ -765,7 +746,7 @@ public boolean deleteBackup(Backup backup, boolean forced) { public void syncBackupMetrics(Long zoneId) { } - + @Override public List listRestorePoints(VirtualMachine vm) { return null; @@ -780,7 +761,7 @@ public Backup createNewBackupEntryForRestorePoint(Backup.RestorePoint restorePoi public boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backupOffering) { final CommvaultClient client = getClient(vm.getDataCenterId()); final Host host = getVMHypervisorHostForBackup(vm); - String clientId = client.getClientId(hostVO.getName()); + String clientId = client.getClientId(host.getName()); String applicationId = client.getApplicationId(clientId); return client.createBackupSet(vm.getInstanceName(), applicationId, clientId, backupOffering.getExternalId()); } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java index 8ce978ae0a33..46665fbdfa8e 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java @@ -37,7 +37,6 @@ import org.apache.cloudstack.utils.qemu.QemuImg; import org.apache.cloudstack.utils.qemu.QemuImgException; import org.apache.cloudstack.utils.qemu.QemuImgFile; -import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.libvirt.LibvirtException; @@ -56,6 +55,8 @@ public class LibvirtCommvaultRestoreBackupCommandWrapper extends CommandWrapper< private static final String ATTACH_RBD_DISK_XML_COMMAND = " virsh attach-device %s /dev/stdin < restoreVolumePools, List restoreVolumePaths, List backedVolumesUUIDs, - String backupPath, String mountDirectory, int timeout) { + String backupPath, int timeout) { String diskType = "root"; try { for (int idx = 0; idx < restoreVolumePaths.size(); idx++) { @@ -122,7 +127,7 @@ private void restoreVolumesOfExistingVM(KVMStoragePoolManager storagePoolMgr, Li } } - private void restoreVolumesOfDestroyedVMs(KVMStoragePoolManager storagePoolMgr, List volumePools, List volumePaths, String vmName, String backupPath, String mountDirectory, int timeout) { + private void restoreVolumesOfDestroyedVMs(KVMStoragePoolManager storagePoolMgr, List volumePools, List volumePaths, String vmName, String backupPath, int timeout) { String diskType = "root"; try { for (int i = 0; i < volumePaths.size(); i++) { @@ -295,4 +300,21 @@ private String getXmlForRbdDisk(KVMStoragePoolManager storagePoolMgr, PrimaryDat diskBuilder.append("\n"); return diskBuilder.toString(); } + + private void fetchBackupFile(String hostName, String backupPath) { + int mkdirExit = Script.runSimpleBashScriptForExitValue(String.format(MKDIR_P, backupPath)); + if (mkdirExit != 0) { + throw new CloudRuntimeException(String.format("Failed to create local backup directory: %s", backupPath)); + } + + String cmd = String.format(RSYNC_DIR_FROM_REMOTE, hostName, backupPath, backupPath); + logger.debug("Fetching commvault backup directory from remote host. cmd={}", cmd); + + int exit = Script.runSimpleBashScriptForExitValue(cmd); + if (exit != 0) { + throw new CloudRuntimeException(String.format( + "Failed to fetch backup directory from remote host [%s]. remotePath=[%s], localPath=[%s]", + hostName, backupPath, backupPath)); + } + } } \ No newline at end of file diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java index 13c9fa1b76f4..36ece812f227 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java @@ -19,7 +19,6 @@ package com.cloud.hypervisor.kvm.resource.wrapper; -import com.amazonaws.util.CollectionUtils; import com.cloud.agent.api.Answer; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; @@ -30,12 +29,11 @@ import com.cloud.storage.Storage; import com.cloud.utils.Pair; import com.cloud.utils.script.Script; -import org.apache.cloudstack.backup.CommvaultBackupAnswer; +import org.apache.cloudstack.backup.BackupAnswer; import org.apache.cloudstack.backup.CommvaultTakeBackupCommand; import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; From ef58ff8e62d2d0b75b6c5759b980c116e4930709 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 19 Feb 2026 13:25:55 +0900 Subject: [PATCH 13/35] =?UTF-8?q?commvault=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=BD=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kvm/resource/LibvirtComputingResource.java | 10 ++++++++++ .../LibvirtCommvaultTakeBackupCommandWrapper.java | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index d8313d7c4ad1..857446c57187 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -426,6 +426,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private String vmActivityCheckPathRbd; private String vmActivityCheckPathClvm; private String nasBackupPath; + private String cvtBackupPath; private String securityGroupPath; private String ovsPvlanDhcpHostPath; private String ovsPvlanVmPath; @@ -856,6 +857,10 @@ public String getNasBackupPath() { return nasBackupPath; } + public String getCvtBackupPath() { + return cvtBackupPath; + } + public String getOvsPvlanDhcpHostPath() { return ovsPvlanDhcpHostPath; } @@ -1193,6 +1198,11 @@ public boolean configure(final String name, final Map params) th throw new ConfigurationException("Unable to find nasbackup.sh"); } + cvtBackupPath = Script.findScript(kvmScriptsDir, "cvtbackup.sh"); + if (cvtBackupPath == null) { + throw new ConfigurationException("Unable to find cvtbackup.sh"); + } + createTmplPath = Script.findScript(storageScriptsDir, "createtmplt.sh"); if (createTmplPath == null) { throw new ConfigurationException("Unable to find the createtmplt.sh"); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java index 36ece812f227..277d38e8573d 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultTakeBackupCommandWrapper.java @@ -65,7 +65,7 @@ public Answer execute(CommvaultTakeBackupCommand command, LibvirtComputingResour List commands = new ArrayList<>(); commands.add(new String[]{ - libvirtComputingResource.getNasBackupPath(), + libvirtComputingResource.getCvtBackupPath(), "-o", "backup", "-v", vmName, "-p", backupPath, From 6fa2893ec6382a89fad3874b13a2d103e987ecd4 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 19 Feb 2026 14:03:45 +0900 Subject: [PATCH 14/35] =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EC=98=A4=ED=94=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/cloudstack/backup/CommvaultBackupProvider.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index a195d08a0ba7..dbc468cf2e41 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -355,7 +355,7 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { String jobId = client.createBackup(subclientId, storagePolicyId, displayName, commCellName, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, subclientGUID, subclientName, csGUID, backupsetName); if (jobId != null) { String jobStatus = client.getJobStatus(jobId); - if (jobStatus.equalsIgnoreCase("Completed")) { + if (jobStatus.contains("Completed")) { String jobDetails = client.getJobDetails(jobId); if (jobDetails != null) { JSONObject jsonObject2 = new JSONObject(jobDetails); @@ -497,7 +497,7 @@ private Pair restoreVMBackup(VirtualMachine vm, Backup backup) String jobId2 = client.restoreFullVM(subclientId, displayName, backupsetGUID, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, backupsetName, commCellId, endTime, path); if (jobId2 != null) { String jobStatus = client.getJobStatus(jobId2); - if (jobStatus.equalsIgnoreCase("Completed")) { + if (jobStatus.contains("Completed")) { List backedVolumesUUIDs = backup.getBackedUpVolumes().stream() .sorted(Comparator.comparingLong(Backup.VolumeInfo::getDeviceId)) .map(Backup.VolumeInfo::getUuid) @@ -622,7 +622,7 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI String jobId2 = client.restoreFullVM(subclientId, displayName, backupsetGUID, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, backupsetName, commCellId, endTime, path); if (jobId2 != null) { String jobStatus = client.getJobStatus(jobId2); - if (jobStatus.equalsIgnoreCase("Completed")) { + if (jobStatus.contains("Completed")) { final VolumeVO volume = volumeDao.findByUuid(backupVolumeInfo.getUuid()); final DiskOffering diskOffering = diskOfferingDao.findByUuid(backupVolumeInfo.getDiskOfferingId()); String cacheMode = null; @@ -963,7 +963,7 @@ public boolean installBackupAgent(final Long zoneId) { String jobId = client.installAgent(host.getPrivateIpAddress(), commCellId, commServeHostName, credentials.first(), credentials.second()); if (jobId != null) { String jobStatus = client.getJobStatus(jobId); - if (!jobStatus.equalsIgnoreCase("Completed")) { + if (!jobStatus.contains("Completed")) { LOG.error("installing agent on the Commvault Backup Provider failed jogId : " + jobId + " , jobStatus : " + jobStatus); ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, EventTypes.EVENT_HOST_AGENT_INSTALL, "Failed install the commvault client agent on the host : " + host.getPrivateIpAddress(), User.UID_SYSTEM, ApiCommandResourceType.Host.toString()); From 6898ff86b62253f1d9c50512b7ded2e27af9b99a Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 19 Feb 2026 14:05:26 +0900 Subject: [PATCH 15/35] Update CommvaultBackupProvider.java --- .../org/apache/cloudstack/backup/CommvaultBackupProvider.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index dbc468cf2e41..5e1c843b78ae 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -355,6 +355,7 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { String jobId = client.createBackup(subclientId, storagePolicyId, displayName, commCellName, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, subclientGUID, subclientName, csGUID, backupsetName); if (jobId != null) { String jobStatus = client.getJobStatus(jobId); + String externalId = backupPath + "," + jobId; if (jobStatus.contains("Completed")) { String jobDetails = client.getJobDetails(jobId); if (jobDetails != null) { @@ -366,7 +367,6 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { String formattedString = formatterDateTime.format(endDate); String size = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("detailInfo").get("sizeOfApplication")); String type = String.valueOf(jsonObject2.getJSONObject("job").getJSONObject("jobDetail").getJSONObject("generalInfo").get("backupType")); - String externalId = backupPath + "," + jobId; backupVO.setExternalId(externalId); backupVO.setType(type.toUpperCase()); try { @@ -386,9 +386,11 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { throw new CloudRuntimeException("Failed to update backup"); } } else { + backupVO.setExternalId(externalId); LOG.error("Failed to take backup for VM " + vm.getInstanceName() + " to get details job commvault api"); } } else { + backupVO.setExternalId(externalId); LOG.error("Failed to take backup for VM " + vm.getInstanceName() + " to create backup job status is " + jobStatus); } } else { From 8d600c97bc8ccbc65bea6f35e7c69e939e9c13a8 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 19 Feb 2026 14:32:27 +0900 Subject: [PATCH 16/35] Update CommvaultBackupProvider.java --- .../apache/cloudstack/backup/CommvaultBackupProvider.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index 5e1c843b78ae..a986603a1bd6 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -322,6 +322,9 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { } if (answer != null && answer.getResult()) { + int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); + Ternary credentials = getKVMHyperisorCredentials(vmHost); + String cmd = String.format(RM_COMMAND, backupPath); // 생성된 백업 폴더 경로로 해당 백업 세트의 백업 콘텐츠 경로 업데이트 String clientId = client.getClientId(vmHost.getName()); String subClientEntity = client.getSubclient(clientId, vm.getInstanceName()); @@ -381,8 +384,10 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { List vols = new ArrayList<>(volumeDao.findByInstance(vm.getId())); backupVO.setBackedUpVolumes(backupManager.createVolumeInfoFromVolumes(vols)); if (backupDao.update(backupVO.getId(), backupVO)) { + executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, cmd); return new Pair<>(true, backupVO); } else { + executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, cmd); throw new CloudRuntimeException("Failed to update backup"); } } else { @@ -403,9 +408,6 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { } backupVO.setStatus(Backup.Status.Failed); backupDao.remove(backupVO.getId()); - int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); - Ternary credentials = getKVMHyperisorCredentials(vmHost); - String cmd = String.format(RM_COMMAND, backupPath); executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, cmd); return new Pair<>(false, null); } else { From 936f0d1ebb9c9acf2410de0e31e723ded9f9a499 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 19 Feb 2026 15:05:39 +0900 Subject: [PATCH 17/35] Update CommvaultBackupProvider.java --- .../apache/cloudstack/backup/CommvaultBackupProvider.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index a986603a1bd6..314232a63084 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -1106,16 +1106,17 @@ protected Ternary getKVMHyperisorCredentials(Host host) private boolean executeDeleteBackupPathCommand(Host host, String username, String password, int port, String command) { try { - Pair response = SshHelper.sshExecute(host.getPrivateIpAddress(), port, + HostVO hostVO = hostDao.findById(host.getId()); + Pair response = SshHelper.sshExecute(hostVO.getPrivateIpAddress(), port, username, null, password, command, 120000, 120000, 3600000); if (!response.first()) { - LOG.error(String.format("failed on HYPERVISOR %s due to: %s", host, response.second())); + LOG.error(String.format("failed on HYPERVISOR %s due to: %s", hostVO, response.second())); } else { return true; } } catch (final Exception e) { - throw new CloudRuntimeException(String.format("Failed to delete backup path on host %s due to: %s", host.getName(), e.getMessage())); + throw new CloudRuntimeException(String.format("Failed to delete backup path on host %s due to: %s", hostVO.getName(), e.getMessage())); } return false; } From f5097dd42fe6fc8ebfdaa627845e5b8b7b0f2bc7 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 19 Feb 2026 15:10:25 +0900 Subject: [PATCH 18/35] Update CommvaultBackupProvider.java --- .../org/apache/cloudstack/backup/CommvaultBackupProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index 314232a63084..c95ecc062105 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -1105,8 +1105,8 @@ protected Ternary getKVMHyperisorCredentials(Host host) } private boolean executeDeleteBackupPathCommand(Host host, String username, String password, int port, String command) { + HostVO hostVO = hostDao.findById(host.getId()); try { - HostVO hostVO = hostDao.findById(host.getId()); Pair response = SshHelper.sshExecute(hostVO.getPrivateIpAddress(), port, username, null, password, command, 120000, 120000, 3600000); From c3f54fc1f1c588e7bc2b9969968f24068d8d6962 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 19 Feb 2026 15:55:47 +0900 Subject: [PATCH 19/35] Update CommvaultBackupProvider.java --- .../backup/CommvaultBackupProvider.java | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index c95ecc062105..79e9d92e725f 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -258,6 +258,7 @@ protected Host getVMHypervisorHostForBackup(VirtualMachine vm) { @Override public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { final Host vmHost = getVMHypervisorHostForBackup(vm); + final HostVO vmHostVO = hostDao.findById(vmHost.getId()); if (CollectionUtils.isNotEmpty(vmSnapshotDao.findByVmAndByType(vm.getId(), VMSnapshot.Type.DiskAndMemory))) { LOG.debug("Commvault backup provider cannot take backups of a VM [{}] with disk-and-memory VM snapshots. Restoring the backup will corrupt any newer disk-and-memory " + "VM snapshots.", vm); @@ -323,7 +324,7 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { if (answer != null && answer.getResult()) { int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); - Ternary credentials = getKVMHyperisorCredentials(vmHost); + Ternary credentials = getKVMHyperisorCredentials(vmHostVO); String cmd = String.format(RM_COMMAND, backupPath); // 생성된 백업 폴더 경로로 해당 백업 세트의 백업 콘텐츠 경로 업데이트 String clientId = client.getClientId(vmHost.getName()); @@ -384,10 +385,15 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { List vols = new ArrayList<>(volumeDao.findByInstance(vm.getId())); backupVO.setBackedUpVolumes(backupManager.createVolumeInfoFromVolumes(vols)); if (backupDao.update(backupVO.getId(), backupVO)) { - executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, cmd); + LOG.info(vmHostVO); + LOG.info(credentials.first()); + LOG.info(credentials.second()); + LOG.info(sshPort); + LOG.info(cmd); + executeDeleteBackupPathCommand(vmHostVO, credentials.first(), credentials.second(), sshPort, cmd); return new Pair<>(true, backupVO); } else { - executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, cmd); + executeDeleteBackupPathCommand(vmHostVO, credentials.first(), credentials.second(), sshPort, cmd); throw new CloudRuntimeException("Failed to update backup"); } } else { @@ -408,7 +414,7 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { } backupVO.setStatus(Backup.Status.Failed); backupDao.remove(backupVO.getId()); - executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, cmd); + executeDeleteBackupPathCommand(vmHostVO, credentials.first(), credentials.second(), sshPort, cmd); return new Pair<>(false, null); } else { LOG.error("Failed to take backup for VM {}: {}", vm.getInstanceName(), answer != null ? answer.getDetails() : "No answer received"); @@ -496,6 +502,7 @@ private Pair restoreVMBackup(VirtualMachine vm, Backup backup) } // 복원된 호스트 정의 final HostVO restoreHost = hostDao.findByName(clientName); + final HostVO restoreHostVO = hostDao.findById(restoreHost.getId()); LOG.info(String.format("Restoring vm %s from backup %s on the Commvault Backup Provider", vm, backup)); // 복원 실행 String jobId2 = client.restoreFullVM(subclientId, displayName, backupsetGUID, clientId, companyId, companyName, instanceName, appName, applicationId, clientName, backupsetId, instanceId, backupsetName, commCellId, endTime, path); @@ -514,6 +521,7 @@ private Pair restoreVMBackup(VirtualMachine vm, Backup backup) LOG.debug("Restoring vm {} from backup {} on the Commvault Backup Provider", vm, backup); // 가상머신이 실행중인 호스트 정의 final Host vmHost = getVMHypervisorHost(vm); + final HostVO vmHostVO = hostDao.findById(vmHost.getId()); CommvaultRestoreBackupCommand restoreCommand = new CommvaultRestoreBackupCommand(); restoreCommand.setBackupPath(backup.getExternalId()); restoreCommand.setVmName(vm.getName()); @@ -537,13 +545,13 @@ private Pair restoreVMBackup(VirtualMachine vm, Backup backup) } if (!answer.getResult()) { int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); - Ternary credentials = getKVMHyperisorCredentials(vmHost); + Ternary credentials = getKVMHyperisorCredentials(vmHostVO); String command = String.format(RM_COMMAND, path); - executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, command); + executeDeleteBackupPathCommand(vmHostVO, credentials.first(), credentials.second(), sshPort, command); if (restoreHost.getId() != vmHost.getId()) { - credentials = getKVMHyperisorCredentials(restoreHost); + credentials = getKVMHyperisorCredentials(restoreHostVO); command = String.format(RM_COMMAND, path); - executeDeleteBackupPathCommand(restoreHost, credentials.first(), credentials.second(), sshPort, command); + executeDeleteBackupPathCommand(restoreHostVO, credentials.first(), credentials.second(), sshPort, command); } } return new Pair<>(answer.getResult(), answer.getDetails()); @@ -642,8 +650,10 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI final StoragePoolVO pool = primaryDataStoreDao.findByUuid(dataStoreUuid); // 백업 볼륨 복원 및 연결 시 연결할 가상머신이 실행중인 경우 해당 호스트, 정지중인 경우 랜덤 호스트 정의백업 final HostVO vmHost = hostDao.findByIp(hostIp); + final HostVO vmHostVO = hostDao.findById(vmHost.getId()); // 복원된 호스트 정의 final HostVO restoreHost = hostDao.findByName(clientName); + final HostVO restoreHostVO = hostDao.findById(restoreHost.getId()); LOG.info(String.format("Restoring volume %s from backup %s on the Commvault Backup Provider", volume.getUuid(), backup)); LOG.debug("Restoring vm volume {} from backup {} on the Commvault Backup Provider", backupVolumeInfo, backup); VolumeVO restoredVolume = new VolumeVO(Volume.Type.DATADISK, null, backup.getZoneId(), @@ -702,13 +712,13 @@ public Pair restoreBackedUpVolume(Backup backup, Backup.VolumeI return new Pair<>(answer.getResult(), answer.getDetails()); } else { int sshPort = NumbersUtil.parseInt(configDao.getValue("kvm.ssh.port"), 22); - Ternary credentials = getKVMHyperisorCredentials(vmHost); + Ternary credentials = getKVMHyperisorCredentials(vmHostVO); String command = String.format(RM_COMMAND, path); - executeDeleteBackupPathCommand(vmHost, credentials.first(), credentials.second(), sshPort, command); + executeDeleteBackupPathCommand(vmHostVO, credentials.first(), credentials.second(), sshPort, command); if (restoreHost.getId() != vmHost.getId()) { - credentials = getKVMHyperisorCredentials(restoreHost); + credentials = getKVMHyperisorCredentials(restoreHostVO); command = String.format(RM_COMMAND, path); - executeDeleteBackupPathCommand(restoreHost, credentials.first(), credentials.second(), sshPort, command); + executeDeleteBackupPathCommand(restoreHostVO, credentials.first(), credentials.second(), sshPort, command); } } } else { @@ -1086,37 +1096,35 @@ private CommvaultClient getClient(final Long zoneId) { throw new CloudRuntimeException("Failed to build Commvault API client"); } - protected Ternary getKVMHyperisorCredentials(Host host) { + protected Ternary getKVMHyperisorCredentials(HostVO host) { String username = null; String password = null; - HostVO hostVO = hostDao.findById(host.getId()); - if (hostVO != null && hostVO.getHypervisorType() == Hypervisor.HypervisorType.KVM) { - hostDao.loadDetails(hostVO); - password = hostVO.getDetail("password"); - username = hostVO.getDetail("username"); + if (host != null && hostVO.getHypervisorType() == Hypervisor.HypervisorType.KVM) { + hostDao.loadDetails(host); + password = host.getDetail("password"); + username = host.getDetail("username"); } if ( password == null || username == null) { - throw new CloudRuntimeException("Cannot find login credentials for HYPERVISOR " + Objects.requireNonNull(hostVO).getUuid()); + throw new CloudRuntimeException("Cannot find login credentials for HYPERVISOR " + Objects.requireNonNull(host).getUuid()); } return new Ternary<>(username, password, null); } - private boolean executeDeleteBackupPathCommand(Host host, String username, String password, int port, String command) { - HostVO hostVO = hostDao.findById(host.getId()); + private boolean executeDeleteBackupPathCommand(HostVO host, String username, String password, int port, String command) { try { - Pair response = SshHelper.sshExecute(hostVO.getPrivateIpAddress(), port, + Pair response = SshHelper.sshExecute(host.getPrivateIpAddress(), port, username, null, password, command, 120000, 120000, 3600000); if (!response.first()) { - LOG.error(String.format("failed on HYPERVISOR %s due to: %s", hostVO, response.second())); + LOG.error(String.format("failed on HYPERVISOR %s due to: %s", host, response.second())); } else { return true; } } catch (final Exception e) { - throw new CloudRuntimeException(String.format("Failed to delete backup path on host %s due to: %s", hostVO.getName(), e.getMessage())); + throw new CloudRuntimeException(String.format("Failed to delete backup path on host %s due to: %s", host.getName(), e.getMessage())); } return false; } From 4b8a3a401c84e5079f8fa40bbe19d9bf825dc9e8 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 19 Feb 2026 15:59:39 +0900 Subject: [PATCH 20/35] Update CommvaultBackupProvider.java --- .../org/apache/cloudstack/backup/CommvaultBackupProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index 79e9d92e725f..fc865cccd16c 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -1101,7 +1101,7 @@ protected Ternary getKVMHyperisorCredentials(HostVO host String username = null; String password = null; - if (host != null && hostVO.getHypervisorType() == Hypervisor.HypervisorType.KVM) { + if (host != null && host.getHypervisorType() == ypervisor.HypervisorType.KVM) { hostDao.loadDetails(host); password = host.getDetail("password"); username = host.getDetail("username"); From 81700160b46c522d3f18f4c8923d2ccc287d00b4 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Thu, 19 Feb 2026 16:03:43 +0900 Subject: [PATCH 21/35] Update CommvaultBackupProvider.java --- .../org/apache/cloudstack/backup/CommvaultBackupProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index fc865cccd16c..48a584a65874 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -1101,7 +1101,7 @@ protected Ternary getKVMHyperisorCredentials(HostVO host String username = null; String password = null; - if (host != null && host.getHypervisorType() == ypervisor.HypervisorType.KVM) { + if (host != null && host.getHypervisorType() == Hypervisor.HypervisorType.KVM) { hostDao.loadDetails(host); password = host.getDetail("password"); username = host.getDetail("username"); From a02ba87ba5a8e67a230a7246d65d4ff7f0ff2deb Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 20 Feb 2026 09:40:25 +0900 Subject: [PATCH 22/35] =?UTF-8?q?=EA=B0=80=EC=83=81=EB=A8=B8=EC=8B=A0=20?= =?UTF-8?q?=EB=B0=B1=EC=97=85=20=EB=B3=B5=EC=9B=90=EC=8B=9C=20path=20?= =?UTF-8?q?=EC=9E=AC=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/cloudstack/backup/CommvaultBackupProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index 48a584a65874..04eff2ef77b9 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -523,7 +523,7 @@ private Pair restoreVMBackup(VirtualMachine vm, Backup backup) final Host vmHost = getVMHypervisorHost(vm); final HostVO vmHostVO = hostDao.findById(vmHost.getId()); CommvaultRestoreBackupCommand restoreCommand = new CommvaultRestoreBackupCommand(); - restoreCommand.setBackupPath(backup.getExternalId()); + restoreCommand.setBackupPath(path); restoreCommand.setVmName(vm.getName()); restoreCommand.setBackupVolumesUUIDs(backedVolumesUUIDs); Pair, List> volumePoolsAndPaths = getVolumePoolsAndPaths(restoreVolumes); From 9665e3470cfa4adff0d7e5041e2515eec0ae1479 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 20 Feb 2026 09:40:43 +0900 Subject: [PATCH 23/35] Update CommvaultBackupProvider.java --- .../apache/cloudstack/backup/CommvaultBackupProvider.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index 04eff2ef77b9..6b385c846e87 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -385,11 +385,6 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM) { List vols = new ArrayList<>(volumeDao.findByInstance(vm.getId())); backupVO.setBackedUpVolumes(backupManager.createVolumeInfoFromVolumes(vols)); if (backupDao.update(backupVO.getId(), backupVO)) { - LOG.info(vmHostVO); - LOG.info(credentials.first()); - LOG.info(credentials.second()); - LOG.info(sshPort); - LOG.info(cmd); executeDeleteBackupPathCommand(vmHostVO, credentials.first(), credentials.second(), sshPort, cmd); return new Pair<>(true, backupVO); } else { From 422f7ecc131259af3f132ef8643ab6635ec87420 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 20 Feb 2026 10:03:44 +0900 Subject: [PATCH 24/35] Update plugins.js --- ui/src/utils/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/utils/plugins.js b/ui/src/utils/plugins.js index 1a1b7aefe6e2..2c7c79b954e2 100644 --- a/ui/src/utils/plugins.js +++ b/ui/src/utils/plugins.js @@ -666,7 +666,7 @@ export const backupUtilPlugin = { if (!provider && typeof provider !== 'string') { return false } - return ['nas'].includes(provider.toLowerCase()) + return ['nas', 'commvault'].includes(provider.toLowerCase()) } } } From ced2ab5cb93d543a8c13eaabe7f4ce01c0e3c253 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 20 Feb 2026 10:07:04 +0900 Subject: [PATCH 25/35] Update CommvaultBackupProvider.java --- .../org/apache/cloudstack/backup/CommvaultBackupProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index 6b385c846e87..fd3f50e316a7 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -471,7 +471,7 @@ private Pair restoreVMBackup(VirtualMachine vm, Backup backup) final CommvaultClient client = getClient(vm.getDataCenterId()); final String externalId = backup.getExternalId(); String jobId = externalId.substring(externalId.lastIndexOf(',') + 1).trim(); - String path = externalId.substring(0, externalId.lastIndexOf(',')); + final String path = externalId.substring(0, externalId.lastIndexOf(',')); String jobDetails = client.getJobDetails(jobId); if (jobDetails == null) { throw new CloudRuntimeException("Failed to get job details commvault api"); @@ -518,6 +518,7 @@ private Pair restoreVMBackup(VirtualMachine vm, Backup backup) final Host vmHost = getVMHypervisorHost(vm); final HostVO vmHostVO = hostDao.findById(vmHost.getId()); CommvaultRestoreBackupCommand restoreCommand = new CommvaultRestoreBackupCommand(); + LOG.info(path); restoreCommand.setBackupPath(path); restoreCommand.setVmName(vm.getName()); restoreCommand.setBackupVolumesUUIDs(backedVolumesUUIDs); From 6701fd432f5602567fd9fd4f5968f3ace654f829 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 20 Feb 2026 10:16:41 +0900 Subject: [PATCH 26/35] Update BackupManagerImpl.java --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index a3b8a88583d2..38b0bb123b7f 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -1575,7 +1575,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, BackupProvider backupProvider = getBackupProvider(offering.getProvider()); VolumeVO backedUpVolume = volumeDao.findByUuid(backedUpVolumeUuid); Pair restoreInfo; - if (!"nas".equals(offering.getProvider()) || (backedUpVolume == null)) { + if ((!"nas".equals(offering.getProvider()) && !"commvault".equals(offering.getProvider())) || backedUpVolume == null) { restoreInfo = getRestoreVolumeHostAndDatastore(vm); } else { restoreInfo = getRestoreVolumeHostAndDatastoreForNas(vm, backedUpVolume); From 25be8e1a16b043e84a6d1e0bf07abf104344235e Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 20 Feb 2026 10:21:57 +0900 Subject: [PATCH 27/35] Update DeployVM.vue --- ui/src/views/compute/DeployVM.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index b3e2dd922b9b..3395605d0f51 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -2667,6 +2667,7 @@ export default { duration: 0 }) } + this.performPostDeployBackupActions(vm) if (!values.stayonpage) { // eventBus.emit('vm-refresh-data') } From 40500222abc09d312f8e8b0c1e19b72a3f01a1ba Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 20 Feb 2026 10:33:08 +0900 Subject: [PATCH 28/35] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=EC=8B=9C=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20=ED=95=98?= =?UTF-8?q?=EC=9C=84=EA=B9=8C=EC=A7=80=20=EC=A0=84=EB=B6=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=ED=95=98=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java index 46665fbdfa8e..5ffce02fd138 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCommvaultRestoreBackupCommandWrapper.java @@ -37,13 +37,12 @@ import org.apache.cloudstack.utils.qemu.QemuImg; import org.apache.cloudstack.utils.qemu.QemuImgException; import org.apache.cloudstack.utils.qemu.QemuImgFile; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.libvirt.LibvirtException; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -166,7 +165,7 @@ private void restoreVolume(KVMStoragePoolManager storagePoolMgr, String backupPa private void deleteBackupDirectory(String backupDirectory) { try { - Files.deleteIfExists(Paths.get(backupDirectory)); + FileUtils.deleteDirectory(new File(backupDirectory)); } catch (IOException e) { logger.error(String.format("Failed to delete backup directory: %s", backupDirectory), e); throw new CloudRuntimeException("Failed to delete the backup directory"); From 30c96f9463a6dd5f063b03dc57b41dd003fd4c55 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 20 Feb 2026 11:11:29 +0900 Subject: [PATCH 29/35] Update StartBackup.vue --- ui/src/views/compute/StartBackup.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/views/compute/StartBackup.vue b/ui/src/views/compute/StartBackup.vue index de2680d14476..96c337ab0bd0 100644 --- a/ui/src/views/compute/StartBackup.vue +++ b/ui/src/views/compute/StartBackup.vue @@ -40,7 +40,7 @@ - +