Skip to content

Commit 33bb92a

Browse files
Veeam: Support Veeam 11 and 12 (apache#8241)
This PR fixes several issues in the testing of Veeam 11 and Veeam12 - Import Veeam.Backup.PowerShell and silently ignore the warning messages - Fix issue when assign vm to backup offerings, which caused by separator (\r\n) - Fix authorization failure in veeam 12a, which is because v1_4 is not supported in veeam 12a any more - Fix exception if backup name has space - Fix backup metrics in veeam12, which is because powershell command does not return the values needed - Fix Incorrect datetime value, which is because powershell command returns a datetime which is not supported in Java - Fix issue during backup restoration if VM has both ROOT and DATA disks. This PR also has the following update - Add integration test test/integration/smoke/test_backup_recovery_veeam.py - Make some UI changes - Add zone setting backup.plugin.veeam.version. If it is not set, CloudStack will get veeam version via powershell commands. - Add zone setting backup.plugin.veeam.task.poll.interval and backup.plugin.veeam.task.poll.max.retry
1 parent 76aff0f commit 33bb92a

25 files changed

Lines changed: 1654 additions & 62 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ public class ListVMsCmd extends BaseListTaggedResourcesCmd implements UserCmd {
9595
@Parameter(name = ApiConstants.DETAILS,
9696
type = CommandType.LIST,
9797
collectionType = CommandType.STRING,
98-
description = "comma separated list of host details requested, "
99-
+ "value can be a list of [all, group, nics, stats, secgrp, tmpl, servoff, diskoff, iso, volume, min, affgrp]."
98+
description = "comma separated list of vm details requested, "
99+
+ "value can be a list of [all, group, nics, stats, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp]."
100100
+ " If no parameter is passed in, the details will be defaulted to all")
101101
private List<String> viewDetails;
102102

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package org.apache.cloudstack.backup;
21+
22+
import com.cloud.agent.api.Command;
23+
24+
public class PrepareForBackupRestorationCommand extends Command {
25+
26+
private String vmName;
27+
28+
protected PrepareForBackupRestorationCommand() {
29+
}
30+
31+
public PrepareForBackupRestorationCommand(String vmName) {
32+
this.vmName = vmName;
33+
}
34+
35+
public String getVmName() {
36+
return vmName;
37+
}
38+
39+
@Override
40+
public boolean executeInSequence() {
41+
return true;
42+
}
43+
}

engine/schema/src/main/java/com/cloud/vm/NicVO.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
import javax.persistence.Table;
3131
import javax.persistence.Transient;
3232

33+
import org.apache.commons.lang3.builder.EqualsBuilder;
34+
import org.apache.commons.lang3.builder.HashCodeBuilder;
35+
3336
import com.cloud.network.Networks.AddressFormat;
3437
import com.cloud.network.Networks.Mode;
3538
import com.cloud.utils.db.GenericDao;
@@ -399,6 +402,15 @@ public void setNsxLogicalSwitchPortUuid(String nsxLogicalSwitchPortUuid) {
399402
}
400403

401404
@Override
405+
public int hashCode() {
406+
return new HashCodeBuilder(17, 31).append(id).toHashCode();
407+
}
408+
409+
@Override
410+
public boolean equals(Object obj) {
411+
return EqualsBuilder.reflectionEquals(this, obj);
412+
}
413+
402414
public Integer getMtu() {
403415
return mtu;
404416
}

engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ public interface NicDao extends GenericDao<NicVO, Long> {
9191

9292
NicVO findByMacAddress(String macAddress);
9393

94+
NicVO findByNetworkIdAndMacAddressIncludingRemoved(long networkId, String mac);
95+
9496
List<NicVO> findNicsByIpv6GatewayIpv6CidrAndReserver(String ipv6Gateway, String ipv6Cidr, String reserverName);
9597

9698
NicVO findByIpAddressAndVmType(String ip, VirtualMachine.Type vmType);

engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,14 @@ public NicVO findByNetworkIdAndMacAddress(long networkId, String mac) {
219219
return findOneBy(sc);
220220
}
221221

222+
@Override
223+
public NicVO findByNetworkIdAndMacAddressIncludingRemoved(long networkId, String mac) {
224+
SearchCriteria<NicVO> sc = AllFieldsSearch.create();
225+
sc.setParameters("network", networkId);
226+
sc.setParameters("macAddress", mac);
227+
return findOneIncludingRemovedBy(sc);
228+
}
229+
222230
@Override
223231
public NicVO findDefaultNicForVM(long instanceId) {
224232
SearchCriteria<NicVO> sc = AllFieldsSearch.create();

engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
package org.apache.cloudstack.backup;
1919

20+
import com.cloud.utils.db.GenericDao;
21+
22+
import java.util.Date;
2023
import java.util.UUID;
2124

2225
import javax.persistence.Column;
@@ -51,6 +54,9 @@ public class BackupVO implements Backup {
5154
@Column(name = "date")
5255
private String date;
5356

57+
@Column(name = GenericDao.REMOVED_COLUMN)
58+
private Date removed;
59+
5460
@Column(name = "size")
5561
private Long size;
5662

@@ -192,4 +198,12 @@ public Class<?> getEntityType() {
192198
public String getName() {
193199
return null;
194200
}
201+
202+
public Date getRemoved() {
203+
return removed;
204+
}
205+
206+
public void setRemoved(Date removed) {
207+
this.removed = removed;
208+
}
195209
}

plugins/backup/veeam/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@
2828
</parent>
2929

3030
<dependencies>
31+
<dependency>
32+
<groupId>org.apache.cloudstack</groupId>
33+
<artifactId>cloud-core</artifactId>
34+
<version>${project.version}</version>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.apache.cloudstack</groupId>
38+
<artifactId>cloud-engine-api</artifactId>
39+
<version>${project.version}</version>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.apache.cloudstack</groupId>
43+
<artifactId>cloud-engine-components-api</artifactId>
44+
<version>${project.version}</version>
45+
</dependency>
3146
<dependency>
3247
<groupId>org.apache.cloudstack</groupId>
3348
<artifactId>cloud-plugin-hypervisor-vmware</artifactId>

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

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import javax.inject.Inject;
3131

32+
import org.apache.cloudstack.api.ApiCommandResourceType;
3233
import org.apache.cloudstack.api.InternalIdentity;
3334
import org.apache.cloudstack.backup.Backup.Metric;
3435
import org.apache.cloudstack.backup.dao.BackupDao;
@@ -40,11 +41,17 @@
4041
import org.apache.commons.lang3.BooleanUtils;
4142
import org.apache.log4j.Logger;
4243

44+
import com.cloud.agent.AgentManager;
45+
import com.cloud.agent.api.Answer;
46+
import com.cloud.event.ActionEventUtils;
47+
import com.cloud.event.EventTypes;
48+
import com.cloud.event.EventVO;
4349
import com.cloud.hypervisor.Hypervisor;
4450
import com.cloud.hypervisor.vmware.VmwareDatacenter;
4551
import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap;
4652
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
4753
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
54+
import com.cloud.user.User;
4855
import com.cloud.utils.Pair;
4956
import com.cloud.utils.component.AdapterBase;
5057
import com.cloud.utils.db.Transaction;
@@ -53,6 +60,7 @@
5360
import com.cloud.utils.exception.CloudRuntimeException;
5461
import com.cloud.vm.VMInstanceVO;
5562
import com.cloud.vm.VirtualMachine;
63+
import com.cloud.vm.VirtualMachineManager;
5664
import com.cloud.vm.dao.VMInstanceDao;
5765

5866
public class VeeamBackupProvider extends AdapterBase implements BackupProvider, Configurable {
@@ -64,6 +72,10 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
6472
"backup.plugin.veeam.url", "https://localhost:9398/api/",
6573
"The Veeam backup and recovery URL.", true, ConfigKey.Scope.Zone);
6674

75+
public ConfigKey<Integer> VeeamVersion = new ConfigKey<>("Advanced", Integer.class,
76+
"backup.plugin.veeam.version", "0",
77+
"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);
78+
6779
private ConfigKey<String> VeeamUsername = new ConfigKey<>("Advanced", String.class,
6880
"backup.plugin.veeam.username", "administrator",
6981
"The Veeam backup and recovery username.", true, ConfigKey.Scope.Zone);
@@ -81,6 +93,12 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
8193
private static ConfigKey<Integer> VeeamRestoreTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.restore.timeout", "600",
8294
"The Veeam B&R API restore backup timeout in seconds.", true, ConfigKey.Scope.Zone);
8395

96+
private static ConfigKey<Integer> VeeamTaskPollInterval = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.task.poll.interval", "5",
97+
"The time interval in seconds when the management server polls for Veeam task status.", true, ConfigKey.Scope.Zone);
98+
99+
private static ConfigKey<Integer> VeeamTaskPollMaxRetry = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.task.poll.max.retry", "120",
100+
"The max number of retrying times when the management server polls for Veeam task status.", true, ConfigKey.Scope.Zone);
101+
84102
@Inject
85103
private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao;
86104
@Inject
@@ -89,11 +107,16 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
89107
private BackupDao backupDao;
90108
@Inject
91109
private VMInstanceDao vmInstanceDao;
110+
@Inject
111+
private AgentManager agentMgr;
112+
@Inject
113+
private VirtualMachineManager virtualMachineManager;
92114

93115
protected VeeamClient getClient(final Long zoneId) {
94116
try {
95-
return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId),
96-
VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId));
117+
return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamVersion.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId),
118+
VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId), VeeamRestoreTimeout.valueIn(zoneId),
119+
VeeamTaskPollInterval.valueIn(zoneId), VeeamTaskPollMaxRetry.valueIn(zoneId));
97120
} catch (URISyntaxException e) {
98121
throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage());
99122
} catch (NoSuchAlgorithmException | KeyManagementException e) {
@@ -234,7 +257,36 @@ public boolean deleteBackup(Backup backup, boolean forced) {
234257
@Override
235258
public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) {
236259
final String restorePointId = backup.getExternalId();
237-
return getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), restorePointId);
260+
try {
261+
return getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), restorePointId);
262+
} catch (Exception ex) {
263+
LOG.error(String.format("Failed to restore Full VM due to: %s. Retrying after some preparation", ex.getMessage()));
264+
prepareForBackupRestoration(vm);
265+
return getClient(vm.getDataCenterId()).restoreFullVM(vm.getInstanceName(), restorePointId);
266+
}
267+
}
268+
269+
private void prepareForBackupRestoration(VirtualMachine vm) {
270+
if (!Hypervisor.HypervisorType.VMware.equals(vm.getHypervisorType())) {
271+
return;
272+
}
273+
LOG.info("Preparing for restoring VM " + vm);
274+
PrepareForBackupRestorationCommand command = new PrepareForBackupRestorationCommand(vm.getInstanceName());
275+
Long hostId = virtualMachineManager.findClusterAndHostIdForVm(vm.getId()).second();
276+
if (hostId == null) {
277+
throw new CloudRuntimeException("Cannot find a host to prepare for restoring VM " + vm);
278+
}
279+
try {
280+
Answer answer = agentMgr.easySend(hostId, command);
281+
if (answer != null && answer.getResult()) {
282+
LOG.info("Succeeded to prepare for restoring VM " + vm);
283+
} else {
284+
throw new CloudRuntimeException(String.format("Failed to prepare for restoring VM %s. details: %s", vm,
285+
(answer != null ? answer.getDetails() : null)));
286+
}
287+
} catch (Exception e) {
288+
throw new CloudRuntimeException(String.format("Failed to prepare for restoring VM %s due to exception %s", vm, e));
289+
}
238290
}
239291

