Skip to content

Commit 9be3107

Browse files
committed
Merge remote-tracking branch 'apache/main' into HEAD
2 parents ca41556 + 333973a commit 9be3107

29 files changed

Lines changed: 2547 additions & 287 deletions

File tree

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ public interface Backup extends ControlledEntity, InternalIdentity, Identity {
3939
Long getHostId();
4040

4141
enum Status {
42-
Allocated, Queued, BackingUp, ReadyForImageTransfer, FinalizingImageTransfer, BackedUp, Error, Failed, Restoring, Removed, Expunged
42+
Allocated, Queued, BackingUp, ReadyForImageTransfer, FinalizingImageTransfer, BackedUp, Error, Failed, Restoring, Removed, Expunged,
43+
// Hidden: a chain backup kept as a tombstone after the user deleted it while it still has
44+
// live descendants (incremental chains). Excluded from listBackups and from all backup
45+
// operations (which require BackedUp); swept from the DB once its last descendant is gone.
46+
Hidden
4347
}
4448

4549
class Metric {

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@ public interface BackupProvider {
8787
*/
8888
boolean deleteBackup(Backup backup, boolean forced);
8989

90+
/**
91+
* Whether {@link #deleteBackup(Backup, boolean)} owns DB-row removal and resource-count /
92+
* usage accounting for every backup it physically removes. Providers that manage incremental
93+
* chains (e.g. NAS) delete several backups per call — the leaf plus swept delete-pending
94+
* ancestors — and decrement once per removed backup themselves, so the manager must NOT
95+
* decrement or remove the row again. Defaults to {@code false}: the manager does the
96+
* single-backup accounting (the historical behaviour for non-chain providers).
97+
*/
98+
default boolean handlesChainDeleteResourceAccounting() {
99+
return false;
100+
}
101+
90102
Pair<Boolean, String> restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid);
91103

92104
/**
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 com.cloud.agent.api;
21+
22+
import com.cloud.agent.api.to.DataStoreTO;
23+
24+
/**
25+
* This command is used to cleanup the converted instance disks from the storage pool: vmVolumesStore and the prefix: vmVolumesPrefix.
26+
*/
27+
public class CleanupConvertedInstanceDisksCommand extends Command {
28+
29+
private DataStoreTO vmVolumesStore;
30+
private String vmVolumesPrefix;
31+
32+
public CleanupConvertedInstanceDisksCommand(DataStoreTO vmVolumesStore, String vmVolumesPrefix) {
33+
this.vmVolumesStore = vmVolumesStore;
34+
this.vmVolumesPrefix = vmVolumesPrefix;
35+
}
36+
37+
public DataStoreTO getVmVolumesStore() {
38+
return vmVolumesStore;
39+
}
40+
41+
public void setVmVolumesStore(DataStoreTO vmVolumesStore) {
42+
this.vmVolumesStore = vmVolumesStore;
43+
}
44+
45+
public String getVmVolumesPrefix() {
46+
return vmVolumesPrefix;
47+
}
48+
49+
public void setVmVolumesPrefix(String vmVolumesPrefix) {
50+
this.vmVolumesPrefix = vmVolumesPrefix;
51+
}
52+
53+
@Override
54+
public boolean executeInSequence() {
55+
return false;
56+
}
57+
}

core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ public class BackupAnswer extends Answer {
2929
private Long virtualSize;
3030
private Map<String, String> volumes;
3131
Boolean needsCleanup;
32+
// Set by the NAS backup provider after a checkpoint/bitmap was created during this backup.
33+
// The provider persists it in backup_details under NASBackupChainKeys.BITMAP_NAME.
34+
private String bitmapCreated;
35+
// Set when an incremental was requested but the agent had to fall back to a full
36+
// (e.g. VM was stopped). Provider should record this backup as type=full.
37+
private Boolean incrementalFallback;
3238

3339
public BackupAnswer(final Command command, final boolean success, final String details) {
3440
super(command, success, details);
@@ -68,4 +74,21 @@ public Boolean getNeedsCleanup() {
6874
public void setNeedsCleanup(Boolean needsCleanup) {
6975
this.needsCleanup = needsCleanup;
7076
}
77+
78+
public String getBitmapCreated() {
79+
return bitmapCreated;
80+
}
81+
82+
public void setBitmapCreated(String bitmapCreated) {
83+
this.bitmapCreated = bitmapCreated;
84+
}
85+
86+
public Boolean getIncrementalFallback() {
87+
return incrementalFallback != null && incrementalFallback;
88+
}
89+
90+
public void setIncrementalFallback(Boolean incrementalFallback) {
91+
this.incrementalFallback = incrementalFallback;
92+
}
93+
7194
}

core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ public class TakeBackupCommand extends Command {
3636
@LogLevel(LogLevel.Log4jLevel.Off)
3737
private String mountOptions;
3838

39+
// Incremental backup fields (NAS provider; null/empty for legacy full-only callers).
40+
private String mode; // "full" or "incremental"; null => legacy behaviour (script default)
41+
private String bitmapNew; // Checkpoint/bitmap name to create with this backup (timestamp-based)
42+
private String bitmapParent; // Incremental: parent bitmap to read changes since
43+
44+
// Per-volume parent backup file paths (one per VM volume, ordered by deviceId — same
45+
// order as volumePaths). The script rebases each new qcow2 onto the matching parent.
46+
// Backup file UUIDs differ across volumes, so a single parentPath would have rebased
47+
// every data disk onto the root file. New callers MUST populate parentPaths.
48+
private List<String> parentPaths;
49+
3950
public TakeBackupCommand(String vmName, String backupPath) {
4051
super();
4152
this.vmName = vmName;
@@ -106,6 +117,38 @@ public void setQuiesce(Boolean quiesce) {
106117
this.quiesce = quiesce;
107118
}
108119

120+
public String getMode() {
121+
return mode;
122+
}
123+
124+
public void setMode(String mode) {
125+
this.mode = mode;
126+
}
127+
128+
public String getBitmapNew() {
129+
return bitmapNew;
130+
}
131+
132+
public void setBitmapNew(String bitmapNew) {
133+
this.bitmapNew = bitmapNew;
134+
}
135+
136+
public String getBitmapParent() {
137+
return bitmapParent;
138+
}
139+
140+
public void setBitmapParent(String bitmapParent) {
141+
this.bitmapParent = bitmapParent;
142+
}
143+
144+
public List<String> getParentPaths() {
145+
return parentPaths;
146+
}
147+
148+
public void setParentPaths(List<String> parentPaths) {
149+
this.parentPaths = parentPaths;
150+
}
151+
109152
@Override
110153
public boolean executeInSequence() {
111154
return true;

engine/components-api/src/main/java/com/cloud/storage/StorageManager.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,14 @@ public interface StorageManager extends StorageService {
195195
true,
196196
ConfigKey.Scope.StoragePool,
197197
null);
198+
ConfigKey<Boolean> XenserverCreateCloneFull = new ConfigKey<>(Boolean.class,
199+
"xenserver.create.full.clone",
200+
"Storage",
201+
"false",
202+
"If set to true, creates VMs as full clones on XenServer hypervisor (uses VDI.copy instead of VDI.clone, removing the linked-clone parent relationship).",
203+
true,
204+
ConfigKey.Scope.StoragePool,
205+
null);
198206
ConfigKey<Boolean> VmwareAllowParallelExecution = new ConfigKey<>(Boolean.class,
199207
"vmware.allow.parallel.command.execution",
200208
"Advanced",

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public class BackupVO implements Backup {
6666
private String externalId;
6767

6868
@Column(name = "type")
69-
private String backupType;
69+
private String type;
7070

7171
@Column(name = "date")
7272
@Temporal(value = TemporalType.DATE)
@@ -125,7 +125,7 @@ public BackupVO() {
125125
@Override
126126
public String toString() {
127127
return String.format("Backup %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
128-
this, "id", "uuid", "vmId", "backupType", "externalId"));
128+
this, "id", "uuid", "vmId", "type", "externalId"));
129129
}
130130

131131
@Override
@@ -157,11 +157,11 @@ public void setExternalId(String externalId) {
157157
}
158158

159159
public String getType() {
160-
return backupType;
160+
return type;
161161
}
162162

163163
public void setType(String type) {
164-
this.backupType = type;
164+
this.type = type;
165165
}
166166

167167
@Override

engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-common-daos-between-management-and-usage-context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<bean id="hostTransferMapDaoImpl" class="com.cloud.cluster.agentlb.dao.HostTransferMapDaoImpl" />
4747
<bean id="imageStoreDaoImpl" class="org.apache.cloudstack.storage.datastore.db.ImageStoreDaoImpl" />
4848
<bean id="imageStoreObjectDownloadDaoImpl" class="org.apache.cloudstack.storage.datastore.db.ImageStoreObjectDownloadDaoImpl" />
49+
<bean id="kmsWrappedKeyDaoImpl" class="org.apache.cloudstack.kms.dao.KMSWrappedKeyDaoImpl" />
4950
<bean id="networkOfferingDaoImpl" class="com.cloud.offerings.dao.NetworkOfferingDaoImpl" />
5051
<bean id="networkOfferingDetailsDaoImpl" class="com.cloud.offerings.dao.NetworkOfferingDetailsDaoImpl" />
5152
<bean id="networkOfferingServiceMapDaoImpl" class="com.cloud.offerings.dao.NetworkOfferingServiceMapDaoImpl" />

engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@
316316
<bean id="apiKeyPairPermissionsDaoImpl" class="org.apache.cloudstack.acl.dao.ApiKeyPairPermissionsDaoImpl" />
317317
<bean id="kmsKeyDaoImpl" class="org.apache.cloudstack.kms.dao.KMSKeyDaoImpl" />
318318
<bean id="kmsKekVersionDaoImpl" class="org.apache.cloudstack.kms.dao.KMSKekVersionDaoImpl" />
319-
<bean id="kmsWrappedKeyDaoImpl" class="org.apache.cloudstack.kms.dao.KMSWrappedKeyDaoImpl" />
320319
<bean id="hsmProfileDaoImpl" class="org.apache.cloudstack.kms.dao.HSMProfileDaoImpl" />
321320
<bean id="hsmProfileDetailsDaoImpl" class="org.apache.cloudstack.kms.dao.HSMProfileDetailsDaoImpl" />
322321
<bean id="dnsServerDao" class="org.apache.cloudstack.dns.dao.DnsServerDaoImpl" />

engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ protected Answer copyObject(DataObject srcData, DataObject destData, Host destHo
191191
srcForCopy = cacheData = cacheMgr.createCacheObject(srcData, destScope);
192192
}
193193

194-
CopyCommand cmd = new CopyCommand(srcForCopy.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO()), primaryStorageDownloadWait,
194+
CopyCommand cmd = new CopyCommand(srcForCopy.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO()), primaryStorageDownloadWait,
195195
VirtualMachineManager.ExecuteInSequence.value());
196196
EndPoint ep = destHost != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(destHost) : selector.select(srcForCopy, destData);
197197
if (ep == null) {
@@ -257,6 +257,43 @@ protected DataTO addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(DataTO
257257
return dataTO;
258258
}
259259

260+
/**
261+
* Adds {@code 'xenserver.create.full.clone'} value for a given primary storage, whose HV is XenServer, on datastore's {@code fullCloneFlag} field
262+
* @param dataTO Dest data store TO
263+
* @return dataTO including fullCloneFlag, if provided
264+
*/
265+
protected DataTO addFullCloneAndDiskprovisiongStrictnessFlagOnXenServerDest(DataTO dataTO) {
266+
if (dataTO != null && dataTO.getHypervisorType().equals(Hypervisor.HypervisorType.XenServer)){
267+
DataStoreTO dataStoreTO = dataTO.getDataStore();
268+
if (dataStoreTO != null && dataStoreTO instanceof PrimaryDataStoreTO){
269+
PrimaryDataStoreTO primaryDataStoreTO = (PrimaryDataStoreTO) dataStoreTO;
270+
primaryDataStoreTO.setFullCloneFlag(StorageManager.XenserverCreateCloneFull.valueIn(primaryDataStoreTO.getId()));
271+
}
272+
}
273+
return dataTO;
274+
}
275+
276+
/**
277+
* Dispatches to the per-hypervisor {@code addFullCloneAndDiskprovisiongStrictnessFlagOn*Dest} helper
278+
* based on {@code dataTO.getHypervisorType()}. Returns {@code dataTO} unchanged for hypervisors
279+
* that do not have a full-clone toggle.
280+
* @param dataTO Dest data store TO
281+
* @return dataTO including fullCloneFlag, if provided
282+
*/
283+
protected DataTO addFullCloneAndDiskprovisiongStrictnessFlagOnDest(DataTO dataTO) {
284+
if (dataTO == null) {
285+
return dataTO;
286+
}
287+
Hypervisor.HypervisorType hypervisorType = dataTO.getHypervisorType();
288+
if (Hypervisor.HypervisorType.VMware.equals(hypervisorType)) {
289+
return addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(dataTO);
290+
}
291+
if (Hypervisor.HypervisorType.XenServer.equals(hypervisorType)) {
292+
return addFullCloneAndDiskprovisiongStrictnessFlagOnXenServerDest(dataTO);
293+
}
294+
return dataTO;
295+
}
296+
260297
protected Answer copyObject(DataObject srcData, DataObject destData) {
261298
return copyObject(srcData, destData, null);
262299
}
@@ -315,7 +352,7 @@ protected Answer copyVolumeFromSnapshot(DataObject snapObj, DataObject volObj) {
315352

316353
updateLockHostForVolume(ep, volObj);
317354

318-
CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(volObj.getTO()), _createVolumeFromSnapshotWait, VirtualMachineManager.ExecuteInSequence.value());
355+
CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(volObj.getTO()), _createVolumeFromSnapshotWait, VirtualMachineManager.ExecuteInSequence.value());
319356

320357
Answer answer = null;
321358
if (ep == null) {
@@ -361,7 +398,7 @@ private void updateLockHostForVolume(EndPoint ep, DataObject volObj) {
361398
}
362399

363400
protected Answer cloneVolume(DataObject template, DataObject volume) {
364-
CopyCommand cmd = new CopyCommand(template.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(volume.getTO()), 0, VirtualMachineManager.ExecuteInSequence.value());
401+
CopyCommand cmd = new CopyCommand(template.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(volume.getTO()), 0, VirtualMachineManager.ExecuteInSequence.value());
365402
try {
366403
EndPoint ep = selector.select(volume, anyVolumeRequiresEncryption(volume));
367404
Answer answer = null;
@@ -445,7 +482,7 @@ protected Answer copyVolumeBetweenPools(DataObject srcData, DataObject destData)
445482

446483
objOnImageStore.processEvent(Event.CopyingRequested);
447484

448-
CopyCommand cmd = new CopyCommand(objOnImageStore.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO()), _copyvolumewait, VirtualMachineManager.ExecuteInSequence.value());
485+
CopyCommand cmd = new CopyCommand(objOnImageStore.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO()), _copyvolumewait, VirtualMachineManager.ExecuteInSequence.value());
449486
EndPoint ep = selector.select(objOnImageStore, destData, encryptionRequired);
450487
if (ep == null) {
451488
String errMsg = String.format(NO_REMOTE_ENDPOINT_WITH_ENCRYPTION, encryptionRequired);
@@ -692,7 +729,7 @@ protected Answer createTemplateFromSnapshot(DataObject srcData, DataObject destD
692729
ep = selector.select(srcData, destData);
693730
}
694731

695-
CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO()), _createprivatetemplatefromsnapshotwait, VirtualMachineManager.ExecuteInSequence.value());
732+
CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO()), _createprivatetemplatefromsnapshotwait, VirtualMachineManager.ExecuteInSequence.value());
696733
Answer answer = null;
697734
if (ep == null) {
698735
logger.error(NO_REMOTE_ENDPOINT_SSVM);
@@ -730,7 +767,7 @@ protected Answer copySnapshot(DataObject srcData, DataObject destData) {
730767
Scope selectedScope = pickCacheScopeForCopy(srcData, destData);
731768
cacheData = cacheMgr.getCacheObject(srcData, selectedScope);
732769

733-
CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO()), _backupsnapshotwait, VirtualMachineManager.ExecuteInSequence.value());
770+
CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO()), _backupsnapshotwait, VirtualMachineManager.ExecuteInSequence.value());
734771
cmd.setCacheTO(cacheData.getTO());
735772
cmd.setOptions(options);
736773
EndPoint ep = selector.select(srcData, destData, encryptionRequired);
@@ -741,7 +778,7 @@ protected Answer copySnapshot(DataObject srcData, DataObject destData) {
741778
answer = ep.sendMessage(cmd);
742779
}
743780
} else {
744-
addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO());
781+
addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO());
745782
CopyCommand cmd = new CopyCommand(srcData.getTO(), destData.getTO(), _backupsnapshotwait, VirtualMachineManager.ExecuteInSequence.value());
746783
cmd.setOptions(options);
747784
EndPoint ep = selector.select(srcData, destData, StorageAction.BACKUPSNAPSHOT, encryptionRequired);

0 commit comments

Comments
 (0)