This is an automated email from the ASF dual-hosted git repository. dahn pushed a commit to branch 4.22 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 11df71e55cc8d5cd709d3c7b99f432ee8dce2974 Merge: 7324ef45d46 ae5308bdd20 Author: Daan Hoogland <[email protected]> AuthorDate: Tue Feb 17 11:52:39 2026 +0100 Merge branch '4.20' into '4.22' .../cloudstack/backup/RestoreBackupCommand.java | 18 +-- .../java/com/cloud/resource/ResourceManager.java | 2 + .../java/com/cloud/dc/ClusterDetailsDaoImpl.java | 2 +- .../com/cloud/dc/dao/DataCenterDetailsDaoImpl.java | 2 +- .../main/java/com/cloud/host/dao/HostDaoImpl.java | 2 +- .../storage/datastore/db/ImageStoreDaoImpl.java | 2 +- .../src/main/java/com/cloud/utils/db/Filter.java | 13 +- .../java/com/cloud/utils/db/GenericDaoBase.java | 6 +- .../test/java/com/cloud/utils/db/FilterTest.java | 58 +++++++ .../com/cloud/utils/db/GenericDaoBaseTest.java | 68 ++++++++ .../apache/cloudstack/quota/QuotaManagerImpl.java | 12 +- .../activationrule/presetvariables/Account.java | 2 - .../presetvariables/BackupOffering.java | 1 - .../presetvariables/ComputeOffering.java | 3 - .../presetvariables/Configuration.java | 1 - .../DiskOfferingPresetVariables.java | 12 -- .../activationrule/presetvariables/Domain.java | 1 - .../presetvariables/GenericPresetVariable.java | 18 +-- .../quota/activationrule/presetvariables/Host.java | 2 - .../presetvariables/PresetVariableHelper.java | 12 +- .../quota/activationrule/presetvariables/Role.java | 9 +- .../activationrule/presetvariables/Storage.java | 11 +- .../activationrule/presetvariables/Tariff.java | 1 - .../activationrule/presetvariables/Value.java | 48 ++---- .../cloudstack/quota/QuotaManagerImplTest.java | 24 +-- .../presetvariables/AccountTest.java | 34 ---- .../presetvariables/BackupOfferingTest.java | 36 ----- .../presetvariables/ComputeOfferingTest.java | 35 ----- .../presetvariables/ComputingResourcesTest.java | 40 ----- .../activationrule/presetvariables/DomainTest.java | 35 ----- .../presetvariables/GenericPresetVariableTest.java | 73 --------- .../activationrule/presetvariables/HostTest.java | 34 ---- .../presetvariables/PresetVariableHelperTest.java | 134 +++++----------- .../presetvariables/ResourceTest.java | 40 ----- .../activationrule/presetvariables/RoleTest.java | 34 ---- .../presetvariables/StorageTest.java | 41 ----- .../activationrule/presetvariables/ValueTest.java | 175 --------------------- .../cloudstack/backup/NASBackupProvider.java | 30 +++- .../LibvirtRestoreBackupCommandWrapper.java | 67 ++++---- .../LibvirtRestoreBackupCommandWrapperTest.java | 19 ++- .../cloudstack/metrics/PrometheusExporterImpl.java | 43 +++++ .../metrics/PrometheusExporterImplTest.java | 108 +++++++++++++ .../com/cloud/resource/ResourceManagerImpl.java | 27 +++- .../storage/heuristics/HeuristicRuleHelper.java | 20 +-- .../heuristics/presetvariables/Account.java | 2 - .../storage/heuristics/presetvariables/Domain.java | 1 - .../GenericHeuristicPresetVariable.java | 17 +- .../presetvariables/SecondaryStorage.java | 4 - .../heuristics/presetvariables/Snapshot.java | 10 +- .../heuristics/presetvariables/Template.java | 24 ++- .../storage/heuristics/presetvariables/Volume.java | 10 +- .../cloud/resource/MockResourceManagerImpl.java | 5 + .../heuristics/HeuristicRuleHelperTest.java | 16 ++ .../heuristics/presetvariables/AccountTest.java | 46 ------ .../heuristics/presetvariables/DomainTest.java | 41 ----- .../GenericHeuristicPresetVariableTest.java | 40 ----- .../presetvariables/SecondaryStorageTest.java | 45 ------ .../heuristics/presetvariables/SnapshotTest.java | 44 ------ .../heuristics/presetvariables/TemplateTest.java | 46 ------ .../heuristics/presetvariables/VolumeTest.java | 44 ------ .../storage/template/UploadManagerImpl.java | 51 +++++- .../storage/template/UploadManagerImplTest.java | 85 ++++++++++ .../java/com/cloud/usage/UsageManagerImpl.java | 20 ++- .../utils/jsinterpreter/JsInterpreter.java | 33 ++-- .../utils/jsinterpreter/TagAsRuleHelper.java | 21 ++- .../utils/jsinterpreter/JsInterpreterTest.java | 18 --- 66 files changed, 710 insertions(+), 1268 deletions(-) diff --cc core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java index 8e68f4f1e41,0bc6865d9e5..f5ad5fbea2c --- a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java @@@ -31,14 -30,11 +31,14 @@@ public class RestoreBackupCommand exten private String backupPath; private String backupRepoType; private String backupRepoAddress; - private List<String> volumePaths; + private List<String> backupVolumesUUIDs; + private List<PrimaryDataStoreTO> restoreVolumePools; + private List<String> restoreVolumePaths; + private List<String> backupFiles; private String diskType; private Boolean vmExists; - private String restoreVolumeUUID; private VirtualMachine.State vmState; + private Integer mountTimeout; protected RestoreBackupCommand() { super(); @@@ -76,22 -72,22 +76,30 @@@ this.backupRepoAddress = backupRepoAddress; } - public List<String> getVolumePaths() { - return volumePaths; + public List<PrimaryDataStoreTO> getRestoreVolumePools() { + return restoreVolumePools; } - public void setVolumePaths(List<String> volumePaths) { - this.volumePaths = volumePaths; + public void setRestoreVolumePools(List<PrimaryDataStoreTO> restoreVolumePools) { + this.restoreVolumePools = restoreVolumePools; + } + + public List<String> getRestoreVolumePaths() { + return restoreVolumePaths; + } + + public void setRestoreVolumePaths(List<String> restoreVolumePaths) { + this.restoreVolumePaths = restoreVolumePaths; } + public List<String> getBackupFiles() { + return backupFiles; + } + + public void setBackupFiles(List<String> backupFiles) { + this.backupFiles = backupFiles; + } + public Boolean isVmExists() { return vmExists; } diff --cc engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java index 8f218841b07,8c3604d352b..2d8fcca6cdb --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java @@@ -1385,34 -1343,12 +1385,34 @@@ public class HostDaoImpl extends Generi return listBy(sc); } + @Override + public List<HostVO> findHypervisorHostInZone(long zoneId) { + SearchCriteria<HostVO> sc = TypeStatusStateSearch.create(); + sc.setParameters("type", Host.Type.Routing); + sc.setParameters("zone", zoneId); + sc.setParameters("status", Status.Up); + sc.setParameters("resourceState", ResourceState.Enabled); + + return listBy(sc); + } + + @Override + public List<HostVO> findHypervisorHostInPod(long podId) { + SearchCriteria<HostVO> sc = TypeStatusStateSearch.create(); + sc.setParameters("type", Host.Type.Routing); + sc.setParameters("pod", podId); + sc.setParameters("status", Status.Up); + sc.setParameters("resourceState", ResourceState.Enabled); + + return listBy(sc); + } + @Override public HostVO findAnyStateHypervisorHostInCluster(long clusterId) { - SearchCriteria<HostVO> sc = TypeClusterStatusSearch.create(); + SearchCriteria<HostVO> sc = TypeStatusStateSearch.create(); sc.setParameters("type", Host.Type.Routing); sc.setParameters("cluster", clusterId); - List<HostVO> list = listBy(sc, new Filter(1)); + List<HostVO> list = listBy(sc, new Filter(1, true)); return list.isEmpty() ? null : list.get(0); } diff --cc framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java index 7949259bc82,7c3eaead63f..816144aa2f1 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java @@@ -468,14 -467,9 +468,14 @@@ public class QuotaManagerImpl extends M } + Configuration configuration = presetVariables.getConfiguration(); + if (configuration != null) { + jsInterpreter.injectVariable("configuration", configuration.toString()); + } + jsInterpreter.injectVariable("resourceType", presetVariables.getResourceType()); - jsInterpreter.injectVariable("value", presetVariables.getValue().toString()); - jsInterpreter.injectVariable("zone", presetVariables.getZone().toString()); + jsInterpreter.injectVariable("value", presetVariables.getValue()); + jsInterpreter.injectVariable("zone", presetVariables.getZone()); } /** diff --cc framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Account.java index 2420d577f10,289958fe447..e34c8d3f31d --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Account.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Account.java @@@ -36,15 -28,6 +36,13 @@@ public class Account extends GenericPre public void setRole(Role role) { this.role = role; - fieldNamesToIncludeInToString.add("role"); } + public String getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = DateUtil.displayDateInTimezone(TimeZone.getTimeZone("GMT"), created); - fieldNamesToIncludeInToString.add("created"); + } } diff --cc framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/ComputeOffering.java index 09182711ca8,9f9575052d3..74cb695010b --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/ComputeOffering.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/ComputeOffering.java @@@ -32,16 -27,6 +32,13 @@@ public class ComputeOffering extends Ge public void setCustomized(boolean customized) { this.customized = customized; - fieldNamesToIncludeInToString.add("customized"); } + public boolean offerHa() { + return offerHa; + } + + public void setOfferHa(boolean offerHa) { + this.offerHa = offerHa; - fieldNamesToIncludeInToString.add("offerHa"); + } - } diff --cc framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Configuration.java index e59f78af8d9,00000000000..48fee552c5a mode 100644,000000..100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Configuration.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Configuration.java @@@ -1,35 -1,0 +1,34 @@@ +// 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.quota.activationrule.presetvariables; + +import org.apache.cloudstack.quota.constant.QuotaTypes; + +public class Configuration extends GenericPresetVariable{ + + @PresetVariableDefinition(description = "A boolean informing if the cluster configuration force.ha is enabled or not.", supportedTypes = {QuotaTypes.RUNNING_VM}) + private boolean forceHa; + + public boolean getForceHa() { + return forceHa; + } + + public void setForceHa(boolean forceHa) { + this.forceHa = forceHa; - fieldNamesToIncludeInToString.add("forceHa"); + } +} diff --cc framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/DiskOfferingPresetVariables.java index b2f5f69502f,00000000000..68ba6a331eb mode 100644,000000..100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/DiskOfferingPresetVariables.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/DiskOfferingPresetVariables.java @@@ -1,165 -1,0 +1,153 @@@ +// 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.quota.activationrule.presetvariables; + +public class DiskOfferingPresetVariables extends GenericPresetVariable { + + @PresetVariableDefinition(description = "A long informing the bytes read rate of the disk offering.") + private Long bytesReadRate; + + @PresetVariableDefinition(description = "A long informing the burst bytes read rate of the disk offering.") + private Long bytesReadBurst; + + @PresetVariableDefinition(description = "The length (in seconds) of the bytes read burst.") + private Long bytesReadBurstLength; + + @PresetVariableDefinition(description = "A long informing the bytes write rate of the disk offering.") + private Long bytesWriteRate; + + @PresetVariableDefinition(description = "A long informing the burst bytes write rate of the disk offering.") + private Long bytesWriteBurst; + + @PresetVariableDefinition(description = "The length (in seconds) of the bytes write burst.") + private Long bytesWriteBurstLength; + + @PresetVariableDefinition(description = "A long informing the I/O requests read rate of the disk offering.") + private Long iopsReadRate; + + @PresetVariableDefinition(description = "A long informing the burst I/O requests read rate of the disk offering.") + private Long iopsReadBurst; + + @PresetVariableDefinition(description = "The length (in seconds) of the IOPS read burst.") + private Long iopsReadBurstLength; + + @PresetVariableDefinition(description = "A long informing the I/O requests write rate of the disk offering.") + private Long iopsWriteRate; + + @PresetVariableDefinition(description = "A long informing the burst I/O requests write rate of the disk offering.") + private Long iopsWriteBurst; + + @PresetVariableDefinition(description = "The length (in seconds) of the IOPS write burst.") + private Long iopsWriteBurstLength; + + public Long getBytesReadRate() { + return bytesReadRate; + } + + public void setBytesReadRate(Long bytesReadRate) { + this.bytesReadRate = bytesReadRate; - fieldNamesToIncludeInToString.add("bytesReadRate"); + } + + public Long getBytesReadBurst() { + return bytesReadBurst; + } + + public void setBytesReadBurst(Long bytesReadBurst) { + this.bytesReadBurst = bytesReadBurst; - fieldNamesToIncludeInToString.add("bytesReadBurst"); + } + + public Long getBytesReadBurstLength() { + return bytesReadBurstLength; + } + + public void setBytesReadBurstLength(Long bytesReadBurstLength) { + this.bytesReadBurstLength = bytesReadBurstLength; - fieldNamesToIncludeInToString.add("bytesReadBurstLength"); + } + + public Long getBytesWriteRate() { + return bytesWriteRate; + } + + public void setBytesWriteRate(Long bytesWriteRate) { + this.bytesWriteRate = bytesWriteRate; - fieldNamesToIncludeInToString.add("bytesWriteRate"); + } + + public Long getBytesWriteBurst() { + return bytesWriteBurst; + } + + public void setBytesWriteBurst(Long bytesWriteBurst) { + this.bytesWriteBurst = bytesWriteBurst; - fieldNamesToIncludeInToString.add("bytesWriteBurst"); + } + + public Long getBytesWriteBurstLength() { + return bytesWriteBurstLength; + } + + public void setBytesWriteBurstLength(Long bytesWriteBurstLength) { + this.bytesWriteBurstLength = bytesWriteBurstLength; - fieldNamesToIncludeInToString.add("bytesWriteBurstLength"); + } + + public Long getIopsReadRate() { + return iopsReadRate; + } + + public void setIopsReadRate(Long iopsReadRate) { + this.iopsReadRate = iopsReadRate; - fieldNamesToIncludeInToString.add("iopsReadRate"); + } + + public Long getIopsReadBurst() { + return iopsReadBurst; + } + + public void setIopsReadBurst(Long iopsReadBurst) { + this.iopsReadBurst = iopsReadBurst; - fieldNamesToIncludeInToString.add("iopsReadBurst"); + } + + public Long getIopsReadBurstLength() { + return iopsReadBurstLength; + } + + public void setIopsReadBurstLength(Long iopsReadBurstLength) { + this.iopsReadBurstLength = iopsReadBurstLength; - fieldNamesToIncludeInToString.add("iopsReadBurstLength"); + } + + public Long getIopsWriteRate() { + return iopsWriteRate; + } + + public void setIopsWriteRate(Long iopsWriteRate) { + this.iopsWriteRate = iopsWriteRate; - fieldNamesToIncludeInToString.add("iopsWriteRate"); + } + + public Long getIopsWriteBurst() { + return iopsWriteBurst; + } + + public void setIopsWriteBurst(Long iopsWriteBurst) { + this.iopsWriteBurst = iopsWriteBurst; - fieldNamesToIncludeInToString.add("iopsWriteBurst"); + } + + public Long getIopsWriteBurstLength() { + return iopsWriteBurstLength; + } + + public void setIopsWriteBurstLength(Long iopsWriteBurstLength) { + this.iopsWriteBurstLength = iopsWriteBurstLength; - fieldNamesToIncludeInToString.add("iopsWriteBurstLength"); + } +} diff --cc framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java index 2a6ad132f63,918cf78b4e6..a2fca7b80fd --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java @@@ -538,8 -490,7 +538,8 @@@ public class PresetVariableHelper value.setDiskOffering(getPresetVariableValueDiskOffering(volumeVo.getDiskOfferingId())); value.setId(volumeVo.getUuid()); value.setName(volumeVo.getName()); - value.setProvisioningType(volumeVo.getProvisioningType()); - value.setVolumeType(volumeVo.getVolumeType()); ++ value.setVolumeType(volumeVo.getVolumeType().toString()); + value.setProvisioningType(volumeVo.getProvisioningType().toString()); Long poolId = volumeVo.getPoolId(); if (poolId == null) { diff --cc framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java index 77e539db0f3,98f9c2678a8..ac776d13c57 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java @@@ -94,10 -90,6 +90,10 @@@ public class Value extends GenericPrese @PresetVariableDefinition(description = "The volume format. Values can be: RAW, VHD, VHDX, OVA and QCOW2.", supportedTypes = {QuotaTypes.VOLUME, QuotaTypes.VOLUME_SECONDARY}) private String volumeFormat; + + @PresetVariableDefinition(description = "The volume type. Values can be: UNKNOWN, ROOT, SWAP, DATADISK and ISO.", supportedTypes = {QuotaTypes.VOLUME}) - private Volume.Type volumeType; ++ private String volumeType; + private String state; public Host getHost() { @@@ -196,16 -178,14 +182,14 @@@ public void setTemplate(GenericPresetVariable template) { this.template = template; - fieldNamesToIncludeInToString.add("template"); } - public GenericPresetVariable getDiskOffering() { + public DiskOfferingPresetVariables getDiskOffering() { return diskOffering; } - public void setDiskOffering(GenericPresetVariable diskOffering) { + public void setDiskOffering(DiskOfferingPresetVariables diskOffering) { this.diskOffering = diskOffering; - fieldNamesToIncludeInToString.add("diskOffering"); } public Storage getStorage() { @@@ -262,15 -236,6 +240,14 @@@ return volumeFormat; } - public Volume.Type getVolumeType() { ++ public String getVolumeType() { + return volumeType; + } + - public void setVolumeType(Volume.Type volumeType) { ++ public void setVolumeType(String volumeType) { + this.volumeType = volumeType; - fieldNamesToIncludeInToString.add("volumeType"); + } + public String getState() { return state; } diff --cc framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java index c692cb7c1e7,095ab422ee7..85397503587 --- a/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java @@@ -214,15 -208,14 +214,15 @@@ public class PresetVariableHelperTest value.setComputeOffering(getComputeOfferingForTests()); value.setTags(Collections.singletonMap("tag1", "value1")); value.setTemplate(getGenericPresetVariableForTests()); - value.setDiskOffering(getGenericPresetVariableForTests()); + value.setDiskOffering(getDiskOfferingForTests()); - value.setProvisioningType(ProvisioningType.THIN); + value.setProvisioningType(ProvisioningType.THIN.toString()); value.setStorage(getStorageForTests()); value.setSize(ByteScaleUtils.GiB); - value.setSnapshotType(Snapshot.Type.HOURLY); + value.setSnapshotType(Snapshot.Type.HOURLY.toString()); value.setTag("tag_test"); - value.setVmSnapshotType(VMSnapshot.Type.Disk); + value.setVmSnapshotType(VMSnapshot.Type.Disk.toString()); value.setComputingResources(getComputingResourcesForTests()); - value.setVolumeType(Volume.Type.DATADISK); ++ value.setVolumeType(Volume.Type.DATADISK.toString()); return value; } @@@ -296,11 -280,11 +296,11 @@@ return quotaTypesMap.entrySet(); } - private List<UserVmDetailVO> getVmDetailsForTests() { - List<UserVmDetailVO> details = new LinkedList<>(); - details.add(new UserVmDetailVO(1l, "test_with_value", "277", false)); - details.add(new UserVmDetailVO(1l, "test_with_invalid_value", "invalid", false)); - details.add(new UserVmDetailVO(1l, "test_with_null", null, false)); + private List<VMInstanceDetailVO> getVmDetailsForTests() { + List<VMInstanceDetailVO> details = new LinkedList<>(); - details.add(new VMInstanceDetailVO(1l, "test_with_value", "277", false)); - details.add(new VMInstanceDetailVO(1l, "test_with_invalid_value", "invalid", false)); - details.add(new VMInstanceDetailVO(1l, "test_with_null", null, false)); ++ details.add(new VMInstanceDetailVO(1L, "test_with_value", "277", false)); ++ details.add(new VMInstanceDetailVO(1L, "test_with_invalid_value", "invalid", false)); ++ details.add(new VMInstanceDetailVO(1L, "test_with_null", null, false)); return details; } @@@ -428,12 -367,10 +420,11 @@@ Account account = getAccountForTests(); Mockito.doReturn(account.getId()).when(accountVoMock).getUuid(); Mockito.doReturn(account.getName()).when(accountVoMock).getName(); + Mockito.doReturn(account.getCreated()).when(accountVoMock).getCreated(); -- Account result = presetVariableHelperSpy.getPresetVariableAccount(1l); ++ Account result = presetVariableHelperSpy.getPresetVariableAccount(1L); assertPresetVariableIdAndName(account, result); - validateFieldNamesToIncludeInToString(Arrays.asList("created", "id", "name"), result); } @Test @@@ -467,9 -404,9 +458,9 @@@ Mockito.doReturn(role.getId()).when(roleVoMock).getUuid(); Mockito.doReturn(role.getName()).when(roleVoMock).getName(); - Mockito.doReturn(role.getType()).when(roleVoMock).getRoleType(); + Mockito.doReturn(RoleType.fromString(role.getType())).when(roleVoMock).getRoleType(); -- Role result = presetVariableHelperSpy.getPresetVariableRole(1l); ++ Role result = presetVariableHelperSpy.getPresetVariableRole(1L); assertPresetVariableIdAndName(role, result); Assert.assertEquals(role.getType(), result.getType()); @@@ -489,7 -424,7 +478,7 @@@ Mockito.doReturn(domain.getName()).when(domainVoMock).getName(); Mockito.doReturn(domain.getPath()).when(domainVoMock).getPath(); -- Domain result = presetVariableHelperSpy.getPresetVariableDomain(1l); ++ Domain result = presetVariableHelperSpy.getPresetVariableDomain(1L); assertPresetVariableIdAndName(domain, result); Assert.assertEquals(domain.getPath(), result.getPath()); @@@ -507,10 -440,9 +494,9 @@@ Mockito.doReturn(expected.getId()).when(dataCenterVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(dataCenterVoMock).getName(); -- GenericPresetVariable result = presetVariableHelperSpy.getPresetVariableZone(1l); ++ GenericPresetVariable result = presetVariableHelperSpy.getPresetVariableZone(1L); assertPresetVariableIdAndName(expected, result); - validateFieldNamesToIncludeInToString(Arrays.asList("id", "name"), result); } @Test @@@ -541,7 -472,7 +526,7 @@@ Mockito.doReturn(new Date()).when(usageVoMock).getEndDate(); Mockito.doReturn(expected).when(usageDaoMock).listAccountResourcesInThePeriod(Mockito.anyLong(), Mockito.anyInt(), Mockito.any(Date.class), Mockito.any(Date.class)); -- List<Resource> result = presetVariableHelperSpy.getPresetVariableAccountResources(usageVoMock, 1l, 0); ++ List<Resource> result = presetVariableHelperSpy.getPresetVariableAccountResources(usageVoMock, 1L, 0); for (int i = 0; i < expected.size(); i++) { Assert.assertEquals(expected.get(i).first(), result.get(i).getZoneId()); @@@ -614,7 -543,7 +597,6 @@@ public void setPresetVariableHostInValueIfUsageTypeIsRunningVmTestQuotaTypeIsRunningVmSetHost() { Value result = new Value(); Host expectedHost = getHostForTests(); -- List<HostTagVO> expectedHostTags = getHostTagsForTests(); Mockito.doReturn(expectedHost).when(presetVariableHelperSpy).getPresetVariableValueHost(Mockito.anyLong()); presetVariableHelperSpy.setPresetVariableHostInValueIfUsageTypeIsRunningVm(result, UsageTypes.RUNNING_VM, vmInstanceVoMock); @@@ -638,7 -566,7 +619,7 @@@ Mockito.doReturn(expected.getName()).when(hostVoMock).getName(); Mockito.doReturn(hostTagVOListMock).when(hostTagsDaoMock).getHostTags(Mockito.anyLong()); -- Host result = presetVariableHelperSpy.getPresetVariableValueHost(1l); ++ Host result = presetVariableHelperSpy.getPresetVariableValueHost(1L); assertPresetVariableIdAndName(expected, result); Assert.assertEquals(expected.getTags(), result.getTags()); @@@ -657,7 -584,7 +637,7 @@@ Mockito.doReturn(expected.getName()).when(hostVoMock).getName(); Mockito.doReturn(hostTagVOListMock).when(hostTagsDaoMock).getHostTags(Mockito.anyLong()); -- Host result = presetVariableHelperSpy.getPresetVariableValueHost(1l); ++ Host result = presetVariableHelperSpy.getPresetVariableValueHost(1L); assertPresetVariableIdAndName(expected, result); Assert.assertEquals(new ArrayList<>(), result.getTags()); @@@ -674,29 -600,13 +653,28 @@@ String expected = "os_display_name"; Mockito.doReturn(expected).when(guestOsVoMock).getDisplayName(); -- String result = presetVariableHelperSpy.getPresetVariableValueOsName(1l); ++ String result = presetVariableHelperSpy.getPresetVariableValueOsName(1L); Assert.assertEquals(expected, result); } @Test - public void getPresetVariableValueComputeOfferingTestSetFieldsAndReturnObject() { + public void getPresetVariableValueComputeOfferingForTestSetFieldsAndReturnObjectForRunningVm() { + ComputeOffering expected = getComputeOfferingForTests(); + Mockito.doReturn(expected.getId()).when(serviceOfferingVoMock).getUuid(); + Mockito.doReturn(expected.getName()).when(serviceOfferingVoMock).getName(); + Mockito.doReturn(expected.isCustomized()).when(serviceOfferingVoMock).isDynamic(); + Mockito.doReturn(expected.offerHa()).when(serviceOfferingVoMock).isOfferHA(); + + ComputeOffering result = presetVariableHelperSpy.getPresetVariableValueComputeOffering(serviceOfferingVoMock, UsageTypes.RUNNING_VM); + + assertPresetVariableIdAndName(expected, result); + Assert.assertEquals(expected.isCustomized(), result.isCustomized()); + Assert.assertEquals(expected.offerHa(), result.offerHa()); - validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "customized", "offerHa"), result); + } + + @Test + public void getPresetVariableValueComputeOfferingForTestSetFieldsAndReturnObjectForAllocatedVm() { ComputeOffering expected = getComputeOfferingForTests(); Mockito.doReturn(expected.getId()).when(serviceOfferingVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(serviceOfferingVoMock).getName(); @@@ -720,10 -628,9 +696,9 @@@ Mockito.doReturn(expected.getId()).when(vmTemplateVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(vmTemplateVoMock).getName(); -- GenericPresetVariable result = presetVariableHelperSpy.getPresetVariableValueTemplate(1l); ++ GenericPresetVariable result = presetVariableHelperSpy.getPresetVariableValueTemplate(1L); assertPresetVariableIdAndName(expected, result); - validateFieldNamesToIncludeInToString(Arrays.asList("id", "name"), result); } @Test @@@ -735,7 -642,7 +710,7 @@@ Mockito.doReturn(listExpected).when(resourceTagDaoMock).listBy(Mockito.anyLong(), Mockito.any(ResourceObjectType.class)); Arrays.asList(ResourceObjectType.values()).forEach(type -> { -- Map<String, String> result = presetVariableHelperSpy.getPresetVariableValueResourceTags(1l, type); ++ Map<String, String> result = presetVariableHelperSpy.getPresetVariableValueResourceTags(1L, type); for (ResourceTag expected: listExpected) { Assert.assertEquals(expected.getValue(), result.get(expected.getKey())); @@@ -760,15 -667,14 +735,15 @@@ VolumeVO volumeVoMock = Mockito.mock(VolumeVO.class); Mockito.doReturn(volumeVoMock).when(volumeDaoMock).findByIdIncludingRemoved(Mockito.anyLong()); -- Mockito.doReturn(1l).when(volumeVoMock).getPoolId(); ++ Mockito.doReturn(1L).when(volumeVoMock).getPoolId(); mockMethodValidateIfObjectIsNull(); Mockito.doReturn(expected.getId()).when(volumeVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(volumeVoMock).getName(); Mockito.doReturn(expected.getDiskOffering()).when(presetVariableHelperSpy).getPresetVariableValueDiskOffering(Mockito.anyLong()); - Mockito.doReturn(expected.getProvisioningType()).when(volumeVoMock).getProvisioningType(); - Mockito.doReturn(expected.getVolumeType()).when(volumeVoMock).getVolumeType(); + Mockito.doReturn(ProvisioningType.getProvisioningType(expected.getProvisioningType())).when(volumeVoMock).getProvisioningType(); ++ Mockito.doReturn(Volume.Type.valueOf(expected.getVolumeType())).when(volumeVoMock).getVolumeType(); Mockito.doReturn(expected.getStorage()).when(presetVariableHelperSpy).getPresetVariableValueStorage(Mockito.anyLong(), Mockito.anyInt()); Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class)); Mockito.doReturn(expected.getSize()).when(volumeVoMock).getSize(); @@@ -811,8 -714,7 +784,8 @@@ Mockito.doReturn(expected.getId()).when(volumeVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(volumeVoMock).getName(); Mockito.doReturn(expected.getDiskOffering()).when(presetVariableHelperSpy).getPresetVariableValueDiskOffering(Mockito.anyLong()); - Mockito.doReturn(expected.getProvisioningType()).when(volumeVoMock).getProvisioningType(); - Mockito.doReturn(expected.getVolumeType()).when(volumeVoMock).getVolumeType(); ++ Mockito.doReturn(Volume.Type.valueOf(expected.getVolumeType())).when(volumeVoMock).getVolumeType(); + Mockito.doReturn(ProvisioningType.getProvisioningType(expected.getProvisioningType())).when(volumeVoMock).getProvisioningType(); Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class)); Mockito.doReturn(expected.getSize()).when(volumeVoMock).getSize(); Mockito.doReturn(imageFormat).when(volumeVoMock).getFormat(); @@@ -850,11 -749,9 +821,9 @@@ Mockito.doReturn(expected.getId()).when(diskOfferingVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(diskOfferingVoMock).getName(); -- GenericPresetVariable result = presetVariableHelperSpy.getPresetVariableValueDiskOffering(1l); ++ GenericPresetVariable result = presetVariableHelperSpy.getPresetVariableValueDiskOffering(1L); assertPresetVariableIdAndName(expected, result); - validateFieldNamesToIncludeInToString(Arrays.asList("bytesReadBurst", "bytesReadBurstLength", "bytesReadRate", "bytesWriteBurst", "bytesWriteBurstLength", "bytesWriteRate", - "id", "iopsReadBurst", "iopsReadBurstLength", "iopsReadRate", "iopsWriteBurst", "iopsWriteBurstLength", "iopsWriteRate", "name"), result); } @Test @@@ -862,7 -759,7 +831,7 @@@ Storage expected = getStorageForTests(); Mockito.doReturn(expected).when(presetVariableHelperSpy).getSecondaryStorageForSnapshot(Mockito.anyLong(), Mockito.anyInt()); -- Storage result = presetVariableHelperSpy.getPresetVariableValueStorage(1l, 2); ++ Storage result = presetVariableHelperSpy.getPresetVariableValueStorage(1L, 2); Assert.assertEquals(expected, result); Mockito.verify(primaryStorageDaoMock, Mockito.never()).findByIdIncludingRemoved(Mockito.anyLong()); @@@ -880,10 -777,10 +849,10 @@@ Mockito.doReturn(expected.getId()).when(storagePoolVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(storagePoolVoMock).getName(); - Mockito.doReturn(expected.getScope()).when(storagePoolVoMock).getScope(); + Mockito.doReturn(ScopeType.validateAndGetScopeType(expected.getScope())).when(storagePoolVoMock).getScope(); Mockito.doReturn(storageTagVOListMock).when(storagePoolTagsDaoMock).findStoragePoolTags(Mockito.anyLong()); -- Storage result = presetVariableHelperSpy.getPresetVariableValueStorage(1l, 2); ++ Storage result = presetVariableHelperSpy.getPresetVariableValueStorage(1L, 2); assertPresetVariableIdAndName(expected, result); Assert.assertEquals(expected.getScope(), result.getScope()); @@@ -904,10 -799,10 +871,10 @@@ Mockito.doReturn(expected.getId()).when(storagePoolVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(storagePoolVoMock).getName(); - Mockito.doReturn(expected.getScope()).when(storagePoolVoMock).getScope(); + Mockito.doReturn(ScopeType.validateAndGetScopeType(expected.getScope())).when(storagePoolVoMock).getScope(); Mockito.doReturn(storageTagVOListMock).when(storagePoolTagsDaoMock).findStoragePoolTags(Mockito.anyLong()); -- Storage result = presetVariableHelperSpy.getPresetVariableValueStorage(1l, 2); ++ Storage result = presetVariableHelperSpy.getPresetVariableValueStorage(1L, 2); assertPresetVariableIdAndName(expected, result); Assert.assertEquals(expected.getScope(), result.getScope()); @@@ -921,7 -814,7 +886,7 @@@ public void getSecondaryStorageForSnapshotTestAllTypesAndDoNotBackupSnapshotReturnNull() { presetVariableHelperSpy.backupSnapshotAfterTakingSnapshot = false; getQuotaTypesForTests().forEach(type -> { -- Storage result = presetVariableHelperSpy.getSecondaryStorageForSnapshot(1l, type.getKey()); ++ Storage result = presetVariableHelperSpy.getSecondaryStorageForSnapshot(1L, type.getKey()); Assert.assertNull(result); }); } @@@ -930,7 -823,7 +895,7 @@@ public void getSecondaryStorageForSnapshotTestAllTypesExceptSnapshotAndBackupSnapshotReturnNull() { presetVariableHelperSpy.backupSnapshotAfterTakingSnapshot = true; getQuotaTypesForTests(UsageTypes.SNAPSHOT).forEach(type -> { -- Storage result = presetVariableHelperSpy.getSecondaryStorageForSnapshot(1l, type.getKey()); ++ Storage result = presetVariableHelperSpy.getSecondaryStorageForSnapshot(1L, type.getKey()); Assert.assertNull(result); }); } @@@ -947,10 -840,9 +912,9 @@@ Mockito.doReturn(expected.getName()).when(imageStoreVoMock).getName(); presetVariableHelperSpy.backupSnapshotAfterTakingSnapshot = true; -- Storage result = presetVariableHelperSpy.getSecondaryStorageForSnapshot(1l, UsageTypes.SNAPSHOT); ++ Storage result = presetVariableHelperSpy.getSecondaryStorageForSnapshot(1L, UsageTypes.SNAPSHOT); assertPresetVariableIdAndName(expected, result); - validateFieldNamesToIncludeInToString(Arrays.asList("id", "name"), result); } @Test @@@ -1025,7 -915,7 +987,7 @@@ Mockito.doReturn(expected.getName()).when(snapshotVoMock).getName(); Mockito.doReturn(expected.getSize()).when(snapshotVoMock).getSize(); Mockito.doReturn((short) 3).when(snapshotVoMock).getSnapshotType(); -- Mockito.doReturn(1l).when(presetVariableHelperSpy).getSnapshotDataStoreId(Mockito.anyLong(), Mockito.anyLong()); ++ Mockito.doReturn(1L).when(presetVariableHelperSpy).getSnapshotDataStoreId(Mockito.anyLong(), Mockito.anyLong()); Mockito.doReturn(expected.getStorage()).when(presetVariableHelperSpy).getPresetVariableValueStorage(Mockito.anyLong(), Mockito.anyInt()); Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class)); Mockito.doReturn(hypervisorType).when(snapshotVoMock).getHypervisorType(); @@@ -1056,12 -944,12 +1016,12 @@@ public void getSnapshotDataStoreIdTestDoNotBackupSnapshotToSecondaryRetrievePrimaryStorage() { SnapshotDataStoreVO snapshotDataStoreVoMock = Mockito.mock(SnapshotDataStoreVO.class); -- Long expected = 1l; ++ Long expected = 1L; Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoMock).findOneBySnapshotAndDatastoreRole(Mockito.anyLong(), Mockito.any(DataStoreRole.class)); Mockito.doReturn(expected).when(snapshotDataStoreVoMock).getDataStoreId(); presetVariableHelperSpy.backupSnapshotAfterTakingSnapshot = false; -- Long result = presetVariableHelperSpy.getSnapshotDataStoreId(1l, 1l); ++ Long result = presetVariableHelperSpy.getSnapshotDataStoreId(1L, 1L); Assert.assertEquals(expected, result); @@@ -1078,7 -966,7 +1038,7 @@@ public void getSnapshotDataStoreIdTestBackupSnapshotToSecondaryRetrieveSecondaryStorage() { SnapshotDataStoreVO snapshotDataStoreVoMock = Mockito.mock(SnapshotDataStoreVO.class); -- Long expected = 2l; ++ Long expected = 2L; ImageStoreVO imageStore = Mockito.mock(ImageStoreVO.class); Mockito.when(imageStoreDaoMock.findById(Mockito.anyLong())).thenReturn(imageStore); Mockito.when(imageStore.getDataCenterId()).thenReturn(1L); @@@ -1086,7 -974,7 +1046,7 @@@ Mockito.doReturn(expected).when(snapshotDataStoreVoMock).getDataStoreId(); presetVariableHelperSpy.backupSnapshotAfterTakingSnapshot = true; -- Long result = presetVariableHelperSpy.getSnapshotDataStoreId(2l, 1L); ++ Long result = presetVariableHelperSpy.getSnapshotDataStoreId(2L, 1L); Assert.assertEquals(expected, result); @@@ -1228,7 -1109,7 +1181,7 @@@ @Test public void getDetailByNameTestReturnsValue() { -- int expected = Integer.valueOf(getVmDetailsForTests().get(0).getValue()); ++ int expected = Integer.parseInt(getVmDetailsForTests().get(0).getValue()); int result = presetVariableHelperSpy.getDetailByName(getVmDetailsForTests(), "test_with_value", expected); Assert.assertEquals(expected, result); } @@@ -1311,7 -1190,7 +1262,7 @@@ Mockito.doReturn(expected.getName()).when(backupOfferingVoMock).getName(); Mockito.doReturn(expected.getExternalId()).when(backupOfferingVoMock).getExternalId(); -- BackupOffering result = presetVariableHelperSpy.getPresetVariableValueBackupOffering(1l); ++ BackupOffering result = presetVariableHelperSpy.getPresetVariableValueBackupOffering(1L); assertPresetVariableIdAndName(expected, result); Assert.assertEquals(expected.getExternalId(), result.getExternalId()); diff --cc plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 3b2c2692b0d,565ea29acf8..f2ea8ac71c9 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@@ -68,8 -56,8 +68,7 @@@ import java.util.Date import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.HashMap; import java.util.Objects; - import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@@ -279,24 -210,12 +278,25 @@@ public class NASBackupProvider extends return backupDao.persist(backup); } + @Override + public Pair<Boolean, String> restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid) { + return restoreVMBackup(vm, backup); + } + @Override public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { + return restoreVMBackup(vm, backup).first(); + } + + private Pair<Boolean, String> restoreVMBackup(VirtualMachine vm, Backup backup) { - List<String> backedVolumesUUIDs = backup.getBackedUpVolumes().stream() + List<Backup.VolumeInfo> backedVolumes = backup.getBackedUpVolumes(); - List<VolumeVO> volumes = backedVolumes.stream() - .map(volume -> volumeDao.findByUuid(volume.getUuid())) - .sorted((v1, v2) -> Long.compare(v1.getDeviceId(), v2.getDeviceId())) ++ List<String> backedVolumesUUIDs = backedVolumes.stream() + .sorted(Comparator.comparingLong(Backup.VolumeInfo::getDeviceId)) + .map(Backup.VolumeInfo::getUuid) + .collect(Collectors.toList()); + + List<VolumeVO> restoreVolumes = volumeDao.findByInstance(vm.getId()).stream() + .sorted(Comparator.comparingLong(VolumeVO::getDeviceId)) .collect(Collectors.toList()); LOG.debug("Restoring vm {} from backup {} on the NAS Backup Provider", vm, backup); @@@ -309,27 -228,31 +309,36 @@@ restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); restoreCommand.setMountOptions(backupRepository.getMountOptions()); restoreCommand.setVmName(vm.getName()); - restoreCommand.setVolumePaths(getVolumePaths(volumes)); + restoreCommand.setBackupVolumesUUIDs(backedVolumesUUIDs); + Pair<List<PrimaryDataStoreTO>, List<String>> volumePoolsAndPaths = getVolumePoolsAndPaths(restoreVolumes); + restoreCommand.setRestoreVolumePools(volumePoolsAndPaths.first()); + restoreCommand.setRestoreVolumePaths(volumePoolsAndPaths.second()); + restoreCommand.setBackupFiles(getBackupFiles(backedVolumes)); restoreCommand.setVmExists(vm.getRemoved() == null); restoreCommand.setVmState(vm.getState()); + restoreCommand.setMountTimeout(NASBackupRestoreMountTimeout.value()); - BackupAnswer answer = null; + BackupAnswer answer; try { answer = (BackupAnswer) agentManager.send(host.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 initiate backup timed out, please try again"); + throw new CloudRuntimeException("Operation to restore backup timed out, please try again"); } - return answer.getResult(); + return new Pair<>(answer.getResult(), answer.getDetails()); } + private List<String> getBackupFiles(List<Backup.VolumeInfo> backedVolumes) { + List<String> backupFiles = new ArrayList<>(); + for (Backup.VolumeInfo backedVolume : backedVolumes) { + backupFiles.add(backedVolume.getPath()); + } + return backupFiles; + } + - private List<String> getVolumePaths(List<VolumeVO> volumes) { + private Pair<List<PrimaryDataStoreTO>, List<String>> getVolumePoolsAndPaths(List<VolumeVO> volumes) { + List<PrimaryDataStoreTO> volumePools = new ArrayList<>(); List<String> volumePaths = new ArrayList<>(); for (VolumeVO volume : volumes) { StoragePoolVO storagePool = primaryDataStoreDao.findById(volume.getPoolId()); @@@ -360,14 -273,20 +369,20 @@@ } @Override - public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid, Pair<String, VirtualMachine.State> vmNameAndState) { - final VolumeVO volume = volumeDao.findByUuid(volumeUuid); - final VirtualMachine backupSourceVm = vmInstanceDao.findById(backup.getVmId()); - final StoragePoolHostVO dataStore = storagePoolHostDao.findByUuid(dataStoreUuid); + public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, Pair<String, VirtualMachine.State> vmNameAndState) { + final VolumeVO volume = volumeDao.findByUuid(backupVolumeInfo.getUuid()); + final DiskOffering diskOffering = diskOfferingDao.findByUuid(backupVolumeInfo.getDiskOfferingId()); + final StoragePoolVO pool = primaryDataStoreDao.findByUuid(dataStoreUuid); final HostVO hostVO = hostDao.findByIp(hostIp); - LOG.debug("Restoring vm volume {} from backup {} on the NAS Backup Provider", backupVolumeInfo, backup); - Backup.VolumeInfo matchingVolume = getBackedUpVolumeInfo(backup.getBackedUpVolumes(), volumeUuid); ++ Backup.VolumeInfo matchingVolume = getBackedUpVolumeInfo(backup.getBackedUpVolumes(), volume.getUuid()); + if (matchingVolume == null) { - throw new CloudRuntimeException(String.format("Unable to find volume %s in the list of backed up volumes for backup %s, cannot proceed with restore", volumeUuid, backup)); ++ throw new CloudRuntimeException(String.format("Unable to find volume %s in the list of backed up volumes for backup %s, cannot proceed with restore", volume.getUuid(), backup)); + } + Long backedUpVolumeSize = matchingVolume.getSize(); + + LOG.debug("Restoring vm volume {} from backup {} on the NAS Backup Provider", volume, backup); - BackupRepository backupRepository = getBackupRepository(backupSourceVm, backup); + BackupRepository backupRepository = getBackupRepository(backup); VolumeVO restoredVolume = new VolumeVO(Volume.Type.DATADISK, null, backup.getZoneId(), backup.getDomainId(), backup.getAccountId(), 0, null, @@@ -380,17 -298,12 +395,17 @@@ restoredVolume.setUuid(volumeUUID); restoredVolume.setRemoved(null); restoredVolume.setDisplayVolume(true); - restoredVolume.setPoolId(dataStore.getPoolId()); + restoredVolume.setPoolId(pool.getId()); + restoredVolume.setPoolType(pool.getPoolType()); restoredVolume.setPath(restoredVolume.getUuid()); restoredVolume.setState(Volume.State.Copying); - restoredVolume.setSize(backupVolumeInfo.getSize()); - restoredVolume.setFormat(Storage.ImageFormat.QCOW2); + restoredVolume.setSize(backedUpVolumeSize); - restoredVolume.setDiskOfferingId(volume.getDiskOfferingId()); + restoredVolume.setDiskOfferingId(diskOffering.getId()); + if (pool.getPoolType() != Storage.StoragePoolType.RBD) { + restoredVolume.setFormat(Storage.ImageFormat.QCOW2); + } else { + restoredVolume.setFormat(Storage.ImageFormat.RAW); + } RestoreBackupCommand restoreCommand = new RestoreBackupCommand(); restoreCommand.setBackupPath(backup.getExternalId()); @@@ -404,10 -316,8 +419,10 @@@ restoreCommand.setMountOptions(backupRepository.getMountOptions()); restoreCommand.setVmExists(null); restoreCommand.setVmState(vmNameAndState.second()); - restoreCommand.setRestoreVolumeUUID(backupVolumeInfo.getUuid()); + restoreCommand.setMountTimeout(NASBackupRestoreMountTimeout.value()); ++ restoreCommand.setBackupFiles(Collections.singletonList(matchingVolume.getPath())); - BackupAnswer answer = null; + BackupAnswer answer; try { answer = (BackupAnswer) agentManager.send(hostVO.getId(), restoreCommand); } catch (AgentUnavailableException e) { diff --cc plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java index fd94013dd50,47b903c47a7..714e3844b34 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java @@@ -69,78 -57,68 +69,85 @@@ public class LibvirtRestoreBackupComman String mountOptions = command.getMountOptions(); Boolean vmExists = command.isVmExists(); String diskType = command.getDiskType(); - List<String> volumePaths = command.getVolumePaths(); + List<String> backedVolumeUUIDs = command.getBackupVolumesUUIDs(); + List<PrimaryDataStoreTO> restoreVolumePools = command.getRestoreVolumePools(); + List<String> restoreVolumePaths = command.getRestoreVolumePaths(); - String restoreVolumeUuid = command.getRestoreVolumeUUID(); + Integer mountTimeout = command.getMountTimeout() * 1000; + int timeout = command.getWait(); + KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr(); + List<String> backupFiles = command.getBackupFiles(); String newVolumeId = null; try { + String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions, mountTimeout); if (Objects.isNull(vmExists)) { - String volumePath = volumePaths.get(0); + PrimaryDataStoreTO volumePool = restoreVolumePools.get(0); + String volumePath = restoreVolumePaths.get(0); + String backupFile = backupFiles.get(0); int lastIndex = volumePath.lastIndexOf("/"); newVolumeId = volumePath.substring(lastIndex + 1); - restoreVolume(storagePoolMgr, backupPath, volumePool, volumePath, diskType, restoreVolumeUuid, - restoreVolume(backupPath, backupRepoType, backupRepoAddress, volumePath, diskType, backupFile, - new Pair<>(vmName, command.getVmState()), mountOptions); ++ restoreVolume(storagePoolMgr, backupPath, volumePool, volumePath, diskType, backupFile, + new Pair<>(vmName, command.getVmState()), mountDirectory, timeout); } else if (Boolean.TRUE.equals(vmExists)) { - restoreVolumesOfExistingVM(storagePoolMgr, restoreVolumePools, restoreVolumePaths, backedVolumeUUIDs, backupPath, mountDirectory, timeout); - restoreVolumesOfExistingVM(volumePaths, backupPath, backupFiles, backupRepoType, backupRepoAddress, mountOptions); ++ restoreVolumesOfExistingVM(storagePoolMgr, restoreVolumePools, restoreVolumePaths, backedVolumeUUIDs, backupPath, backupFiles, mountDirectory, timeout); } else { - restoreVolumesOfDestroyedVMs(storagePoolMgr, restoreVolumePools, restoreVolumePaths, vmName, backupPath, mountDirectory, timeout); - restoreVolumesOfDestroyedVMs(volumePaths, vmName, backupPath, backupFiles, backupRepoType, backupRepoAddress, mountOptions); ++ restoreVolumesOfDestroyedVMs(storagePoolMgr, restoreVolumePools, restoreVolumePaths, backupPath, backupFiles, mountDirectory, timeout); } } catch (CloudRuntimeException e) { - String errorMessage = "Failed to restore backup for VM: " + vmName + "."; - if (e.getMessage() != null && !e.getMessage().isEmpty()) { - errorMessage += " Details: " + e.getMessage(); - } - logger.error(errorMessage); + String errorMessage = e.getMessage() != null ? e.getMessage() : ""; return new BackupAnswer(command, false, errorMessage); } return new BackupAnswer(command, true, newVolumeId); } - private void restoreVolumesOfExistingVM(List<String> volumePaths, String backupPath, List<String> backupFiles, - String backupRepoType, String backupRepoAddress, String mountOptions) { + 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<PrimaryDataStoreTO> restoreVolumePools, List<String> restoreVolumePaths, List<String> backedVolumesUUIDs, - String backupPath, String mountDirectory, int timeout) { ++ private void restoreVolumesOfExistingVM(KVMStoragePoolManager storagePoolMgr, List<PrimaryDataStoreTO> restoreVolumePools, ++ List<String> restoreVolumePaths, List<String> backedVolumesUUIDs, ++ String backupPath, List<String> backupFiles, String mountDirectory, int timeout) { String diskType = "root"; - String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); try { - for (int idx = 0; idx < volumePaths.size(); idx++) { - String volumePath = volumePaths.get(idx); + for (int idx = 0; idx < restoreVolumePaths.size(); idx++) { + PrimaryDataStoreTO restoreVolumePool = restoreVolumePools.get(idx); + String restoreVolumePath = restoreVolumePaths.get(idx); + String backupFile = backupFiles.get(idx); - String bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType); + String backupVolumeUuid = backedVolumesUUIDs.get(idx); - Pair<String, String> bkpPathAndVolUuid = getBackupPath(mountDirectory, null, backupPath, diskType, backupVolumeUuid); ++ String fullPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType); 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())); - if (!replaceVolumeWithBackup(volumePath, bkpPath)) { - throw new CloudRuntimeException(String.format("Unable to restore backup from volume [%s].", volumePath)); ++ ++ verifyBackupFile(fullPath, backupVolumeUuid); ++ if (!replaceVolumeWithBackup(storagePoolMgr, restoreVolumePool, restoreVolumePath, fullPath, timeout)) { ++ throw new CloudRuntimeException(String.format("Unable to restore contents from the backup volume [%s].", backupVolumeUuid)); } } } finally { unmountBackupDirectory(mountDirectory); deleteTemporaryDirectory(mountDirectory); } - } - private void restoreVolumesOfDestroyedVMs(KVMStoragePoolManager storagePoolMgr, List<PrimaryDataStoreTO> volumePools, List<String> volumePaths, String vmName, String backupPath, String mountDirectory, int timeout) { - private void restoreVolumesOfDestroyedVMs(List<String> volumePaths, String vmName, String backupPath, List<String> backupFiles, - String backupRepoType, String backupRepoAddress, String mountOptions) { - String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); ++ private void restoreVolumesOfDestroyedVMs(KVMStoragePoolManager storagePoolMgr, List<PrimaryDataStoreTO> volumePools, ++ List<String> volumePaths, String backupPath, List<String> backupFiles, String mountDirectory, int timeout) { String diskType = "root"; try { - for (int idx = 0; idx < volumePaths.size(); idx++) { - String volumePath = volumePaths.get(idx); - String backupFile = backupFiles.get(idx); + for (int i = 0; i < volumePaths.size(); i++) { + PrimaryDataStoreTO volumePool = volumePools.get(i); + String volumePath = volumePaths.get(i); - Pair<String, String> bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, null); ++ String backupFile = backupFiles.get(i); + String bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType); ++ String volumeUuid = volumePath.substring(volumePath.lastIndexOf(File.separator) + 1); 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())); - if (!replaceVolumeWithBackup(volumePath, bkpPath)) { - throw new CloudRuntimeException(String.format("Unable to restore backup from volume [%s].", volumePath)); ++ verifyBackupFile(bkpPath, volumeUuid); ++ if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, bkpPath, timeout)) { ++ throw new CloudRuntimeException(String.format("Unable to restore contents from the backup volume [%s].", volumeUuid)); } } } finally { @@@ -149,17 -127,17 +156,20 @@@ } } - private void restoreVolume(KVMStoragePoolManager storagePoolMgr, String backupPath, PrimaryDataStoreTO volumePool, String volumePath, String diskType, String volumeUUID, - private void restoreVolume(String backupPath, String backupRepoType, String backupRepoAddress, String volumePath, - String diskType, String backupFile, Pair<String, VirtualMachine.State> vmNameAndState, String mountOptions) { - String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); ++ private void restoreVolume(KVMStoragePoolManager storagePoolMgr, String backupPath, PrimaryDataStoreTO volumePool, String volumePath, String diskType, String backupFile, + Pair<String, VirtualMachine.State> vmNameAndState, String mountDirectory, int timeout) { - Pair<String, String> bkpPathAndVolUuid; + String bkpPath; ++ String volumeUuid; try { - bkpPathAndVolUuid = getBackupPath(mountDirectory, 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())); + bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType); - if (!replaceVolumeWithBackup(volumePath, bkpPath)) { - throw new CloudRuntimeException(String.format("Unable to restore backup from volume [%s].", volumePath)); ++ volumeUuid = volumePath.substring(volumePath.lastIndexOf(File.separator) + 1); ++ verifyBackupFile(bkpPath, volumeUuid); ++ if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, bkpPath, timeout, true)) { ++ throw new CloudRuntimeException(String.format("Unable to restore contents from the backup volume [%s].", volumeUuid)); ++ } if (VirtualMachine.State.Running.equals(vmNameAndState.second())) { - if (!attachVolumeToVm(vmNameAndState.first(), volumePath)) { + if (!attachVolumeToVm(storagePoolMgr, vmNameAndState.first(), volumePool, volumePath)) { throw new CloudRuntimeException(String.format("Failed to attach volume to VM: %s", vmNameAndState.first())); } } @@@ -170,43 -150,35 +180,43 @@@ } - private String mountBackupDirectory(String backupRepoAddress, String backupRepoType, String mountOptions) { + private String mountBackupDirectory(String backupRepoAddress, String backupRepoType, String mountOptions, Integer mountTimeout) { String randomChars = RandomStringUtils.random(5, true, false); String mountDirectory = String.format("%s.%s",BACKUP_TEMP_FILE_PREFIX , randomChars); + try { mountDirectory = Files.createTempDirectory(mountDirectory).toString(); - String mount = String.format(MOUNT_COMMAND, backupRepoType, backupRepoAddress, mountDirectory); - if ("cifs".equals(backupRepoType)) { - if (Objects.isNull(mountOptions) || mountOptions.trim().isEmpty()) { - mountOptions = "nobrl"; - } else { - mountOptions += ",nobrl"; - } - } - if (Objects.nonNull(mountOptions) && !mountOptions.trim().isEmpty()) { - mount += " -o " + mountOptions; + } catch (IOException e) { - logger.error(String.format("Failed to create the tmp mount directory {} for restore", mountDirectory), e); ++ logger.error("Failed to create the tmp mount directory {} for restore", mountDirectory, e); + throw new CloudRuntimeException("Failed to create the tmp mount directory for restore on the KVM host"); + } + + String mount = String.format(MOUNT_COMMAND, backupRepoType, backupRepoAddress, mountDirectory); + if ("cifs".equals(backupRepoType)) { + if (Objects.isNull(mountOptions) || mountOptions.trim().isEmpty()) { + mountOptions = "nobrl"; + } else { + mountOptions += ",nobrl"; } - Script.runSimpleBashScript(mount); - } catch (Exception e) { - throw new CloudRuntimeException(String.format("Failed to mount %s to %s", backupRepoType, backupRepoAddress), e); + } + if (Objects.nonNull(mountOptions) && !mountOptions.trim().isEmpty()) { + mount += " -o " + mountOptions; + } + + int exitValue = Script.runSimpleBashScriptForExitValue(mount, mountTimeout, false); + if (exitValue != 0) { - logger.error(String.format("Failed to mount repository {} of type {} to the directory {}", backupRepoAddress, backupRepoType, mountDirectory)); ++ logger.error("Failed to mount repository {} of type {} to the directory {}", backupRepoAddress, backupRepoType, mountDirectory); + throw new CloudRuntimeException("Failed to mount the backup repository on the KVM host"); } return mountDirectory; } private void unmountBackupDirectory(String backupDirectory) { - try { - String umountCmd = String.format(UMOUNT_COMMAND, backupDirectory); - Script.runSimpleBashScript(umountCmd); - } catch (Exception e) { - throw new CloudRuntimeException(String.format("Failed to unmount backup directory: %s", backupDirectory), e); + String umountCmd = String.format(UMOUNT_COMMAND, backupDirectory); + int exitValue = Script.runSimpleBashScriptForExitValue(umountCmd); + if (exitValue != 0) { - logger.error(String.format("Failed to unmount backup directory {}", backupDirectory)); ++ logger.error("Failed to unmount backup directory {}", backupDirectory); + throw new CloudRuntimeException("Failed to unmount the backup directory"); } } @@@ -214,26 -186,19 +224,25 @@@ try { Files.deleteIfExists(Paths.get(backupDirectory)); } catch (IOException e) { - logger.error(String.format("Failed to delete backup directory: %s", backupDirectory), e); - throw new CloudRuntimeException(String.format("Failed to delete backup directory: %s", backupDirectory), e); ++ logger.error("Failed to delete backup directory: {}}", backupDirectory, e); + throw new CloudRuntimeException("Failed to delete the backup directory"); } } - private Pair<String, String> getBackupPath(String mountDirectory, String volumePath, String backupPath, String diskType, String volumeUuid) { + private String getBackupPath(String mountDirectory, String backupPath, String backupFile, String diskType) { String bkpPath = String.format(FILE_PATH_PLACEHOLDER, mountDirectory, backupPath); - 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); + String backupFileName = String.format("%s.%s.qcow2", diskType.toLowerCase(Locale.ROOT), backupFile); bkpPath = String.format(FILE_PATH_PLACEHOLDER, bkpPath, backupFileName); - return new Pair<>(bkpPath, volUuid); + return bkpPath; } - private boolean replaceVolumeWithBackup(String volumePath, String backupPath) { - int exitValue = Script.runSimpleBashScriptForExitValue(String.format(RSYNC_COMMAND, backupPath, volumePath)); + 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; } diff --cc plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java index 7bcd0bf18e6,00000000000..d72dc0d8ac3 mode 100644,000000..100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java @@@ -1,526 -1,0 +1,529 @@@ +// 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.utils.script.Script; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.backup.BackupAnswer; +import org.apache.cloudstack.backup.RestoreBackupCommand; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LibvirtRestoreBackupCommandWrapperTest { + + private LibvirtRestoreBackupCommandWrapper wrapper; + private LibvirtComputingResource libvirtComputingResource; + private RestoreBackupCommand command; + + @Before + public void setUp() { + wrapper = new LibvirtRestoreBackupCommandWrapper(); + libvirtComputingResource = Mockito.mock(LibvirtComputingResource.class); + command = Mockito.mock(RestoreBackupCommand.class); + } + + @Test + public void testExecuteWithVmExistsNull() throws Exception { + when(command.getVmName()).thenReturn("test-vm"); + when(command.getBackupPath()).thenReturn("backup/path"); + when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + when(command.getBackupRepoType()).thenReturn("nfs"); + when(command.getMountOptions()).thenReturn("rw"); + when(command.isVmExists()).thenReturn(null); + when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); - when(command.getRestoreVolumeUUID()).thenReturn("volume-123"); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + when(command.getVmState()).thenReturn(VirtualMachine.State.Running); + when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(0); // Mount success + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString())) + .thenReturn(0); // Other commands success + scriptMock.when(() -> Script.runSimpleBashScript(anyString())) + .thenReturn("vda"); // Current device + + filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertTrue(backupAnswer.getResult()); + Assert.assertEquals("volume-123", backupAnswer.getDetails()); + } + } + } + + @Test + public void testExecuteWithVmExistsTrue() throws Exception { + when(command.getVmName()).thenReturn("test-vm"); + when(command.getBackupPath()).thenReturn("backup/path"); + when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + when(command.getBackupRepoType()).thenReturn("nfs"); + when(command.getMountOptions()).thenReturn("rw"); + when(command.isVmExists()).thenReturn(true); + when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); + when(command.getBackupVolumesUUIDs()).thenReturn(Arrays.asList("volume-123")); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(0); // Mount success + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString())) + .thenReturn(0); // Other commands success + + filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertTrue(backupAnswer.getResult()); + } + } + } + + @Test + public void testExecuteWithVmExistsFalse() throws Exception { + when(command.getVmName()).thenReturn("test-vm"); + when(command.getBackupPath()).thenReturn("backup/path"); + when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + when(command.getBackupRepoType()).thenReturn("nfs"); + when(command.getMountOptions()).thenReturn("rw"); + when(command.isVmExists()).thenReturn(false); + when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(0); // Mount success + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString())) + .thenReturn(0); // Other commands success + + filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertTrue(backupAnswer.getResult()); + } + } + } + + @Test + public void testExecuteWithCifsMountType() throws Exception { + when(command.getVmName()).thenReturn("test-vm"); + when(command.getBackupPath()).thenReturn("backup/path"); + when(command.getBackupRepoAddress()).thenReturn("//192.168.1.100/backup"); + when(command.getBackupRepoType()).thenReturn("cifs"); + when(command.getMountOptions()).thenReturn("username=user,password=pass"); + when(command.isVmExists()).thenReturn(null); + when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); - when(command.getRestoreVolumeUUID()).thenReturn("volume-123"); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + when(command.getVmState()).thenReturn(VirtualMachine.State.Running); + when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(0); // Mount success + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString())) + .thenReturn(0); // Other commands success + scriptMock.when(() -> Script.runSimpleBashScript(anyString())) + .thenReturn("vda"); // Current device + + filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertTrue(backupAnswer.getResult()); + } + } + } + + @Test + public void testExecuteWithMountFailure() throws Exception { + lenient().when(command.getVmName()).thenReturn("test-vm"); + lenient().when(command.getBackupPath()).thenReturn("backup/path"); + lenient().when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + lenient().when(command.getBackupRepoType()).thenReturn("nfs"); + lenient().when(command.getMountOptions()).thenReturn("rw"); + lenient().when(command.isVmExists()).thenReturn(null); + lenient().when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + lenient().when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); - lenient().when(command.getRestoreVolumeUUID()).thenReturn("volume-123"); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + lenient().when(command.getVmState()).thenReturn(VirtualMachine.State.Running); + lenient().when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(1); // Mount failure + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertFalse(backupAnswer.getResult()); + Assert.assertTrue(backupAnswer.getDetails().contains("Failed to mount the backup repository")); + } + } + } + + @Test + public void testExecuteWithBackupFileNotFound() throws Exception { + when(command.getVmName()).thenReturn("test-vm"); + when(command.getBackupPath()).thenReturn("backup/path"); + when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + when(command.getBackupRepoType()).thenReturn("nfs"); + when(command.getMountOptions()).thenReturn("rw"); + when(command.isVmExists()).thenReturn(null); + when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); - when(command.getRestoreVolumeUUID()).thenReturn("volume-123"); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + when(command.getVmState()).thenReturn(VirtualMachine.State.Running); + when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(0); // Mount success + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString())) + .thenAnswer(invocation -> { + String command = invocation.getArgument(0); + if (command.contains("ls ")) { + return 1; // File not found + } + return 0; // Other commands success + }); + + filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertFalse(backupAnswer.getResult()); + Assert.assertTrue(backupAnswer.getDetails().contains("Backup file for the volume [volume-123] does not exist")); + } + } + } + + @Test + public void testExecuteWithCorruptBackupFile() throws Exception { + when(command.getVmName()).thenReturn("test-vm"); + when(command.getBackupPath()).thenReturn("backup/path"); + when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + when(command.getBackupRepoType()).thenReturn("nfs"); + when(command.getMountOptions()).thenReturn("rw"); + when(command.isVmExists()).thenReturn(null); + when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); - when(command.getRestoreVolumeUUID()).thenReturn("volume-123"); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + when(command.getVmState()).thenReturn(VirtualMachine.State.Running); + when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(0); // Mount success + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString())) + .thenAnswer(invocation -> { + String command = invocation.getArgument(0); + if (command.contains("ls ")) { + return 0; // File exists + } else if (command.contains("qemu-img check")) { + return 1; // Corrupt file + } + return 0; // Other commands success + }); + + filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertFalse(backupAnswer.getResult()); + Assert.assertTrue(backupAnswer.getDetails().contains("Backup qcow2 file for the volume [volume-123] is corrupt")); + } + } + } + + @Test + public void testExecuteWithRsyncFailure() throws Exception { + when(command.getVmName()).thenReturn("test-vm"); + when(command.getBackupPath()).thenReturn("backup/path"); + when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + when(command.getBackupRepoType()).thenReturn("nfs"); + when(command.getMountOptions()).thenReturn("rw"); + when(command.isVmExists()).thenReturn(null); + when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); - when(command.getRestoreVolumeUUID()).thenReturn("volume-123"); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + when(command.getVmState()).thenReturn(VirtualMachine.State.Running); + when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(0); // Mount success + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString())) + .thenAnswer(invocation -> { + String command = invocation.getArgument(0); + if (command.contains("ls ")) { + return 0; // File exists + } else if (command.contains("qemu-img check")) { + return 0; // File is valid + } else if (command.contains("rsync")) { + return 1; // Rsync failure + } + return 0; // Other commands success + }); + + filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertFalse(backupAnswer.getResult()); + Assert.assertTrue(backupAnswer.getDetails().contains("Unable to restore contents from the backup volume [volume-123]")); + } + } + } + + @Test + public void testExecuteWithAttachVolumeFailure() throws Exception { + when(command.getVmName()).thenReturn("test-vm"); + when(command.getBackupPath()).thenReturn("backup/path"); + when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + when(command.getBackupRepoType()).thenReturn("nfs"); + when(command.getMountOptions()).thenReturn("rw"); + when(command.isVmExists()).thenReturn(null); + when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); - when(command.getRestoreVolumeUUID()).thenReturn("volume-123"); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + when(command.getVmState()).thenReturn(VirtualMachine.State.Running); + when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(0); // Mount success + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString())) + .thenAnswer(invocation -> { + String command = invocation.getArgument(0); + if (command.contains("ls ")) { + return 0; // File exists + } else if (command.contains("qemu-img check")) { + return 0; // File is valid + } else if (command.contains("rsync")) { + return 0; // Rsync success + } else if (command.contains("virsh attach-disk")) { + return 1; // Attach failure + } + return 0; // Other commands success + }); + scriptMock.when(() -> Script.runSimpleBashScript(anyString())) + .thenReturn("vda"); // Current device + + filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertFalse(backupAnswer.getResult()); + Assert.assertTrue(backupAnswer.getDetails().contains("Failed to attach volume to VM: test-vm")); + } + } + } + + @Test + public void testExecuteWithTempDirectoryCreationFailure() throws Exception { + lenient().when(command.getVmName()).thenReturn("test-vm"); + lenient().when(command.getBackupPath()).thenReturn("backup/path"); + lenient().when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + lenient().when(command.getBackupRepoType()).thenReturn("nfs"); + lenient().when(command.getMountOptions()).thenReturn("rw"); + lenient().when(command.isVmExists()).thenReturn(null); + lenient().when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore)); + lenient().when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123")); - lenient().when(command.getRestoreVolumeUUID()).thenReturn("volume-123"); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123")); + lenient().when(command.getVmState()).thenReturn(VirtualMachine.State.Running); + lenient().when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + filesMock.when(() -> Files.createTempDirectory(anyString())) + .thenThrow(new IOException("Failed to create temp directory")); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertFalse(backupAnswer.getResult()); + Assert.assertTrue(backupAnswer.getDetails().contains("Failed to create the tmp mount directory for restore")); + } + } + + @Test + public void testExecuteWithMultipleVolumes() throws Exception { + when(command.getVmName()).thenReturn("test-vm"); + when(command.getBackupPath()).thenReturn("backup/path"); + when(command.getBackupRepoAddress()).thenReturn("192.168.1.100:/backup"); + when(command.getBackupRepoType()).thenReturn("nfs"); + when(command.getMountOptions()).thenReturn("rw"); + when(command.isVmExists()).thenReturn(true); + when(command.getDiskType()).thenReturn("root"); + PrimaryDataStoreTO primaryDataStore1 = Mockito.mock(PrimaryDataStoreTO.class); + PrimaryDataStoreTO primaryDataStore2 = Mockito.mock(PrimaryDataStoreTO.class); + when(command.getRestoreVolumePools()).thenReturn(Arrays.asList( + primaryDataStore1, + primaryDataStore2 + )); + when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList( + "/var/lib/libvirt/images/volume-123", + "/var/lib/libvirt/images/volume-456" + )); + when(command.getBackupVolumesUUIDs()).thenReturn(Arrays.asList("volume-123", "volume-456")); ++ when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123", "volume-456")); + when(command.getMountTimeout()).thenReturn(30); + + try (MockedStatic<Files> filesMock = mockStatic(Files.class)) { + Path tempPath = Mockito.mock(Path.class); + when(tempPath.toString()).thenReturn("/tmp/csbackup.abc123"); + filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); + + try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) { + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class))) + .thenReturn(0); // Mount success + scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString())) + .thenReturn(0); // All other commands success + + filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true); + + Answer result = wrapper.execute(command, libvirtComputingResource); + + Assert.assertNotNull(result); + Assert.assertTrue(result instanceof BackupAnswer); + BackupAnswer backupAnswer = (BackupAnswer) result; + Assert.assertTrue(backupAnswer.getResult()); + } + } + } +} diff --cc services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManagerImpl.java index ae02d7e8aa7,e98791822d0..aacca9926f7 --- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManagerImpl.java +++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManagerImpl.java @@@ -32,11 -33,10 +34,11 @@@ import java.util.concurrent.Executors import javax.naming.ConfigurationException; - import com.cloud.agent.api.Answer; - - import com.cloud.agent.api.ConvertSnapshotCommand; import org.apache.cloudstack.storage.resource.SecondaryStorageResource; + import org.apache.commons.lang3.StringUtils; + import com.cloud.agent.api.Answer; ++import com.cloud.agent.api.ConvertSnapshotCommand; import com.cloud.agent.api.storage.CreateEntityDownloadURLAnswer; import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand; import com.cloud.agent.api.storage.DeleteEntityDownloadURLCommand;
