Skip to content

Commit 09968db

Browse files
CSTACKEX-18_2: quiecing VM would be done based on user input for VM level snapshot
1 parent 723561b commit 09968db

File tree

2 files changed

+63
-52
lines changed

2 files changed

+63
-52
lines changed

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/vmsnapshot/OntapVMSnapshotStrategy.java

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,12 @@ private boolean allVolumesOnOntapManagedStorage(long vmId) {
198198
* Creates a per-volume disk snapshot as part of a VM snapshot operation.
199199
*
200200
* <p>Overrides the parent to ensure {@code quiescevm} is always {@code false}
201-
* in the per-volume snapshot payload. ONTAP handles quiescing at the VM level
202-
* via QEMU guest agent freeze/thaw in {@link #takeVMSnapshot}, so the
203-
* individual volume snapshot must not request quiescing again. Without this
204-
* override, {@link org.apache.cloudstack.storage.snapshot.DefaultSnapshotStrategy#takeSnapshot}
201+
* in the per-volume snapshot payload. When quiescing is requested, ONTAP handles
202+
* it at the VM level via QEMU guest agent freeze/thaw in {@link #takeVMSnapshot},
203+
* so the individual volume snapshot must not request quiescing again. Without this
204+
* override, {@link org.apache.cloudstack.storage.snapshot.StorageSystemSnapshotStrategy#takeSnapshot}
205+
* would attempt a second freeze/thaw for each volume, and
206+
* {@link org.apache.cloudstack.storage.snapshot.DefaultSnapshotStrategy#takeSnapshot}
205207
* would reject the request with "can't handle quiescevm equal true for volume snapshot"
206208
* when the user selects the quiesce option in the UI.</p>
207209
*/
@@ -226,9 +228,11 @@ protected SnapshotInfo createDiskSnapshot(VMSnapshot vmSnapshot, List<SnapshotIn
226228
* Takes a VM-level snapshot by freezing the VM, creating per-volume snapshots
227229
* on ONTAP storage (file clones), and then thawing the VM.
228230
*
229-
* <p>The quiesce option is always {@code true} for ONTAP to ensure filesystem
230-
* consistency across all volumes. The QEMU guest agent must be installed and
231-
* running inside the guest VM.</p>
231+
* <p>If the user requests quiescing ({@code quiescevm=true}), the QEMU guest
232+
* agent is used to freeze/thaw the VM file systems for application-consistent
233+
* snapshots. If {@code quiescevm=false}, the snapshots are crash-consistent
234+
* only. The QEMU guest agent must be installed and running inside the guest VM
235+
* for quiescing to work.</p>
232236
*/
233237
@Override
234238
public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
@@ -264,12 +268,19 @@ public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
264268
current = vmSnapshotHelper.getSnapshotWithParents(currentSnapshot);
265269
}
266270

267-
// For ONTAP managed NFS, always quiesce the VM for filesystem consistency
268-
boolean quiescevm = true;
271+
// Respect the user's quiesce option from the VM snapshot request
272+
boolean quiescevm = true; // default to true for safety
269273
VMSnapshotOptions options = vmSnapshotVO.getOptions();
270-
if (options != null && !options.needQuiesceVM()) {
271-
logger.info("Quiesce option was set to false, but overriding to true for ONTAP managed storage " +
272-
"to ensure filesystem consistency across all volumes");
274+
if (options != null) {
275+
quiescevm = options.needQuiesceVM();
276+
}
277+
278+
if (quiescevm) {
279+
logger.info("Quiesce option is enabled for ONTAP VM Snapshot of VM [{}]. " +
280+
"VM file systems will be frozen/thawed for application-consistent snapshots.", userVm.getInstanceName());
281+
} else {
282+
logger.info("Quiesce option is disabled for ONTAP VM Snapshot of VM [{}]. " +
283+
"Snapshots will be crash-consistent only.", userVm.getInstanceName());
273284
}
274285

275286
VMSnapshotTO target = new VMSnapshotTO(vmSnapshot.getId(), vmSnapshot.getName(),
@@ -284,7 +295,7 @@ public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
284295
CreateVMSnapshotCommand ccmd = new CreateVMSnapshotCommand(
285296
userVm.getInstanceName(), userVm.getUuid(), target, volumeTOs, guestOS.getDisplayName());
286297

287-
logger.info("Creating ONTAP VM Snapshot for VM [{}] with quiesce=true", userVm.getInstanceName());
298+
logger.info("Creating ONTAP VM Snapshot for VM [{}] with quiesce={}", userVm.getInstanceName(), quiescevm);
288299

289300
// Prepare volume info list
290301
List<VolumeInfo> volumeInfos = new ArrayList<>();
@@ -295,22 +306,26 @@ public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
295306
prev_chain_size += volumeVO.getVmSnapshotChainSize() == null ? 0 : volumeVO.getVmSnapshotChainSize();
296307
}
297308

298-
// ── Step 1: Freeze the VM ──
299-
FreezeThawVMCommand freezeCommand = new FreezeThawVMCommand(userVm.getInstanceName());
300-
freezeCommand.setOption(FreezeThawVMCommand.FREEZE);
301-
freezeAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, freezeCommand);
302-
startFreeze = System.nanoTime();
309+
// ── Step 1: Freeze the VM (only if quiescing is requested) ──
310+
if (quiescevm) {
311+
FreezeThawVMCommand freezeCommand = new FreezeThawVMCommand(userVm.getInstanceName());
312+
freezeCommand.setOption(FreezeThawVMCommand.FREEZE);
313+
freezeAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, freezeCommand);
314+
startFreeze = System.nanoTime();
303315

304-
thawCmd = new FreezeThawVMCommand(userVm.getInstanceName());
305-
thawCmd.setOption(FreezeThawVMCommand.THAW);
316+
thawCmd = new FreezeThawVMCommand(userVm.getInstanceName());
317+
thawCmd.setOption(FreezeThawVMCommand.THAW);
306318

307-
if (freezeAnswer == null || !freezeAnswer.getResult()) {
308-
String detail = (freezeAnswer != null) ? freezeAnswer.getDetails() : "no response from agent";
309-
throw new CloudRuntimeException("Could not freeze VM [" + userVm.getInstanceName() +
310-
"] for ONTAP snapshot. Ensure qemu-guest-agent is installed and running. Details: " + detail);
311-
}
319+
if (freezeAnswer == null || !freezeAnswer.getResult()) {
320+
String detail = (freezeAnswer != null) ? freezeAnswer.getDetails() : "no response from agent";
321+
throw new CloudRuntimeException("Could not freeze VM [" + userVm.getInstanceName() +
322+
"] for ONTAP snapshot. Ensure qemu-guest-agent is installed and running. Details: " + detail);
323+
}
312324

313-
logger.info("VM [{}] frozen successfully via QEMU guest agent", userVm.getInstanceName());
325+
logger.info("VM [{}] frozen successfully via QEMU guest agent", userVm.getInstanceName());
326+
} else {
327+
logger.info("Skipping VM freeze for VM [{}] as quiesce is not requested", userVm.getInstanceName());
328+
}
314329

315330
// ── Step 2: Create per-volume snapshots (ONTAP file clones) ──
316331
try {
@@ -328,19 +343,21 @@ public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
328343
TimeUnit.MILLISECONDS.convert(System.nanoTime() - startSnapshot, TimeUnit.NANOSECONDS));
329344
}
330345
} finally {
331-
// ── Step 3: Thaw the VM (always, even on error) ──
332-
try {
333-
thawAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, thawCmd);
334-
if (thawAnswer != null && thawAnswer.getResult()) {
335-
logger.info("VM [{}] thawed successfully. Total freeze duration: {} ms",
336-
userVm.getInstanceName(),
337-
TimeUnit.MILLISECONDS.convert(System.nanoTime() - startFreeze, TimeUnit.NANOSECONDS));
338-
} else {
339-
logger.warn("Failed to thaw VM [{}]: {}", userVm.getInstanceName(),
340-
(thawAnswer != null) ? thawAnswer.getDetails() : "no response");
346+
// ── Step 3: Thaw the VM (only if it was frozen, always even on error) ──
347+
if (quiescevm && freezeAnswer != null && freezeAnswer.getResult()) {
348+
try {
349+
thawAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, thawCmd);
350+
if (thawAnswer != null && thawAnswer.getResult()) {
351+
logger.info("VM [{}] thawed successfully. Total freeze duration: {} ms",
352+
userVm.getInstanceName(),
353+
TimeUnit.MILLISECONDS.convert(System.nanoTime() - startFreeze, TimeUnit.NANOSECONDS));
354+
} else {
355+
logger.warn("Failed to thaw VM [{}]: {}", userVm.getInstanceName(),
356+
(thawAnswer != null) ? thawAnswer.getDetails() : "no response");
357+
}
358+
} catch (Exception thawEx) {
359+
logger.error("Exception while thawing VM [{}]: {}", userVm.getInstanceName(), thawEx.getMessage(), thawEx);
341360
}
342-
} catch (Exception thawEx) {
343-
logger.error("Exception while thawing VM [{}]: {}", userVm.getInstanceName(), thawEx.getMessage(), thawEx);
344361
}
345362
}
346363

plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/vmsnapshot/OntapVMSnapshotStrategyTest.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
* <li>canHandle(Long vmId, Long rootPoolId, boolean snapshotMemory) — allocation-phase checks</li>
9090
* <li>takeVMSnapshot — success path with freeze/thaw and per-volume snapshot</li>
9191
* <li>takeVMSnapshot — failure scenarios (freeze failure, disk snapshot failure, agent errors)</li>
92-
* <li>Quiesce override behavior (always true for ONTAP)</li>
92+
* <li>Quiesce behavior (honors user input; freeze/thaw only when quiesce=true)</li>
9393
* </ul>
9494
*/
9595
@ExtendWith(MockitoExtension.class)
@@ -677,11 +677,11 @@ void testTakeVMSnapshot_StateTransitionFails_ThrowsCloudRuntimeException() throw
677677
}
678678

679679
// ══════════════════════════════════════════════════════════════════════════
680-
// Tests: Quiesce Override
680+
// Tests: Quiesce Behavior
681681
// ══════════════════════════════════════════════════════════════════════════
682682

683683
@Test
684-
void testTakeVMSnapshot_QuiesceOverriddenToTrue() throws Exception {
684+
void testTakeVMSnapshot_QuiesceFalse_SkipsFreezeThaw() throws Exception {
685685
VMSnapshotVO vmSnapshot = createTakeSnapshotVmSnapshot();
686686
// Explicitly set quiesce to false
687687
VMSnapshotOptions options = mock(VMSnapshotOptions.class);
@@ -691,14 +691,6 @@ void testTakeVMSnapshot_QuiesceOverriddenToTrue() throws Exception {
691691
setupTakeSnapshotCommon(vmSnapshot);
692692
setupSingleVolumeForTakeSnapshot();
693693

694-
FreezeThawVMAnswer freezeAnswer = mock(FreezeThawVMAnswer.class);
695-
when(freezeAnswer.getResult()).thenReturn(true);
696-
FreezeThawVMAnswer thawAnswer = mock(FreezeThawVMAnswer.class);
697-
when(thawAnswer.getResult()).thenReturn(true);
698-
when(agentMgr.send(eq(HOST_ID), any(FreezeThawVMCommand.class)))
699-
.thenReturn(freezeAnswer)
700-
.thenReturn(thawAnswer);
701-
702694
SnapshotInfo snapshotInfo = mock(SnapshotInfo.class);
703695
doReturn(snapshotInfo).when(strategy).createDiskSnapshot(any(), any(), any());
704696
doNothing().when(strategy).processAnswer(any(), any(), any(), any());
@@ -707,10 +699,12 @@ void testTakeVMSnapshot_QuiesceOverriddenToTrue() throws Exception {
707699

708700
VMSnapshot result = strategy.takeVMSnapshot(vmSnapshot);
709701

710-
// Snapshot should succeed even with quiesce=false, because ONTAP overrides to true
702+
// Snapshot should succeed with quiesce=false (crash-consistent, no freeze/thaw)
711703
assertNotNull(result);
712-
// The freeze command is always sent (quiesce=true)
713-
verify(agentMgr, times(2)).send(eq(HOST_ID), any(FreezeThawVMCommand.class));
704+
// No freeze/thaw commands should be sent when quiesce is false
705+
verify(agentMgr, never()).send(eq(HOST_ID), any(FreezeThawVMCommand.class));
706+
// Per-volume snapshot should still be created
707+
verify(strategy).createDiskSnapshot(any(), any(), any());
714708
}
715709

716710
@Test

0 commit comments

Comments
 (0)