|
61 | 61 | import com.cloud.utils.exception.CloudRuntimeException; |
62 | 62 | import com.fasterxml.jackson.core.JsonProcessingException; |
63 | 63 | import com.fasterxml.jackson.core.type.TypeReference; |
| 64 | +import com.fasterxml.jackson.databind.JsonNode; |
64 | 65 | import com.fasterxml.jackson.databind.ObjectMapper; |
65 | 66 | import org.apache.logging.log4j.LogManager; |
66 | 67 | import org.apache.logging.log4j.Logger; |
@@ -591,8 +592,10 @@ private void login() { |
591 | 592 | } |
592 | 593 |
|
593 | 594 | apiVersion = connectionDetails.get(FlashArrayAdapter.API_VERSION); |
| 595 | + boolean apiVersionExplicit = apiVersion != null; |
594 | 596 | if (apiVersion == null) { |
595 | 597 | apiVersion = queryParms.get(FlashArrayAdapter.API_VERSION); |
| 598 | + apiVersionExplicit = apiVersion != null; |
596 | 599 | if (apiVersion == null) { |
597 | 600 | apiVersion = API_VERSION_DEFAULT; |
598 | 601 | } |
@@ -660,72 +663,117 @@ private void login() { |
660 | 663 | skipTlsValidation = true; |
661 | 664 | } |
662 | 665 |
|
| 666 | + // Resolve the long-lived API token. Prefer a pre-minted api_token (Purity REST 2.x flow); |
| 667 | + // fall back to legacy username/password auth via Purity REST 1.x for backward compatibility. |
| 668 | + String apiToken = connectionDetails.get(ProviderAdapter.API_TOKEN_KEY); |
| 669 | + if (apiToken != null && apiToken.isEmpty()) { |
| 670 | + apiToken = null; |
| 671 | + } |
| 672 | + boolean usingLegacyUserPass = apiToken == null; |
| 673 | + if (usingLegacyUserPass && (username == null || password == null)) { |
| 674 | + throw new CloudRuntimeException("FlashArray adapter requires either " + ProviderAdapter.API_TOKEN_KEY |
| 675 | + + " (preferred) or both " + ProviderAdapter.API_USERNAME_KEY + " and " |
| 676 | + + ProviderAdapter.API_PASSWORD_KEY + " in the connection details"); |
| 677 | + } |
| 678 | + |
| 679 | + CloseableHttpClient client = getClient(); |
663 | 680 | CloseableHttpResponse response = null; |
664 | 681 | try { |
665 | | - HttpPost request = new HttpPost(url + "/" + apiLoginVersion + "/auth/apitoken"); |
666 | | - // request.addHeader("Content-Type", "application/json"); |
667 | | - // request.addHeader("Accept", "application/json"); |
668 | | - ArrayList<NameValuePair> postParms = new ArrayList<NameValuePair>(); |
669 | | - postParms.add(new BasicNameValuePair("username", username)); |
670 | | - postParms.add(new BasicNameValuePair("password", password)); |
671 | | - request.setEntity(new UrlEncodedFormEntity(postParms, "UTF-8")); |
672 | | - CloseableHttpClient client = getClient(); |
673 | | - response = (CloseableHttpResponse) client.execute(request); |
| 682 | + // Discover the latest supported API version from the array unless one was explicitly configured. |
| 683 | + // GET /api/api_version is unauthenticated and returns {"version":["1.0",...,"2.36"]}. |
| 684 | + if (!apiVersionExplicit) { |
| 685 | + HttpGet vReq = new HttpGet(url + "/api_version"); |
| 686 | + CloseableHttpResponse vResp = null; |
| 687 | + try { |
| 688 | + vResp = (CloseableHttpResponse) client.execute(vReq); |
| 689 | + if (vResp.getStatusLine().getStatusCode() == 200) { |
| 690 | + JsonNode root = mapper.readTree(vResp.getEntity().getContent()); |
| 691 | + JsonNode versions = root.get("version"); |
| 692 | + if (versions != null && versions.isArray() && versions.size() > 0) { |
| 693 | + apiVersion = versions.get(versions.size() - 1).asText(); |
| 694 | + } |
| 695 | + } else { |
| 696 | + logger.warn("Unexpected HTTP " + vResp.getStatusLine().getStatusCode() |
| 697 | + + " from FlashArray [" + url + "] /api_version, falling back to default " |
| 698 | + + API_VERSION_DEFAULT); |
| 699 | + } |
| 700 | + } catch (Exception e) { |
| 701 | + logger.warn("Failed to discover Purity REST API version from " + url |
| 702 | + + "/api_version, falling back to default " + API_VERSION_DEFAULT, e); |
| 703 | + } finally { |
| 704 | + if (vResp != null) { |
| 705 | + vResp.close(); |
| 706 | + } |
| 707 | + } |
| 708 | + } |
674 | 709 |
|
675 | | - int statusCode = response.getStatusLine().getStatusCode(); |
676 | | - FlashArrayApiToken apitoken = null; |
677 | | - if (statusCode == 200 | statusCode == 201) { |
678 | | - apitoken = mapper.readValue(response.getEntity().getContent(), FlashArrayApiToken.class); |
679 | | - if (apitoken == null) { |
| 710 | + if (usingLegacyUserPass) { |
| 711 | + logger.warn("FlashArray adapter at [" + url + "] is using deprecated username/password " |
| 712 | + + "login against Purity REST 1.x. Replace with a pre-minted " |
| 713 | + + ProviderAdapter.API_TOKEN_KEY + " detail; the username/password code path will be " |
| 714 | + + "removed in a future release."); |
| 715 | + HttpPost request = new HttpPost(url + "/" + apiLoginVersion + "/auth/apitoken"); |
| 716 | + ArrayList<NameValuePair> postParms = new ArrayList<NameValuePair>(); |
| 717 | + postParms.add(new BasicNameValuePair("username", username)); |
| 718 | + postParms.add(new BasicNameValuePair("password", password)); |
| 719 | + request.setEntity(new UrlEncodedFormEntity(postParms, "UTF-8")); |
| 720 | + response = (CloseableHttpResponse) client.execute(request); |
| 721 | + int statusCode = response.getStatusLine().getStatusCode(); |
| 722 | + if (statusCode == 200 || statusCode == 201) { |
| 723 | + FlashArrayApiToken legacyToken = mapper.readValue(response.getEntity().getContent(), |
| 724 | + FlashArrayApiToken.class); |
| 725 | + if (legacyToken == null || legacyToken.getApiToken() == null) { |
| 726 | + throw new CloudRuntimeException( |
| 727 | + "Authentication responded successfully but no api token was returned"); |
| 728 | + } |
| 729 | + apiToken = legacyToken.getApiToken(); |
| 730 | + } else if (statusCode == 401 || statusCode == 403) { |
| 731 | + throw new CloudRuntimeException( |
| 732 | + "Authentication or Authorization to FlashArray [" + url + "] with user [" + username |
| 733 | + + "] failed, unable to retrieve session token"); |
| 734 | + } else { |
680 | 735 | throw new CloudRuntimeException( |
681 | | - "Authentication responded successfully but no api token was returned"); |
| 736 | + "Unexpected HTTP response code from FlashArray [" + url + "] - [" + statusCode |
| 737 | + + "] - " + response.getStatusLine().getReasonPhrase()); |
682 | 738 | } |
683 | | - } else if (statusCode == 401 || statusCode == 403) { |
684 | | - throw new CloudRuntimeException( |
685 | | - "Authentication or Authorization to FlashArray [" + url + "] with user [" + username |
686 | | - + "] failed, unable to retrieve session token"); |
687 | | - } else { |
688 | | - throw new CloudRuntimeException( |
689 | | - "Unexpected HTTP response code from FlashArray [" + url + "] - [" + statusCode |
690 | | - + "] - " + response.getStatusLine().getReasonPhrase()); |
| 739 | + response.close(); |
| 740 | + response = null; |
691 | 741 | } |
692 | 742 |
|
693 | | - // now we need to get the access token |
694 | | - request = new HttpPost(url + "/" + apiVersion + "/login"); |
695 | | - request.addHeader("api-token", apitoken.getApiToken()); |
| 743 | + // Exchange the long-lived api-token for a short-lived x-auth-token (REST 2.x). |
| 744 | + HttpPost request = new HttpPost(url + "/" + apiVersion + "/login"); |
| 745 | + request.addHeader("api-token", apiToken); |
696 | 746 | response = (CloseableHttpResponse) client.execute(request); |
697 | | - |
698 | | - statusCode = response.getStatusLine().getStatusCode(); |
699 | | - if (statusCode == 200 | statusCode == 201) { |
| 747 | + int statusCode = response.getStatusLine().getStatusCode(); |
| 748 | + if (statusCode == 200 || statusCode == 201) { |
700 | 749 | Header[] headers = response.getHeaders("x-auth-token"); |
701 | 750 | if (headers == null || headers.length == 0) { |
702 | 751 | throw new CloudRuntimeException( |
703 | | - "Getting access token responded successfully but access token was not available"); |
| 752 | + "FlashArray /login responded successfully but no x-auth-token header was returned"); |
704 | 753 | } |
705 | 754 | accessToken = headers[0].getValue(); |
706 | 755 | } else if (statusCode == 401 || statusCode == 403) { |
707 | 756 | throw new CloudRuntimeException( |
708 | | - "Authentication or Authorization to FlashArray [" + url + "] with user [" + username |
709 | | - + "] failed, unable to retrieve session token"); |
| 757 | + "FlashArray [" + url + "] rejected the api-token at /" + apiVersion + "/login"); |
710 | 758 | } else { |
711 | 759 | throw new CloudRuntimeException( |
712 | | - "Unexpected HTTP response code from FlashArray [" + url + "] - [" + statusCode |
713 | | - + "] - " + response.getStatusLine().getReasonPhrase()); |
| 760 | + "Unexpected HTTP response code from FlashArray [" + url + "] /" + apiVersion |
| 761 | + + "/login - [" + statusCode + "] - " |
| 762 | + + response.getStatusLine().getReasonPhrase()); |
714 | 763 | } |
715 | | - |
716 | 764 | } catch (UnsupportedEncodingException e) { |
717 | | - throw new CloudRuntimeException("Error creating input for login, check username/password encoding"); |
| 765 | + throw new CloudRuntimeException("Error encoding login form for FlashArray [" + url + "]", e); |
718 | 766 | } catch (UnsupportedOperationException e) { |
719 | 767 | throw new CloudRuntimeException("Error processing login response from FlashArray [" + url + "]", e); |
720 | 768 | } catch (IOException e) { |
721 | 769 | throw new CloudRuntimeException("Error sending login request to FlashArray [" + url + "]", e); |
722 | 770 | } finally { |
723 | | - try { |
724 | | - if (response != null) { |
| 771 | + if (response != null) { |
| 772 | + try { |
725 | 773 | response.close(); |
| 774 | + } catch (IOException e) { |
| 775 | + logger.debug("Error closing response from login attempt to FlashArray", e); |
726 | 776 | } |
727 | | - } catch (IOException e) { |
728 | | - logger.debug("Error closing response from login attempt to FlashArray", e); |
729 | 777 | } |
730 | 778 | } |
731 | 779 | } |
|
0 commit comments