2929
3030import org .apache .cloudstack .backup .dao .BackupDao ;
3131import org .apache .cloudstack .backup .veeam .VeeamClient ;
32+ import org .apache .cloudstack .backup .veeam .VeeamClientBase ;
33+ import org .apache .cloudstack .backup .veeam .VeeamClientV2 ;
3234import org .apache .cloudstack .backup .veeam .api .Job ;
3335import org .apache .cloudstack .framework .config .ConfigKey ;
3436import 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