240292
@Override
@@ -330,6 +382,10 @@ public void doInTransactionWithoutResult(TransactionStatus status) {
330382
+ "domain_id: %s, zone_id: %s].", backup.getUuid(), backup.getVmId(), backup.getExternalId(), backup.getType(), backup.getDate(),
331383
backup.getBackupOfferingId(), backup.getAccountId(), backup.getDomainId(), backup.getZoneId()));
332384
backupDao.persist(backup);
385+
386+
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_BACKUP_CREATE,
387+
String.format("Created backup %s for VM ID: %s", backup.getUuid(), vm.getUuid()),
388+
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
333389
}
334390
}
335391
for (final Long backupIdToRemove : removeList) {
@@ -349,11 +405,14 @@ public String getConfigComponentName() {
349405
public ConfigKey<?>[] getConfigKeys() {
350406
return new ConfigKey[]{
351407
VeeamUrl,
408+
VeeamVersion,
352409
VeeamUsername,
353410
VeeamPassword,
354411
VeeamValidateSSLSecurity,
355412
VeeamApiRequestTimeout,
356-
VeeamRestoreTimeout
413+
VeeamRestoreTimeout,
414+
VeeamTaskPollInterval,
415+
VeeamTaskPollMaxRetry
357416
};
358417
}
359418

0 commit comments

Comments
 (0)