Skip to content

Bugfix/cstackex 327-Concurrent iSCSI VM deployment fails intermittently: post-clone LUN resize returns 404 - #118

Open
sp16743 wants to merge 4 commits into
mainfrom
bugfix/CSTACKEX-327
Open

sp16743 wants to merge 4 commits into
mainfrom
bugfix/CSTACKEX-327

Conversation

@sp16743

@sp16743 sp16743 commented Oct 9, 2026 •

Copy link
Copy Markdown

Description

After an iSCSI template clone, CloudStack grows the new LUN when the service offering disk is larger than the template. ONTAP returns the LUN UUID from the clone, but the immediate resize PATCH /api/storage/luns/{uuid} can return HTTP 404 (entry doesn't exist, target uuid) before that LUN is visible.

resizeCloudStackVolume now retries only that PATCH, using the same UUID. A 404 is retried for up to 30 attempts, one second apart. Any other error fails on the first attempt.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

Before the fix:
Screenshot 2026-09-30 at 3 57 53 PM

How Has This Been Tested?

Screenshot 2026-10-27 at 10 16 34 AM

Live concurrent deployment:
-Created an ONTAP iSCSI primary storage pool.
-Deployed ten CentOS VMs concurrently using the ISCSI compute offering.
-Nine deployments reached LUN cloning; one failed earlier due to insufficient host capacity.
-Nine clone POST requests returned LUN UUIDs.
-Five cloned LUNs were visible on the first GET.
-Four cloned LUNs returned 404 on the first GET.
-For all four, CloudStack waited and retried; the LUN became visible on attempt 2/5.
-CloudStack then resized each LUN to 10 GiB using PATCH and continued VM deployment successfully.
-No clone failed with Cloned Lun uuid was not visible.

How did you try to break this feature and the system with this change?

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

🔴 Test Coverage Grade: D — Marginal

Metric Value
Line coverage 24.82%
Branch coverage 18.97%

Grade Scale

Grade Line Coverage Meaning
🟢 A ≥ 80% Excellent - this code sleeps well at night 😴
🟡 B 60-79% Good - almost there, don't stop now 😉
🟠 C 40-59% Acceptable - your code is wearing a seatbelt, but no airbags 😬
🔴 D 20-39% Marginal - boldly shipping where no test has gone before 🖖
⛔ F < 20% Failing - tests? what tests? 🔥

Branch coverage is shown as a secondary signal. Grade is determined by line coverage.
View full Actions run

@github-actions

Copy link
Copy Markdown

This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch.

if (sizeInBytes <= 0) {
throw new CloudRuntimeException("Failed to resize Lun, invalid size " + sizeInBytes);
}
String authHeader = OntapStorageUtils.generateAuthHeader(storage.getUsername(), storage.getPassword());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of adding the retry logic here, during lun clone return the job response and poll for that job to get complete and perform the resize later

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

🔴 Test Coverage Grade: D — Marginal

Metric Value
Line coverage 25.83%
Branch coverage 20.12%

Grade Scale

Grade Line Coverage Meaning
🟢 A ≥ 80% Excellent - this code sleeps well at night 😴
🟡 B 60-79% Good - almost there, don't stop now 😉
🟠 C 40-59% Acceptable - your code is wearing a seatbelt, but no airbags 😬
🔴 D 20-39% Marginal - boldly shipping where no test has gone before 🖖
⛔ F < 20% Failing - tests? what tests? 🔥

Branch coverage is shown as a secondary signal. Grade is determined by line coverage.
View full Actions run

}
}
}
throw new CloudRuntimeException("Cloned Lun uuid was not visible for: " + lunUuid);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If lun uuid itself is not visible, what's the point of logging lunUuid? 😆

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is no point. I have changed it.

@sandeeplocharla sandeeplocharla left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

String authHeader = OntapStorageUtils.generateAuthHeader(storage.getUsername(), storage.getPassword());
OntapResponse<Lun> clonedLun = sanFeignClient.createLun(authHeader, true, lunRequest);
if (clonedLun == null || CollectionUtils.isEmpty(clonedLun.getRecords())) {
OntapResponse<Lun> created = sanFeignClient.createLun(authHeader, true, lunRequest);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change the variable name to clonedLun

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

OntapResponse<Lun> clonedLun = sanFeignClient.createLun(authHeader, true, lunRequest);
if (clonedLun == null || CollectionUtils.isEmpty(clonedLun.getRecords())) {
OntapResponse<Lun> created = sanFeignClient.createLun(authHeader, true, lunRequest);
if (created == null || created.getRecords() == null || created.getRecords().isEmpty()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use collectionUtils/Stringutils for null or empty check

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it now

}
Lun lun = clonedLun.getRecords().get(0);
String lunUuid = created.getRecords().get(0).getUuid();
Lun lun = getLunByUuidUntilVisible(authHeader, lunUuid, lunRequest.getName());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if the get lun call failed, cloned lun will. still exists and retry will see the duplicate lun

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it.The cloned LUN is deleted ,so a subsequent CloudStack retry does not encounter a duplicate LUN name.

for (int attempt = 1; attempt <= OntapStorageConstants.ONTAP_LUN_CLONE_GET_MAX_RETRIES; attempt++) {
try {
Lun lun = sanFeignClient.getLunByUUID(authHeader, lunUuid);
if (lun != null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can not decode based on lun != null condition that lun is visible.
return the get response record instead

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it.The code now uses the collection GET with the LUN UUID, verifies that the response contains a record, and returns that record instead of relying on lun != null.

public static final int ONTAP_FLEXVOL_JOB_POLL_INTERVAL_MS = 2000;
public static final int ONTAP_FLEXVOL_RESOLVE_MAX_RETRIES = 30;
/** GET-by-UUID retries after LUN clone response. */
public static final int ONTAP_LUN_CLONE_GET_MAX_RETRIES = 5;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this interval and no of retries are less for clone

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the retries to 10

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants