Skip to content

Commit 226ce1d

Browse files
committed
Veeam: support v13 with oAuth2 and JSON response
1 parent 71f47d6 commit 226ce1d

File tree

11 files changed

+1265
-76
lines changed

11 files changed

+1265
-76
lines changed

api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public interface BackupProvider {
9797
/**
9898
* Restore a volume from a backup
9999
*/
100-
Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, Pair<String, VirtualMachine.State> vmNameAndState);
100+
Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, VirtualMachine vm);
101101

102102
/**
103103
* Syncs backup metrics (backup size, protected size) from the plugin and stores it within the provider

plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) {
9696
}
9797

9898
@Override
99-
public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, Pair<String, VirtualMachine.State> vmNameAndState) {
99+
public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, VirtualMachine vm) {
100100
final VolumeVO volume = volumeDao.findByUuid(backupVolumeInfo.getUuid());
101101
final StoragePoolHostVO dataStore = storagePoolHostDao.findByUuid(dataStoreUuid);
102102
final DiskOffering diskOffering = diskOfferingDao.findByUuid(backupVolumeInfo.getDiskOfferingId());

plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ private String getVolumePathPrefix(StoragePoolVO storagePool) {
360360
}
361361

362362
@Override
363-
public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, Pair<String, VirtualMachine.State> vmNameAndState) {
363+
public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, VirtualMachine vm) {
364364
final VolumeVO volume = volumeDao.findByUuid(backupVolumeInfo.getUuid());
365365
final DiskOffering diskOffering = diskOfferingDao.findByUuid(backupVolumeInfo.getDiskOfferingId());
366366
final StoragePoolVO pool = primaryDataStoreDao.findByUuid(dataStoreUuid);
@@ -396,14 +396,14 @@ public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeI
396396
restoreCommand.setBackupPath(backup.getExternalId());
397397
restoreCommand.setBackupRepoType(backupRepository.getType());
398398
restoreCommand.setBackupRepoAddress(backupRepository.getAddress());
399-
restoreCommand.setVmName(vmNameAndState.first());
399+
restoreCommand.setVmName(vm.getName());
400400
restoreCommand.setRestoreVolumePaths(Collections.singletonList(String.format("%s/%s", getVolumePathPrefix(pool), volumeUUID)));
401401
DataStore dataStore = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
402402
restoreCommand.setRestoreVolumePools(Collections.singletonList(dataStore != null ? (PrimaryDataStoreTO)dataStore.getTO() : null));
403403
restoreCommand.setDiskType(backupVolumeInfo.getType().name().toLowerCase(Locale.ROOT));
404404
restoreCommand.setMountOptions(backupRepository.getMountOptions());
405405
restoreCommand.setVmExists(null);
406-
restoreCommand.setVmState(vmNameAndState.second());
406+
restoreCommand.setVmState(vm.getState());
407407
restoreCommand.setRestoreVolumeUUID(backupVolumeInfo.getUuid());
408408
restoreCommand.setMountTimeout(NASBackupRestoreMountTimeout.value());
409409

plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) {
394394
}
395395

396396
@Override
397-
public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, Pair<String, VirtualMachine.State> vmNameAndState) {
397+
public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, VirtualMachine vm) {
398398
String networkerServer;
399399
VolumeVO volume = volumeDao.findByUuid(backupVolumeInfo.getUuid());
400400
final DiskOffering diskOffering = diskOfferingDao.findByUuid(backupVolumeInfo.getDiskOfferingId());

plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java

Lines changed: 97 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
import org.apache.cloudstack.backup.dao.BackupDao;
3131
import org.apache.cloudstack.backup.veeam.VeeamClient;
32+
import org.apache.cloudstack.backup.veeam.VeeamClientBase;
33+
import org.apache.cloudstack.backup.veeam.VeeamClientV2;
3234
import org.apache.cloudstack.backup.veeam.api.Job;
3335
import org.apache.cloudstack.framework.config.ConfigKey;
3436
import org.apache.cloudstack.framework.config.Configurable;
@@ -87,6 +89,10 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
8789
private static ConfigKey<Integer> VeeamTaskPollMaxRetry = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.task.poll.max.retry", "120",
8890
"The max number of retrying times when the management server polls for Veeam task status.", true, ConfigKey.Scope.Zone);
8991

92+
private static ConfigKey<String> VeeamKvmHierarchyRef = new ConfigKey<>("Advanced", String.class, "backup.plugin.veeam.kvm.hierarchy.ref", "",
93+
"The hierarchy reference name for KVM VMs in Veeam. This is typically the CloudStack Management Server IP/hostname " +
94+
"registered in Veeam B&R as the managed server for KVM backups.", true, ConfigKey.Scope.Zone);
95+
9096
@Inject
9197
private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao;
9298
@Inject
@@ -106,11 +112,21 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
106112

107113
private Map<String, Backup.Metric> backupFilesMetricsMap = new HashMap<>();
108114

109-
protected VeeamClient getClient(final Long zoneId) {
115+
protected VeeamClientBase getClient(final Long zoneId) {
110116
try {
111-
return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamVersion.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId),
112-
VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId),
113-
VeeamTaskPollInterval.valueIn(zoneId), VeeamTaskPollMaxRetry.valueIn(zoneId));
117+
Integer version = VeeamVersion.valueIn(zoneId);
118+
// Use VeeamClientV2 for version 13 and above, VeeamClient for older versions
119+
if (version != null && version >= 13) {
120+
logger.debug("Creating VeeamClientV2 for Veeam version " + version);
121+
return new VeeamClientV2(VeeamUrl.valueIn(zoneId), version, VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId),
122+
VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId),
123+
VeeamTaskPollInterval.valueIn(zoneId), VeeamTaskPollMaxRetry.valueIn(zoneId));
124+
} else {
125+
logger.debug("Creating legacy VeeamClient for Veeam version " + (version != null ? version : "auto-detect"));
126+
return new VeeamClient(VeeamUrl.valueIn(zoneId), version, VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId),
127+
VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId),
128+
VeeamTaskPollInterval.valueIn(zoneId), VeeamTaskPollMaxRetry.valueIn(zoneId));
129+
}
114130
} catch (URISyntaxException e) {
115131
throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage());
116132
} catch (NoSuchAlgorithmException | KeyManagementException e) {
@@ -145,7 +161,7 @@ public boolean isValidProviderOffering(final Long zoneId, final String uuid) {
145161

146162
private VmwareDatacenter findVmwareDatacenterForVM(final VirtualMachine vm) {
147163
if (vm == null || vm.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
148-
throw new CloudRuntimeException("The Veeam backup provider is only applicable for VMware VMs");
164+
return null;
149165
}
150166
final VmwareDatacenterZoneMap zoneMap = vmwareDatacenterZoneMapDao.findByZoneId(vm.getDataCenterId());
151167
if (zoneMap == null) {
@@ -158,46 +174,92 @@ private VmwareDatacenter findVmwareDatacenterForVM(final VirtualMachine vm) {
158174
return vmwareDatacenter;
159175
}
160176

177+
private String getHierarchyReferenceForVM(final VirtualMachine vm) {
178+
if (vm == null) {
179+
throw new CloudRuntimeException("VM cannot be null");
180+
}
181+
if (vm.getHypervisorType() == Hypervisor.HypervisorType.VMware) {
182+
final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm);
183+
return vmwareDC.getVcenterHost();
184+
} else if (vm.getHypervisorType() == Hypervisor.HypervisorType.KVM) {
185+
// KVM is only supported with Veeam 13+
186+
Integer veeamVersion = VeeamVersion.valueIn(vm.getDataCenterId());
187+
if (veeamVersion == null || veeamVersion < 13) {
188+
throw new CloudRuntimeException("KVM VM backup is only supported with Veeam 13 or later. " +
189+
"Please upgrade to Veeam 13+ or set backup.plugin.veeam.version=13");
190+
}
191+
192+
String kvmHierarchyRef = VeeamKvmHierarchyRef.valueIn(vm.getDataCenterId());
193+
if (kvmHierarchyRef == null || kvmHierarchyRef.isEmpty()) {
194+
throw new CloudRuntimeException("Veeam KVM hierarchy reference is not configured for zone: " + vm.getDataCenterId() +
195+
". Please configure 'backup.plugin.veeam.kvm.hierarchy.ref' with the CloudStack Management Server " +
196+
"IP/hostname registered in Veeam B&R.");
197+
}
198+
return kvmHierarchyRef;
199+
}
200+
throw new CloudRuntimeException("The Veeam backup provider is only applicable for VMware and KVM VMs");
201+
}
202+
203+
private void validateSupportedHypervisor(final VirtualMachine vm) {
204+
if (vm == null) {
205+
throw new CloudRuntimeException("VM cannot be null");
206+
}
207+
208+
// KVM is only supported with Veeam 13+
209+
if (vm.getHypervisorType() == Hypervisor.HypervisorType.KVM) {
210+
Integer veeamVersion = VeeamVersion.valueIn(vm.getDataCenterId());
211+
if (veeamVersion == null || veeamVersion < 13) {
212+
throw new CloudRuntimeException("KVM VM backup is only supported with Veeam 13 or later. " +
213+
"Current Veeam version: " + (veeamVersion != null ? veeamVersion : "not set") + ". " +
214+
"Please upgrade to Veeam 13+ to backup KVM VMs.");
215+
}
216+
}
217+
218+
if (vm.getHypervisorType() != Hypervisor.HypervisorType.VMware &&
219+
vm.getHypervisorType() != Hypervisor.HypervisorType.KVM) {
220+
throw new CloudRuntimeException("The Veeam backup provider is only applicable for VMware and KVM VMs");
221+
}
222+
}
223+
161224
private String getGuestBackupName(final String instanceName, final String uuid) {
162225
return String.format("%s%s%s", instanceName, BACKUP_IDENTIFIER, uuid);
163226
}
164227

165228
@Override
166229
public boolean assignVMToBackupOffering(final VirtualMachine vm, final BackupOffering backupOffering) {
167-
final VeeamClient client = getClient(vm.getDataCenterId());
230+
validateSupportedHypervisor(vm);
231+
final VeeamClientBase client = getClient(vm.getDataCenterId());
168232
final Job parentJob = client.listJob(backupOffering.getExternalId());
169233
final String clonedJobName = getGuestBackupName(vm.getInstanceName(), vm.getUuid());
170234

171-
if (!client.cloneVeeamJob(parentJob, clonedJobName)) {
235+
BackupOffering clonedVeeamJob = client.cloneVeeamJob(parentJob, clonedJobName);
236+
if (clonedVeeamJob == null) {
172237
logger.error("Failed to clone pre-defined Veeam job (backup offering) for backup offering [id: {}, name: {}] but will check the list of jobs again if it was eventually succeeded.", backupOffering.getExternalId(), backupOffering.getName());
173238
}
174239

175-
for (final BackupOffering job : client.listJobs()) {
176-
if (job.getName().equals(clonedJobName)) {
177-
final Job clonedJob = client.listJob(job.getExternalId());
178-
if (BooleanUtils.isTrue(clonedJob.getScheduleConfigured()) && !clonedJob.getScheduleEnabled()) {
179-
client.toggleJobSchedule(clonedJob.getId());
180-
}
181-
logger.debug("Veeam job (backup offering) for backup offering [id: {}, name: {}] found, now trying to assign the VM to the job.", backupOffering.getExternalId(), backupOffering.getName());
182-
final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm);
183-
if (client.addVMToVeeamJob(job.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) {
184-
((VMInstanceVO) vm).setBackupExternalId(job.getExternalId());
185-
return true;
186-
}
187-
}
240+
final Job clonedJob = client.listJob(clonedVeeamJob.getExternalId());
241+
if (BooleanUtils.isTrue(clonedJob.getScheduleConfigured()) && !clonedJob.getScheduleEnabled()) {
242+
client.toggleJobSchedule(clonedJob.getId());
243+
}
244+
logger.debug("Veeam job (backup offering) for backup offering [id: {}, name: {}] found, now trying to assign the VM to the job.", backupOffering.getExternalId(), backupOffering.getName());
245+
final String hierarchyRef = getHierarchyReferenceForVM(vm);
246+
if (client.addVMToVeeamJob(clonedVeeamJob.getExternalId(), clonedJobName, parentJob.getId(), vm.getInstanceName(), hierarchyRef, vm)) {
247+
((VMInstanceVO) vm).setBackupExternalId(clonedVeeamJob.getExternalId());
248+
return true;
188249
}
189250
return false;
190251
}
191252

192253
@Override
193254
public boolean removeVMFromBackupOffering(final VirtualMachine vm) {
194-
final VeeamClient client = getClient(vm.getDataCenterId());
195-
final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm);
255+
validateSupportedHypervisor(vm);
256+
final VeeamClientBase client = getClient(vm.getDataCenterId());
257+
final String hierarchyRef = getHierarchyReferenceForVM(vm);
196258
if (vm.getBackupExternalId() == null) {
197259
throw new CloudRuntimeException("The VM does not have a backup job assigned.");
198260
}
199261
try {
200-
if (!client.removeVMFromVeeamJob(vm.getBackupExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) {
262+
if (!client.removeVMFromVeeamJob(vm.getBackupExternalId(), vm.getInstanceName(), hierarchyRef)) {
201263
logger.warn("Failed to remove VM from Veeam Job id: " + vm.getBackupExternalId());
202264
}
203265
} catch (Exception e) {
@@ -220,7 +282,7 @@ public boolean willDeleteBackupsOnOfferingRemoval() {
220282

221283
@Override
222284
public Pair<Boolean, Backup> takeBackup(final VirtualMachine vm, Boolean quiesceVM) {
223-
final VeeamClient client = getClient(vm.getDataCenterId());
285+
final VeeamClientBase client = getClient(vm.getDataCenterId());
224286
Boolean result = client.startBackupJob(vm.getBackupExternalId());
225287
return new Pair<>(result, null);
226288
}
@@ -238,7 +300,7 @@ public boolean deleteBackup(Backup backup, boolean forced) {
238300
throw new CloudRuntimeException("Veeam backup provider does not have a safe way to remove a single restore point, which results in all backup chain being removed. "
239301
+ "Use forced:true to skip this verification and remove the complete backup chain.");
240302
}
241-
VeeamClient client = getClient(vm.getDataCenterId());
303+
VeeamClientBase client = getClient(vm.getDataCenterId());
242304
boolean result = client.deleteBackup(backup.getExternalId());
243305
if (BooleanUtils.isFalse(result)) {
244306
return false;
@@ -291,10 +353,11 @@ private void prepareForBackupRestoration(VirtualMachine vm) {
291353
}
292354

293355
@Override
294-
public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, Pair<String, VirtualMachine.State> vmNameAndState) {
356+
public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, VirtualMachine vm) {
357+
final String hierarchyRef = getHierarchyReferenceForVM(vm);
295358
final Long zoneId = backup.getZoneId();
296359
final String restorePointId = backup.getExternalId();
297-
return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, null, hostIp, dataStoreUuid);
360+
return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, null, hostIp, dataStoreUuid, hierarchyRef);
298361
}
299362

300363
@Override
@@ -331,17 +394,19 @@ public Backup createNewBackupEntryForRestorePoint(Backup.RestorePoint restorePoi
331394

332395
@Override
333396
public List<Backup.RestorePoint> listRestorePoints(VirtualMachine vm) {
334-
final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm);
397+
validateSupportedHypervisor(vm);
398+
final String hierarchyRef = getHierarchyReferenceForVM(vm);
335399
String backupName = getGuestBackupName(vm.getInstanceName(), vm.getUuid());
336-
return getClient(vm.getDataCenterId()).listRestorePoints(backupName, vmwareDC.getVcenterHost(), vm.getInstanceName(), backupFilesMetricsMap);
400+
return getClient(vm.getDataCenterId()).listRestorePoints(backupName, hierarchyRef, vm.getInstanceName(), backupFilesMetricsMap);
337401
}
338402

339403
@Override
340404
public Pair<Boolean, String> restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid) {
405+
final String hierarchyRef = getHierarchyReferenceForVM(vm);
341406
final Long zoneId = backup.getZoneId();
342407
final String restorePointId = backup.getExternalId();
343408
final String restoreLocation = vm.getInstanceName();
344-
return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, restoreLocation, hostIp, dataStoreUuid);
409+
return getClient(zoneId).restoreVMToDifferentLocation(restorePointId, restoreLocation, hostIp, dataStoreUuid, hierarchyRef);
345410
}
346411

347412
@Override
@@ -379,7 +444,8 @@ public ConfigKey<?>[] getConfigKeys() {
379444
VeeamApiRequestTimeout,
380445
VeeamRestoreTimeout,
381446
VeeamTaskPollInterval,
382-
VeeamTaskPollMaxRetry
447+
VeeamTaskPollMaxRetry,
448+
VeeamKvmHierarchyRef
383449
};
384450
}
385451

0 commit comments

Comments
 (0)