From dda8a5f35d68084851bdab5694e8f412d74ad5d9 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 28 Mar 2025 13:05:49 +0530 Subject: [PATCH 1/3] utils: fix extra slash in Redfish default systems url path This removes extra slash from the base system url in Redfish oobm client utility. Fixes #10441 Signed-off-by: Rohit Yadav --- .../cloudstack/utils/redfish/RedfishClient.java | 8 ++++---- .../cloudstack/utils/redfish/RedfishClientTest.java | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java b/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java index e6af2315ba24..b561c55e116c 100644 --- a/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java +++ b/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java @@ -75,8 +75,8 @@ public class RedfishClient { private boolean ignoreSsl; private int redfishRequestMaxRetries; - private final static String SYSTEMS_URL_PATH = "redfish/v1/Systems/"; - private final static String COMPUTER_SYSTEM_RESET_URL_PATH = "/Actions/ComputerSystem.Reset"; + private final static String SYSTEMS_URL_PATH = "redfish/v1/Systems"; + private final static String COMPUTER_SYSTEM_RESET_URL_PATH = "Actions/ComputerSystem.Reset"; private final static String REDFISH_RESET_TYPE = "ResetType"; private final static String POWER_STATE = "PowerState"; private final static String APPLICATION_JSON = "application/json"; @@ -265,12 +265,12 @@ private String getRequestPathForCommand(RedfishCmdType cmd, String resourceId) { if (StringUtils.isBlank(resourceId)) { throw new RedfishException(String.format("Command '%s' requires a valid resource ID '%s'.", cmd, resourceId)); } - return String.format("%s%s", SYSTEMS_URL_PATH, resourceId); + return String.format("%s/%s", SYSTEMS_URL_PATH, resourceId); case ComputerSystemReset: if (StringUtils.isBlank(resourceId)) { throw new RedfishException(String.format("Command '%s' requires a valid resource ID '%s'.", cmd, resourceId)); } - return String.format("%s%s%s", SYSTEMS_URL_PATH, resourceId, COMPUTER_SYSTEM_RESET_URL_PATH); + return String.format("%s/%s/%s", SYSTEMS_URL_PATH, resourceId, COMPUTER_SYSTEM_RESET_URL_PATH); default: throw new RedfishException(String.format("Redfish client does not support command '%s'.", cmd)); } diff --git a/utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java b/utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java index 58dfbceb66af..83098cabf385 100644 --- a/utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java +++ b/utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java @@ -41,9 +41,9 @@ public class RedfishClientTest { private static final String PASSWORD = "password"; private static final String oobAddress = "oob.host.address"; private static final String systemId = "SystemID.1"; - private final static String COMPUTER_SYSTEM_RESET_URL_PATH = "/Actions/ComputerSystem.Reset"; + private final static String COMPUTER_SYSTEM_RESET_URL_PATH = "Actions/ComputerSystem.Reset"; private final static Integer REDFISHT_REQUEST_RETRIES = Integer.valueOf(2); - private static final String url = "https://address.system.net/redfish/v1/Systems/"; + private static final String url = "https://address.system.net/redfish/v1/Systems"; private static final HttpRequestBase httpReq = new HttpGet(url); @Mock @@ -87,7 +87,7 @@ public void validateAddressAndPrepareForUrlTestIpv6() { public void buildRequestUrlTestHttpsGetSystemId() { RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, true, false, REDFISHT_REQUEST_RETRIES); String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.GetSystemId, systemId); - String expected = String.format("https://%s/redfish/v1/Systems/", oobAddress); + String expected = String.format("https://%s/redfish/v1/Systems", oobAddress); Assert.assertEquals(expected, result); } @@ -95,7 +95,7 @@ public void buildRequestUrlTestHttpsGetSystemId() { public void buildRequestUrlTestGetSystemId() { RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, false, false, REDFISHT_REQUEST_RETRIES); String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.GetSystemId, systemId); - String expected = String.format("http://%s/redfish/v1/Systems/", oobAddress); + String expected = String.format("http://%s/redfish/v1/Systems", oobAddress); Assert.assertEquals(expected, result); } @@ -103,7 +103,7 @@ public void buildRequestUrlTestGetSystemId() { public void buildRequestUrlTestHttpsComputerSystemReset() { RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, true, false, REDFISHT_REQUEST_RETRIES); String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.ComputerSystemReset, systemId); - String expected = String.format("https://%s/redfish/v1/Systems/%s%s", oobAddress, systemId, COMPUTER_SYSTEM_RESET_URL_PATH); + String expected = String.format("https://%s/redfish/v1/Systems/%s/%s", oobAddress, systemId, COMPUTER_SYSTEM_RESET_URL_PATH); Assert.assertEquals(expected, result); } @@ -111,7 +111,7 @@ public void buildRequestUrlTestHttpsComputerSystemReset() { public void buildRequestUrlTestComputerSystemReset() { RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, false, false, REDFISHT_REQUEST_RETRIES); String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.ComputerSystemReset, systemId); - String expected = String.format("http://%s/redfish/v1/Systems/%s%s", oobAddress, systemId, COMPUTER_SYSTEM_RESET_URL_PATH); + String expected = String.format("http://%s/redfish/v1/Systems/%s/%s", oobAddress, systemId, COMPUTER_SYSTEM_RESET_URL_PATH); Assert.assertEquals(expected, result); } From 566ea2a22fc8a955ea862809810879a1b926d969 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 15 Apr 2025 13:14:34 +0530 Subject: [PATCH 2/3] utils: read the full http response content & not just newline Fix the code to read all of the json content and now just upto newline :facepalm: Signed-off-by: Rohit Yadav --- .../org/apache/cloudstack/utils/redfish/RedfishClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java b/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java index b561c55e116c..3c8b90f3c811 100644 --- a/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java +++ b/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java @@ -29,6 +29,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -340,7 +341,7 @@ protected String processGetSystemIdResponse(CloseableHttpResponse response) { try { in = response.getEntity().getContent(); BufferedReader streamReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); - jsonString = streamReader.readLine(); + jsonString = streamReader.lines().collect(Collectors.joining()); } catch (UnsupportedOperationException | IOException e) { throw new RedfishException("Failed to process system Response", e); } @@ -384,8 +385,7 @@ protected RedfishPowerState processGetSystemRequestResponse(CloseableHttpRespons try { in = response.getEntity().getContent(); BufferedReader streamReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); - - jsonString = streamReader.readLine(); + jsonString = streamReader.lines().collect(Collectors.joining()); String powerState = new JsonParser().parse(jsonString).getAsJsonObject().get(POWER_STATE).getAsString(); return RedfishPowerState.valueOf(powerState); } catch (UnsupportedOperationException | IOException e) { From b66f149778a91dabc0535102902fdaa158546870 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 17 Apr 2025 10:34:50 +0530 Subject: [PATCH 3/3] utils: fix potential NPE and error message This fixes the error message when Redfish doesn't support power related POST requests. Also fixes an NPE edge case discovered via the emulator. Signed-off-by: Rohit Yadav --- .../utils/redfish/RedfishClient.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java b/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java index 3c8b90f3c811..f9952b2c03bb 100644 --- a/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java +++ b/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java @@ -37,7 +37,9 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; +import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.nio.TrustAllManager; +import com.google.gson.JsonElement; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; @@ -84,6 +86,7 @@ public class RedfishClient { private final static String ACCEPT = "accept"; private final static String ODATA_ID = "@odata.id"; private final static String MEMBERS = "Members"; + private final static String LINKS = "Links"; private final static String EXPECTED_HTTP_STATUS = "2XX"; private final static int WAIT_FOR_REQUEST_RETRY = 2; @@ -306,8 +309,8 @@ public void executeComputerSystemReset(String hostAddress, RedfishResetCmd reset int statusCode = response.getStatusLine().getStatusCode(); if (statusCode < HttpStatus.SC_OK || statusCode >= HttpStatus.SC_MULTIPLE_CHOICES) { - throw new RedfishException(String.format("Failed to get System power state for host '%s' with request '%s: %s'. The expected HTTP status code is '%s' but it got '%s'.", - HttpGet.METHOD_NAME, url, hostAddress, EXPECTED_HTTP_STATUS, statusCode)); + throw new RedfishException(String.format("Failed to execute System power command for host by performing '%s' request on URL '%s' and host address '%s'. The expected HTTP status code is '%s' but it got '%s'.", + HttpPost.METHOD_NAME, url, hostAddress, EXPECTED_HTTP_STATUS, statusCode)); } logger.debug(String.format("Sending ComputerSystem.Reset Command '%s' to host '%s' with request '%s %s'", resetCommand, hostAddress, HttpPost.METHOD_NAME, url)); } @@ -348,9 +351,18 @@ protected String processGetSystemIdResponse(CloseableHttpResponse response) { // retrieving the system ID (e.g. 'System.Embedded.1') via JsonParser: // (...) Members":[{"@odata.id":"/redfish/v1/Systems/System.Embedded.1"}] (...) - JsonArray jArray = new JsonParser().parse(jsonString).getAsJsonObject().get(MEMBERS).getAsJsonArray(); - JsonObject jsonnObject = jArray.get(0).getAsJsonObject(); - String jsonObjectAsString = jsonnObject.get(ODATA_ID).getAsString(); + JsonArray jArray = null; + JsonElement jsonElement = new JsonParser().parse(jsonString); + if (jsonElement.getAsJsonObject().get(MEMBERS) != null) { + jArray = jsonElement.getAsJsonObject().get(MEMBERS).getAsJsonArray(); + } else if (jsonElement.getAsJsonObject().get(LINKS) != null){ + jArray = jsonElement.getAsJsonObject().get(LINKS).getAsJsonObject().get(MEMBERS).getAsJsonArray(); + } + if (jArray == null || jArray.size() < 1) { + throw new CloudRuntimeException("Members not found in the Redfish Systems JSON, unable to determine Redfish system ID"); + } + JsonObject jsonObject = jArray.get(0).getAsJsonObject(); + String jsonObjectAsString = jsonObject.get(ODATA_ID).getAsString(); String[] arrayOfStrings = StringUtils.split(jsonObjectAsString, '/'); return arrayOfStrings[arrayOfStrings.length - 1];