mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge remote-tracking branch 'apache/4.18' into 4.19
This commit is contained in:
commit
b8904f75dd
@ -96,8 +96,8 @@ public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements
|
|||||||
@Parameter(name = ApiConstants.DETAILS,
|
@Parameter(name = ApiConstants.DETAILS,
|
||||||
type = CommandType.LIST,
|
type = CommandType.LIST,
|
||||||
collectionType = CommandType.STRING,
|
collectionType = CommandType.STRING,
|
||||||
description = "comma separated list of host details requested, "
|
description = "comma separated list of vm details requested, "
|
||||||
+ "value can be a list of [all, group, nics, stats, secgrp, tmpl, servoff, diskoff, iso, volume, min, affgrp]."
|
+ "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")
|
+ " If no parameter is passed in, the details will be defaulted to all")
|
||||||
private List<String> viewDetails;
|
private List<String> viewDetails;
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package org.apache.cloudstack.backup;
|
package org.apache.cloudstack.backup;
|
||||||
|
|
||||||
|
import com.cloud.utils.db.GenericDao;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -55,6 +57,9 @@ public class BackupVO implements Backup {
|
|||||||
@Temporal(value = TemporalType.DATE)
|
@Temporal(value = TemporalType.DATE)
|
||||||
private Date date;
|
private Date date;
|
||||||
|
|
||||||
|
@Column(name = GenericDao.REMOVED_COLUMN)
|
||||||
|
private Date removed;
|
||||||
|
|
||||||
@Column(name = "size")
|
@Column(name = "size")
|
||||||
private Long size;
|
private Long size;
|
||||||
|
|
||||||
@ -196,4 +201,12 @@ public class BackupVO implements Backup {
|
|||||||
public String getName() {
|
public String getName() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Date getRemoved() {
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemoved(Date removed) {
|
||||||
|
this.removed = removed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,21 @@
|
|||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<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>
|
<dependency>
|
||||||
<groupId>org.apache.cloudstack</groupId>
|
<groupId>org.apache.cloudstack</groupId>
|
||||||
<artifactId>cloud-plugin-hypervisor-vmware</artifactId>
|
<artifactId>cloud-plugin-hypervisor-vmware</artifactId>
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||||
import org.apache.cloudstack.api.InternalIdentity;
|
import org.apache.cloudstack.api.InternalIdentity;
|
||||||
import org.apache.cloudstack.backup.Backup.Metric;
|
import org.apache.cloudstack.backup.Backup.Metric;
|
||||||
import org.apache.cloudstack.backup.dao.BackupDao;
|
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.commons.lang3.BooleanUtils;
|
||||||
import org.apache.log4j.Logger;
|
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.Hypervisor;
|
||||||
import com.cloud.dc.VmwareDatacenter;
|
import com.cloud.dc.VmwareDatacenter;
|
||||||
import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap;
|
import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap;
|
||||||
import com.cloud.dc.dao.VmwareDatacenterDao;
|
import com.cloud.dc.dao.VmwareDatacenterDao;
|
||||||
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
|
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
|
||||||
|
import com.cloud.user.User;
|
||||||
import com.cloud.utils.Pair;
|
import com.cloud.utils.Pair;
|
||||||
import com.cloud.utils.component.AdapterBase;
|
import com.cloud.utils.component.AdapterBase;
|
||||||
import com.cloud.utils.db.Transaction;
|
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.utils.exception.CloudRuntimeException;
|
||||||
import com.cloud.vm.VMInstanceVO;
|
import com.cloud.vm.VMInstanceVO;
|
||||||
import com.cloud.vm.VirtualMachine;
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import com.cloud.vm.VirtualMachineManager;
|
||||||
import com.cloud.vm.dao.VMInstanceDao;
|
import com.cloud.vm.dao.VMInstanceDao;
|
||||||
|
|
||||||
public class VeeamBackupProvider extends AdapterBase implements BackupProvider, Configurable {
|
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/",
|
"backup.plugin.veeam.url", "https://localhost:9398/api/",
|
||||||
"The Veeam backup and recovery URL.", true, ConfigKey.Scope.Zone);
|
"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,
|
private ConfigKey<String> VeeamUsername = new ConfigKey<>("Advanced", String.class,
|
||||||
"backup.plugin.veeam.username", "administrator",
|
"backup.plugin.veeam.username", "administrator",
|
||||||
"The Veeam backup and recovery username.", true, ConfigKey.Scope.Zone);
|
"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",
|
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);
|
"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
|
@Inject
|
||||||
private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao;
|
private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao;
|
||||||
@Inject
|
@Inject
|
||||||
@ -89,11 +107,16 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
|
|||||||
private BackupDao backupDao;
|
private BackupDao backupDao;
|
||||||
@Inject
|
@Inject
|
||||||
private VMInstanceDao vmInstanceDao;
|
private VMInstanceDao vmInstanceDao;
|
||||||
|
@Inject
|
||||||
|
private AgentManager agentMgr;
|
||||||
|
@Inject
|
||||||
|
private VirtualMachineManager virtualMachineManager;
|
||||||
|
|
||||||
protected VeeamClient getClient(final Long zoneId) {
|
protected VeeamClient getClient(final Long zoneId) {
|
||||||
try {
|
try {
|
||||||
return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.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));
|
VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId),
|
||||||
|
VeeamTaskPollInterval.valueIn(zoneId), VeeamTaskPollMaxRetry.valueIn(zoneId));
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage());
|
throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage());
|
||||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
@ -189,6 +212,7 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
|
|||||||
LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName);
|
LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName);
|
||||||
throw new CloudRuntimeException("Failed to delete Veeam B&R job and backup, an operation may be in progress. Please try again after some time.");
|
throw new CloudRuntimeException("Failed to delete Veeam B&R job and backup, an operation may be in progress. Please try again after some time.");
|
||||||
}
|
}
|
||||||
|
client.syncBackupRepository();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +246,8 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.syncBackupRepository();
|
||||||
|
|
||||||
List<Backup> allBackups = backupDao.listByVmId(backup.getZoneId(), backup.getVmId());
|
List<Backup> allBackups = backupDao.listByVmId(backup.getZoneId(), backup.getVmId());
|
||||||
for (Backup b : allBackups) {
|
for (Backup b : allBackups) {
|
||||||
if (b.getId() != backup.getId()) {
|
if (b.getId() != backup.getId()) {
|
||||||
@ -234,7 +260,36 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
|
|||||||
@Override
|
@Override
|
||||||
public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) {
|
public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) {
|
||||||
final String restorePointId = backup.getExternalId();
|
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
|
@Override
|
||||||
@ -330,6 +385,10 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
|
|||||||
+ "domain_id: %s, zone_id: %s].", backup.getUuid(), backup.getVmId(), backup.getExternalId(), backup.getType(), backup.getDate(),
|
+ "domain_id: %s, zone_id: %s].", backup.getUuid(), backup.getVmId(), backup.getExternalId(), backup.getType(), backup.getDate(),
|
||||||
backup.getBackupOfferingId(), backup.getAccountId(), backup.getDomainId(), backup.getZoneId()));
|
backup.getBackupOfferingId(), backup.getAccountId(), backup.getDomainId(), backup.getZoneId()));
|
||||||
backupDao.persist(backup);
|
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) {
|
for (final Long backupIdToRemove : removeList) {
|
||||||
@ -349,11 +408,14 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
|
|||||||
public ConfigKey<?>[] getConfigKeys() {
|
public ConfigKey<?>[] getConfigKeys() {
|
||||||
return new ConfigKey[]{
|
return new ConfigKey[]{
|
||||||
VeeamUrl,
|
VeeamUrl,
|
||||||
|
VeeamVersion,
|
||||||
VeeamUsername,
|
VeeamUsername,
|
||||||
VeeamPassword,
|
VeeamPassword,
|
||||||
VeeamValidateSSLSecurity,
|
VeeamValidateSSLSecurity,
|
||||||
VeeamApiRequestTimeout,
|
VeeamApiRequestTimeout,
|
||||||
VeeamRestoreTimeout
|
VeeamRestoreTimeout,
|
||||||
|
VeeamTaskPollInterval,
|
||||||
|
VeeamTaskPollMaxRetry
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,12 +20,15 @@ package org.apache.cloudstack.backup.veeam;
|
|||||||
import static org.apache.cloudstack.backup.VeeamBackupProvider.BACKUP_IDENTIFIER;
|
import static org.apache.cloudstack.backup.VeeamBackupProvider.BACKUP_IDENTIFIER;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
@ -44,6 +47,8 @@ import org.apache.cloudstack.api.ApiErrorCode;
|
|||||||
import org.apache.cloudstack.api.ServerApiException;
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
import org.apache.cloudstack.backup.Backup;
|
import org.apache.cloudstack.backup.Backup;
|
||||||
import org.apache.cloudstack.backup.BackupOffering;
|
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.BackupJobCloneInfo;
|
||||||
import org.apache.cloudstack.backup.veeam.api.CreateObjectInJobSpec;
|
import org.apache.cloudstack.backup.veeam.api.CreateObjectInJobSpec;
|
||||||
import org.apache.cloudstack.backup.veeam.api.EntityReferences;
|
import org.apache.cloudstack.backup.veeam.api.EntityReferences;
|
||||||
@ -57,7 +62,10 @@ import org.apache.cloudstack.backup.veeam.api.ObjectsInJob;
|
|||||||
import org.apache.cloudstack.backup.veeam.api.Ref;
|
import org.apache.cloudstack.backup.veeam.api.Ref;
|
||||||
import org.apache.cloudstack.backup.veeam.api.RestoreSession;
|
import org.apache.cloudstack.backup.veeam.api.RestoreSession;
|
||||||
import org.apache.cloudstack.backup.veeam.api.Task;
|
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.cloudstack.utils.security.SSLUtils;
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.http.HttpHeaders;
|
import org.apache.http.HttpHeaders;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
@ -73,6 +81,7 @@ import org.apache.http.entity.StringEntity;
|
|||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
import com.cloud.utils.NumbersUtil;
|
||||||
import com.cloud.utils.Pair;
|
import com.cloud.utils.Pair;
|
||||||
import com.cloud.utils.exception.CloudRuntimeException;
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
import com.cloud.utils.nio.TrustAllManager;
|
import com.cloud.utils.nio.TrustAllManager;
|
||||||
@ -92,18 +101,31 @@ public class VeeamClient {
|
|||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
private static final String RESTORE_VM_SUFFIX = "CS-RSTR-";
|
private static final String RESTORE_VM_SUFFIX = "CS-RSTR-";
|
||||||
private static final String SESSION_HEADER = "X-RestSvcSessionId";
|
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 String veeamServerIp;
|
||||||
|
private final Integer veeamServerVersion;
|
||||||
private String veeamServerUsername;
|
private String veeamServerUsername;
|
||||||
private String veeamServerPassword;
|
private String veeamServerPassword;
|
||||||
private String veeamSessionId = null;
|
private String veeamSessionId = null;
|
||||||
private int restoreTimeout;
|
private final int restoreTimeout;
|
||||||
private final int veeamServerPort = 22;
|
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,
|
public VeeamClient(final String url, final Integer version, final String username, final String password, final boolean validateCertificate, final int timeout,
|
||||||
final int restoreTimeout) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
|
final int restoreTimeout, final int taskPollInterval, final int taskPollMaxRetry) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
|
||||||
this.apiURI = new URI(url);
|
this.apiURI = new URI(url);
|
||||||
this.restoreTimeout = restoreTimeout;
|
this.restoreTimeout = restoreTimeout;
|
||||||
|
this.taskPollInterval = taskPollInterval;
|
||||||
|
this.taskPollMaxRetry = taskPollMaxRetry;
|
||||||
|
|
||||||
final RequestConfig config = RequestConfig.custom()
|
final RequestConfig config = RequestConfig.custom()
|
||||||
.setConnectTimeout(timeout * 1000)
|
.setConnectTimeout(timeout * 1000)
|
||||||
@ -127,6 +149,7 @@ public class VeeamClient {
|
|||||||
|
|
||||||
authenticate(username, password);
|
authenticate(username, password);
|
||||||
setVeeamSshCredentials(this.apiURI.getHost(), 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) {
|
protected void setVeeamSshCredentials(String hostIp, String username, String password) {
|
||||||
@ -137,7 +160,7 @@ public class VeeamClient {
|
|||||||
|
|
||||||
private void authenticate(final String username, final String password) {
|
private void authenticate(final String username, final String password) {
|
||||||
// https://helpcenter.veeam.com/docs/backup/rest/http_authentication.html?ver=95u4
|
// 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()));
|
request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()));
|
||||||
try {
|
try {
|
||||||
final HttpResponse response = httpClient.execute(request);
|
final HttpResponse response = httpClient.execute(request);
|
||||||
@ -160,6 +183,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) {
|
private void checkResponseOK(final HttpResponse response) {
|
||||||
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) {
|
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) {
|
||||||
LOG.debug("Requested Veeam resource does not exist");
|
LOG.debug("Requested Veeam resource does not exist");
|
||||||
@ -240,7 +283,7 @@ public class VeeamClient {
|
|||||||
final ObjectMapper objectMapper = new XmlMapper();
|
final ObjectMapper objectMapper = new XmlMapper();
|
||||||
final EntityReferences references = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class);
|
final EntityReferences references = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class);
|
||||||
for (final Ref ref : references.getRefs()) {
|
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();
|
return ref.getUid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,7 +331,7 @@ public class VeeamClient {
|
|||||||
|
|
||||||
private boolean checkTaskStatus(final HttpResponse response) throws IOException {
|
private boolean checkTaskStatus(final HttpResponse response) throws IOException {
|
||||||
final Task task = parseTaskResponse(response);
|
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 HttpResponse taskResponse = get("/tasks/" + task.getTaskId());
|
||||||
final Task polledTask = parseTaskResponse(taskResponse);
|
final Task polledTask = parseTaskResponse(taskResponse);
|
||||||
if (polledTask.getState().equals("Finished")) {
|
if (polledTask.getState().equals("Finished")) {
|
||||||
@ -311,7 +354,7 @@ public class VeeamClient {
|
|||||||
throw new CloudRuntimeException("Failed to assign VM to backup offering due to: " + polledTask.getResult().getMessage());
|
throw new CloudRuntimeException("Failed to assign VM to backup offering due to: " + polledTask.getResult().getMessage());
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(5000);
|
Thread.sleep(this.taskPollInterval * 1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.debug("Failed to sleep while polling for Veeam task status due to: ", e);
|
LOG.debug("Failed to sleep while polling for Veeam task status due to: ", e);
|
||||||
}
|
}
|
||||||
@ -326,6 +369,10 @@ public class VeeamClient {
|
|||||||
if (session.getResult().equals("Success")) {
|
if (session.getResult().equals("Success")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (session.getResult().equalsIgnoreCase("Failed")) {
|
||||||
|
String sessionUid = session.getUid();
|
||||||
|
throw new CloudRuntimeException(String.format("Restore job [%s] failed.", sessionUid));
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
@ -357,7 +404,7 @@ public class VeeamClient {
|
|||||||
final ObjectMapper objectMapper = new XmlMapper();
|
final ObjectMapper objectMapper = new XmlMapper();
|
||||||
final EntityReferences references = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class);
|
final EntityReferences references = objectMapper.readValue(response.getEntity().getContent(), EntityReferences.class);
|
||||||
for (final Ref ref : references.getRefs()) {
|
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;
|
return ref;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,7 +417,7 @@ public class VeeamClient {
|
|||||||
|
|
||||||
protected String getRepositoryNameFromJob(String backupName) {
|
protected String getRepositoryNameFromJob(String backupName) {
|
||||||
final List<String> cmds = Arrays.asList(
|
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"
|
"$Job.GetBackupTargetRepository() ^| select Name ^| Format-List"
|
||||||
);
|
);
|
||||||
Pair<Boolean, String> result = executePowerShellCommands(cmds);
|
Pair<Boolean, String> result = executePowerShellCommands(cmds);
|
||||||
@ -378,7 +425,7 @@ public class VeeamClient {
|
|||||||
throw new CloudRuntimeException(String.format("Failed to get Repository Name from Job [name: %s].", backupName));
|
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)+:(.)*")) {
|
if (block.matches("Name(\\s)+:(.)*")) {
|
||||||
return block.split(":")[1].trim();
|
return block.split(":")[1].trim();
|
||||||
}
|
}
|
||||||
@ -555,7 +602,12 @@ public class VeeamClient {
|
|||||||
*/
|
*/
|
||||||
protected String transformPowerShellCommandList(List<String> cmds) {
|
protected String transformPowerShellCommandList(List<String> cmds) {
|
||||||
StringJoiner joiner = new StringJoiner(";");
|
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");
|
||||||
|
joiner.add("$ProgressPreference='SilentlyContinue'");
|
||||||
|
}
|
||||||
for (String cmd : cmds) {
|
for (String cmd : cmds) {
|
||||||
joiner.add(cmd);
|
joiner.add(cmd);
|
||||||
}
|
}
|
||||||
@ -586,22 +638,20 @@ public class VeeamClient {
|
|||||||
|
|
||||||
public boolean setJobSchedule(final String jobName) {
|
public boolean setJobSchedule(final String jobName) {
|
||||||
Pair<Boolean, String> result = executePowerShellCommands(Arrays.asList(
|
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 }"
|
"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) {
|
public boolean deleteJobAndBackup(final String jobName) {
|
||||||
Pair<Boolean, String> result = executePowerShellCommands(Arrays.asList(
|
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 }",
|
"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 }",
|
"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) {
|
public boolean deleteBackup(final String restorePointId) {
|
||||||
@ -609,43 +659,135 @@ public class VeeamClient {
|
|||||||
Pair<Boolean, String> result = executePowerShellCommands(Arrays.asList(
|
Pair<Boolean, String> result = executePowerShellCommands(Arrays.asList(
|
||||||
String.format("$restorePoint = Get-VBRRestorePoint ^| Where-Object { $_.Id -eq '%s' }", restorePointId),
|
String.format("$restorePoint = Get-VBRRestorePoint ^| Where-Object { $_.Id -eq '%s' }", restorePointId),
|
||||||
"if ($restorePoint) { Remove-VBRRestorePoint -Oib $restorePoint -Confirm:$false",
|
"if ($restorePoint) { Remove-VBRRestorePoint -Oib $restorePoint -Confirm:$false",
|
||||||
"$repo = Get-VBRBackupRepository",
|
|
||||||
"Sync-VBRBackupRepository -Repository $repo",
|
|
||||||
"} else { ",
|
"} else { ",
|
||||||
" Write-Output \"Failed to delete\"",
|
" Write-Output 'Failed to delete'",
|
||||||
" Exit 1",
|
" Exit 1",
|
||||||
"}"
|
"}"
|
||||||
));
|
));
|
||||||
return result.first() && !result.second().contains(FAILED_TO_DELETE);
|
return result != null && result.first() && !result.second().contains(FAILED_TO_DELETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean syncBackupRepository() {
|
||||||
|
LOG.debug("Trying to sync backup repository.");
|
||||||
|
Pair<Boolean, String> result = executePowerShellCommands(Arrays.asList(
|
||||||
|
"$repo = Get-VBRBackupRepository",
|
||||||
|
"$Syncs = Sync-VBRBackupRepository -Repository $repo",
|
||||||
|
"while ((Get-VBRSession -ID $Syncs.ID).Result -ne 'Success') { Start-Sleep -Seconds 10 }"
|
||||||
|
));
|
||||||
|
LOG.debug("Done syncing backup repository.");
|
||||||
|
return result != null && result.first();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Backup.Metric> getBackupMetrics() {
|
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 String separator = "=====";
|
||||||
final List<String> cmds = Arrays.asList(
|
final List<String> cmds = Arrays.asList(
|
||||||
"$backups = Get-VBRBackup",
|
"$backups = Get-VBRBackup",
|
||||||
"foreach ($backup in $backups) {" +
|
"foreach ($backup in $backups) {" +
|
||||||
"$backup.JobName;" +
|
" $backup.JobName;" +
|
||||||
"$storageGroups = $backup.GetStorageGroups();" +
|
" $storageGroups = $backup.GetStorageGroups();" +
|
||||||
"foreach ($group in $storageGroups) {" +
|
" foreach ($group in $storageGroups) {" +
|
||||||
"$usedSize = 0;" +
|
" $usedSize = 0;" +
|
||||||
"$dataSize = 0;" +
|
" $dataSize = 0;" +
|
||||||
"$sizePerStorage = $group.GetStorages().Stats.BackupSize;" +
|
" $sizePerStorage = $group.GetStorages().Stats.BackupSize;" +
|
||||||
"$dataPerStorage = $group.GetStorages().Stats.DataSize;" +
|
" $dataPerStorage = $group.GetStorages().Stats.DataSize;" +
|
||||||
"foreach ($size in $sizePerStorage) {" +
|
" foreach ($size in $sizePerStorage) {" +
|
||||||
"$usedSize += $size;" +
|
" $usedSize += $size;" +
|
||||||
"}" +
|
" }" +
|
||||||
"foreach ($size in $dataPerStorage) {" +
|
" foreach ($size in $dataPerStorage) {" +
|
||||||
"$dataSize += $size;" +
|
" $dataSize += $size;" +
|
||||||
"}" +
|
" }" +
|
||||||
"$usedSize;" +
|
" $usedSize;" +
|
||||||
"$dataSize;" +
|
" $dataSize;" +
|
||||||
"}" +
|
" }" +
|
||||||
"echo \"" + separator + "\"" +
|
" echo \"" + separator + "\"" +
|
||||||
"}"
|
"}"
|
||||||
);
|
);
|
||||||
Pair<Boolean, String> response = executePowerShellCommands(cmds);
|
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<>();
|
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");
|
final String[] parts = block.split("\r\n");
|
||||||
if (parts.length != 3) {
|
if (parts.length != 3) {
|
||||||
continue;
|
continue;
|
||||||
@ -683,9 +825,9 @@ public class VeeamClient {
|
|||||||
return new Backup.RestorePoint(id, created, type);
|
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(
|
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),
|
String.format("if ($backup) { $restore = (Get-VBRRestorePoint -Backup:$backup -Name \"%s\" ^| Where-Object {$_.IsConsistent -eq $true})", vmInternalName),
|
||||||
"if ($restore) { $restore ^| Format-List } }"
|
"if ($restore) { $restore ^| Format-List } }"
|
||||||
);
|
);
|
||||||
@ -706,6 +848,71 @@ public class VeeamClient {
|
|||||||
return restorePoints;
|
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) {
|
public Pair<Boolean, String> restoreVMToDifferentLocation(String restorePointId, String hostIp, String dataStoreUuid) {
|
||||||
final String restoreLocation = RESTORE_VM_SUFFIX + UUID.randomUUID().toString();
|
final String restoreLocation = RESTORE_VM_SUFFIX + UUID.randomUUID().toString();
|
||||||
final String datastoreId = dataStoreUuid.replace("-","");
|
final String datastoreId = dataStoreUuid.replace("-","");
|
||||||
@ -723,4 +930,8 @@ public class VeeamClient {
|
|||||||
}
|
}
|
||||||
return new Pair<>(result.first(), restoreLocation);
|
return new Pair<>(result.first(), restoreLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isLegacyServer() {
|
||||||
|
return this.veeamServerVersion != null && (this.veeamServerVersion > 0 && this.veeamServerVersion < 11);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,9 +27,13 @@ import static com.github.tomakehurst.wiremock.client.WireMock.verify;
|
|||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
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.BackupOffering;
|
||||||
import org.apache.cloudstack.backup.veeam.api.RestoreSession;
|
import org.apache.cloudstack.backup.veeam.api.RestoreSession;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
@ -62,9 +66,10 @@ public class VeeamClientTest {
|
|||||||
.withStatus(201)
|
.withStatus(201)
|
||||||
.withHeader("X-RestSvcSessionId", "some-session-auth-id")
|
.withHeader("X-RestSvcSessionId", "some-session-auth-id")
|
||||||
.withBody("")));
|
.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);
|
mockClient = Mockito.mock(VeeamClient.class);
|
||||||
Mockito.when(mockClient.getRepositoryNameFromJob(Mockito.anyString())).thenCallRealMethod();
|
Mockito.when(mockClient.getRepositoryNameFromJob(Mockito.anyString())).thenCallRealMethod();
|
||||||
|
Mockito.when(mockClient.getVeeamServerVersion()).thenCallRealMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -139,7 +144,7 @@ public class VeeamClientTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getRepositoryNameFromJobTestSuccess() throws Exception {
|
public void getRepositoryNameFromJobTestSuccess() throws Exception {
|
||||||
String backupName = "TEST-BACKUP3";
|
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());
|
Mockito.doReturn(response).when(mockClient).executePowerShellCommands(Mockito.anyList());
|
||||||
String repositoryNameFromJob = mockClient.getRepositoryNameFromJob(backupName);
|
String repositoryNameFromJob = mockClient.getRepositoryNameFromJob(backupName);
|
||||||
Assert.assertEquals("test", repositoryNameFromJob);
|
Assert.assertEquals("test", repositoryNameFromJob);
|
||||||
@ -162,4 +167,324 @@ public class VeeamClientTest {
|
|||||||
}
|
}
|
||||||
Mockito.verify(mockClient, times(10)).get(Mockito.anyString());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,7 +108,7 @@ public class CryptSetup {
|
|||||||
|
|
||||||
public boolean isSupported() {
|
public boolean isSupported() {
|
||||||
final Script script = new Script(commandPath);
|
final Script script = new Script(commandPath);
|
||||||
script.add("--usage");
|
script.add("--version");
|
||||||
final String result = script.execute();
|
final String result = script.execute();
|
||||||
return result == null;
|
return result == null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1029,12 +1029,12 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co
|
|||||||
/**
|
/**
|
||||||
* Get dest volume full path
|
* Get dest volume full path
|
||||||
*/
|
*/
|
||||||
private String getDestVolumeFullPath(VirtualDisk restoredDisk, VirtualMachineMO restoredVm, VirtualMachineMO vmMo) throws Exception {
|
private String getDestVolumeFullPath(VirtualMachineMO vmMo) throws Exception {
|
||||||
VirtualDisk vmDisk = vmMo.getVirtualDisks().get(0);
|
VirtualDisk vmDisk = vmMo.getVirtualDisks().get(0);
|
||||||
String vmDiskPath = vmMo.getVmdkFileBaseName(vmDisk);
|
String vmDiskPath = vmMo.getVmdkFileBaseName(vmDisk);
|
||||||
String vmDiskFullPath = getVolumeFullPath(vmMo.getVirtualDisks().get(0));
|
String vmDiskFullPath = getVolumeFullPath(vmMo.getVirtualDisks().get(0));
|
||||||
String restoredVolumePath = restoredVm.getVmdkFileBaseName(restoredDisk);
|
String uuid = UUID.randomUUID().toString().replace("-", "");
|
||||||
return vmDiskFullPath.replace(vmDiskPath, restoredVolumePath);
|
return vmDiskFullPath.replace(vmDiskPath, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1086,17 +1086,18 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co
|
|||||||
VirtualDisk restoredDisk = findRestoredVolume(volumeInfo, vmRestored);
|
VirtualDisk restoredDisk = findRestoredVolume(volumeInfo, vmRestored);
|
||||||
String diskPath = vmRestored.getVmdkFileBaseName(restoredDisk);
|
String diskPath = vmRestored.getVmdkFileBaseName(restoredDisk);
|
||||||
|
|
||||||
s_logger.debug("Restored disk size=" + toHumanReadableSize(restoredDisk.getCapacityInKB()) + " path=" + diskPath);
|
s_logger.debug("Restored disk size=" + toHumanReadableSize(restoredDisk.getCapacityInKB() * Resource.ResourceType.bytesToKiB) + " path=" + diskPath);
|
||||||
|
|
||||||
// Detach restored VM disks
|
// Detach restored VM disks
|
||||||
vmRestored.detachAllDisks();
|
vmRestored.detachDisk(String.format("%s/%s.vmdk", location, diskPath), false);
|
||||||
|
|
||||||
String srcPath = getVolumeFullPath(restoredDisk);
|
String srcPath = getVolumeFullPath(restoredDisk);
|
||||||
String destPath = getDestVolumeFullPath(restoredDisk, vmRestored, vmMo);
|
String destPath = getDestVolumeFullPath(vmMo);
|
||||||
|
|
||||||
VirtualDiskManagerMO virtualDiskManagerMO = new VirtualDiskManagerMO(dcMo.getContext());
|
VirtualDiskManagerMO virtualDiskManagerMO = new VirtualDiskManagerMO(dcMo.getContext());
|
||||||
|
|
||||||
// Copy volume to the VM folder
|
// Copy volume to the VM folder
|
||||||
|
s_logger.debug(String.format("Moving volume from %s to %s", srcPath, destPath));
|
||||||
virtualDiskManagerMO.moveVirtualDisk(srcPath, dcMo.getMor(), destPath, dcMo.getMor(), true);
|
virtualDiskManagerMO.moveVirtualDisk(srcPath, dcMo.getMor(), destPath, dcMo.getMor(), true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1110,11 +1111,13 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co
|
|||||||
vmRestored.destroy();
|
vmRestored.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
VirtualDisk attachedDisk = getAttachedDisk(vmMo, diskPath);
|
s_logger.debug(String.format("Attaching disk %s to vm %s", destPath, vm.getId()));
|
||||||
|
VirtualDisk attachedDisk = getAttachedDisk(vmMo, destPath);
|
||||||
if (attachedDisk == null) {
|
if (attachedDisk == null) {
|
||||||
s_logger.error("Failed to get the attached the (restored) volume " + diskPath);
|
s_logger.error("Failed to get the attached the (restored) volume " + destPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
s_logger.debug(String.format("Creating volume entry for disk %s attached to vm %s", destPath, vm.getId()));
|
||||||
createVolume(attachedDisk, vmMo, vm.getDomainId(), vm.getDataCenterId(), vm.getAccountId(), vm.getId(), poolId, vm.getTemplateId(), backup, false);
|
createVolume(attachedDisk, vmMo, vm.getDomainId(), vm.getDataCenterId(), vm.getAccountId(), vm.getId(), poolId, vm.getTemplateId(), backup, false);
|
||||||
|
|
||||||
if (vm.getBackupOfferingId() == null) {
|
if (vm.getBackupOfferingId() == null) {
|
||||||
@ -1126,9 +1129,9 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private VirtualDisk getAttachedDisk(VirtualMachineMO vmMo, String diskPath) throws Exception {
|
private VirtualDisk getAttachedDisk(VirtualMachineMO vmMo, String diskFullPath) throws Exception {
|
||||||
for (VirtualDisk disk : vmMo.getVirtualDisks()) {
|
for (VirtualDisk disk : vmMo.getVirtualDisks()) {
|
||||||
if (vmMo.getVmdkFileBaseName(disk).equals(diskPath)) {
|
if (getVolumeFullPath(disk).equals(diskFullPath)) {
|
||||||
return disk;
|
return disk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,7 @@ import com.vmware.vim25.HostDatastoreBrowserSearchResults;
|
|||||||
import com.vmware.vim25.HostDatastoreBrowserSearchSpec;
|
import com.vmware.vim25.HostDatastoreBrowserSearchSpec;
|
||||||
import com.vmware.vim25.VirtualMachineConfigSummary;
|
import com.vmware.vim25.VirtualMachineConfigSummary;
|
||||||
import org.apache.cloudstack.api.ApiConstants;
|
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.CopyCommand;
|
||||||
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
|
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
|
||||||
import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
|
import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
|
||||||
@ -611,6 +612,8 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
|
|||||||
answer = execute((GetHypervisorGuestOsNamesCommand) cmd);
|
answer = execute((GetHypervisorGuestOsNamesCommand) cmd);
|
||||||
} else if (clz == ListDataStoreObjectsCommand.class) {
|
} else if (clz == ListDataStoreObjectsCommand.class) {
|
||||||
answer = execute((ListDataStoreObjectsCommand) cmd);
|
answer = execute((ListDataStoreObjectsCommand) cmd);
|
||||||
|
} else if (clz == PrepareForBackupRestorationCommand.class) {
|
||||||
|
answer = execute((PrepareForBackupRestorationCommand) cmd);
|
||||||
} else {
|
} else {
|
||||||
answer = Answer.createUnsupportedCommandAnswer(cmd);
|
answer = Answer.createUnsupportedCommandAnswer(cmd);
|
||||||
}
|
}
|
||||||
@ -7622,6 +7625,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() {
|
private Integer getVmwareWindowTimeInterval() {
|
||||||
Integer windowInterval = VmwareManager.VMWARE_STATS_TIME_WINDOW.value();
|
Integer windowInterval = VmwareManager.VMWARE_STATS_TIME_WINDOW.value();
|
||||||
if (windowInterval == null || windowInterval < 20) {
|
if (windowInterval == null || windowInterval < 20) {
|
||||||
|
|||||||
@ -218,7 +218,10 @@ public class VmwareStorageLayoutHelper implements Configurable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void syncVolumeToRootFolder(DatacenterMO dcMo, DatastoreMO ds, String vmdkName, String vmName, String excludeFolders) throws Exception {
|
public static void syncVolumeToRootFolder(DatacenterMO dcMo, DatastoreMO ds, String vmdkName, String vmName, String excludeFolders) throws Exception {
|
||||||
String fileDsFullPath = ds.searchFileInSubFolders(vmdkName + ".vmdk", false, excludeFolders);
|
String fileDsFullPath = ds.searchFileInSubFolders(String.format("%s/%s.vmdk", vmName, vmdkName), false, excludeFolders);
|
||||||
|
if (fileDsFullPath == null) {
|
||||||
|
fileDsFullPath = ds.searchFileInSubFolders(vmdkName + ".vmdk", false, excludeFolders);
|
||||||
|
}
|
||||||
if (fileDsFullPath == null)
|
if (fileDsFullPath == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -409,6 +412,22 @@ public class VmwareStorageLayoutHelper implements Configurable {
|
|||||||
return String.format("[%s] %s/%s", dsMo.getName(), vmName, vmdkFileName);
|
return String.format("[%s] %s/%s", dsMo.getName(), vmName, vmdkFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getDatastoreVolumePath(DatastoreMO dsMo, String vmName, String volumePath) throws Exception {
|
||||||
|
String datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, vmName, volumePath + ".vmdk");
|
||||||
|
if (dsMo.folderExists(String.format("[%s]", dsMo.getName()), vmName) && dsMo.fileExists(datastoreVolumePath)) {
|
||||||
|
return datastoreVolumePath;
|
||||||
|
}
|
||||||
|
datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, volumePath, volumePath + ".vmdk");
|
||||||
|
if (dsMo.folderExists(String.format("[%s]", dsMo.getName()), volumePath) && dsMo.fileExists(datastoreVolumePath)) {
|
||||||
|
return datastoreVolumePath;
|
||||||
|
}
|
||||||
|
datastoreVolumePath = VmwareStorageLayoutHelper.getLegacyDatastorePathFromVmdkFileName(dsMo, volumePath + ".vmdk");
|
||||||
|
if (dsMo.fileExists(datastoreVolumePath)) {
|
||||||
|
return datastoreVolumePath;
|
||||||
|
}
|
||||||
|
return dsMo.searchFileInSubFolders(volumePath + ".vmdk", false, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getConfigComponentName() {
|
public String getConfigComponentName() {
|
||||||
return VmwareStorageLayoutHelper.class.getSimpleName();
|
return VmwareStorageLayoutHelper.class.getSimpleName();
|
||||||
|
|||||||
@ -2062,17 +2062,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
|
|||||||
datastoreVolumePath = dsMo.getDatastorePath((vmdkPath != null ? vmdkPath : dsMo.getName()) + ".vmdk");
|
datastoreVolumePath = dsMo.getDatastorePath((vmdkPath != null ? vmdkPath : dsMo.getName()) + ".vmdk");
|
||||||
} else {
|
} else {
|
||||||
if (dsMo.getDatastoreType().equalsIgnoreCase("VVOL")) {
|
if (dsMo.getDatastoreType().equalsIgnoreCase("VVOL")) {
|
||||||
datastoreVolumePath = VmwareStorageLayoutHelper.getLegacyDatastorePathFromVmdkFileName(dsMo, volumePath + ".vmdk");
|
datastoreVolumePath = VmwareStorageLayoutHelper.getDatastoreVolumePath(dsMo, vmName, volumePath);
|
||||||
if (!dsMo.fileExists(datastoreVolumePath)) {
|
|
||||||
datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, vmName, volumePath + ".vmdk");
|
|
||||||
}
|
|
||||||
if (!dsMo.folderExists(String.format("[%s]", dsMo.getName()), vmName) || !dsMo.fileExists(datastoreVolumePath)) {
|
|
||||||
datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, volumePath, volumePath + ".vmdk");
|
|
||||||
}
|
|
||||||
if (!dsMo.folderExists(String.format("[%s]", dsMo.getName()), volumePath) || !dsMo.fileExists(datastoreVolumePath)) {
|
|
||||||
datastoreVolumePath = dsMo.searchFileInSubFolders(volumePath + ".vmdk", true, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
datastoreVolumePath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dsMo.getOwnerDatacenter().first(), vmName, dsMo, volumePath, VmwareManager.s_vmwareSearchExcludeFolder.value());
|
datastoreVolumePath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dsMo.getOwnerDatacenter().first(), vmName, dsMo, volumePath, VmwareManager.s_vmwareSearchExcludeFolder.value());
|
||||||
}
|
}
|
||||||
@ -2101,16 +2091,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
|
|||||||
}
|
}
|
||||||
dsMo = new DatastoreMO(context, morDs);
|
dsMo = new DatastoreMO(context, morDs);
|
||||||
|
|
||||||
datastoreVolumePath = VmwareStorageLayoutHelper.getLegacyDatastorePathFromVmdkFileName(dsMo, volumePath + ".vmdk");
|
datastoreVolumePath = VmwareStorageLayoutHelper.getDatastoreVolumePath(dsMo, vmName, volumePath);
|
||||||
if (!dsMo.fileExists(datastoreVolumePath)) {
|
|
||||||
datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, vmName, volumePath + ".vmdk");
|
|
||||||
}
|
|
||||||
if (!dsMo.folderExists(String.format("[%s]", dsMo.getName()), vmName) || !dsMo.fileExists(datastoreVolumePath)) {
|
|
||||||
datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, volumePath, volumePath + ".vmdk");
|
|
||||||
}
|
|
||||||
if (!dsMo.folderExists(String.format("[%s]", dsMo.getName()), volumePath) || !dsMo.fileExists(datastoreVolumePath)) {
|
|
||||||
datastoreVolumePath = dsMo.searchFileInSubFolders(volumePath + ".vmdk", true, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -619,7 +619,7 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio
|
|||||||
// FIXME: take a global database lock here for safety.
|
// FIXME: take a global database lock here for safety.
|
||||||
boolean onWindows = isOnWindows();
|
boolean onWindows = isOnWindows();
|
||||||
if(!onWindows) {
|
if(!onWindows) {
|
||||||
Script.runSimpleBashScript("if [ -f " + privkeyfile + " ]; then rm -f " + privkeyfile + "; fi; ssh-keygen -t rsa -m PEM -N '' -f " + privkeyfile + " -q 2>/dev/null || ssh-keygen -t rsa -N '' -f " + privkeyfile + " -q");
|
Script.runSimpleBashScript("if [ -f " + privkeyfile + " ]; then rm -f " + privkeyfile + "; fi; ssh-keygen -t ed25519 -m PEM -N '' -f " + privkeyfile + " -q 2>/dev/null || ssh-keygen -t ed25519 -N '' -f " + privkeyfile + " -q");
|
||||||
}
|
}
|
||||||
|
|
||||||
final String privateKey;
|
final String privateKey;
|
||||||
|
|||||||
@ -79,6 +79,7 @@ import com.cloud.dc.dao.DataCenterDao;
|
|||||||
import com.cloud.event.ActionEvent;
|
import com.cloud.event.ActionEvent;
|
||||||
import com.cloud.event.ActionEventUtils;
|
import com.cloud.event.ActionEventUtils;
|
||||||
import com.cloud.event.EventTypes;
|
import com.cloud.event.EventTypes;
|
||||||
|
import com.cloud.event.EventVO;
|
||||||
import com.cloud.event.UsageEventUtils;
|
import com.cloud.event.UsageEventUtils;
|
||||||
import com.cloud.exception.InvalidParameterValueException;
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
import com.cloud.exception.PermissionDeniedException;
|
import com.cloud.exception.PermissionDeniedException;
|
||||||
@ -487,6 +488,11 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||||||
throw new CloudRuntimeException("The assigned backup offering does not allow ad-hoc user backup");
|
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());
|
final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
|
||||||
if (backupProvider != null && backupProvider.takeBackup(vm)) {
|
if (backupProvider != null && backupProvider.takeBackup(vm)) {
|
||||||
return true;
|
return true;
|
||||||
@ -565,10 +571,21 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||||||
} catch (final Exception e) {
|
} 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,
|
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);
|
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());
|
throw new CloudRuntimeException("Error during vm backup restoration and import: " + e.getMessage());
|
||||||
}
|
}
|
||||||
if (vm == null) {
|
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;
|
return vm != null;
|
||||||
}
|
}
|
||||||
@ -618,9 +635,17 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||||||
try {
|
try {
|
||||||
updateVmState(vm, VirtualMachine.Event.RestoringRequested, VirtualMachine.State.Restoring);
|
updateVmState(vm, VirtualMachine.Event.RestoringRequested, VirtualMachine.State.Restoring);
|
||||||
updateVolumeState(vm, Volume.Event.RestoreRequested, Volume.State.Restoring);
|
updateVolumeState(vm, Volume.Event.RestoreRequested, Volume.State.Restoring);
|
||||||
|
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());
|
final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
|
||||||
if (!backupProvider.restoreVMFromBackup(vm, backup)) {
|
if (!backupProvider.restoreVMFromBackup(vm, backup)) {
|
||||||
throw new CloudRuntimeException(String.format("Error restoring %s from backup [%s].", vm, backupDetailsInMessage));
|
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());
|
||||||
}
|
}
|
||||||
// The restore process is executed by a backup provider outside of ACS, I am using the catch-all (Exception) to
|
// The restore process is executed by a backup provider outside of ACS, I am using the catch-all (Exception) to
|
||||||
// ensure that no provider-side exception is missed. Therefore, we have a proper handling of exceptions, and rollbacks if needed.
|
// ensure that no provider-side exception is missed. Therefore, we have a proper handling of exceptions, and rollbacks if needed.
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#runs the console proxy as a standalone server
|
|
||||||
#i.e., not in the system vm
|
|
||||||
|
|
||||||
CP=./:./conf
|
|
||||||
for file in *.jar
|
|
||||||
do
|
|
||||||
CP=${CP}:$file
|
|
||||||
done
|
|
||||||
keyvalues=
|
|
||||||
#LOGHOME=/var/log/cloud/
|
|
||||||
LOGHOME=$PWD/
|
|
||||||
|
|
||||||
java -Djavax.net.ssl.trustStore=./certs/realhostip.keystore -Dlog.home=$LOGHOME -cp $CP com.cloud.agent.AgentShell $keyvalues $@
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#runs the secondary storage service as a standalone server
|
|
||||||
#i.e., not in the system vm
|
|
||||||
|
|
||||||
CP=./:./conf
|
|
||||||
for file in *.jar
|
|
||||||
do
|
|
||||||
CP=${CP}:$file
|
|
||||||
done
|
|
||||||
keyvalues=
|
|
||||||
#LOGHOME=/var/log/cloud/
|
|
||||||
LOGHOME=$PWD/
|
|
||||||
|
|
||||||
java -Djavax.net.ssl.trustStore=./certs/realhostip.keystore -Dlog.home=$LOGHOME -cp $CP com.cloud.agent.AgentShell $keyvalues $@
|
|
||||||
@ -33,7 +33,7 @@ setup_console_proxy() {
|
|||||||
echo "$public_ip $NAME" >> /etc/hosts
|
echo "$public_ip $NAME" >> /etc/hosts
|
||||||
|
|
||||||
log_it "Applying iptables rule for VNC port ${VNCPORT}"
|
log_it "Applying iptables rule for VNC port ${VNCPORT}"
|
||||||
sed -i 's/8080/${VNCPORT}/' /etc/iptables/rules.v4
|
sed -i "s/8080/${VNCPORT}/" /etc/iptables/rules.v4
|
||||||
echo "${VNCPORT}" > /root/vncport
|
echo "${VNCPORT}" > /root/vncport
|
||||||
log_it "Creating VNC port ${VNCPORT} file for VNC server configuration"
|
log_it "Creating VNC port ${VNCPORT} file for VNC server configuration"
|
||||||
|
|
||||||
|
|||||||
308
test/integration/smoke/test_backup_recovery_veeam.py
Normal file
308
test/integration/smoke/test_backup_recovery_veeam.py
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
#!/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_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)
|
||||||
|
|
||||||
|
self.vm = VirtualMachine.create(self.user_apiclient, self.services["small"], accountid=self.account.name,
|
||||||
|
domainid=self.account.domainid, serviceofferingid=self.service_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 3")
|
||||||
|
finally:
|
||||||
|
# Delete backup
|
||||||
|
Backup.delete(self.user_apiclient, backup.id, forced=True)
|
||||||
|
# Remove VM from offering
|
||||||
|
self.offering.removeOffering(self.user_apiclient, self.vm_with_datadisk.id)
|
||||||
|
# Delete vm
|
||||||
|
self.vm.delete(self.apiclient)
|
||||||
|
# Delete vm with datadisk
|
||||||
|
self.vm_with_datadisk.delete(self.apiclient)
|
||||||
@ -5981,8 +5981,10 @@ class ResourceDetails:
|
|||||||
cmd.resourcetype = resourcetype
|
cmd.resourcetype = resourcetype
|
||||||
return (apiclient.removeResourceDetail(cmd))
|
return (apiclient.removeResourceDetail(cmd))
|
||||||
|
|
||||||
|
|
||||||
# Backup and Recovery
|
# Backup and Recovery
|
||||||
|
|
||||||
|
|
||||||
class BackupOffering:
|
class BackupOffering:
|
||||||
|
|
||||||
def __init__(self, items):
|
def __init__(self, items):
|
||||||
@ -6047,6 +6049,7 @@ class BackupOffering:
|
|||||||
cmd.forced = forced
|
cmd.forced = forced
|
||||||
return (apiclient.removeVirtualMachineFromBackupOffering(cmd))
|
return (apiclient.removeVirtualMachineFromBackupOffering(cmd))
|
||||||
|
|
||||||
|
|
||||||
class Backup:
|
class Backup:
|
||||||
|
|
||||||
def __init__(self, items):
|
def __init__(self, items):
|
||||||
@ -6058,14 +6061,16 @@ class Backup:
|
|||||||
|
|
||||||
cmd = createBackup.createBackupCmd()
|
cmd = createBackup.createBackupCmd()
|
||||||
cmd.virtualmachineid = vmid
|
cmd.virtualmachineid = vmid
|
||||||
return (apiclient.createBackup(cmd))
|
return Backup(apiclient.createBackup(cmd).__dict__)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete(self, apiclient, id):
|
def delete(self, apiclient, id, forced=None):
|
||||||
"""Delete VM backup"""
|
"""Delete VM backup"""
|
||||||
|
|
||||||
cmd = deleteBackup.deleteBackupCmd()
|
cmd = deleteBackup.deleteBackupCmd()
|
||||||
cmd.id = id
|
cmd.id = id
|
||||||
|
if forced:
|
||||||
|
cmd.forced = forced
|
||||||
return (apiclient.deleteBackup(cmd))
|
return (apiclient.deleteBackup(cmd))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -6077,13 +6082,66 @@ class Backup:
|
|||||||
cmd.listall = True
|
cmd.listall = True
|
||||||
return (apiclient.listBackups(cmd))
|
return (apiclient.listBackups(cmd))
|
||||||
|
|
||||||
def restoreVM(self, apiclient):
|
@classmethod
|
||||||
|
def restoreVM(self, apiclient, backupid):
|
||||||
"""Restore VM from backup"""
|
"""Restore VM from backup"""
|
||||||
|
|
||||||
cmd = restoreBackup.restoreBackupCmd()
|
cmd = restoreBackup.restoreBackupCmd()
|
||||||
cmd.id = self.id
|
cmd.id = backupid
|
||||||
return (apiclient.restoreBackup(cmd))
|
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:
|
class ProjectRole:
|
||||||
|
|
||||||
def __init__(self, items):
|
def __init__(self, items):
|
||||||
|
|||||||
@ -2534,6 +2534,7 @@
|
|||||||
"message.adding.netscaler.provider": "Adding Netscaler provider",
|
"message.adding.netscaler.provider": "Adding Netscaler provider",
|
||||||
"message.advanced.security.group": "Choose this if you wish to use security groups to provide guest Instance isolation.",
|
"message.advanced.security.group": "Choose this if you wish to use security groups to provide guest Instance isolation.",
|
||||||
"message.allowed": "Allowed",
|
"message.allowed": "Allowed",
|
||||||
|
"message.alert.show.all.stats.data": "This may return a lot of data depending on VM statistics and retention settings",
|
||||||
"message.apply.success": "Apply Successfully",
|
"message.apply.success": "Apply Successfully",
|
||||||
"message.assign.instance.another": "Please specify the Account type, domain, Account name and Network (optional) of the new Account. <br> If the default NIC of the Instance is on a shared Network, CloudStack will check if the Network can be used by the new Account if you do not specify one Network. <br> If the default NIC of the Instance is on a isolated Network, and the new Account has more one isolated Networks, you should specify one.",
|
"message.assign.instance.another": "Please specify the Account type, domain, Account name and Network (optional) of the new Account. <br> If the default NIC of the Instance is on a shared Network, CloudStack will check if the Network can be used by the new Account if you do not specify one Network. <br> If the default NIC of the Instance is on a isolated Network, and the new Account has more one isolated Networks, you should specify one.",
|
||||||
"message.assign.vm.failed": "Failed to assign Instance",
|
"message.assign.vm.failed": "Failed to assign Instance",
|
||||||
@ -2781,6 +2782,7 @@
|
|||||||
"message.error.display.text": "Please enter display text.",
|
"message.error.display.text": "Please enter display text.",
|
||||||
"message.error.duration.less.than.interval": "The duration in Autoscale policy cannot be less than interval",
|
"message.error.duration.less.than.interval": "The duration in Autoscale policy cannot be less than interval",
|
||||||
"message.error.enable.saml": "Unable to find Users IDs to enable SAML single sign on, kindly enable it manually.",
|
"message.error.enable.saml": "Unable to find Users IDs to enable SAML single sign on, kindly enable it manually.",
|
||||||
|
"message.error.end.date.and.time": "Please select an end date and time.",
|
||||||
"message.error.endip": "Please enter end IP.",
|
"message.error.endip": "Please enter end IP.",
|
||||||
"message.error.gateway": "Please enter gateway.",
|
"message.error.gateway": "Please enter gateway.",
|
||||||
"message.error.host.name": "Please enter host name.",
|
"message.error.host.name": "Please enter host name.",
|
||||||
|
|||||||
@ -600,7 +600,7 @@ export default {
|
|||||||
},
|
},
|
||||||
enableGroupAction () {
|
enableGroupAction () {
|
||||||
return ['vm', 'alert', 'vmgroup', 'ssh', 'userdata', 'affinitygroup', 'autoscalevmgroup', 'volume', 'snapshot',
|
return ['vm', 'alert', 'vmgroup', 'ssh', 'userdata', 'affinitygroup', 'autoscalevmgroup', 'volume', 'snapshot',
|
||||||
'vmsnapshot', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway', 'vnfapp',
|
'vmsnapshot', 'backup', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway', 'vnfapp',
|
||||||
'project', 'account', 'systemvm', 'router', 'computeoffering', 'systemoffering',
|
'project', 'account', 'systemvm', 'router', 'computeoffering', 'systemoffering',
|
||||||
'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment', 'buckets'
|
'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment', 'buckets'
|
||||||
].includes(this.$route.name)
|
].includes(this.$route.name)
|
||||||
|
|||||||
@ -31,9 +31,9 @@ export default {
|
|||||||
permission: ['listVirtualMachinesMetrics'],
|
permission: ['listVirtualMachinesMetrics'],
|
||||||
resourceType: 'UserVm',
|
resourceType: 'UserVm',
|
||||||
params: () => {
|
params: () => {
|
||||||
var params = { details: 'servoff,tmpl,nics' }
|
var params = { details: 'servoff,tmpl,nics,backoff' }
|
||||||
if (store.getters.metrics) {
|
if (store.getters.metrics) {
|
||||||
params = { details: 'servoff,tmpl,nics,stats' }
|
params = { details: 'servoff,tmpl,nics,backoff,stats' }
|
||||||
}
|
}
|
||||||
params.isvnf = false
|
params.isvnf = false
|
||||||
return params
|
return params
|
||||||
|
|||||||
@ -451,7 +451,11 @@ export default {
|
|||||||
label: 'label.delete.backup',
|
label: 'label.delete.backup',
|
||||||
message: 'message.delete.backup',
|
message: 'message.delete.backup',
|
||||||
dataView: true,
|
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']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -41,6 +41,9 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="column.key === 'intervaltype'" :name="text">
|
||||||
|
<label>{{ record.intervaltype }}</label>
|
||||||
|
</template>
|
||||||
<template v-if="column.key === 'time'" :name="text">
|
<template v-if="column.key === 'time'" :name="text">
|
||||||
<label class="interval-content">
|
<label class="interval-content">
|
||||||
<span v-if="record.intervaltype==='HOURLY'">{{ record.schedule + ' ' + $t('label.min.past.hour') }}</span>
|
<span v-if="record.intervaltype==='HOURLY'">{{ record.schedule + ' ' + $t('label.min.past.hour') }}</span>
|
||||||
@ -115,7 +118,10 @@ export default {
|
|||||||
width: 30
|
width: 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'time',
|
title: this.$t('label.intervaltype'),
|
||||||
|
dataIndex: 'intervaltype'
|
||||||
|
},
|
||||||
|
{
|
||||||
title: this.$t('label.time'),
|
title: this.$t('label.time'),
|
||||||
dataIndex: 'schedule'
|
dataIndex: 'schedule'
|
||||||
},
|
},
|
||||||
|
|||||||
@ -64,19 +64,19 @@
|
|||||||
<div class="list__label">{{ $t('label.protocol') }}</div>
|
<div class="list__label">{{ $t('label.protocol') }}</div>
|
||||||
<div>{{ element.protocol }}</div>
|
<div>{{ element.protocol }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list__col" v-if="element.startport">
|
<div class="list__col" v-if="element.startport !== undefined">
|
||||||
<div class="list__label">{{ $t('label.startport') }}</div>
|
<div class="list__label">{{ $t('label.startport') }}</div>
|
||||||
<div>{{ element.startport }}</div>
|
<div>{{ element.startport }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list__col" v-if="element.endport">
|
<div class="list__col" v-if="element.endport !== undefined">
|
||||||
<div class="list__label">{{ $t('label.endport') }}</div>
|
<div class="list__label">{{ $t('label.endport') }}</div>
|
||||||
<div>{{ element.endport }}</div>
|
<div>{{ element.endport }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list__col" v-if="element.icmpcode">
|
<div class="list__col" v-if="element.icmpcode !== undefined">
|
||||||
<div class="list__label">{{ $t('label.icmpcode') }}</div>
|
<div class="list__label">{{ $t('label.icmpcode') }}</div>
|
||||||
<div>{{ element.icmpcode }}</div>
|
<div>{{ element.icmpcode }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list__col" v-if="element.icmptype">
|
<div class="list__col" v-if="element.icmptype !== undefined">
|
||||||
<div class="list__label">{{ $t('label.icmptype') }}</div>
|
<div class="list__label">{{ $t('label.icmptype') }}</div>
|
||||||
<div>{{ element.icmptype }}</div>
|
<div>{{ element.icmptype }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3736,4 +3736,29 @@ public class VirtualMachineMO extends BaseMO {
|
|||||||
String workerTag = String.format("%d-%s", System.currentTimeMillis(), getContext().getStockObject("noderuninfo"));
|
String workerTag = String.format("%d-%s", System.currentTimeMillis(), getContext().getStockObject("noderuninfo"));
|
||||||
setCustomFieldValue(CustomFieldConstants.CLOUD_WORKER_TAG, workerTag);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,8 @@ public class VmdkFileDescriptor {
|
|||||||
private static final String VMDK_CREATE_TYPE_VMFSSPARSE = "vmfsSparse";
|
private static final String VMDK_CREATE_TYPE_VMFSSPARSE = "vmfsSparse";
|
||||||
private static final String VMDK_CREATE_TYPE_SESPARSE = "SEsparse";
|
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_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 Properties _properties = new Properties();
|
||||||
private String _baseFileName;
|
private String _baseFileName;
|
||||||
@ -225,4 +227,61 @@ public class VmdkFileDescriptor {
|
|||||||
|
|
||||||
return bos.toByteArray();
|
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();
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user