This is an automated email from the ASF dual-hosted git repository.

weizhou pushed a commit to branch 4.18
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.18 by this push:
     new 33bb92acce2 Veeam: Support Veeam 11 and 12 (#8241)
33bb92acce2 is described below

commit 33bb92acce2dd2f3b8acccc4c0d81fcc37a4714f
Author: Wei Zhou <[email protected]>
AuthorDate: Fri Jan 19 18:42:01 2024 +0100

    Veeam: Support Veeam 11 and 12 (#8241)
    
    This PR fixes several issues in the testing of Veeam 11 and Veeam12
    - Import Veeam.Backup.PowerShell and silently ignore the warning messages
    - Fix issue when assign vm to backup offerings, which caused by separator 
(\r\n)
    - Fix authorization failure in veeam 12a, which is because v1_4 is not 
supported in veeam 12a any more
    - Fix exception if backup name has space
    - Fix backup metrics in veeam12, which is because powershell command does 
not return the values needed
    - Fix Incorrect datetime value, which is because powershell command returns 
a datetime which is not supported in Java
    - Fix issue during backup restoration if VM has both ROOT and DATA disks.
    
    This PR also has the following update
    - Add integration test test/integration/smoke/test_backup_recovery_veeam.py
    - Make some UI changes
    - Add zone setting backup.plugin.veeam.version. If it is not set, 
CloudStack will get veeam version via powershell commands.
    - Add zone setting backup.plugin.veeam.task.poll.interval and 
backup.plugin.veeam.task.poll.max.retry
---
 .../cloudstack/api/command/user/vm/ListVMsCmd.java |   4 +-
 .../backup/PrepareForBackupRestorationCommand.java |  43 +++
 .../schema/src/main/java/com/cloud/vm/NicVO.java   |  12 +
 .../src/main/java/com/cloud/vm/dao/NicDao.java     |   2 +
 .../src/main/java/com/cloud/vm/dao/NicDaoImpl.java |   8 +
 .../org/apache/cloudstack/backup/BackupVO.java     |  14 +
 plugins/backup/veeam/pom.xml                       |  15 +
 .../cloudstack/backup/VeeamBackupProvider.java     |  67 ++++-
 .../cloudstack/backup/veeam/VeeamClient.java       | 285 +++++++++++++++---
 .../cloudstack/backup/veeam/api/BackupFile.java    | 160 ++++++++++
 .../cloudstack/backup/veeam/api/BackupFiles.java   |  39 +++
 .../backup/veeam/api/VmRestorePoint.java           | 149 ++++++++++
 .../backup/veeam/api/VmRestorePoints.java          |  39 +++
 .../cloudstack/backup/veeam/VeeamClientTest.java   | 329 ++++++++++++++++++++-
 .../java/com/cloud/hypervisor/guru/VMwareGuru.java |  17 +-
 .../hypervisor/vmware/resource/VmwareResource.java |  32 ++
 .../cloudstack/backup/BackupManagerImpl.java       |  29 +-
 .../smoke/test_backup_recovery_veeam.py            | 302 +++++++++++++++++++
 tools/marvin/marvin/lib/base.py                    |  66 ++++-
 ui/src/components/view/ListView.vue                |   2 +-
 ui/src/config/section/compute.js                   |   4 +-
 ui/src/config/section/storage.js                   |   6 +-
 ui/src/views/compute/backup/BackupSchedule.vue     |   8 +
 .../hypervisor/vmware/mo/VirtualMachineMO.java     |  25 ++
 .../hypervisor/vmware/mo/VmdkFileDescriptor.java   |  59 ++++
 25 files changed, 1654 insertions(+), 62 deletions(-)

diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java 
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java
index e609655c580..bd3b0623312 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java
@@ -95,8 +95,8 @@ public class ListVMsCmd extends BaseListTaggedResourcesCmd 
implements UserCmd {
     @Parameter(name = ApiConstants.DETAILS,
                type = CommandType.LIST,
                collectionType = CommandType.STRING,
-               description = "comma separated list of host details requested, "
-                   + "value can be a list of [all, group, nics, stats, secgrp, 
tmpl, servoff, diskoff, iso, volume, min, affgrp]."
+               description = "comma separated list of vm details requested, "
+                   + "value can be a list of [all, group, nics, stats, secgrp, 
tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp]."
                    + " If no parameter is passed in, the details will be 
defaulted to all")
     private List<String> viewDetails;
 
diff --git 
a/core/src/main/java/org/apache/cloudstack/backup/PrepareForBackupRestorationCommand.java
 
b/core/src/main/java/org/apache/cloudstack/backup/PrepareForBackupRestorationCommand.java
new file mode 100644
index 00000000000..25306fb5f73
--- /dev/null
+++ 
b/core/src/main/java/org/apache/cloudstack/backup/PrepareForBackupRestorationCommand.java
@@ -0,0 +1,43 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.backup;
+
+import com.cloud.agent.api.Command;
+
+public class PrepareForBackupRestorationCommand extends Command {
+
+    private String vmName;
+
+    protected PrepareForBackupRestorationCommand() {
+    }
+
+    public PrepareForBackupRestorationCommand(String vmName) {
+        this.vmName = vmName;
+    }
+
+    public String getVmName() {
+        return vmName;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return true;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/NicVO.java 
b/engine/schema/src/main/java/com/cloud/vm/NicVO.java
index 8905ebf732b..fba7c966c44 100644
--- a/engine/schema/src/main/java/com/cloud/vm/NicVO.java
+++ b/engine/schema/src/main/java/com/cloud/vm/NicVO.java
@@ -30,6 +30,9 @@ import javax.persistence.Id;
 import javax.persistence.Table;
 import javax.persistence.Transient;
 
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
 import com.cloud.network.Networks.AddressFormat;
 import com.cloud.network.Networks.Mode;
 import com.cloud.utils.db.GenericDao;
@@ -399,6 +402,15 @@ public class NicVO implements Nic {
     }
 
     @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 31).append(id).toHashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
     public Integer getMtu() {
         return mtu;
     }
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java 
b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java
index 13eb04ba6b8..68f57329d77 100644
--- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java
@@ -91,6 +91,8 @@ public interface NicDao extends GenericDao<NicVO, Long> {
 
     NicVO findByMacAddress(String macAddress);
 
+    NicVO findByNetworkIdAndMacAddressIncludingRemoved(long networkId, String 
mac);
+
     List<NicVO> findNicsByIpv6GatewayIpv6CidrAndReserver(String ipv6Gateway, 
String ipv6Cidr, String reserverName);
 
     NicVO findByIpAddressAndVmType(String ip, VirtualMachine.Type vmType);
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java 
b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java
index fdc36b4f918..59d2417b073 100644
--- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java
@@ -219,6 +219,14 @@ public class NicDaoImpl extends GenericDaoBase<NicVO, 
Long> implements NicDao {
         return findOneBy(sc);
     }
 
+    @Override
+    public NicVO findByNetworkIdAndMacAddressIncludingRemoved(long networkId, 
String mac) {
+        SearchCriteria<NicVO> sc = AllFieldsSearch.create();
+        sc.setParameters("network", networkId);
+        sc.setParameters("macAddress", mac);
+        return findOneIncludingRemovedBy(sc);
+    }
+
     @Override
     public NicVO findDefaultNicForVM(long instanceId) {
         SearchCriteria<NicVO> sc = AllFieldsSearch.create();
diff --git 
a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java 
b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java
index dc47fcb6bb3..2ecbfd56460 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java
@@ -17,6 +17,9 @@
 
 package org.apache.cloudstack.backup;
 
+import com.cloud.utils.db.GenericDao;
+
+import java.util.Date;
 import java.util.UUID;
 
 import javax.persistence.Column;
@@ -51,6 +54,9 @@ public class BackupVO implements Backup {
     @Column(name = "date")
     private String date;
 
+    @Column(name = GenericDao.REMOVED_COLUMN)
+    private Date removed;
+
     @Column(name = "size")
     private Long size;
 
@@ -192,4 +198,12 @@ public class BackupVO implements Backup {
     public String getName() {
         return null;
     }
+
+    public Date getRemoved() {
+        return removed;
+    }
+
+    public void setRemoved(Date removed) {
+        this.removed = removed;
+    }
 }
diff --git a/plugins/backup/veeam/pom.xml b/plugins/backup/veeam/pom.xml
index 146476c46ed..f0f80d7e337 100644
--- a/plugins/backup/veeam/pom.xml
+++ b/plugins/backup/veeam/pom.xml
@@ -28,6 +28,21 @@
   </parent>
 
   <dependencies>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-engine-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-engine-components-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-plugin-hypervisor-vmware</artifactId>
diff --git 
a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java
 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java
index 02f08d602bb..1a445080b5c 100644
--- 
a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java
+++ 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java
@@ -29,6 +29,7 @@ import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
+import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.InternalIdentity;
 import org.apache.cloudstack.backup.Backup.Metric;
 import org.apache.cloudstack.backup.dao.BackupDao;
@@ -40,11 +41,17 @@ import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.BooleanUtils;
 import org.apache.log4j.Logger;
 
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.Answer;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventTypes;
+import com.cloud.event.EventVO;
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.hypervisor.vmware.VmwareDatacenter;
 import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap;
 import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
 import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
+import com.cloud.user.User;
 import com.cloud.utils.Pair;
 import com.cloud.utils.component.AdapterBase;
 import com.cloud.utils.db.Transaction;
@@ -53,6 +60,7 @@ import com.cloud.utils.db.TransactionStatus;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
 import com.cloud.vm.dao.VMInstanceDao;
 
 public class VeeamBackupProvider extends AdapterBase implements 
BackupProvider, Configurable {
@@ -64,6 +72,10 @@ public class VeeamBackupProvider extends AdapterBase 
implements BackupProvider,
             "backup.plugin.veeam.url", "https://localhost:9398/api/";,
             "The Veeam backup and recovery URL.", true, ConfigKey.Scope.Zone);
 
+    public ConfigKey<Integer> VeeamVersion = new ConfigKey<>("Advanced", 
Integer.class,
+            "backup.plugin.veeam.version", "0",
+            "The version of Veeam backup and recovery. CloudStack will get 
Veeam server version via PowerShell commands if it is 0 or not set", true, 
ConfigKey.Scope.Zone);
+
     private ConfigKey<String> VeeamUsername = new ConfigKey<>("Advanced", 
String.class,
             "backup.plugin.veeam.username", "administrator",
             "The Veeam backup and recovery username.", true, 
ConfigKey.Scope.Zone);
@@ -81,6 +93,12 @@ public class VeeamBackupProvider extends AdapterBase 
implements BackupProvider,
     private static ConfigKey<Integer> VeeamRestoreTimeout = new 
ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.restore.timeout", 
"600",
             "The Veeam B&R API restore backup timeout in seconds.", true, 
ConfigKey.Scope.Zone);
 
+    private static ConfigKey<Integer> VeeamTaskPollInterval = new 
ConfigKey<>("Advanced", Integer.class, 
"backup.plugin.veeam.task.poll.interval", "5",
+            "The time interval in seconds when the management server polls for 
Veeam task status.", true, ConfigKey.Scope.Zone);
+
+    private static ConfigKey<Integer> VeeamTaskPollMaxRetry = new 
ConfigKey<>("Advanced", Integer.class, 
"backup.plugin.veeam.task.poll.max.retry", "120",
+            "The max number of retrying times when the management server polls 
for Veeam task status.", true, ConfigKey.Scope.Zone);
+
     @Inject
     private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao;
     @Inject
@@ -89,11 +107,16 @@ public class VeeamBackupProvider extends AdapterBase 
implements BackupProvider,
     private BackupDao backupDao;
     @Inject
     private VMInstanceDao vmInstanceDao;
+    @Inject
+    private AgentManager agentMgr;
+    @Inject
+    private VirtualMachineManager virtualMachineManager;
 
     protected VeeamClient getClient(final Long zoneId) {
         try {
-            return new VeeamClient(VeeamUrl.valueIn(zoneId), 
VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId),
-                    VeeamValidateSSLSecurity.valueIn(zoneId), 
VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId));
+            return new VeeamClient(VeeamUrl.valueIn(zoneId), 
VeeamVersion.valueIn(zoneId), VeeamUsername.valueIn(zoneId), 
VeeamPassword.valueIn(zoneId),
+                    VeeamValidateSSLSecurity.valueIn(zoneId), 
VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId),
+                    VeeamTaskPollInterval.valueIn(zoneId), 
VeeamTaskPollMaxRetry.valueIn(zoneId));
         } catch (URISyntaxException e) {
             throw new CloudRuntimeException("Failed to parse Veeam API URL: " 
+ e.getMessage());
         } catch (NoSuchAlgorithmException | KeyManagementException e) {
@@ -234,7 +257,36 @@ public class VeeamBackupProvider extends AdapterBase 
implements BackupProvider,
     @Override
     public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) {
         final String restorePointId = backup.getExternalId();
-        return 
getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), 
restorePointId);
+        try {
+            return 
getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), 
restorePointId);
+        } catch (Exception ex) {
+            LOG.error(String.format("Failed to restore Full VM due to: %s. 
Retrying after some preparation", ex.getMessage()));
+            prepareForBackupRestoration(vm);
+            return 
getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), 
restorePointId);
+        }
+    }
+
+    private void prepareForBackupRestoration(VirtualMachine vm) {
+        if (!Hypervisor.HypervisorType.VMware.equals(vm.getHypervisorType())) {
+            return;
+        }
+        LOG.info("Preparing for restoring VM " + vm);
+        PrepareForBackupRestorationCommand command = new 
PrepareForBackupRestorationCommand(vm.getInstanceName());
+        Long hostId = 
virtualMachineManager.findClusterAndHostIdForVm(vm.getId()).second();
+        if (hostId == null) {
+            throw new CloudRuntimeException("Cannot find a host to prepare for 
restoring VM " + vm);
+        }
+        try {
+            Answer answer = agentMgr.easySend(hostId, command);
+            if (answer != null && answer.getResult()) {
+                LOG.info("Succeeded to prepare for restoring VM " + vm);
+            } else {
+                throw new CloudRuntimeException(String.format("Failed to 
prepare for restoring VM %s. details: %s", vm,
+                        (answer != null ? answer.getDetails() : null)));
+            }
+        } catch (Exception e) {
+            throw new CloudRuntimeException(String.format("Failed to prepare 
for restoring VM %s due to exception %s", vm, e));
+        }
     }
 
     @Override
@@ -330,6 +382,10 @@ public class VeeamBackupProvider extends AdapterBase 
implements BackupProvider,
                                         + "domain_id: %s, zone_id: %s].", 
backup.getUuid(), backup.getVmId(), backup.getExternalId(), backup.getType(), 
backup.getDate(),
                                 backup.getBackupOfferingId(), 
backup.getAccountId(), backup.getDomainId(), backup.getZoneId()));
                         backupDao.persist(backup);
+
+                        
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), 
EventVO.LEVEL_INFO, EventTypes.EVENT_VM_BACKUP_CREATE,
+                                String.format("Created backup %s for VM ID: 
%s", backup.getUuid(), vm.getUuid()),
+                                vm.getId(), 
ApiCommandResourceType.VirtualMachine.toString(),0);
                     }
                 }
                 for (final Long backupIdToRemove : removeList) {
@@ -349,11 +405,14 @@ public class VeeamBackupProvider extends AdapterBase 
implements BackupProvider,
     public ConfigKey<?>[] getConfigKeys() {
         return new ConfigKey[]{
                 VeeamUrl,
+                VeeamVersion,
                 VeeamUsername,
                 VeeamPassword,
                 VeeamValidateSSLSecurity,
                 VeeamApiRequestTimeout,
-                VeeamRestoreTimeout
+                VeeamRestoreTimeout,
+                VeeamTaskPollInterval,
+                VeeamTaskPollMaxRetry
         };
     }
 
diff --git 
a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java
 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java
index 40fbe97028a..7d3bfc50d18 100644
--- 
a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java
+++ 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java
@@ -20,12 +20,15 @@ package org.apache.cloudstack.backup.veeam;
 import static 
org.apache.cloudstack.backup.VeeamBackupProvider.BACKUP_IDENTIFIER;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.SocketTimeoutException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Base64;
@@ -42,6 +45,8 @@ import org.apache.cloudstack.api.ApiErrorCode;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.backup.Backup;
 import org.apache.cloudstack.backup.BackupOffering;
+import org.apache.cloudstack.backup.veeam.api.BackupFile;
+import org.apache.cloudstack.backup.veeam.api.BackupFiles;
 import org.apache.cloudstack.backup.veeam.api.BackupJobCloneInfo;
 import org.apache.cloudstack.backup.veeam.api.CreateObjectInJobSpec;
 import org.apache.cloudstack.backup.veeam.api.EntityReferences;
@@ -55,7 +60,10 @@ import org.apache.cloudstack.backup.veeam.api.ObjectsInJob;
 import org.apache.cloudstack.backup.veeam.api.Ref;
 import org.apache.cloudstack.backup.veeam.api.RestoreSession;
 import org.apache.cloudstack.backup.veeam.api.Task;
+import org.apache.cloudstack.backup.veeam.api.VmRestorePoint;
+import org.apache.cloudstack.backup.veeam.api.VmRestorePoints;
 import org.apache.cloudstack.utils.security.SSLUtils;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.http.HttpHeaders;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
@@ -71,6 +79,7 @@ import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.log4j.Logger;
 
+import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.Pair;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.nio.TrustAllManager;
@@ -90,18 +99,31 @@ public class VeeamClient {
     private final HttpClient httpClient;
     private static final String RESTORE_VM_SUFFIX = "CS-RSTR-";
     private static final String SESSION_HEADER = "X-RestSvcSessionId";
+    private static final String BACKUP_REFERENCE = "BackupReference";
+    private static final String HIERARCHY_ROOT_REFERENCE = 
"HierarchyRootReference";
+    private static final String REPOSITORY_REFERENCE = "RepositoryReference";
+    private static final String RESTORE_POINT_REFERENCE = 
"RestorePointReference";
+    private static final String BACKUP_FILE_REFERENCE = "BackupFileReference";
+    private static final SimpleDateFormat dateFormat = new 
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+    private static final SimpleDateFormat newDateFormat = new 
SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
 
     private String veeamServerIp;
+    private final Integer veeamServerVersion;
     private String veeamServerUsername;
     private String veeamServerPassword;
     private String veeamSessionId = null;
-    private int restoreTimeout;
+    private final int restoreTimeout;
     private final int veeamServerPort = 22;
+    private final int taskPollInterval;
+    private final int taskPollMaxRetry;
 
-    public VeeamClient(final String url, final String username, final String 
password, final boolean validateCertificate, final int timeout,
-            final int restoreTimeout) throws URISyntaxException, 
NoSuchAlgorithmException, KeyManagementException {
+    public VeeamClient(final String url, final Integer version, final String 
username, final String password, final boolean validateCertificate, final int 
timeout,
+            final int restoreTimeout, final int taskPollInterval, final int 
taskPollMaxRetry) throws URISyntaxException, NoSuchAlgorithmException, 
KeyManagementException {
         this.apiURI = new URI(url);
         this.restoreTimeout = restoreTimeout;
+        this.taskPollInterval = taskPollInterval;
+        this.taskPollMaxRetry = taskPollMaxRetry;
 
         final RequestConfig config = RequestConfig.custom()
                 .setConnectTimeout(timeout * 1000)
@@ -125,6 +147,7 @@ public class VeeamClient {
 
         authenticate(username, password);
         setVeeamSshCredentials(this.apiURI.getHost(), username, password);
+        this.veeamServerVersion = (version != null && version != 0) ? version 
: getVeeamServerVersion();
     }
 
     protected void setVeeamSshCredentials(String hostIp, String username, 
String password) {
@@ -135,7 +158,7 @@ public class VeeamClient {
 
     private void authenticate(final String username, final String password) {
         // 
https://helpcenter.veeam.com/docs/backup/rest/http_authentication.html?ver=95u4
-        final HttpPost request = new HttpPost(apiURI.toString() + 
"/sessionMngr/?v=v1_4");
+        final HttpPost request = new HttpPost(apiURI.toString() + 
"/sessionMngr/?v=latest");
         request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + 
Base64.getEncoder().encodeToString((username + ":" + password).getBytes()));
         try {
             final HttpResponse response = httpClient.execute(request);
@@ -158,6 +181,26 @@ public class VeeamClient {
         }
     }
 
+    protected Integer getVeeamServerVersion() {
+        final List<String> cmds = Arrays.asList(
+                "$InstallPath = Get-ItemProperty -Path 
'HKLM:\\Software\\Veeam\\Veeam Backup and Replication\\' ^| Select 
-ExpandProperty CorePath",
+                "Add-Type -LiteralPath 
\\\"$InstallPath\\Veeam.Backup.Configuration.dll\\\"",
+                "$ProductData = 
[Veeam.Backup.Configuration.BackupProduct]::Create()",
+                "$Version = $ProductData.ProductVersion.ToString()",
+                "if ($ProductData.MarketName -ne '') {$Version += \\\" 
$($ProductData.MarketName)\\\"}",
+                "$Version"
+        );
+        Pair<Boolean, String> response = executePowerShellCommands(cmds);
+        if (response == null || !response.first() || response.second() == null 
|| StringUtils.isBlank(response.second().trim())) {
+            LOG.error("Failed to get veeam server version, using default 
version");
+            return 0;
+        } else {
+            Integer majorVersion = 
NumbersUtil.parseInt(response.second().trim().split("\\.")[0], 0);
+            LOG.info(String.format("Veeam server full version is %s, major 
version is %s", response.second().trim(), majorVersion));
+            return majorVersion;
+        }
+    }
+
     private void checkResponseOK(final HttpResponse response) {
         if (response.getStatusLine().getStatusCode() == 
HttpStatus.SC_NO_CONTENT) {
             LOG.debug("Requested Veeam resource does not exist");
@@ -238,7 +281,7 @@ public class VeeamClient {
             final ObjectMapper objectMapper = new XmlMapper();
             final EntityReferences references = 
objectMapper.readValue(response.getEntity().getContent(), 
EntityReferences.class);
             for (final Ref ref : references.getRefs()) {
-                if (ref.getName().equals(vmwareDcName) && 
ref.getType().equals("HierarchyRootReference")) {
+                if (ref.getName().equals(vmwareDcName) && 
ref.getType().equals(HIERARCHY_ROOT_REFERENCE)) {
                     return ref.getUid();
                 }
             }
@@ -286,7 +329,7 @@ public class VeeamClient {
 
     private boolean checkTaskStatus(final HttpResponse response) throws 
IOException {
         final Task task = parseTaskResponse(response);
-        for (int i = 0; i < 120; i++) {
+        for (int i = 0; i < this.taskPollMaxRetry; i++) {
             final HttpResponse taskResponse = get("/tasks/" + 
task.getTaskId());
             final Task polledTask = parseTaskResponse(taskResponse);
             if (polledTask.getState().equals("Finished")) {
@@ -309,7 +352,7 @@ public class VeeamClient {
                 throw new CloudRuntimeException("Failed to assign VM to backup 
offering due to: " + polledTask.getResult().getMessage());
             }
             try {
-                Thread.sleep(5000);
+                Thread.sleep(this.taskPollInterval * 1000);
             } catch (InterruptedException e) {
                 LOG.debug("Failed to sleep while polling for Veeam task status 
due to: ", e);
             }
@@ -324,6 +367,10 @@ public class VeeamClient {
             if (session.getResult().equals("Success")) {
                 return true;
             }
+            if (session.getResult().equalsIgnoreCase("Failed")) {
+                String sessionUid = session.getUid();
+                throw new CloudRuntimeException(String.format("Restore job 
[%s] failed.", sessionUid));
+            }
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException ignored) {
@@ -355,7 +402,7 @@ public class VeeamClient {
             final ObjectMapper objectMapper = new XmlMapper();
             final EntityReferences references = 
objectMapper.readValue(response.getEntity().getContent(), 
EntityReferences.class);
             for (final Ref ref : references.getRefs()) {
-                if (ref.getType().equals("RepositoryReference") && 
ref.getName().equals(repositoryName)) {
+                if (ref.getType().equals(REPOSITORY_REFERENCE) && 
ref.getName().equals(repositoryName)) {
                     return ref;
                 }
             }
@@ -368,7 +415,7 @@ public class VeeamClient {
 
     protected String getRepositoryNameFromJob(String backupName) {
         final List<String> cmds = Arrays.asList(
-                String.format("$Job = Get-VBRJob -name \"%s\"", backupName),
+                String.format("$Job = Get-VBRJob -name '%s'", backupName),
                 "$Job.GetBackupTargetRepository() ^| select Name ^| 
Format-List"
         );
         Pair<Boolean, String> result = executePowerShellCommands(cmds);
@@ -376,7 +423,7 @@ public class VeeamClient {
             throw new CloudRuntimeException(String.format("Failed to get 
Repository Name from Job [name: %s].", backupName));
         }
 
-        for (String block : result.second().split("\n\n")) {
+        for (String block : result.second().split("\r\n")) {
            if (block.matches("Name(\\s)+:(.)*")) {
                return block.split(":")[1].trim();
            }
@@ -553,7 +600,11 @@ public class VeeamClient {
      */
     protected String transformPowerShellCommandList(List<String> cmds) {
         StringJoiner joiner = new StringJoiner(";");
-        joiner.add("PowerShell Add-PSSnapin VeeamPSSnapin");
+        if (isLegacyServer()) {
+            joiner.add("PowerShell Add-PSSnapin VeeamPSSnapin");
+        } else {
+            joiner.add("PowerShell Import-Module Veeam.Backup.PowerShell 
-WarningAction SilentlyContinue");
+        }
         for (String cmd : cmds) {
             joiner.add(cmd);
         }
@@ -584,22 +635,22 @@ public class VeeamClient {
 
     public boolean setJobSchedule(final String jobName) {
         Pair<Boolean, String> result = executePowerShellCommands(Arrays.asList(
-                String.format("$job = Get-VBRJob -Name \"%s\"", jobName),
+                String.format("$job = Get-VBRJob -Name '%s'", jobName),
                 "if ($job) { Set-VBRJobSchedule -Job $job -Daily -At \"11:00\" 
-DailyKind Weekdays }"
         ));
-        return result.first() && !result.second().isEmpty() && 
!result.second().contains(FAILED_TO_DELETE);
+        return result != null && result.first() && !result.second().isEmpty() 
&& !result.second().contains(FAILED_TO_DELETE);
     }
 
     public boolean deleteJobAndBackup(final String jobName) {
         Pair<Boolean, String> result = executePowerShellCommands(Arrays.asList(
-                String.format("$job = Get-VBRJob -Name \"%s\"", jobName),
+                String.format("$job = Get-VBRJob -Name '%s'", jobName),
                 "if ($job) { Remove-VBRJob -Job $job -Confirm:$false }",
-                String.format("$backup = Get-VBRBackup -Name \"%s\"", jobName),
+                String.format("$backup = Get-VBRBackup -Name '%s'", jobName),
                 "if ($backup) { Remove-VBRBackup -Backup $backup -FromDisk 
-Confirm:$false }",
                 "$repo = Get-VBRBackupRepository",
                 "Sync-VBRBackupRepository -Repository $repo"
         ));
-        return result.first() && !result.second().contains(FAILED_TO_DELETE);
+        return result != null && result.first() && 
!result.second().contains(FAILED_TO_DELETE);
     }
 
     public boolean deleteBackup(final String restorePointId) {
@@ -610,40 +661,123 @@ public class VeeamClient {
                     "$repo = Get-VBRBackupRepository",
                     "Sync-VBRBackupRepository -Repository $repo",
                 "} else { ",
-                    " Write-Output \"Failed to delete\"",
+                    " Write-Output 'Failed to delete'",
                     " Exit 1",
                 "}"
         ));
-        return result.first() && !result.second().contains(FAILED_TO_DELETE);
+        return result != null && result.first() && 
!result.second().contains(FAILED_TO_DELETE);
     }
 
     public Map<String, Backup.Metric> getBackupMetrics() {
+        if (isLegacyServer()) {
+            return getBackupMetricsLegacy();
+        } else {
+            return getBackupMetricsViaVeeamAPI();
+        }
+    }
+
+    public Map<String, Backup.Metric> getBackupMetricsViaVeeamAPI() {
+        LOG.debug("Trying to get backup metrics via Veeam B&R API");
+
+        try {
+            final HttpResponse response = 
get(String.format("/backupFiles?format=Entity"));
+            checkResponseOK(response);
+            return 
processHttpResponseForBackupMetrics(response.getEntity().getContent());
+        } catch (final IOException e) {
+            LOG.error("Failed to get backup metrics via Veeam B&R API due 
to:", e);
+            checkResponseTimeOut(e);
+        }
+        return new HashMap<>();
+    }
+
+    protected Map<String, Backup.Metric> 
processHttpResponseForBackupMetrics(final InputStream content) {
+        Map<String, Backup.Metric> metrics = new HashMap<>();
+        try {
+            final ObjectMapper objectMapper = new XmlMapper();
+            final BackupFiles backupFiles = objectMapper.readValue(content, 
BackupFiles.class);
+            if (backupFiles == null || 
CollectionUtils.isEmpty(backupFiles.getBackupFiles())) {
+                throw new CloudRuntimeException("Could not get backup metrics 
via Veeam B&R API");
+            }
+            for (final BackupFile backupFile : backupFiles.getBackupFiles()) {
+                String vmUuid = null;
+                String backupName = null;
+                List<Link> links = backupFile.getLink();
+                for (Link link : links) {
+                    if (BACKUP_REFERENCE.equals(link.getType())) {
+                        backupName = link.getName();
+                        break;
+                    }
+                }
+                if (backupName != null && 
backupName.contains(BACKUP_IDENTIFIER)) {
+                    final String[] names = backupName.split(BACKUP_IDENTIFIER);
+                    if (names.length > 1) {
+                        vmUuid = names[1];
+                    }
+                }
+                if (vmUuid == null) {
+                    continue;
+                }
+                if (vmUuid.contains(" - ")) {
+                    vmUuid = vmUuid.split(" - ")[0];
+                }
+                Long usedSize = 0L;
+                Long dataSize = 0L;
+                if (metrics.containsKey(vmUuid)) {
+                    usedSize = metrics.get(vmUuid).getBackupSize();
+                    dataSize = metrics.get(vmUuid).getDataSize();
+                }
+                if (backupFile.getBackupSize() != null) {
+                    usedSize += Long.valueOf(backupFile.getBackupSize());
+                }
+                if (backupFile.getDataSize() != null) {
+                    dataSize += Long.valueOf(backupFile.getDataSize());
+                }
+                metrics.put(vmUuid, new Backup.Metric(usedSize, dataSize));
+            }
+        } catch (final IOException e) {
+            LOG.error("Failed to process response to get backup metrics via 
Veeam B&R API due to:", e);
+            checkResponseTimeOut(e);
+        }
+        return metrics;
+    }
+
+    public Map<String, Backup.Metric> getBackupMetricsLegacy() {
         final String separator = "=====";
         final List<String> cmds = Arrays.asList(
-            "$backups = Get-VBRBackup",
-            "foreach ($backup in $backups) {" +
-               "$backup.JobName;" +
-               "$storageGroups = $backup.GetStorageGroups();" +
-               "foreach ($group in $storageGroups) {" +
-                    "$usedSize = 0;" +
-                    "$dataSize = 0;" +
-                    "$sizePerStorage = $group.GetStorages().Stats.BackupSize;" 
+
-                    "$dataPerStorage = $group.GetStorages().Stats.DataSize;" +
-                    "foreach ($size in $sizePerStorage) {" +
-                        "$usedSize += $size;" +
-                    "}" +
-                    "foreach ($size in $dataPerStorage) {" +
-                        "$dataSize += $size;" +
-                    "}" +
-                    "$usedSize;" +
-                    "$dataSize;" +
-               "}" +
-               "echo \"" + separator + "\"" +
-            "}"
+                "$backups = Get-VBRBackup",
+                "foreach ($backup in $backups) {" +
+                        "    $backup.JobName;" +
+                        "    $storageGroups = $backup.GetStorageGroups();" +
+                        "    foreach ($group in $storageGroups) {" +
+                        "        $usedSize = 0;" +
+                        "        $dataSize = 0;" +
+                        "        $sizePerStorage = 
$group.GetStorages().Stats.BackupSize;" +
+                        "        $dataPerStorage = 
$group.GetStorages().Stats.DataSize;" +
+                        "        foreach ($size in $sizePerStorage) {" +
+                        "            $usedSize += $size;" +
+                        "        }" +
+                        "        foreach ($size in $dataPerStorage) {" +
+                        "            $dataSize += $size;" +
+                        "        }" +
+                        "        $usedSize;" +
+                        "        $dataSize;" +
+                        "    }" +
+                        "    echo \"" + separator + "\"" +
+                        "}"
         );
         Pair<Boolean, String> response = executePowerShellCommands(cmds);
+        if (response == null || !response.first()) {
+            throw new CloudRuntimeException("Failed to get backup metrics via 
PowerShell command");
+        }
+        return processPowerShellResultForBackupMetrics(response.second());
+    }
+
+    protected Map<String, Backup.Metric> 
processPowerShellResultForBackupMetrics(final String result) {
+        LOG.debug("Processing powershell result: " + result);
+
+        final String separator = "=====";
         final Map<String, Backup.Metric> sizes = new HashMap<>();
-        for (final String block : response.second().split(separator + "\r\n")) 
{
+        for (final String block : result.split(separator + "\r\n")) {
             final String[] parts = block.split("\r\n");
             if (parts.length != 3) {
                 continue;
@@ -677,9 +811,9 @@ public class VeeamClient {
         return new Backup.RestorePoint(id, created, type);
     }
 
-    public List<Backup.RestorePoint> listRestorePoints(String backupName, 
String vmInternalName) {
+    public List<Backup.RestorePoint> listRestorePointsLegacy(String 
backupName, String vmInternalName) {
         final List<String> cmds = Arrays.asList(
-                String.format("$backup = Get-VBRBackup -Name \"%s\"", 
backupName),
+                String.format("$backup = Get-VBRBackup -Name '%s'", 
backupName),
                 String.format("if ($backup) { $restore = (Get-VBRRestorePoint 
-Backup:$backup -Name \"%s\" ^| Where-Object {$_.IsConsistent -eq $true})", 
vmInternalName),
                 "if ($restore) { $restore ^| Format-List } }"
         );
@@ -700,6 +834,71 @@ public class VeeamClient {
         return restorePoints;
     }
 
+    public List<Backup.RestorePoint> listRestorePoints(String backupName, 
String vmInternalName) {
+        if (isLegacyServer()) {
+            return listRestorePointsLegacy(backupName, vmInternalName);
+        } else {
+            return listVmRestorePointsViaVeeamAPI(vmInternalName);
+        }
+    }
+
+    public List<Backup.RestorePoint> listVmRestorePointsViaVeeamAPI(String 
vmInternalName) {
+        LOG.debug(String.format("Trying to list VM restore points via Veeam 
B&R API for VM %s: ", vmInternalName));
+
+        try {
+            final HttpResponse response = 
get(String.format("/vmRestorePoints?format=Entity"));
+            checkResponseOK(response);
+            return 
processHttpResponseForVmRestorePoints(response.getEntity().getContent(), 
vmInternalName);
+        } catch (final IOException e) {
+            LOG.error("Failed to list VM restore points via Veeam B&R API due 
to:", e);
+            checkResponseTimeOut(e);
+        }
+        return new ArrayList<>();
+    }
+
+    public List<Backup.RestorePoint> 
processHttpResponseForVmRestorePoints(InputStream content, String 
vmInternalName) {
+        List<Backup.RestorePoint> vmRestorePointList = new ArrayList<>();
+        try {
+            final ObjectMapper objectMapper = new XmlMapper();
+            final VmRestorePoints vmRestorePoints = 
objectMapper.readValue(content, VmRestorePoints.class);
+            if (vmRestorePoints == null) {
+                throw new CloudRuntimeException("Could not get VM restore 
points via Veeam B&R API");
+            }
+            for (final VmRestorePoint vmRestorePoint : 
vmRestorePoints.getVmRestorePoints()) {
+                LOG.debug(String.format("Processing VM restore point Name=%s, 
VmDisplayName=%s for vm name=%s",
+                        vmRestorePoint.getName(), 
vmRestorePoint.getVmDisplayName(), vmInternalName));
+                if (!vmInternalName.equals(vmRestorePoint.getVmDisplayName())) 
{
+                    continue;
+                }
+                boolean isReady = true;
+                List<Link> links = vmRestorePoint.getLink();
+                for (Link link : links) {
+                    if (Arrays.asList(BACKUP_FILE_REFERENCE, 
RESTORE_POINT_REFERENCE).contains(link.getType()) && 
!link.getRel().equals("Up")) {
+                        LOG.info(String.format("The VM restore point is not 
ready. Reference: %s, state: %s", link.getType(), link.getRel()));
+                        isReady = false;
+                        break;
+                    }
+                }
+                if (!isReady) {
+                    continue;
+                }
+                String vmRestorePointId = 
vmRestorePoint.getUid().substring(vmRestorePoint.getUid().lastIndexOf(':') + 1);
+                String created = 
formatDate(vmRestorePoint.getCreationTimeUtc());
+                String type = vmRestorePoint.getPointType();
+                LOG.debug(String.format("Adding restore point %s, %s, %s", 
vmRestorePointId, created, type));
+                vmRestorePointList.add(new 
Backup.RestorePoint(vmRestorePointId, created, type));
+            }
+        } catch (final IOException | ParseException e) {
+            LOG.error("Failed to process response to get VM restore points via 
Veeam B&R API due to:", e);
+            checkResponseTimeOut(e);
+        }
+        return vmRestorePointList;
+    }
+
+    private String formatDate(String date) throws ParseException {
+        return 
newDateFormat.format(dateFormat.parse(StringUtils.substring(date, 0, 19)));
+    }
+
     public Pair<Boolean, String> restoreVMToDifferentLocation(String 
restorePointId, String hostIp, String dataStoreUuid) {
         final String restoreLocation = RESTORE_VM_SUFFIX + 
UUID.randomUUID().toString();
         final String datastoreId = dataStoreUuid.replace("-","");
@@ -717,4 +916,8 @@ public class VeeamClient {
         }
         return new Pair<>(result.first(), restoreLocation);
     }
+
+    private boolean isLegacyServer() {
+        return this.veeamServerVersion != null && (this.veeamServerVersion > 0 
&& this.veeamServerVersion < 11);
+    }
 }
diff --git 
a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupFile.java
 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupFile.java
new file mode 100644
index 00000000000..2b28793b1fb
--- /dev/null
+++ 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupFile.java
@@ -0,0 +1,160 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.backup.veeam.api;
+
+import 
com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+import java.util.List;
+
+@JacksonXmlRootElement(localName = "BackupFile")
+public class BackupFile {
+    @JacksonXmlProperty(localName = "Type", isAttribute = true)
+    private String type;
+
+    @JacksonXmlProperty(localName = "Href", isAttribute = true)
+    private String href;
+
+    @JacksonXmlProperty(localName = "Name", isAttribute = true)
+    private String name;
+
+    @JacksonXmlProperty(localName = "UID", isAttribute = true)
+    private String uid;
+
+    @JacksonXmlProperty(localName = "Link")
+    @JacksonXmlElementWrapper(localName = "Links")
+    private List<Link> link;
+
+    @JacksonXmlProperty(localName = "FilePath")
+    private String filePath;
+
+    @JacksonXmlProperty(localName = "BackupSize")
+    private String backupSize;
+
+    @JacksonXmlProperty(localName = "DataSize")
+    private String dataSize;
+
+    @JacksonXmlProperty(localName = "DeduplicationRatio")
+    private String deduplicationRatio;
+
+    @JacksonXmlProperty(localName = "CompressRatio")
+    private String compressRatio;
+
+    @JacksonXmlProperty(localName = "CreationTimeUtc")
+    private String creationTimeUtc;
+
+    @JacksonXmlProperty(localName = "FileType")
+    private String fileType;
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getHref() {
+        return href;
+    }
+
+    public void setHref(String href) {
+        this.href = href;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getUid() {
+        return uid;
+    }
+
+    public void setUid(String uid) {
+        this.uid = uid;
+    }
+
+    public List<Link> getLink() {
+        return link;
+    }
+
+    public void setLink(List<Link> link) {
+        this.link = link;
+    }
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    public String getBackupSize() {
+        return backupSize;
+    }
+
+    public void setBackupSize(String backupSize) {
+        this.backupSize = backupSize;
+    }
+
+    public String getDataSize() {
+        return dataSize;
+    }
+
+    public void setDataSize(String dataSize) {
+        this.dataSize = dataSize;
+    }
+
+    public String getDeduplicationRatio() {
+        return deduplicationRatio;
+    }
+
+    public void setDeduplicationRatio(String deduplicationRatio) {
+        this.deduplicationRatio = deduplicationRatio;
+    }
+
+    public String getCompressRatio() {
+        return compressRatio;
+    }
+
+    public void setCompressRatio(String compressRatio) {
+        this.compressRatio = compressRatio;
+    }
+
+    public String getCreationTimeUtc() {
+        return creationTimeUtc;
+    }
+
+    public void setCreationTimeUtc(String creationTimeUtc) {
+        this.creationTimeUtc = creationTimeUtc;
+    }
+
+    public String getFileType() {
+        return fileType;
+    }
+
+    public void setFileType(String fileType) {
+        this.fileType = fileType;
+    }
+}
diff --git 
a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupFiles.java
 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupFiles.java
new file mode 100644
index 00000000000..4ff7d0c088b
--- /dev/null
+++ 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/BackupFiles.java
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.backup.veeam.api;
+
+import java.util.List;
+
+import 
com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+@JacksonXmlRootElement(localName = "BackupFiles")
+public class BackupFiles {
+    @JacksonXmlProperty(localName = "BackupFile")
+    @JacksonXmlElementWrapper(localName = "BackupFile", useWrapping = false)
+    private List<BackupFile> backupFiles;
+
+    public List<BackupFile> getBackupFiles() {
+        return backupFiles;
+    }
+
+    public void setBackupFiles(List<BackupFile> backupFiles) {
+        this.backupFiles = backupFiles;
+    }
+}
diff --git 
a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VmRestorePoint.java
 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VmRestorePoint.java
new file mode 100644
index 00000000000..beaa11cd5d4
--- /dev/null
+++ 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VmRestorePoint.java
@@ -0,0 +1,149 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.backup.veeam.api;
+
+import 
com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+import java.util.List;
+
+@JacksonXmlRootElement(localName = "VmRestorePoint")
+public class VmRestorePoint {
+    @JacksonXmlProperty(localName = "Type", isAttribute = true)
+    private String type;
+
+    @JacksonXmlProperty(localName = "Href", isAttribute = true)
+    private String href;
+
+    @JacksonXmlProperty(localName = "Name", isAttribute = true)
+    private String name;
+
+    @JacksonXmlProperty(localName = "UID", isAttribute = true)
+    private String uid;
+
+    @JacksonXmlProperty(localName = "VmDisplayName", isAttribute = true)
+    private String vmDisplayName;
+
+    @JacksonXmlProperty(localName = "Link")
+    @JacksonXmlElementWrapper(localName = "Links")
+    private List<Link> link;
+
+    @JacksonXmlProperty(localName = "CreationTimeUTC")
+    private String creationTimeUtc;
+
+    @JacksonXmlProperty(localName = "VmName")
+    private String vmName;
+
+    @JacksonXmlProperty(localName = "Algorithm")
+    private String algorithm;
+
+    @JacksonXmlProperty(localName = "PointType")
+    private String pointType;
+
+    @JacksonXmlProperty(localName = "HierarchyObjRef")
+    private String hierarchyObjRef;
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getHref() {
+        return href;
+    }
+
+    public void setHref(String href) {
+        this.href = href;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getUid() {
+        return uid;
+    }
+
+    public void setUid(String uid) {
+        this.uid = uid;
+    }
+
+    public String getVmDisplayName() {
+        return vmDisplayName;
+    }
+
+    public void setVmDisplayName(String vmDisplayName) {
+        this.vmDisplayName = vmDisplayName;
+    }
+
+    public List<Link> getLink() {
+        return link;
+    }
+
+    public void setLink(List<Link> link) {
+        this.link = link;
+    }
+
+    public String getCreationTimeUtc() {
+        return creationTimeUtc;
+    }
+
+    public void setCreationTimeUtc(String creationTimeUtc) {
+        this.creationTimeUtc = creationTimeUtc;
+    }
+
+    public String getVmName() {
+        return vmName;
+    }
+
+    public void setVmName(String vmName) {
+        this.vmName = vmName;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public String getPointType() {
+        return pointType;
+    }
+
+    public void setPointType(String pointType) {
+        this.pointType = pointType;
+    }
+
+    public String getHierarchyObjRef() {
+        return hierarchyObjRef;
+    }
+
+    public void setHierarchyObjRef(String hierarchyObjRef) {
+        this.hierarchyObjRef = hierarchyObjRef;
+    }
+}
diff --git 
a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VmRestorePoints.java
 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VmRestorePoints.java
new file mode 100644
index 00000000000..2b59a3ef23c
--- /dev/null
+++ 
b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/api/VmRestorePoints.java
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.backup.veeam.api;
+
+import java.util.List;
+
+import 
com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+@JacksonXmlRootElement(localName = "VmRestorePoints")
+public class VmRestorePoints {
+    @JacksonXmlProperty(localName = "VmRestorePoint")
+    @JacksonXmlElementWrapper(localName = "VmRestorePoint", useWrapping = 
false)
+    private List<VmRestorePoint> VmRestorePoints;
+
+    public List<VmRestorePoint> getVmRestorePoints() {
+        return VmRestorePoints;
+    }
+
+    public void setVmRestorePoints(List<VmRestorePoint> vmRestorePoints) {
+        VmRestorePoints = vmRestorePoints;
+    }
+}
diff --git 
a/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java
 
b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java
index a155d351bc6..48d1f886b48 100644
--- 
a/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java
+++ 
b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java
@@ -27,9 +27,13 @@ import static 
com.github.tomakehurst.wiremock.client.WireMock.verify;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.times;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.List;
+import java.util.Map;
 
+import org.apache.cloudstack.backup.Backup;
 import org.apache.cloudstack.backup.BackupOffering;
 import org.apache.cloudstack.backup.veeam.api.RestoreSession;
 import org.apache.http.HttpResponse;
@@ -62,9 +66,10 @@ public class VeeamClientTest {
                         .withStatus(201)
                         .withHeader("X-RestSvcSessionId", 
"some-session-auth-id")
                         .withBody("")));
-        client = new VeeamClient("http://localhost:9399/api/";, adminUsername, 
adminPassword, true, 60, 600);
+        client = new VeeamClient("http://localhost:9399/api/";, 12, 
adminUsername, adminPassword, true, 60, 600, 5, 120);
         mockClient = Mockito.mock(VeeamClient.class);
         
Mockito.when(mockClient.getRepositoryNameFromJob(Mockito.anyString())).thenCallRealMethod();
+        Mockito.when(mockClient.getVeeamServerVersion()).thenCallRealMethod();
     }
 
     @Test
@@ -139,7 +144,7 @@ public class VeeamClientTest {
     @Test
     public void getRepositoryNameFromJobTestSuccess() throws Exception {
         String backupName = "TEST-BACKUP3";
-        Pair<Boolean, String> response = new Pair<Boolean, 
String>(Boolean.TRUE, "\n\nName : test");
+        Pair<Boolean, String> response = new Pair<Boolean, 
String>(Boolean.TRUE, "\r\nName : test");
         
Mockito.doReturn(response).when(mockClient).executePowerShellCommands(Mockito.anyList());
         String repositoryNameFromJob = 
mockClient.getRepositoryNameFromJob(backupName);
         Assert.assertEquals("test", repositoryNameFromJob);
@@ -162,4 +167,324 @@ public class VeeamClientTest {
         }
         Mockito.verify(mockClient, times(10)).get(Mockito.anyString());
     }
+
+    private void verifyBackupMetrics(Map<String, Backup.Metric> metrics) {
+        Assert.assertEquals(2, metrics.size());
+
+        
Assert.assertTrue(metrics.containsKey("d1bd8abd-fc73-4b77-9047-7be98a2ecb72"));
+        Assert.assertEquals(537776128L, (long) 
metrics.get("d1bd8abd-fc73-4b77-9047-7be98a2ecb72").getBackupSize());
+        Assert.assertEquals(2147506644L, (long) 
metrics.get("d1bd8abd-fc73-4b77-9047-7be98a2ecb72").getDataSize());
+
+        
Assert.assertTrue(metrics.containsKey("0d752ca6-d628-4d85-a739-75275e4661e6"));
+        Assert.assertEquals(1268682752L, (long) 
metrics.get("0d752ca6-d628-4d85-a739-75275e4661e6").getBackupSize());
+        Assert.assertEquals(15624049921L, (long) 
metrics.get("0d752ca6-d628-4d85-a739-75275e4661e6").getDataSize());
+    }
+
+    @Test
+    public void testProcessPowerShellResultForBackupMetrics() {
+        String result = 
"i-2-3-VM-CSBKP-d1bd8abd-fc73-4b77-9047-7be98a2ecb72\r\n" +
+                "537776128\r\n" +
+                "2147506644\r\n" +
+                "=====\r\n" +
+                "i-13-22-VM-CSBKP-b3b3cb75-cfbf-4496-9c63-a08a93347276\r\n" +
+                "=====\r\n" +
+                "backup-job-based-on-sla\r\n" +
+                "=====\r\n" +
+                "i-12-20-VM-CSBKP-9f292f11-00ec-4915-84f0-e3895828640e\r\n" +
+                "=====\r\n" +
+                "i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\r\n" +
+                "1268682752\r\n" +
+                "15624049921\r\n" +
+                "=====\r\n";
+
+        Map<String, Backup.Metric> metrics = 
client.processPowerShellResultForBackupMetrics(result);
+
+        verifyBackupMetrics(metrics);
+    }
+
+    @Test
+    public void testProcessHttpResponseForBackupMetricsForV11() {
+        String xmlResponse = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+                "<BackupFiles xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"; 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"; 
xmlns=\"http://www.veeam.com/ent/v1.0\";>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/d2110f5f-aa22-4e67-8084-5d8597f26d63?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-28T000059_745D.vbk\"
 UID=\"urn:veeam:BackupFile:d2110f5f-aa22-4e67-8084-5d8597f26d63\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/d2110f5f-aa22-4e67-8084-5d8597f26d63\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-28T000059_745D.vbk\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/d2110f5f-aa22-4e67-8084-5d8597f26d63/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/d2110f5f-aa22-4e67-8084-5d8597f26d63/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-28T000059_745D.vbk</FilePath>\n"
 +
+                "    <BackupSize>579756032</BackupSize>\n" +
+                "    <DataSize>7516219400</DataSize>\n" +
+                "    <DeduplicationRatio>5.83</DeduplicationRatio>\n" +
+                "    <CompressRatio>2.22</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-10-27T23:00:13.74Z</CreationTimeUtc>\n" +
+                "    <FileType>vbk</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/7c54d13d-7b9c-465a-8ec8-7a276bde57dd?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-05T000022_7987.vib\"
 UID=\"urn:veeam:BackupFile:7c54d13d-7b9c-465a-8ec8-7a276bde57dd\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/7c54d13d-7b9c-465a-8ec8-7a276bde57dd\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-05T000022_7987.vib\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/7c54d13d-7b9c-465a-8ec8-7a276bde57dd/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/7c54d13d-7b9c-465a-8ec8-7a276bde57dd/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-05T000022_7987.vib</FilePath>\n"
 +
+                "    <BackupSize>12083200</BackupSize>\n" +
+                "    <DataSize>69232800</DataSize>\n" +
+                "    <DeduplicationRatio>1</DeduplicationRatio>\n" +
+                "    <CompressRatio>6.67</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-11-05T00:00:22.827Z</CreationTimeUtc>\n" +
+                "    <FileType>vib</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/4b1181fd-7b1e-4af1-a76b-8284a8953b99?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-01T000035_BEBF.vib\"
 UID=\"urn:veeam:BackupFile:4b1181fd-7b1e-4af1-a76b-8284a8953b99\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/4b1181fd-7b1e-4af1-a76b-8284a8953b99\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-01T000035_BEBF.vib\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/4b1181fd-7b1e-4af1-a76b-8284a8953b99/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/4b1181fd-7b1e-4af1-a76b-8284a8953b99/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-01T000035_BEBF.vib</FilePath>\n"
 +
+                "    <BackupSize>12398592</BackupSize>\n" +
+                "    <DataSize>71329948</DataSize>\n" +
+                "    <DeduplicationRatio>1</DeduplicationRatio>\n" +
+                "    <CompressRatio>6.67</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-11-01T00:00:35.163Z</CreationTimeUtc>\n" +
+                "    <FileType>vib</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/66b39f48-af76-4373-b333-996fc04da894?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-04T000109_2AC1.vbk\"
 UID=\"urn:veeam:BackupFile:66b39f48-af76-4373-b333-996fc04da894\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/66b39f48-af76-4373-b333-996fc04da894\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-04T000109_2AC1.vbk\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/66b39f48-af76-4373-b333-996fc04da894/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/66b39f48-af76-4373-b333-996fc04da894/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-04T000109_2AC1.vbk</FilePath>\n"
 +
+                "    <BackupSize>581083136</BackupSize>\n" +
+                "    <DataSize>7516219404</DataSize>\n" +
+                "    <DeduplicationRatio>5.82</DeduplicationRatio>\n" +
+                "    <CompressRatio>2.22</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-11-04T00:00:24.973Z</CreationTimeUtc>\n" +
+                "    <FileType>vbk</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/8e9a854e-9bb8-4a34-815c-a6ab17a1e72f?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-29T000033_F468.vib\"
 UID=\"urn:veeam:BackupFile:8e9a854e-9bb8-4a34-815c-a6ab17a1e72f\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/8e9a854e-9bb8-4a34-815c-a6ab17a1e72f\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-29T000033_F468.vib\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/8e9a854e-9bb8-4a34-815c-a6ab17a1e72f/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/8e9a854e-9bb8-4a34-815c-a6ab17a1e72f/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-29T000033_F468.vib</FilePath>\n"
 +
+                "    <BackupSize>11870208</BackupSize>\n" +
+                "    <DataSize>72378524</DataSize>\n" +
+                "    <DeduplicationRatio>1</DeduplicationRatio>\n" +
+                "    <CompressRatio>7.14</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-10-28T23:00:33.233Z</CreationTimeUtc>\n" +
+                "    <FileType>vib</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/cf4536c0-d752-4ba5-ad7f-bbc17c7e107b?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-30T000022_0CE3.vib\"
 UID=\"urn:veeam:BackupFile:cf4536c0-d752-4ba5-ad7f-bbc17c7e107b\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/cf4536c0-d752-4ba5-ad7f-bbc17c7e107b\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-30T000022_0CE3.vib\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/cf4536c0-d752-4ba5-ad7f-bbc17c7e107b/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/cf4536c0-d752-4ba5-ad7f-bbc17c7e107b/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-30T000022_0CE3.vib</FilePath>\n"
 +
+                "    <BackupSize>14409728</BackupSize>\n" +
+                "    <DataSize>76572828</DataSize>\n" +
+                "    <DeduplicationRatio>1</DeduplicationRatio>\n" +
+                "    <CompressRatio>6.25</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-10-30T00:00:22.7Z</CreationTimeUtc>\n" +
+                "    <FileType>vib</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/2dd7f5b6-8a10-406d-9c4f-c0dfa987e85c?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-06T000018_055B.vib\"
 UID=\"urn:veeam:BackupFile:2dd7f5b6-8a10-406d-9c4f-c0dfa987e85c\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/2dd7f5b6-8a10-406d-9c4f-c0dfa987e85c\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-06T000018_055B.vib\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/2dd7f5b6-8a10-406d-9c4f-c0dfa987e85c/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/2dd7f5b6-8a10-406d-9c4f-c0dfa987e85c/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-06T000018_055B.vib</FilePath>\n"
 +
+                "    <BackupSize>17883136</BackupSize>\n" +
+                "    <DataSize>80767136</DataSize>\n" +
+                "    <DeduplicationRatio>1</DeduplicationRatio>\n" +
+                "    <CompressRatio>5</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-11-06T00:00:18.253Z</CreationTimeUtc>\n" +
+                "    <FileType>vib</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/3fd6da3a-47bf-45fa-a4c8-c436e3cd34a7?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-02T000029_65BE.vib\"
 UID=\"urn:veeam:BackupFile:3fd6da3a-47bf-45fa-a4c8-c436e3cd34a7\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/3fd6da3a-47bf-45fa-a4c8-c436e3cd34a7\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-02T000029_65BE.vib\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/3fd6da3a-47bf-45fa-a4c8-c436e3cd34a7/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/3fd6da3a-47bf-45fa-a4c8-c436e3cd34a7/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-02T000029_65BE.vib</FilePath>\n"
 +
+                "    <BackupSize>12521472</BackupSize>\n" +
+                "    <DataSize>72378525</DataSize>\n" +
+                "    <DeduplicationRatio>1</DeduplicationRatio>\n" +
+                "    <CompressRatio>6.67</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-11-02T00:00:29.05Z</CreationTimeUtc>\n" +
+                "    <FileType>vib</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/d93d7c7d-068a-4e8f-ba54-e08cea3cb9d2?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-3-VM-CSBKP-d1bd8abd-fc73-4b77-9047-7be98aD2023-10-25T145951_8062.vbk\"
 UID=\"urn:veeam:BackupFile:d93d7c7d-068a-4e8f-ba54-e08cea3cb9d2\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/a34cae53-2d9e-454b-8d3e-0aaa7b34c228\";
 Name=\"i-2-3-VM-CSBKP-d1bd8abd-fc73-4b77-9047-7be98a2ecb72\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/d93d7c7d-068a-4e8f-ba54-e08cea3cb9d2\";
 
Name=\"i-2-3-VM-CSBKP-d1bd8abd-fc73-4b77-9047-7be98aD2023-10-25T145951_8062.vbk\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/d93d7c7d-068a-4e8f-ba54-e08cea3cb9d2/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/d93d7c7d-068a-4e8f-ba54-e08cea3cb9d2/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-3-VM-CSBKP-d1bd8abd-fc73-4b77-9047-7be98a2ecb72\\i-2-3-VM-CSBKP-d1bd8abd-fc73-4b77-9047-7be98aD2023-10-25T145951_8062.vbk</FilePath>\n"
 +
+                "    <BackupSize>537776128</BackupSize>\n" +
+                "    <DataSize>2147506644</DataSize>\n" +
+                "    <DeduplicationRatio>1.68</DeduplicationRatio>\n" +
+                "    <CompressRatio>2.38</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-10-25T13:59:51.76Z</CreationTimeUtc>\n" +
+                "    <FileType>vbk</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/094564ff-02a1-46c7-b9e5-e249b8b9acf6?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-03T000024_7ACF.vib\"
 UID=\"urn:veeam:BackupFile:094564ff-02a1-46c7-b9e5-e249b8b9acf6\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/094564ff-02a1-46c7-b9e5-e249b8b9acf6\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-03T000024_7ACF.vib\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/094564ff-02a1-46c7-b9e5-e249b8b9acf6/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/094564ff-02a1-46c7-b9e5-e249b8b9acf6/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-11-03T000024_7ACF.vib</FilePath>\n"
 +
+                "    <BackupSize>14217216</BackupSize>\n" +
+                "    <DataSize>76572832</DataSize>\n" +
+                "    <DeduplicationRatio>1</DeduplicationRatio>\n" +
+                "    <CompressRatio>6.25</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-11-03T00:00:24.803Z</CreationTimeUtc>\n" +
+                "    <FileType>vib</FileType>\n" +
+                "  </BackupFile>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.141:9398/api/backupFiles/1f6f5c49-92ef-4757-b327-e63ae9f1fdea?format=Entity\";
 Type=\"BackupFile\" 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-31T000015_4624.vib\"
 UID=\"urn:veeam:BackupFile:1f6f5c49-92ef-4757-b327-e63ae9f1fdea\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backups/e7484f82-b01b-47cf-92ad-ac5e8379a4fe\";
 Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\" 
Type=\"BackupReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupServers/bb188236-7b8b-4763-b35a-5d6645d3e95b\";
 Name=\"10.0.3.141\" Type=\"BackupServerReference\" Rel=\"Up\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/1f6f5c49-92ef-4757-b327-e63ae9f1fdea\";
 
Name=\"i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-31T000015_4624.vib\"
 Type=\"BackupFileReference\" Rel=\"Alternate\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/1f6f5c49-92ef-4757-b327-e63ae9f1fdea/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\"/>\n" +
+                "      <Link 
Href=\"https://10.0.3.141:9398/api/backupFiles/1f6f5c49-92ef-4757-b327-e63ae9f1fdea/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\"/>\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275e4661e6\\i-2-5-VM-CSBKP-0d752ca6-d628-4d85-a739-75275eD2023-10-31T000015_4624.vib</FilePath>\n"
 +
+                "    <BackupSize>12460032</BackupSize>\n" +
+                "    <DataSize>72378524</DataSize>\n" +
+                "    <DeduplicationRatio>1</DeduplicationRatio>\n" +
+                "    <CompressRatio>6.67</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-10-31T00:00:15.853Z</CreationTimeUtc>\n" +
+                "    <FileType>vib</FileType>\n" +
+                "  </BackupFile>\n" +
+                "</BackupFiles>\n";
+
+        InputStream inputStream = new 
ByteArrayInputStream(xmlResponse.getBytes());
+        Map<String, Backup.Metric> metrics = 
client.processHttpResponseForBackupMetrics(inputStream);
+
+        verifyBackupMetrics(metrics);
+    }
+
+    @Test
+    public void testGetBackupMetricsViaVeeamAPI() {
+        String xmlResponse = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+                "<BackupFiles\n" +
+                "  xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"; +
+                "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"; +
+                "  xmlns=\"http://www.veeam.com/ent/v1.0\";>\n" +
+                "  <BackupFile 
Href=\"https://10.0.3.142:9398/api/backupFiles/6bf10cad-9181-45d9-9cc5-dd669366a381?format=Entity\";
 Type=\"BackupFile\" Name=\"i-2-4-VM.vm-1036D2023-11-03T162535_89D6.vbk\" 
UID=\"urn:veeam:BackupFile:6bf10cad-9181-45d9-9cc5-dd669366a381\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/backups/957d3817-2480-4c06-85f9-103e625c20e5\";
 Name=\"i-2-4-VM-CSBKP-506760dc-ed77-40d6-a91d-e0914e7a1ad8 - i-2-4-VM\" 
Type=\"BackupReference\" Rel=\"Up\" />\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/backupServers/18cc2a81-1ff0-42cd-8389-62f2bbcc6b7f\";
 Name=\"10.0.3.142\" Type=\"BackupServerReference\" Rel=\"Up\" />\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/backupFiles/6bf10cad-9181-45d9-9cc5-dd669366a381\";
 Name=\"i-2-4-VM.vm-1036D2023-11-03T162535_89D6.vbk\" 
Type=\"BackupFileReference\" Rel=\"Alternate\" />\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/backupFiles/6bf10cad-9181-45d9-9cc5-dd669366a381/restorePoints\";
 Type=\"RestorePointReferenceList\" Rel=\"Related\" />\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/backupFiles/6bf10cad-9181-45d9-9cc5-dd669366a381/vmRestorePoints\";
 Type=\"VmRestorePointReferenceList\" Rel=\"Down\" />\n" +
+                "    </Links>\n" +
+                "    
<FilePath>V:\\Backup\\i-2-4-VM-CSBKP-506760dc-ed77-40d6-a91d-e0914e7a1ad8\\i-2-4-VM.vm-1036D2023-11-03T162535_89D6.vbk</FilePath>\n"
 +
+                "    <BackupSize>535875584</BackupSize>\n" +
+                "    <DataSize>2147507235</DataSize>\n" +
+                "    <DeduplicationRatio>1.68</DeduplicationRatio>\n" +
+                "    <CompressRatio>2.38</CompressRatio>\n" +
+                "    
<CreationTimeUtc>2023-11-03T16:25:35.920773Z</CreationTimeUtc>\n" +
+                "    <FileType>vbk</FileType>\n" +
+                "  </BackupFile>\n" +
+                "</BackupFiles>";
+
+        wireMockRule.stubFor(get(urlMatching(".*/backupFiles\\?format=Entity"))
+                .willReturn(aResponse()
+                        .withHeader("content-type", "application/xml")
+                        .withStatus(200)
+                        .withBody(xmlResponse)));
+        Map<String, Backup.Metric> metrics = 
client.getBackupMetricsViaVeeamAPI();
+
+        Assert.assertEquals(1, metrics.size());
+        
Assert.assertTrue(metrics.containsKey("506760dc-ed77-40d6-a91d-e0914e7a1ad8"));
+        Assert.assertEquals(535875584L, (long) 
metrics.get("506760dc-ed77-40d6-a91d-e0914e7a1ad8").getBackupSize());
+        Assert.assertEquals(2147507235L, (long) 
metrics.get("506760dc-ed77-40d6-a91d-e0914e7a1ad8").getDataSize());
+    }
+
+    @Test
+    public void testListVmRestorePointsViaVeeamAPI() {
+        String xmlResponse = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+                "<VmRestorePoints\n" +
+                "  xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"; +
+                "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"; +
+                "  xmlns=\"http://www.veeam.com/ent/v1.0\";>\n" +
+                "  <VmRestorePoint 
Href=\"https://10.0.3.142:9398/api/vmRestorePoints/f6d504cf-eafe-4cd2-8dfc-e9cfe2f1e977?format=Entity\";
 Type=\"VmRestorePoint\" Name=\"i-2-4-VM@2023-11-03 16:26:12.209913\" 
UID=\"urn:veeam:VmRestorePoint:f6d504cf-eafe-4cd2-8dfc-e9cfe2f1e977\" 
VmDisplayName=\"i-2-4-VM\">\n" +
+                "    <Links>\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/vmRestorePoints/f6d504cf-eafe-4cd2-8dfc-e9cfe2f1e977?action=restore\";
 Rel=\"Restore\" />\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/backupServers/18cc2a81-1ff0-42cd-8389-62f2bbcc6b7f\";
 Name=\"10.0.3.142\" Type=\"BackupServerReference\" Rel=\"Up\" />\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/restorePoints/c030b23e-d7fa-45b6-a5a7-feb8525d2563\";
 Name=\"2023-11-03 16:25:35.920773\" Type=\"RestorePointReference\" Rel=\"Up\" 
/>\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/backupFiles/6bf10cad-9181-45d9-9cc5-dd669366a381\";
 Name=\"i-2-4-VM.vm-1036D2023-11-03T162535_89D6.vbk\" 
Type=\"BackupFileReference\" Rel=\"Up\" />\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/vmRestorePoints/f6d504cf-eafe-4cd2-8dfc-e9cfe2f1e977\";
 Name=\"i-2-4-VM@2023-11-03 16:26:12.209913\" Type=\"VmRestorePointReference\" 
Rel=\"Alternate\" />\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/vmRestorePoints/f6d504cf-eafe-4cd2-8dfc-e9cfe2f1e977/mounts\";
 Type=\"VmRestorePointMountList\" Rel=\"Down\" />\n" +
+                "      <Link 
Href=\"https://10.0.3.142:9398/api/vmRestorePoints/f6d504cf-eafe-4cd2-8dfc-e9cfe2f1e977/mounts\";
 Type=\"VmRestorePointMount\" Rel=\"Create\" />\n" +
+                "    </Links>\n" +
+                "    
<CreationTimeUTC>2023-11-03T16:26:12.209913Z</CreationTimeUTC>\n" +
+                "    <VmName>i-2-4-VM</VmName>\n" +
+                "    <Algorithm>Full</Algorithm>\n" +
+                "    <PointType>Full</PointType>\n" +
+                "    
<HierarchyObjRef>urn:VMware:Vm:adb5423b-b578-4c26-8ab8-cde9c1faec55.vm-1036</HierarchyObjRef>\n"
 +
+                "  </VmRestorePoint>\n" +
+                "</VmRestorePoints>\n";
+        String vmName = "i-2-4-VM";
+
+        
wireMockRule.stubFor(get(urlMatching(".*/vmRestorePoints\\?format=Entity"))
+                .willReturn(aResponse()
+                        .withHeader("content-type", "application/xml")
+                        .withStatus(200)
+                        .withBody(xmlResponse)));
+        List<Backup.RestorePoint> vmRestorePointList = 
client.listVmRestorePointsViaVeeamAPI(vmName);
+
+        Assert.assertEquals(1, vmRestorePointList.size());
+        Assert.assertEquals("f6d504cf-eafe-4cd2-8dfc-e9cfe2f1e977", 
vmRestorePointList.get(0).getId());
+        Assert.assertEquals("2023-11-03 16:26:12", 
vmRestorePointList.get(0).getCreated());
+        Assert.assertEquals("Full", vmRestorePointList.get(0).getType());
+    }
+
+    @Test
+    public void testGetVeeamServerVersionAllGood() {
+        Pair<Boolean, String> response = new Pair<Boolean, 
String>(Boolean.TRUE, "12.0.0.1");
+        
Mockito.doReturn(response).when(mockClient).executePowerShellCommands(Mockito.anyList());
+        Assert.assertEquals(12, (int) mockClient.getVeeamServerVersion());
+    }
+
+    @Test
+    public void testGetVeeamServerVersionWithError() {
+        Pair<Boolean, String> response = new Pair<Boolean, 
String>(Boolean.FALSE, "");
+        
Mockito.doReturn(response).when(mockClient).executePowerShellCommands(Mockito.anyList());
+        Assert.assertEquals(0, (int) mockClient.getVeeamServerVersion());
+    }
+
+    @Test
+    public void testGetVeeamServerVersionWithEmptyVersion() {
+        Pair<Boolean, String> response = new Pair<Boolean, 
String>(Boolean.TRUE, "");
+        
Mockito.doReturn(response).when(mockClient).executePowerShellCommands(Mockito.anyList());
+        Assert.assertEquals(0, (int) mockClient.getVeeamServerVersion());
+    }
 }
diff --git 
a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
 
b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
index db41ab19d56..aa3b314fb3f 100644
--- 
a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
+++ 
b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
@@ -781,8 +781,7 @@ public class VMwareGuru extends HypervisorGuruBase 
implements HypervisorGuru, Co
                 volume = createVolume(disk, vmToImport, domainId, zoneId, 
accountId, instanceId, poolId, templateId, backup, true);
                 operation = "created";
             }
-            s_logger.debug(String.format("VM [id: %s, instanceName: %s] backup 
restore operation %s volume [id: %s].", instanceId, 
vmInstanceVO.getInstanceName(),
-                    operation, volume.getUuid()));
+            s_logger.debug(String.format("Sync volumes to %s in backup restore 
operation: %s volume [id: %s].", vmInstanceVO, operation, volume.getUuid()));
         }
     }
 
@@ -879,9 +878,13 @@ public class VMwareGuru extends HypervisorGuruBase 
implements HypervisorGuru, Co
         String tag = parts[parts.length - 1];
         String[] tagSplit = tag.split("-");
         tag = tagSplit[tagSplit.length - 1];
+
+        s_logger.debug(String.format("Trying to find network with vlan: 
[%s].", vlan));
         NetworkVO networkVO = networkDao.findByVlan(vlan);
         if (networkVO == null) {
             networkVO = createNetworkRecord(zoneId, tag, vlan, accountId, 
domainId);
+            s_logger.debug(String.format("Created new network record [id: %s] 
with details [zoneId: %s, tag: %s, vlan: %s, accountId: %s and domainId: %s].",
+                    networkVO.getUuid(), zoneId, tag, vlan, accountId, 
domainId));
         }
         return networkVO;
     }
@@ -893,6 +896,7 @@ public class VMwareGuru extends HypervisorGuruBase 
implements HypervisorGuru, Co
         Map<String, NetworkVO> mapping = new HashMap<>();
         for (String networkName : vmNetworkNames) {
             NetworkVO networkVO = 
getGuestNetworkFromNetworkMorName(networkName, accountId, zoneId, domainId);
+            s_logger.debug(String.format("Mapping network name [%s] to 
networkVO [id: %s].", networkName, networkVO.getUuid()));
             mapping.put(networkName, networkVO);
         }
         return mapping;
@@ -927,12 +931,19 @@ public class VMwareGuru extends HypervisorGuruBase 
implements HypervisorGuru, Co
             String macAddress = pair.first();
             String networkName = pair.second();
             NetworkVO networkVO = networksMapping.get(networkName);
-            NicVO nicVO = 
nicDao.findByNetworkIdAndMacAddress(networkVO.getId(), macAddress);
+            NicVO nicVO = 
nicDao.findByNetworkIdAndMacAddressIncludingRemoved(networkVO.getId(), 
macAddress);
             if (nicVO != null) {
+                s_logger.warn(String.format("Find NIC in DB with networkId 
[%s] and MAC Address [%s], so this NIC will be removed from list of unmapped 
NICs of VM [id: %s, name: %s].",
+                        networkVO.getId(), macAddress, vm.getUuid(), 
vm.getInstanceName()));
                 allNics.remove(nicVO);
+
+                if (nicVO.getRemoved() != null) {
+                    nicDao.unremove(nicVO.getId());
+                }
             }
         }
         for (final NicVO unMappedNic : allNics) {
+            s_logger.debug(String.format("Removing NIC [%s] from backup 
restored %s.", 
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(unMappedNic, "uuid", 
"macAddress"), vm));
             vmManager.removeNicFromVm(vm, unMappedNic);
         }
     }
diff --git 
a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
 
b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
index 6af074222fa..22d0a796e14 100644
--- 
a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
+++ 
b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
@@ -49,6 +49,7 @@ import javax.naming.ConfigurationException;
 import javax.xml.datatype.XMLGregorianCalendar;
 
 import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.backup.PrepareForBackupRestorationCommand;
 import org.apache.cloudstack.storage.command.CopyCommand;
 import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
 import org.apache.cloudstack.storage.configdrive.ConfigDrive;
@@ -606,6 +607,8 @@ public class VmwareResource extends ServerResourceBase 
implements StoragePoolRes
                 answer = execute((GetVmVncTicketCommand) cmd);
             } else if (clz == GetAutoScaleMetricsCommand.class) {
                 answer = execute((GetAutoScaleMetricsCommand) cmd);
+            } else if (clz == PrepareForBackupRestorationCommand.class) {
+                answer = execute((PrepareForBackupRestorationCommand) cmd);
             } else {
                 answer = Answer.createUnsupportedCommandAnswer(cmd);
             }
@@ -7751,6 +7754,35 @@ public class VmwareResource extends ServerResourceBase 
implements StoragePoolRes
         }
     }
 
+    private Answer execute(PrepareForBackupRestorationCommand command) {
+        try {
+            VmwareHypervisorHost hyperHost = getHyperHost(getServiceContext());
+
+            String vmName = command.getVmName();
+            VirtualMachineMO vmMo = hyperHost.findVmOnHyperHost(vmName);
+
+            if (vmMo == null) {
+                if (hyperHost instanceof HostMO) {
+                    ClusterMO clusterMo = new 
ClusterMO(hyperHost.getContext(), ((HostMO) hyperHost).getParentMor());
+                    vmMo = clusterMo.findVmOnHyperHost(vmName);
+                }
+            }
+
+            if (vmMo == null) {
+                String msg = "VM " + vmName + " no longer exists to execute 
PrepareForBackupRestorationCommand command";
+                s_logger.error(msg);
+                throw new Exception(msg);
+            }
+
+            vmMo.removeChangeTrackPathFromVmdkForDisks();
+
+            return new Answer(command, true, "success");
+        } catch (Exception e) {
+            s_logger.error("Unexpected exception: ", e);
+            return new Answer(command, false, "Unable to execute 
PrepareForBackupRestorationCommand due to " + e.toString());
+        }
+    }
+
     private Integer getVmwareWindowTimeInterval() {
         Integer windowInterval = 
VmwareManager.VMWARE_STATS_TIME_WINDOW.value();
         if (windowInterval == null || windowInterval < 20) {
diff --git 
a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java 
b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java
index 4e18caa684b..bbdf730e06d 100644
--- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java
@@ -75,6 +75,7 @@ import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.event.ActionEvent;
 import com.cloud.event.ActionEventUtils;
 import com.cloud.event.EventTypes;
+import com.cloud.event.EventVO;
 import com.cloud.event.UsageEventUtils;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.PermissionDeniedException;
@@ -477,6 +478,11 @@ public class BackupManagerImpl extends ManagerBase 
implements BackupManager {
             throw new CloudRuntimeException("The assigned backup offering does 
not allow ad-hoc user backup");
         }
 
+        ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, 
vm.getAccountId(),
+                EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM 
ID:" + vm.getUuid(),
+                vmId, ApiCommandResourceType.VirtualMachine.toString(),
+                true, 0);
+
         final BackupProvider backupProvider = 
getBackupProvider(offering.getProvider());
         if (backupProvider != null && backupProvider.takeBackup(vm)) {
             return true;
@@ -555,10 +561,21 @@ public class BackupManagerImpl extends ManagerBase 
implements BackupManager {
         } catch (final Exception e) {
             LOG.error(String.format("Failed to import VM [vmInternalName: %s] 
from backup restoration [%s] with hypervisor [type: %s] due to: [%s].", 
vmInternalName,
                     
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backup, "id", "uuid", 
"vmId", "externalId", "backupType"), hypervisorType, e.getMessage()), e);
+            ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, 
vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
+                    String.format("Failed to import VM %s from backup %s with 
hypervisor [type: %s]", vmInternalName, backup.getUuid(), hypervisorType),
+                    vm.getId(), 
ApiCommandResourceType.VirtualMachine.toString(),0);
             throw new CloudRuntimeException("Error during vm backup 
restoration and import: " + e.getMessage());
         }
         if (vm == null) {
-            LOG.error("Failed to import restored VM " + vmInternalName + " 
with hypervisor type " + hypervisorType + " using backup of VM ID " + 
backup.getVmId());
+            String message = String.format("Failed to import restored VM %s  
with hypervisor type %s using backup of VM ID %s",
+                    vmInternalName, hypervisorType, backup.getVmId());
+            LOG.error(message);
+            ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, 
vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
+                    message, vm.getId(), 
ApiCommandResourceType.VirtualMachine.toString(),0);
+        } else {
+            ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, 
vm.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_BACKUP_RESTORE,
+                    String.format("Restored VM %s from backup %s", 
vm.getUuid(), backup.getUuid()),
+                    vm.getId(), 
ApiCommandResourceType.VirtualMachine.toString(),0);
         }
         return vm != null;
     }
@@ -588,9 +605,17 @@ public class BackupManagerImpl extends ManagerBase 
implements BackupManager {
             throw new CloudRuntimeException("Failed to find backup offering of 
the VM backup");
         }
 
+        ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, 
vm.getAccountId(), EventTypes.EVENT_VM_BACKUP_RESTORE,
+                String.format("Restoring VM %s from backup %s", vm.getUuid(), 
backup.getUuid()),
+                vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),
+                true, 0);
+
         final BackupProvider backupProvider = 
getBackupProvider(offering.getProvider());
         if (!backupProvider.restoreVMFromBackup(vm, backup)) {
-            throw new CloudRuntimeException("Error restoring VM from backup ID 
" + backup.getId());
+            ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, 
vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
+                    String.format("Failed to restore VM %s from backup %s", 
vm.getInstanceName(), backup.getUuid()),
+                    vm.getId(), 
ApiCommandResourceType.VirtualMachine.toString(),0);
+            throw new CloudRuntimeException("Error restoring VM from backup 
with uuid " + backup.getUuid());
         }
         return importRestoredVM(vm.getDataCenterId(), vm.getDomainId(), 
vm.getAccountId(), vm.getUserId(),
                 vm.getInstanceName(), vm.getHypervisorType(), backup);
diff --git a/test/integration/smoke/test_backup_recovery_veeam.py 
b/test/integration/smoke/test_backup_recovery_veeam.py
new file mode 100644
index 00000000000..d0da66fa7c2
--- /dev/null
+++ b/test/integration/smoke/test_backup_recovery_veeam.py
@@ -0,0 +1,302 @@
+#!/usr/bin/env python
+# 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.
+
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.utils import wait_until
+from marvin.lib.base import (Account, ServiceOffering, DiskOffering, Volume, 
VirtualMachine,
+                             BackupOffering, Configurations, Backup, 
BackupSchedule)
+from marvin.lib.common import (get_domain, get_zone, get_template)
+from nose.plugins.attrib import attr
+from marvin.codes import FAILED
+
+import time
+
+class TestVeeamBackupAndRecovery(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        # Setup
+
+        cls.testClient = super(TestVeeamBackupAndRecovery, 
cls).getClsTestClient()
+        cls.apiclient = cls.testClient.getApiClient()
+        cls.services = cls.testClient.getParsedTestDataConfig()
+        cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+        cls.services["mode"] = cls.zone.networktype
+        cls.hypervisor = cls.testClient.getHypervisorInfo()
+        cls.domain = get_domain(cls.apiclient)
+        cls.template = get_template(cls.apiclient, cls.zone.id, 
cls.services["ostype"])
+        if cls.template == FAILED:
+            assert False, "get_template() failed to return template with 
description %s" % cls.services["ostype"]
+        cls.services["small"]["zoneid"] = cls.zone.id
+        cls.services["small"]["template"] = cls.template.id
+        cls._cleanup = []
+
+        # Check backup configuration values, set them to enable the veeam 
provider
+        backup_enabled_cfg = Configurations.list(cls.apiclient, 
name='backup.framework.enabled', zoneid=cls.zone.id)
+        backup_provider_cfg = Configurations.list(cls.apiclient, 
name='backup.framework.provider.plugin', zoneid=cls.zone.id)
+        cls.backup_enabled = backup_enabled_cfg[0].value
+        cls.backup_provider = backup_provider_cfg[0].value
+
+        if cls.backup_enabled == "false":
+            Configurations.update(cls.apiclient, 'backup.framework.enabled', 
value='true', zoneid=cls.zone.id)
+        if cls.backup_provider != "veeam":
+            return
+
+        if cls.hypervisor.lower() != 'vmware':
+            return
+
+        cls.service_offering = ServiceOffering.create(cls.apiclient, 
cls.services["service_offerings"]["small"])
+        cls._cleanup.append(cls.service_offering)
+        cls.disk_offering = DiskOffering.create(cls.apiclient, 
cls.services["disk_offering"])
+        cls._cleanup.append(cls.disk_offering)
+
+    @classmethod
+    def isBackupOfferingUsed(cls, existing_offerings, provider_offering):
+        if not existing_offerings:
+            return False
+        for existing_offering in existing_offerings:
+            if existing_offering.externalid == provider_offering.externalid:
+                return True
+        return False
+
+    def waitForBackUp(self, vm):
+        def checkBackUp():
+            backups = Backup.list(self.user_apiclient, vm.id)
+            if isinstance(backups, list) and len(backups) != 0:
+                return True, None
+            return False, None
+
+        res, _ = wait_until(10, 60, checkBackUp)
+        if not res:
+            self.fail("Failed to wait for backup of VM %s to be Up" % vm.id)
+
+    @classmethod
+    def tearDownClass(cls):
+        if cls.backup_enabled == "false":
+            Configurations.update(cls.apiclient, 'backup.framework.enabled', 
value=cls.backup_enabled, zoneid=cls.zone.id)
+        super(TestVeeamBackupAndRecovery, cls).tearDownClass()
+
+    def setUp(self):
+        if self.backup_provider != "veeam":
+            raise self.skipTest("Skipping test cases which must only run for 
veeam")
+        if self.hypervisor.lower() != 'vmware':
+            raise self.skipTest("Skipping test cases which must only run for 
VMware")
+        self.cleanup = []
+
+        # Import backup offering
+        self.offering = None
+        existing_offerings = BackupOffering.listByZone(self.apiclient, 
self.zone.id)
+        provider_offerings = BackupOffering.listExternal(self.apiclient, 
self.zone.id)
+        if not provider_offerings:
+            self.skipTest("Skipping test cases as the provider offering is 
None")
+        for provider_offering in provider_offerings:
+            if not self.isBackupOfferingUsed(existing_offerings, 
provider_offering):
+                self.debug("Importing backup offering %s - %s" % 
(provider_offering.externalid, provider_offering.name))
+                self.offering = BackupOffering.importExisting(self.apiclient, 
self.zone.id, provider_offering.externalid,
+                                                         
provider_offering.name, provider_offering.description)
+                if not self.offering:
+                    self.fail("Failed to import backup offering %s" % 
provider_offering.name)
+                break
+        if not self.offering:
+            self.skipTest("Skipping test cases as there is no available 
provider offerings to import")
+
+        # Create user account
+        self.account = Account.create(self.apiclient, 
self.services["account"], domainid=self.domain.id)
+        self.user_user = self.account.user[0]
+        self.user_apiclient = self.testClient.getUserApiClient(
+            self.user_user.username, self.domain.name
+        )
+        self.cleanup.append(self.account)
+
+    def tearDown(self):
+        super(TestVeeamBackupAndRecovery, self).tearDown()
+
+    @attr(tags=["advanced", "backup"], required_hardware="false")
+    def test_01_import_list_delete_backup_offering(self):
+        """
+        Import provider backup offering from Veeam Backup and Recovery Provider
+        """
+
+        # Verify offering is listed by user
+        imported_offering = BackupOffering.listByZone(self.user_apiclient, 
self.zone.id)
+        self.assertIsInstance(imported_offering, list, "List Backup Offerings 
should return a valid response")
+        self.assertNotEqual(len(imported_offering), 0, "Check if the list API 
returns a non-empty response")
+        matching_offerings = [x for x in imported_offering if x.id == 
self.offering.id]
+        self.assertNotEqual(len(matching_offerings), 0, "Check if there is a 
matching offering")
+
+        # Delete backup offering
+        self.debug("Deleting backup offering %s" % self.offering.id)
+        self.offering.delete(self.apiclient)
+
+        #  Verify offering is not listed by user
+        imported_offering = BackupOffering.listByZone(self.user_apiclient, 
self.zone.id)
+        if imported_offering:
+            self.assertIsInstance(imported_offering, list, "List Backup 
Offerings should return a valid response")
+            matching_offerings = [x for x in imported_offering if x.id == 
self.offering.id]
+            self.assertEqual(len(matching_offerings), 0, "Check there is not a 
matching offering")
+
+    @attr(tags=["advanced", "backup"], required_hardware="false")
+    def test_02_vm_backup_lifecycle(self):
+        """
+        Test VM backup lifecycle
+        """
+
+        if self.offering:
+            self.cleanup.insert(0, self.offering)
+
+        self.vm = VirtualMachine.create(self.user_apiclient, 
self.services["small"], accountid=self.account.name,
+                                       domainid=self.account.domainid, 
serviceofferingid=self.service_offering.id,
+                                       diskofferingid=self.disk_offering.id)
+
+        # Verify there are no backups for the VM
+        backups = Backup.list(self.user_apiclient, self.vm.id)
+        self.assertEqual(backups, None, "There should not exist any backup for 
the VM")
+
+        # Assign VM to offering and create ad-hoc backup
+        self.offering.assignOffering(self.user_apiclient, self.vm.id)
+        vms = VirtualMachine.list(
+            self.user_apiclient,
+            id=self.vm.id,
+            listall=True
+        )
+        self.assertEqual(
+            isinstance(vms, list),
+            True,
+            "List virtual machines should return a valid list"
+        )
+        self.assertEqual(1, len(vms), "List of the virtual machines should 
have 1 vm")
+        self.assertEqual(self.offering.id, vms[0].backupofferingid, "The 
virtual machine should have backup offering %s" % self.offering.id)
+
+        # Create backup schedule on 01:00AM every Sunday
+        BackupSchedule.create(self.user_apiclient, self.vm.id, 
intervaltype="WEEKLY", timezone="CET", schedule="00:01:1")
+        backupSchedule = BackupSchedule.list(self.user_apiclient, self.vm.id)
+        self.assertIsNotNone(backupSchedule)
+        self.assertEqual("WEEKLY", backupSchedule.intervaltype)
+        self.assertEqual("00:01:1", backupSchedule.schedule)
+        self.assertEqual("CET", backupSchedule.timezone)
+        self.assertEqual(self.vm.id, backupSchedule.virtualmachineid)
+        self.assertEqual(self.vm.name, backupSchedule.virtualmachinename)
+
+        # Update backup schedule on 02:00AM every 20th
+        BackupSchedule.update(self.user_apiclient, self.vm.id, 
intervaltype="MONTHLY", timezone="CET", schedule="00:02:20")
+        backupSchedule = BackupSchedule.list(self.user_apiclient, self.vm.id)
+        self.assertIsNotNone(backupSchedule)
+        self.assertEqual("MONTHLY", backupSchedule.intervaltype)
+        self.assertEqual("00:02:20", backupSchedule.schedule)
+
+        # Delete backup schedule
+        BackupSchedule.delete(self.user_apiclient, self.vm.id)
+
+        # Create backup
+        Backup.create(self.user_apiclient, self.vm.id)
+
+        # Verify backup is created for the VM
+        self.waitForBackUp(self.vm)
+        backups = Backup.list(self.user_apiclient, self.vm.id)
+        self.assertEqual(len(backups), 1, "There should exist only one backup 
for the VM")
+        backup = backups[0]
+
+        # Stop VM
+        self.vm.stop(self.user_apiclient, forced=True)
+        # Restore backup
+        Backup.restoreVM(self.user_apiclient, backup.id)
+
+        # Delete backup
+        Backup.delete(self.user_apiclient, backup.id, forced=True)
+
+        # Verify backup is deleted
+        backups = Backup.list(self.user_apiclient, self.vm.id)
+        self.assertEqual(backups, None, "There should not exist any backup for 
the VM")
+
+        # Remove VM from offering
+        self.offering.removeOffering(self.user_apiclient, self.vm.id)
+
+    @attr(tags=["advanced", "backup"], required_hardware="false")
+    def test_03_restore_volume_attach_vm(self):
+        """
+        Test Volume Restore from Backup and Attach to VM
+        """
+
+        if self.offering:
+            self.cleanup.insert(0, self.offering)
+
+        self.vm = VirtualMachine.create(self.user_apiclient, 
self.services["small"], accountid=self.account.name,
+                                                      
domainid=self.account.domainid, serviceofferingid=self.service_offering.id)
+
+        self.vm_with_datadisk = VirtualMachine.create(self.user_apiclient, 
self.services["small"], accountid=self.account.name,
+                                                      
domainid=self.account.domainid, serviceofferingid=self.service_offering.id,
+                                                      
diskofferingid=self.disk_offering.id)
+
+        # Assign VM to offering and create ad-hoc backup
+        self.offering.assignOffering(self.user_apiclient, 
self.vm_with_datadisk.id)
+
+        # Create backup
+        Backup.create(self.user_apiclient, self.vm_with_datadisk.id)
+
+        # Verify backup is created for the VM with datadisk
+        self.waitForBackUp(self.vm_with_datadisk)
+        backups = Backup.list(self.user_apiclient, self.vm_with_datadisk.id)
+        self.assertEqual(len(backups), 1, "There should exist only one backup 
for the VM with datadisk")
+        backup = backups[0]
+
+        try:
+            volumes = Volume.list(
+                self.user_apiclient,
+                virtualmachineid=self.vm_with_datadisk.id,
+                listall=True
+            )
+            rootDiskId = None
+            dataDiskId = None
+            for volume in volumes:
+                if volume.type == 'ROOT':
+                    rootDiskId = volume.id
+                elif volume.type == 'DATADISK':
+                    dataDiskId = volume.id
+            if rootDiskId:
+                # Restore ROOT volume of vm_with_datadisk and attach to vm
+                Backup.restoreVolumeFromBackupAndAttachToVM(
+                    self.user_apiclient,
+                    backupid=backup.id,
+                    volumeid=rootDiskId,
+                    virtualmachineid=self.vm.id
+                )
+                vm_volumes = Volume.list(
+                    self.user_apiclient,
+                    virtualmachineid=self.vm.id,
+                    listall=True
+                )
+                self.assertTrue(isinstance(vm_volumes, list), "List volumes 
should return a valid list")
+                self.assertEqual(2, len(vm_volumes), "The number of volumes 
should be 2")
+            if dataDiskId:
+                # Restore DATADISK volume of vm_with_datadisk and attach to vm
+                Backup.restoreVolumeFromBackupAndAttachToVM(
+                    self.user_apiclient,
+                    backupid=backup.id,
+                    volumeid=dataDiskId,
+                    virtualmachineid=self.vm.id
+                )
+                vm_volumes = Volume.list(
+                    self.user_apiclient,
+                    virtualmachineid=self.vm.id,
+                    listall=True
+                )
+                self.assertTrue(isinstance(vm_volumes, list), "List volumes 
should return a valid list")
+                self.assertEqual(3, len(vm_volumes), "The number of volumes 
should be 2")
+        finally:
+            # Delete backup
+            Backup.delete(self.user_apiclient, backup.id, forced=True)
diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py
index bf8a6a761b5..68595a01058 100755
--- a/tools/marvin/marvin/lib/base.py
+++ b/tools/marvin/marvin/lib/base.py
@@ -5916,8 +5916,10 @@ class ResourceDetails:
         cmd.resourcetype = resourcetype
         return (apiclient.removeResourceDetail(cmd))
 
+
 # Backup and Recovery
 
+
 class BackupOffering:
 
     def __init__(self, items):
@@ -5982,6 +5984,7 @@ class BackupOffering:
         cmd.forced = forced
         return (apiclient.removeVirtualMachineFromBackupOffering(cmd))
 
+
 class Backup:
 
     def __init__(self, items):
@@ -5993,14 +5996,16 @@ class Backup:
 
         cmd = createBackup.createBackupCmd()
         cmd.virtualmachineid = vmid
-        return (apiclient.createBackup(cmd))
+        return Backup(apiclient.createBackup(cmd).__dict__)
 
     @classmethod
-    def delete(self, apiclient, id):
+    def delete(self, apiclient, id, forced=None):
         """Delete VM backup"""
 
         cmd = deleteBackup.deleteBackupCmd()
         cmd.id = id
+        if forced:
+            cmd.forced = forced
         return (apiclient.deleteBackup(cmd))
 
     @classmethod
@@ -6012,13 +6017,66 @@ class Backup:
         cmd.listall = True
         return (apiclient.listBackups(cmd))
 
-    def restoreVM(self, apiclient):
+    @classmethod
+    def restoreVM(self, apiclient, backupid):
         """Restore VM from backup"""
 
         cmd = restoreBackup.restoreBackupCmd()
-        cmd.id = self.id
+        cmd.id = backupid
         return (apiclient.restoreBackup(cmd))
 
+    @classmethod
+    def restoreVolumeFromBackupAndAttachToVM(self, apiclient, backupid, 
volumeid, virtualmachineid):
+        """Restore VM from backup"""
+
+        cmd = 
restoreVolumeFromBackupAndAttachToVM.restoreVolumeFromBackupAndAttachToVMCmd()
+        cmd.backupid = backupid
+        cmd.volumeid = volumeid
+        cmd.virtualmachineid = virtualmachineid
+        return (apiclient.restoreVolumeFromBackupAndAttachToVM(cmd))
+
+
+class BackupSchedule:
+
+    def __init__(self, items):
+        self.__dict__.update(items)
+
+    @classmethod
+    def create(self, apiclient, vmid, **kwargs):
+        """Create VM backup schedule"""
+
+        cmd = createBackupSchedule.createBackupScheduleCmd()
+        cmd.virtualmachineid = vmid
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        return BackupSchedule(apiclient.createBackupSchedule(cmd).__dict__)
+
+    @classmethod
+    def delete(self, apiclient, vmid):
+        """Delete VM backup schedule"""
+
+        cmd = deleteBackupSchedule.deleteBackupScheduleCmd()
+        cmd.virtualmachineid = vmid
+        return (apiclient.deleteBackupSchedule(cmd))
+
+    @classmethod
+    def list(self, apiclient, vmid):
+        """List VM backup schedule"""
+
+        cmd = listBackupSchedule.listBackupScheduleCmd()
+        cmd.virtualmachineid = vmid
+        cmd.listall = True
+        return (apiclient.listBackupSchedule(cmd))
+
+    @classmethod
+    def update(self, apiclient, vmid, **kwargs):
+        """Update VM backup schedule"""
+
+        cmd = updateBackupSchedule.updateBackupScheduleCmd()
+        cmd.virtualmachineid = vmid
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        return (apiclient.updateBackupSchedule(cmd))
+
+
 class ProjectRole:
 
     def __init__(self, items):
diff --git a/ui/src/components/view/ListView.vue 
b/ui/src/components/view/ListView.vue
index 286d9d16d2a..1afeae9c4a1 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -577,7 +577,7 @@ export default {
     },
     enableGroupAction () {
       return ['vm', 'alert', 'vmgroup', 'ssh', 'userdata', 'affinitygroup', 
'autoscalevmgroup', 'volume', 'snapshot',
-        'vmsnapshot', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 
'vpncustomergateway',
+        'vmsnapshot', 'backup', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 
'vpncustomergateway',
         'project', 'account', 'systemvm', 'router', 'computeoffering', 
'systemoffering',
         'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 
'ilbvm', 'kubernetes', 'comment'
       ].includes(this.$route.name)
diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js
index 007bd8c3f9d..0ef53012ba0 100644
--- a/ui/src/config/section/compute.js
+++ b/ui/src/config/section/compute.js
@@ -32,9 +32,9 @@ export default {
       permission: ['listVirtualMachinesMetrics'],
       resourceType: 'UserVm',
       params: () => {
-        var params = { details: 'servoff,tmpl,nics' }
+        var params = { details: 'servoff,tmpl,nics,backoff' }
         if (store.getters.metrics) {
-          params = { details: 'servoff,tmpl,nics,stats' }
+          params = { details: 'servoff,tmpl,nics,backoff,stats' }
         }
         return params
       },
diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js
index 1cf31350fb2..d73b989f74e 100644
--- a/ui/src/config/section/storage.js
+++ b/ui/src/config/section/storage.js
@@ -488,7 +488,11 @@ export default {
           label: 'label.delete.backup',
           message: 'message.delete.backup',
           dataView: true,
-          show: (record) => { return record.state !== 'Destroyed' }
+          show: (record) => { return record.state !== 'Destroyed' },
+          groupAction: true,
+          popup: true,
+          groupMap: (selection, values) => { return selection.map(x => { 
return { id: x, forced: values.forced } }) },
+          args: ['forced']
         }
       ]
     }
diff --git a/ui/src/views/compute/backup/BackupSchedule.vue 
b/ui/src/views/compute/backup/BackupSchedule.vue
index 914a1121ffb..32da2d440a7 100644
--- a/ui/src/views/compute/backup/BackupSchedule.vue
+++ b/ui/src/views/compute/backup/BackupSchedule.vue
@@ -40,6 +40,9 @@
           </span>
         </label>
       </template>
+      <template #intervaltype="{ text, record }" :name="text">
+        <label>{{ record.intervaltype }}</label>
+      </template>
       <template #time="{ text, record }" :name="text">
         <label class="interval-content">
           <span v-if="record.intervaltype==='HOURLY'">{{ record.schedule + ' ' 
+ $t('label.min.past.hour') }}</span>
@@ -112,6 +115,11 @@ export default {
           width: 30,
           slots: { customRender: 'icon' }
         },
+        {
+          title: this.$t('label.intervaltype'),
+          dataIndex: 'intervaltype',
+          slots: { customRender: 'intervaltype' }
+        },
         {
           title: this.$t('label.time'),
           dataIndex: 'schedule',
diff --git 
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
 
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
index cecdb4700dd..f2e247c2a24 100644
--- 
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
+++ 
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
@@ -3736,4 +3736,29 @@ public class VirtualMachineMO extends BaseMO {
         String workerTag = String.format("%d-%s", System.currentTimeMillis(), 
getContext().getStockObject("noderuninfo"));
         setCustomFieldValue(CustomFieldConstants.CLOUD_WORKER_TAG, workerTag);
     }
+
+    public void removeChangeTrackPathFromVmdkForDisks() throws Exception {
+        VirtualDisk[] disks = getAllDiskDevice();
+        for (int i = 0; i < disks.length; i++) {
+            VirtualDisk disk = disks[i];
+            VirtualDeviceBackingInfo backingInfo = disk.getBacking();
+            if (!(backingInfo instanceof VirtualDiskFlatVer2BackingInfo)) {
+                throw new Exception("Unsupported VirtualDeviceBackingInfo");
+            }
+            VirtualDiskFlatVer2BackingInfo diskBackingInfo = 
(VirtualDiskFlatVer2BackingInfo)backingInfo;
+            s_logger.info("Removing property ChangeTrackPath from VMDK content 
file " + diskBackingInfo.getFileName());
+            Pair<VmdkFileDescriptor, byte[]> vmdkInfo = 
getVmdkFileInfo(diskBackingInfo.getFileName());
+            VmdkFileDescriptor vmdkFileDescriptor = vmdkInfo.first();
+            byte[] content = vmdkInfo.second();
+            if (content == null || content.length == 0) {
+                break;
+            }
+            byte[] newVmdkContent = 
vmdkFileDescriptor.removeChangeTrackPath(content);
+
+            Pair<DatacenterMO, String> dcPair = getOwnerDatacenter();
+            String vmdkUrl = 
getContext().composeDatastoreBrowseUrl(dcPair.second(), 
diskBackingInfo.getFileName());
+            getContext().uploadResourceContent(vmdkUrl, newVmdkContent);
+            s_logger.info("Removed property ChangeTrackPath from VMDK content 
file " + diskBackingInfo.getFileName());
+        }
+    }
 }
diff --git 
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmdkFileDescriptor.java
 
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmdkFileDescriptor.java
index 7ede78f1d6f..26a8db6aa6f 100644
--- 
a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmdkFileDescriptor.java
+++ 
b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmdkFileDescriptor.java
@@ -33,6 +33,8 @@ public class VmdkFileDescriptor {
     private static final String VMDK_CREATE_TYPE_VMFSSPARSE = "vmfsSparse";
     private static final String VMDK_CREATE_TYPE_SESPARSE = "SEsparse";
     private static final String VMDK_PROPERTY_ADAPTER_TYPE = "ddb.adapterType";
+    private static final String VMDK_PROPERTY_CHANGE_TRACK_PATH = 
"changeTrackPath";
+    private static final String VMDK_PROPERTY_CHANGE_TRACK_PATH_COMMENT = "# 
Change Tracking File";
 
     private Properties _properties = new Properties();
     private String _baseFileName;
@@ -225,4 +227,61 @@ public class VmdkFileDescriptor {
 
         return bos.toByteArray();
     }
+
+    public static byte[] removeChangeTrackPath(byte[] vmdkContent) throws 
IOException {
+        assert (vmdkContent != null);
+
+        BufferedReader in = null;
+        BufferedWriter out = null;
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+        try {
+            in = new BufferedReader(new InputStreamReader(new 
ByteArrayInputStream(vmdkContent)));
+            out = new BufferedWriter(new OutputStreamWriter(bos));
+            String line;
+            while ((line = in.readLine()) != null) {
+                // ignore empty and comment lines
+                line = line.trim();
+                if (line.isEmpty()) {
+                    out.newLine();
+                    continue;
+                }
+                if (line.equals(VMDK_PROPERTY_CHANGE_TRACK_PATH_COMMENT)) {
+                    s_logger.debug("Removed line from vmdk: " + line);
+                    continue;
+                }
+                if (line.charAt(0) == '#') {
+                    out.write(line);
+                    out.newLine();
+                    continue;
+                }
+
+                String[] tokens = line.split("=");
+                if (tokens.length == 2) {
+                    String name = tokens[0].trim();
+                    String value = tokens[1].trim();
+                    if (value.charAt(0) == '\"')
+                        value = value.substring(1, value.length() - 1);
+
+                    if (name.equals(VMDK_PROPERTY_CHANGE_TRACK_PATH)) {
+                        s_logger.debug("Removed line from vmdk: " + line);
+                    } else {
+                        out.write(line);
+                        out.newLine();
+                    }
+                } else {
+                    out.write(line);
+                    out.newLine();
+                }
+            }
+        } finally {
+            if (in != null)
+                in.close();
+            if (out != null)
+                out.close();
+        }
+
+        return bos.toByteArray();
+
+    }
 }

Reply via email to