Skip to content

flasharray: authenticate via REST 2.x api-token and discover API version#13060

Open
genegr wants to merge 2 commits intoapache:mainfrom
genegr:feat/flasharray-rest2x-apitoken-auth
Open

flasharray: authenticate via REST 2.x api-token and discover API version#13060
genegr wants to merge 2 commits intoapache:mainfrom
genegr:feat/flasharray-rest2x-apitoken-auth

Conversation

@genegr
Copy link
Copy Markdown

@genegr genegr commented Apr 22, 2026

The FlashArray adapter previously always made an initial call to the deprecated Purity REST 1.x session endpoint using a username and password to obtain a long-lived api_token, then exchanged that token for the REST 2.x x-auth-token session key. Purity 1.x is being removed from the array, so this path has an expiration date, and storing the username and password as pool details is not what the Purity documentation recommends.

Accept a pre-minted api_token in the pool details (ProviderAdapter.API_TOKEN_KEY, already reserved in the base interface) and go straight to the REST 2.x /login endpoint. The api_token is long-lived and is created on the array via the Purity GUI (Users -> API Tokens) or CLI (pureadmin create --api-token).

If api_token is not set, fall back to the legacy username/password flow and emit a deprecation warning so existing deployments keep working during the transition. The fallback path will be removed in a later release.

While here, resolve the API version dynamically by calling the unauthenticated GET /api/api_version endpoint the first time a login happens on a pool, unless the operator pinned a specific version via API_VERSION. This makes the adapter pick up newer Purity releases automatically instead of being stuck on the hard-coded 2.23 default.

Description

The FlashArray adapter currently performs two calls to log in: a POST to the deprecated Purity REST 1.x auth/apitoken endpoint with username/password to obtain a long-lived api_token, then a POST to /api/<ver>/login with api-token: header to get the session x-auth-token. Pure Storage is phasing out REST 1.x, and storing username/password as pool details is not the recommended pattern.

This PR:

  • Accepts a pre-minted long-lived api_token as a pool detail (ProviderAdapter.API_TOKEN_KEY, already reserved in the base interface). When present, the adapter skips the 1.x call entirely and goes straight to POST /api/<ver>/login with api-token: header.
  • Falls back to the legacy username/password flow when api_token is not set, logging a deprecation warning. No existing deployments break; removal of the fallback can happen in a later release.
  • Discovers the latest supported REST API version by calling the unauthenticated GET /api/api_version on first login (unless an explicit api_version pool detail pins it). Replaces the hardcoded 2.23 default, so the adapter tracks newer Purity releases automatically.

Types of changes

  • Enhancement (non-breaking change which adds functionality)

How Has This Been Tested?

Validated on a 4.23-SNAPSHOT lab against a Purity 6.7.7 FlashArray:

  • Added details[api_token]=<long-lived-token> to an existing pool and restarted mgmt → adapter resumes without the deprecation warning, capacity polling and volume create continue working.
  • Removed the api_token detail → adapter logs the deprecation warning and falls back to u/p against /api/1.19/auth/apitoken, still working.
  • /api/api_version discovery resolves to 2.36 on Purity 6.7.7 without manual configuration.

How to Get an API Token on the Array

Purity GUI: Settings → Users → <user> → API Tokens → Create API Token. CLI: ssh pureuser@<array>; pureadmin create --api-token <user>.

The FlashArray adapter previously always made an initial call to the
deprecated Purity REST 1.x session endpoint using a username and password
to obtain a long-lived api_token, then exchanged that token for the
REST 2.x x-auth-token session key. Purity 1.x is being removed from the
array, so this path has an expiration date, and storing the username and
password as pool details is not what the Purity documentation recommends.

Accept a pre-minted api_token in the pool details (ProviderAdapter.API_TOKEN_KEY,
already reserved in the base interface) and go straight to the REST 2.x
/login endpoint. The api_token is long-lived and is created on the array
via the Purity GUI (Users -> API Tokens) or CLI (pureadmin create --api-token).

If api_token is not set, fall back to the legacy username/password flow
and emit a deprecation warning so existing deployments keep working
during the transition. The fallback path will be removed in a later release.

While here, resolve the API version dynamically by calling the unauthenticated
GET /api/api_version endpoint the first time a login happens on a pool,
unless the operator pinned a specific version via API_VERSION. This makes
the adapter pick up newer Purity releases automatically instead of being
stuck on the hard-coded 2.23 default.

