Skip to content

Commit 8b8d750

Browse files
authored
Merge branch '4.22' into fix/oauth2-google-thread-safety-4.22
2 parents 54a5cdd + a8c8c18 commit 8b8d750

26 files changed

Lines changed: 2492 additions & 116 deletions

File tree

‎api/src/main/java/com/cloud/resource/ResourceService.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public interface ResourceService {
9191

9292
DataCenter getZone(Long zoneId);
9393

94-
List<HypervisorType> getSupportedHypervisorTypes(long zoneId, boolean forVirtualRouter, Long podId);
94+
List<HypervisorType> getSupportedHypervisorTypes(long zoneId, boolean forSystemVm, Long podId);
9595

9696
boolean releaseHostReservation(Long hostId);
9797

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@
3131
public interface Backup extends ControlledEntity, InternalIdentity, Identity {
3232

3333
enum Status {
34-
Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged
34+
Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged,
35+
// Hidden: a chain backup kept as a tombstone after the user deleted it while it still has
36+
// live descendants (incremental chains). Excluded from listBackups and from all backup
37+
// operations (which require BackedUp); swept from the DB once its last descendant is gone.
38+
Hidden
3539
}
3640

3741
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
/**

‎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/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)
@@ -113,7 +113,7 @@ public BackupVO() {
113113
@Override
114114
public String toString() {
115115
return String.format("Backup %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
116-
this, "id", "uuid", "vmId", "backupType", "externalId"));
116+
this, "id", "uuid", "vmId", "type", "externalId"));
117117
}
118118

119119
@Override
@@ -145,11 +145,11 @@ public void setExternalId(String externalId) {
145145
}
146146

147147
public String getType() {
148-
return backupType;
148+
return type;
149149
}
150150

151151
public void setType(String type) {
152-
this.backupType = type;
152+
this.type = type;
153153
}
154154

155155
@Override

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,15 @@ private boolean canBypassSecondaryStorage(DataObject srcData, DataObject destDat
472472
return true;
473473
}
474474

475+
if (Hypervisor.HypervisorType.XenServer.equals(((VolumeInfo) srcData).getHypervisorType())) {
476+
return false;
477+
}
478+
475479
if (destData instanceof VolumeInfo) {
480+
if (Hypervisor.HypervisorType.XenServer.equals(((VolumeInfo) destData).getHypervisorType())) {
481+
return false;
482+
}
483+
476484
Scope srcDataStoreScope = srcData.getDataStore().getScope();
477485
Scope destDataStoreScope = destData.getDataStore().getScope();
478486
logger.info("srcDataStoreScope: {}, srcData pool type: {}; destDataStoreScope: {}, destData pool type: {}",

‎engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public void testCanBypassSecondaryStorageForDirectDownload() throws NoSuchMethod
123123
@Test
124124
public void testCanBypassSecondaryStorageForUnsupportedDataObject() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
125125
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
126+
Mockito.doReturn(HypervisorType.KVM).when(srcVolumeInfo).getHypervisorType();
126127

127128
TemplateObject destTemplateInfo = Mockito.spy(new TemplateObject());
128129

@@ -136,12 +137,14 @@ public void testCanBypassSecondaryStorageForUnsupportedDataObject() throws NoSuc
136137
@Test
137138
public void testCanBypassSecondaryStorageForUnsupportedSrcPoolType() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
138139
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
140+
Mockito.doReturn(HypervisorType.KVM).when(srcVolumeInfo).getHypervisorType();
139141
DataStore srcDataStore = Mockito.mock(DataStore.class);
140142
Mockito.doReturn(new ZoneScope(1L)).when(srcDataStore).getScope();
141143
Mockito.doReturn(srcDataStore).when(srcVolumeInfo).getDataStore();
142144
Mockito.doReturn(Storage.StoragePoolType.PowerFlex).when(srcVolumeInfo).getStoragePoolType();
143145

144146
VolumeObject destVolumeInfo = Mockito.spy(new VolumeObject());
147+
Mockito.doReturn(HypervisorType.KVM).when(destVolumeInfo).getHypervisorType();
145148
DataStore destDataStore = Mockito.mock(DataStore.class);
146149
Mockito.doReturn(new ZoneScope(1L)).when(destDataStore).getScope();
147150
Mockito.doReturn(destDataStore).when(destVolumeInfo).getDataStore();
@@ -157,12 +160,14 @@ public void testCanBypassSecondaryStorageForUnsupportedSrcPoolType() throws NoSu
157160
@Test
158161
public void testCanBypassSecondaryStorageForUnsupportedDestPoolType() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
159162
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
163+
Mockito.doReturn(HypervisorType.KVM).when(srcVolumeInfo).getHypervisorType();
160164
DataStore srcDataStore = Mockito.mock(DataStore.class);
161165
Mockito.doReturn(new ZoneScope(1L)).when(srcDataStore).getScope();
162166
Mockito.doReturn(srcDataStore).when(srcVolumeInfo).getDataStore();
163167
Mockito.doReturn(Storage.StoragePoolType.NetworkFilesystem).when(srcVolumeInfo).getStoragePoolType();
164168

165169
VolumeObject destVolumeInfo = Mockito.spy(new VolumeObject());
170+
Mockito.doReturn(HypervisorType.KVM).when(destVolumeInfo).getHypervisorType();
166171
DataStore destDataStore = Mockito.mock(DataStore.class);
167172
Mockito.doReturn(new ZoneScope(1L)).when(destDataStore).getScope();
168173
Mockito.doReturn(destDataStore).when(destVolumeInfo).getDataStore();
@@ -178,12 +183,14 @@ public void testCanBypassSecondaryStorageForUnsupportedDestPoolType() throws NoS
178183
@Test
179184
public void testCanBypassSecondaryStorageWithZoneWideNFSPoolsInSameZone() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
180185
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
186+
Mockito.doReturn(HypervisorType.KVM).when(srcVolumeInfo).getHypervisorType();
181187
DataStore srcDataStore = Mockito.mock(DataStore.class);
182188
Mockito.doReturn(new ZoneScope(1L)).when(srcDataStore).getScope();
183189
Mockito.doReturn(srcDataStore).when(srcVolumeInfo).getDataStore();
184190
Mockito.doReturn(Storage.StoragePoolType.NetworkFilesystem).when(srcVolumeInfo).getStoragePoolType();
185191

186192
VolumeObject destVolumeInfo = Mockito.spy(new VolumeObject());
193+
Mockito.doReturn(HypervisorType.KVM).when(destVolumeInfo).getHypervisorType();
187194
DataStore destDataStore = Mockito.mock(DataStore.class);
188195
Mockito.doReturn(new ZoneScope(1L)).when(destDataStore).getScope();
189196
Mockito.doReturn(destDataStore).when(destVolumeInfo).getDataStore();
@@ -199,12 +206,14 @@ public void testCanBypassSecondaryStorageWithZoneWideNFSPoolsInSameZone() throws
199206
@Test
200207
public void testCanBypassSecondaryStorageWithClusterWideNFSPoolsInSameCluster() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
201208
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
209+
Mockito.doReturn(HypervisorType.KVM).when(srcVolumeInfo).getHypervisorType();
202210
DataStore srcDataStore = Mockito.mock(DataStore.class);
203211
Mockito.doReturn(new ClusterScope(5L, 2L, 1L)).when(srcDataStore).getScope();
204212
Mockito.doReturn(srcDataStore).when(srcVolumeInfo).getDataStore();
205213
Mockito.doReturn(Storage.StoragePoolType.NetworkFilesystem).when(srcVolumeInfo).getStoragePoolType();
206214

207215
VolumeObject destVolumeInfo = Mockito.spy(new VolumeObject());
216+
Mockito.doReturn(HypervisorType.KVM).when(destVolumeInfo).getHypervisorType();
208217
DataStore destDataStore = Mockito.mock(DataStore.class);
209218
Mockito.doReturn(new ClusterScope(5L, 2L, 1L)).when(destDataStore).getScope();
210219
Mockito.doReturn(destDataStore).when(destVolumeInfo).getDataStore();
@@ -220,12 +229,14 @@ public void testCanBypassSecondaryStorageWithClusterWideNFSPoolsInSameCluster()
220229
@Test
221230
public void testCanBypassSecondaryStorageWithLocalAndClusterWideNFSPoolsInSameCluster() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
222231
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
232+
Mockito.doReturn(HypervisorType.KVM).when(srcVolumeInfo).getHypervisorType();
223233
DataStore srcDataStore = Mockito.mock(DataStore.class);
224234
Mockito.doReturn(new HostScope(1L, 1L, 1L)).when(srcDataStore).getScope();
225235
Mockito.doReturn(srcDataStore).when(srcVolumeInfo).getDataStore();
226236
Mockito.doReturn(Storage.StoragePoolType.Filesystem).when(srcVolumeInfo).getStoragePoolType();
227237

228238
VolumeObject destVolumeInfo = Mockito.spy(new VolumeObject());
239+
Mockito.doReturn(HypervisorType.KVM).when(destVolumeInfo).getHypervisorType();
229240
DataStore destDataStore = Mockito.mock(DataStore.class);
230241
Mockito.doReturn(new ClusterScope(1L, 1L, 1L)).when(destDataStore).getScope();
231242
Mockito.doReturn(destDataStore).when(destVolumeInfo).getDataStore();
@@ -244,12 +255,14 @@ public void testCanBypassSecondaryStorageWithLocalAndClusterWideNFSPoolsInSameCl
244255
@Test
245256
public void testCanBypassSecondaryStorageWithLocalAndZoneWideNFSPoolsInSameZone() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
246257
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
258+
Mockito.doReturn(HypervisorType.KVM).when(srcVolumeInfo).getHypervisorType();
247259
DataStore srcDataStore = Mockito.mock(DataStore.class);
248260
Mockito.doReturn(new HostScope(1L, 1L, 1L)).when(srcDataStore).getScope();
249261
Mockito.doReturn(srcDataStore).when(srcVolumeInfo).getDataStore();
250262
Mockito.doReturn(Storage.StoragePoolType.Filesystem).when(srcVolumeInfo).getStoragePoolType();
251263

252264
VolumeObject destVolumeInfo = Mockito.spy(new VolumeObject());
265+
Mockito.doReturn(HypervisorType.KVM).when(destVolumeInfo).getHypervisorType();
253266
DataStore destDataStore = Mockito.mock(DataStore.class);
254267
Mockito.doReturn(new ZoneScope(1L)).when(destDataStore).getScope();
255268
Mockito.doReturn(destDataStore).when(destVolumeInfo).getDataStore();
@@ -268,12 +281,14 @@ public void testCanBypassSecondaryStorageWithLocalAndZoneWideNFSPoolsInSameZone(
268281
@Test
269282
public void testCanBypassSecondaryStorageWithClusterWideNFSAndZoneWideNFSPoolsInSameZone() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
270283
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
284+
Mockito.doReturn(HypervisorType.KVM).when(srcVolumeInfo).getHypervisorType();
271285
DataStore srcDataStore = Mockito.mock(DataStore.class);
272286
Mockito.doReturn(new ClusterScope(5L, 2L, 1L)).when(srcDataStore).getScope();
273287
Mockito.doReturn(srcDataStore).when(srcVolumeInfo).getDataStore();
274288
Mockito.doReturn(Storage.StoragePoolType.NetworkFilesystem).when(srcVolumeInfo).getStoragePoolType();
275289

276290
VolumeObject destVolumeInfo = Mockito.spy(new VolumeObject());
291+
Mockito.doReturn(HypervisorType.KVM).when(destVolumeInfo).getHypervisorType();
277292
DataStore destDataStore = Mockito.mock(DataStore.class);
278293
Mockito.doReturn(new ZoneScope(1L)).when(destDataStore).getScope();
279294
Mockito.doReturn(destDataStore).when(destVolumeInfo).getDataStore();
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.backup;
18+
19+
/**
20+
* Keys used by the NAS backup provider when storing incremental-chain metadata
21+
* in the existing {@code backup_details} key/value table. Stored here (not on
22+
* the {@code backups} table) so other providers do not need a schema change to
23+
* support their own incremental implementations.
24+
*/
25+
public final class NASBackupChainKeys {
26+
27+
/** UUID of the parent backup (full or previous incremental). Empty for full backups. */
28+
public static final String PARENT_BACKUP_ID = "nas.parent_backup_id";
29+
30+
/** QEMU dirty-bitmap name created by this backup, used as the {@code <incremental>} reference for the next one. */
31+
public static final String BITMAP_NAME = "nas.bitmap_name";
32+
33+
/** Identifier shared by every backup in the same chain (the full anchors a chain; its incrementals inherit the id). */
34+
public static final String CHAIN_ID = "nas.chain_id";
35+
36+
/** Position within the chain: 0 for the full, 1 for the first incremental, and so on. */
37+
public static final String CHAIN_POSITION = "nas.chain_position";
38+
39+
/**
40+
* In-memory chain-mode sentinels used by {@code ChainDecision.mode}. The persisted
41+
* full-vs-incremental backup type lives on the {@code backup.type} column (set in
42+
* {@code takeBackup}) — single source of truth. Not duplicated into backup_details.
43+
*/
44+
public static final String TYPE_FULL = "full";
45+
public static final String TYPE_INCREMENTAL = "incremental";
46+
// Feature disabled: behave exactly like the pre-incremental full-only backup — no QEMU
47+
// bitmap/checkpoint is created and no chain metadata is persisted. Matches nasbackup.sh's
48+
// "legacy-full" mode token (which sets make_checkpoint=0).
49+
public static final String TYPE_LEGACY_FULL = "legacy-full";
50+
51+
/**
52+
* VM-scoped detail (stored in {@code vm_instance_details}) holding the QEMU dirty-bitmap
53+
* name that currently exists on the running VM and is therefore the only valid parent
54+
* for the next incremental backup. Written by {@link #BITMAP_NAME} on each successful
55+
* backup; cleared on restore (the restored disk image has no bitmap, so the next backup
56+
* must be a fresh full). When the VM has no detail, {@code decideChain} forces full.
57+
*/
58+
public static final String VM_ACTIVE_CHECKPOINT_ID = "nas.active_checkpoint_id";
59+
60+
private NASBackupChainKeys() {
61+
}
62+
}

0 commit comments

Comments
 (0)