Skip to content

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

Open
genegr wants to merge 3 commits intoapache:4.20from
genegr:feat/flasharray-rest2x-apitoken-auth
Open

flasharray: authenticate via REST 2.x api-token and discover API version#13060
genegr wants to merge 3 commits intoapache:4.20from
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>.

@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

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

❌ Patch coverage is 0% with 58 lines in your changes missing coverage. Please review.
✅ Project coverage is 18.02%. Comparing base (9f96c9d) to head (2938570).

Files with missing lines Patch % Lines
...atastore/adapter/flasharray/FlashArrayAdapter.java 0.00% 58 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               4.20   #13060      +/-   ##
============================================
+ Coverage     16.26%   18.02%   +1.75%     
- Complexity    13435    16620    +3185     
============================================
  Files          5665     6029     +364     
  Lines        500556   542236   +41680     
  Branches      60790    66461    +5671     
============================================
+ Hits          81416    97736   +16320     
- Misses       410036   433483   +23447     
- Partials       9104    11017    +1913     
Flag Coverage Δ
uitests 3.52% <ø> (-0.64%) ⬇️
unittests 19.18% <0.00%> (+2.06%) ⬆️

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.

@genegr
Copy link
Copy Markdown
Author

genegr commented Apr 23, 2026

Pushed 929f88a88d flasharray: address review on login() to address this round of feedback:

  • if (!apiVersionExplicit) instead of if (apiVersion == null) on both the connection-details branch and the query-params branch (@sureshanaparti). Semantically equivalent — now reads "explicitly configured?" which is what the logic actually means.
  • vResp.close() in the /api/api_version finally is now wrapped in try/catch, logging any IOException at debug so a failed close cannot mask an otherwise-successful version discovery (@Copilot).
  • response.close() after the legacy username/password exchange is now wrapped in try/catch with the same debug-level log, for the same reason (@Copilot).

Smoke-tested locally: login via api_token (2.x-only path) and login via username/password (deprecation-warning path) both succeed; pool capacity polling and volume create still work.

@DaanHoogland DaanHoogland requested a review from Copilot April 28, 2026 10:12
@DaanHoogland
Copy link
Copy Markdown
Contributor

@genegr I saw other improvements by you on branch 4.20. Is this alright to go on later releases?

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

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.


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

Comment on lines +715 to +719
if (usingLegacyUserPass) {
logger.warn("FlashArray adapter at [" + url + "] is using deprecated username/password "
+ "login against Purity REST 1.x. Replace with a pre-minted "
+ ProviderAdapter.API_TOKEN_KEY + " detail; the username/password code path will be "
+ "removed in a future release.");
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

The deprecation warning for legacy username/password auth is emitted on every login/refresh. With the default key TTL (~14 minutes), this can spam logs for each configured FlashArray pool. Consider rate-limiting (e.g., log once per adapter instance/endpoint) or downgrading subsequent messages to DEBUG after the first WARN.

Copilot uses AI. Check for mistakes.
try {
vResp.close();
} catch (IOException e) {
logger.debug("Error closing /api/api_version response from FlashArray [" + url + "]", e);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

The request is sent to .../api_version (relative to url), but the debug message says "Error closing /api/api_version response...". Align the log text with the actual endpoint/path to avoid confusion during troubleshooting.

Suggested change
logger.debug("Error closing /api/api_version response from FlashArray [" + url + "]", e);
logger.debug("Error closing /api_version response from FlashArray [" + url + "]", e);

Copilot uses AI. Check for mistakes.
genegr and others added 3 commits April 28, 2026 12:54
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>
- 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>
The Copilot review pointed out the debug message said /api/api_version but the path passed to the helper is /api_version (the helper itself prepends url + apiVersion). Update the log text to match what the code actually invokes.

Signed-off-by: Eugenio Grosso <eugenio.grosso@gmail.com>
@genegr genegr force-pushed the feat/flasharray-rest2x-apitoken-auth branch from 6abdc0d to 2938570 Compare April 28, 2026 12:54
@genegr genegr changed the base branch from main to 4.20 April 28, 2026 12:57
@genegr
Copy link
Copy Markdown
Author

genegr commented Apr 28, 2026

@DaanHoogland — yes, this is fine to ship in 4.20/4.22. Rebased onto apache/cloudstack:4.20; the three commits replay cleanly with no conflicts:

36a732b870  flasharray: authenticate via REST 2.x api-token and discover API version
50b560a4e9  flasharray: address review on login()
29385709cb  flasharray: align api_version close-error log text with actual path

Round 1 (50b560a4e9) addresses the original review:

  • if (!apiVersionExplicit) instead of if (apiVersion == null) on both the connection-details branch and the query-params branch (@sureshanaparti). Semantically equivalent — now reads "explicitly configured?" which is what the logic actually means.
  • vResp.close() in the api_version discovery finally is now wrapped in try/catch, logging any IOException at debug so a failed close cannot mask an otherwise-successful version discovery (@Copilot).
  • response.close() after the legacy username/password exchange is now wrapped in try/catch with the same debug-level log, for the same reason (@Copilot).

Round 2 (29385709cb):

  • Log text matches the actual call — Copilot pointed out the debug message said /api/api_version but the path passed to the helper is /api_version (the helper itself prepends url + apiVersion). Updated to Error closing /api_version response from FlashArray [...].

Still open

  • The deprecation WARN for legacy username/password auth is currently emitted on every login/refresh. With the default key TTL ~14 min, that's noisy across multiple legacy-configured pools. I can either (a) suppress subsequent WARNs via a per-URL Set<String> so it logs once per adapter endpoint, or (b) downgrade subsequent emits to DEBUG. Happy to apply whichever you prefer — flag in this thread and I'll fold it into a follow-up commit.

Smoke-tested locally: login via api_token (2.x-only path) and login via username/password (deprecation-warning path) both succeed; pool capacity polling and volume create still work.

@sureshanaparti
Copy link
Copy Markdown
Contributor

@blueorangutan package

@blueorangutan
Copy link
Copy Markdown

@sureshanaparti a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware 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 17645

@DaanHoogland
Copy link
Copy Markdown
Contributor

@blueorangutan test

@blueorangutan
Copy link
Copy Markdown

@DaanHoogland a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

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.

6 participants