Signed-off-by: Eugenio Grosso <eugenio.grosso@gmail.com>
@winterhazel
Copy link
Copy Markdown
Member

@blueorangutan package

@winterhazel winterhazel added this to the 4.23.0 milestone Apr 22, 2026
@blueorangutan
Copy link
Copy Markdown

@winterhazel a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan
Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 17577


apiVersion = connectionDetails.get(FlashArrayAdapter.API_VERSION);
boolean apiVersionExplicit = apiVersion != null;
if (apiVersion == null) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (apiVersion == null) {
if (!apiVersionExplicit) {

if (apiVersion == null) {
apiVersion = queryParms.get(FlashArrayAdapter.API_VERSION);
apiVersionExplicit = apiVersion != null;
if (apiVersion == null) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (apiVersion == null) {
if (!apiVersionExplicit) {

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the FlashArray managed-storage adapter to support Pure’s REST 2.x preferred authentication model by accepting a long-lived api_token and dynamically discovering the array’s supported REST API version, while keeping a deprecated username/password fallback for existing deployments.

Changes:

  • Add support for authenticating directly to REST 2.x /login using a pre-minted api_token pool detail, with a deprecation warning when falling back to REST 1.x username/password.
  • Discover the latest supported Purity REST API version via unauthenticated GET /api/api_version when api_version is not explicitly configured.
  • Improve some login error handling/messages around token-based auth.
Comments suppressed due to low confidence (1)

plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java:603

  • apiVersion is re-derived from connectionDetails/query params on every login(). When api_version is not explicitly configured, this causes /api_version discovery to run on every session refresh, not just the first login for a pool. Consider caching the discovered version in a field (e.g., only resolve when apiVersion is null/uninitialized, or add an apiVersionResolved flag) so subsequent logins reuse it without another discovery call.
        apiVersion = connectionDetails.get(FlashArrayAdapter.API_VERSION);
        boolean apiVersionExplicit = apiVersion != null;
        if (apiVersion == null) {
            apiVersion = queryParms.get(FlashArrayAdapter.API_VERSION);
            apiVersionExplicit = apiVersion != null;
            if (apiVersion == null) {
                apiVersion = API_VERSION_DEFAULT;
            }
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

+ "/api_version, falling back to default " + API_VERSION_DEFAULT, e);
} finally {
if (vResp != null) {
vResp.close();
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

vResp.close() is called in the inner finally without handling IOException. A close failure would currently bubble up and fail the entire login even though the version discovery response was already processed. Wrap the close in its own try/catch and log at debug (similar to the outer response close) to avoid spurious login failures.

Suggested change
vResp.close();
try {
vResp.close();
} catch (IOException e) {
logger.debug("Unable to close /api_version response", e);
}

Copilot uses AI. Check for mistakes.
Comment on lines +740 to +741
response.close();
response = null;
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

response.close() in the legacy username/password branch is not protected by a try/catch. If closing the response throws, login() will fail even after a successful auth/token exchange. Close it in a try/catch (or rely on the existing outer finally) to avoid turning close errors into authentication failures.

Suggested change
response.close();
response = null;
try {
response.close();
} catch (IOException e) {
logger.warn("Failed to close legacy authentication response from FlashArray [" + url + "]", e);
} finally {
response = null;
}

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 3.52%. Comparing base (3166e64) to head (929f88a).

❗ There is a different number of reports uploaded between BASE (3166e64) and HEAD (929f88a). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (3166e64) HEAD (929f88a)
unittests 1 0
Additional details and impacted files
@@              Coverage Diff              @@
##               main   #13060       +/-   ##
=============================================
- Coverage     18.01%    3.52%   -14.50%     
=============================================
  Files          6029      464     -5565     
  Lines        542160    40137   -502023     
  Branches      66451     7555    -58896     
=============================================
- Hits          97682     1415    -96267     
+ Misses       433461    38534   -394927     
+ Partials      11017      188    -10829     
Flag Coverage Δ
uitests 3.52% <ø> (ø)
unittests ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- Replace nested null-check of apiVersion with !apiVersionExplicit for clarity (sureshanaparti).
- Wrap vResp.close() in the api_version-discovery finally with its own try/catch; log any IOException at debug so a failed close does not mask a successful discovery.
- Wrap the explicit response.close() after the legacy username/password auth with try/catch for the same reason.

Signed-off-by: Eugenio Grosso <eugenio.grosso@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants