From d229e1db3a54c259e554b4133a2014b6540e2918 Mon Sep 17 00:00:00 2001 From: GabrielBrascher Date: Fri, 13 Dec 2019 09:57:25 +0100 Subject: [PATCH 1/3] Redfish OOB implementation - OOB Redfish Driver - Redfish Java Client --- .../OutOfBandManagement.java | 12 +- client/pom.xml | 5 + .../dao/OutOfBandManagementDao.java | 5 +- .../dao/OutOfBandManagementDaoImpl.java | 6 + .../redfish/pom.xml | 42 ++ .../RedfishOutOfBandManagementDriver.java | 120 ++++++ .../driver/redfish/RedfishWrapper.java | 66 +++ .../cloudstack/redfish/module.properties | 18 + .../redfish/spring-redfish-context.xml | 33 ++ plugins/pom.xml | 8 +- ui/scripts/system.js | 4 + .../utils/redfish/RedfishClient.java | 378 ++++++++++++++++++ .../utils/redfish/RedfishException.java | 31 ++ .../utils/redfish/RedfishClientTest.java | 163 ++++++++ 14 files changed, 885 insertions(+), 6 deletions(-) create mode 100644 plugins/outofbandmanagement-drivers/redfish/pom.xml create mode 100644 plugins/outofbandmanagement-drivers/redfish/src/main/java/org/apache/cloudstack/outofbandmanagement/driver/redfish/RedfishOutOfBandManagementDriver.java create mode 100644 plugins/outofbandmanagement-drivers/redfish/src/main/java/org/apache/cloudstack/outofbandmanagement/driver/redfish/RedfishWrapper.java create mode 100644 plugins/outofbandmanagement-drivers/redfish/src/main/resources/META-INF/cloudstack/redfish/module.properties create mode 100644 plugins/outofbandmanagement-drivers/redfish/src/main/resources/META-INF/cloudstack/redfish/spring-redfish-context.xml create mode 100644 utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java create mode 100644 utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishException.java create mode 100644 utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java diff --git a/api/src/main/java/org/apache/cloudstack/outofbandmanagement/OutOfBandManagement.java b/api/src/main/java/org/apache/cloudstack/outofbandmanagement/OutOfBandManagement.java index 972d6261674c..485911c66d3a 100644 --- a/api/src/main/java/org/apache/cloudstack/outofbandmanagement/OutOfBandManagement.java +++ b/api/src/main/java/org/apache/cloudstack/outofbandmanagement/OutOfBandManagement.java @@ -67,13 +67,23 @@ enum Option { PASSWORD } + /** + * + */ enum PowerOperation { ON, OFF, CYCLE, RESET, SOFT, - STATUS, + STATUS } enum PowerState { diff --git a/client/pom.xml b/client/pom.xml index 4c6888a45f44..0d76c9f2ae33 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -303,6 +303,11 @@ cloud-plugin-outofbandmanagement-driver-nested-cloudstack ${project.version} + + org.apache.cloudstack + cloud-plugin-outofbandmanagement-driver-redfish + ${project.version} + org.apache.cloudstack cloud-mom-rabbitmq diff --git a/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/dao/OutOfBandManagementDao.java b/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/dao/OutOfBandManagementDao.java index 8a4ee36710f8..0d4ea147418b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/dao/OutOfBandManagementDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/dao/OutOfBandManagementDao.java @@ -19,13 +19,16 @@ import com.cloud.utils.db.GenericDao; import com.cloud.utils.fsm.StateDao; + import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementVO; import java.util.List; -public interface OutOfBandManagementDao extends GenericDao, StateDao { +public interface OutOfBandManagementDao extends GenericDao, + StateDao { OutOfBandManagement findByHost(long hostId); + OutOfBandManagementVO findByHostAddress(String address); List findAllByManagementServer(long serverId); void expireServerOwnership(long serverId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/dao/OutOfBandManagementDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/dao/OutOfBandManagementDaoImpl.java index 3cdd28fe25a0..af164326da70 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/dao/OutOfBandManagementDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/dao/OutOfBandManagementDaoImpl.java @@ -86,6 +86,12 @@ public OutOfBandManagement findByHost(long hostId) { return findOneBy(sc); } + @Override + public OutOfBandManagementVO findByHostAddress(String address) { + SearchCriteria sc = HostSearch.create("address", address); + return findOneBy(sc); + } + @Override public List findAllByManagementServer(long serverId) { SearchCriteria sc = OutOfBandManagementOwnerSearch.create(); diff --git a/plugins/outofbandmanagement-drivers/redfish/pom.xml b/plugins/outofbandmanagement-drivers/redfish/pom.xml new file mode 100644 index 000000000000..5c678a2faf8b --- /dev/null +++ b/plugins/outofbandmanagement-drivers/redfish/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + cloud-plugin-outofbandmanagement-driver-redfish + Apache CloudStack Plugin - Power Management Driver Redfish + + org.apache.cloudstack + cloudstack-plugins + 4.15.0.0-SNAPSHOT + ../../pom.xml + + + + org.apache.cloudstack + cloud-utils + ${project.version} + + + org.apache.cloudstack + cloud-api + ${project.version} + + + diff --git a/plugins/outofbandmanagement-drivers/redfish/src/main/java/org/apache/cloudstack/outofbandmanagement/driver/redfish/RedfishOutOfBandManagementDriver.java b/plugins/outofbandmanagement-drivers/redfish/src/main/java/org/apache/cloudstack/outofbandmanagement/driver/redfish/RedfishOutOfBandManagementDriver.java new file mode 100644 index 000000000000..3e863342badd --- /dev/null +++ b/plugins/outofbandmanagement-drivers/redfish/src/main/java/org/apache/cloudstack/outofbandmanagement/driver/redfish/RedfishOutOfBandManagementDriver.java @@ -0,0 +1,120 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack.outofbandmanagement.driver.redfish; + +import javax.inject.Inject; + +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement; +import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementDriver; +import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementVO; +import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao; +import org.apache.cloudstack.outofbandmanagement.driver.OutOfBandManagementDriverChangePasswordCommand; +import org.apache.cloudstack.outofbandmanagement.driver.OutOfBandManagementDriverCommand; +import org.apache.cloudstack.outofbandmanagement.driver.OutOfBandManagementDriverPowerCommand; +import org.apache.cloudstack.outofbandmanagement.driver.OutOfBandManagementDriverResponse; +import org.apache.cloudstack.utils.redfish.RedfishClient; +import org.apache.cloudstack.utils.redfish.RedfishClient.RedfishResetCmd; +import org.apache.commons.httpclient.HttpStatus; + +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.common.collect.ImmutableMap; + +public class RedfishOutOfBandManagementDriver extends AdapterBase implements OutOfBandManagementDriver, Configurable { + + @Inject + private OutOfBandManagementDao outOfBandManagementDao; + private RedfishWrapper redfishWrapper = new RedfishWrapper(); + + public static final ConfigKey IGNORE_SSL_CERTIFICATE = new ConfigKey("Advanced", Boolean.class, "redfish.ignore.ssl", "false", + "Default value is false, ensuring that the client requests validate the certificate when using SSL. If set to true the redfish client will ignore SSL certificate validation when sending requests to a Redfish server.", + true, ConfigKey.Scope.Global); + + public static final ConfigKey USE_HTTPS = new ConfigKey("Advanced", Boolean.class, "redfish.use.https", "true", + "Use HTTPS/SSL for all connections.", true, ConfigKey.Scope.Global); + + private static final String HTTP_STATUS_OK = String.valueOf(HttpStatus.SC_OK); + + @Override + public OutOfBandManagementDriverResponse execute(OutOfBandManagementDriverCommand cmd) { + OutOfBandManagementDriverResponse response = new OutOfBandManagementDriverResponse(null, "Unsupported Command", false); + if (cmd instanceof OutOfBandManagementDriverPowerCommand) { + response = execute((OutOfBandManagementDriverPowerCommand)cmd); + } else if (cmd instanceof OutOfBandManagementDriverChangePasswordCommand) { + response = execute((OutOfBandManagementDriverChangePasswordCommand)cmd); + } else { + throw new CloudRuntimeException(String.format("Operation [%s] not supported by the Redfish out-of-band management driver", cmd.getClass().getSimpleName())); + } + return response; + } + + /** + * Sends a HTTPS request to execute the given power command ({@link OutOfBandManagementDriverPowerCommand}) + */ + private OutOfBandManagementDriverResponse execute(final OutOfBandManagementDriverPowerCommand cmd) { + ImmutableMap outOfBandOptions = cmd.getOptions(); + String username = outOfBandOptions.get(OutOfBandManagement.Option.USERNAME); + String password = outOfBandOptions.get(OutOfBandManagement.Option.PASSWORD); + String hostAddress = outOfBandOptions.get(OutOfBandManagement.Option.ADDRESS); + RedfishClient redfishClient = new RedfishClient(username, password, USE_HTTPS.value(), IGNORE_SSL_CERTIFICATE.value()); + + RedfishClient.RedfishPowerState powerState = null; + if (cmd.getPowerOperation() == OutOfBandManagement.PowerOperation.STATUS) { + powerState = redfishClient.getSystemPowerState(hostAddress); + } else { + RedfishResetCmd redfishCmd = redfishWrapper.parsePowerCommand(cmd.getPowerOperation()); + redfishClient.executeComputerSystemReset(hostAddress, redfishCmd); + } + + OutOfBandManagementDriverResponse response = new OutOfBandManagementDriverResponse(HTTP_STATUS_OK, null, true); + if (powerState != null) { + OutOfBandManagement.PowerState oobPowerState = redfishWrapper.parseRedfishPowerStateToOutOfBand(powerState); + response.setPowerState(oobPowerState); + } + return response; + } + + /** + * Executes the password change command (OutOfBandManagementDriverChangePasswordCommand) + */ + private OutOfBandManagementDriverResponse execute(final OutOfBandManagementDriverChangePasswordCommand cmd) { + cmd.getNewPassword(); + String hostAddress = cmd.getOptions().get(OutOfBandManagement.Option.ADDRESS); + OutOfBandManagementVO outOfBandManagement = outOfBandManagementDao.findByHostAddress(hostAddress); + outOfBandManagement.setPassword(cmd.getNewPassword()); + outOfBandManagementDao.update(outOfBandManagement.getId(), outOfBandManagement); + + OutOfBandManagementDriverResponse response = new OutOfBandManagementDriverResponse(HTTP_STATUS_OK, null, true); + + return response; + } + + @Override + public String getConfigComponentName() { + return RedfishOutOfBandManagementDriver.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] {IGNORE_SSL_CERTIFICATE, USE_HTTPS}; + } + +} diff --git a/plugins/outofbandmanagement-drivers/redfish/src/main/java/org/apache/cloudstack/outofbandmanagement/driver/redfish/RedfishWrapper.java b/plugins/outofbandmanagement-drivers/redfish/src/main/java/org/apache/cloudstack/outofbandmanagement/driver/redfish/RedfishWrapper.java new file mode 100644 index 000000000000..09cee3b21aec --- /dev/null +++ b/plugins/outofbandmanagement-drivers/redfish/src/main/java/org/apache/cloudstack/outofbandmanagement/driver/redfish/RedfishWrapper.java @@ -0,0 +1,66 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack.outofbandmanagement.driver.redfish; + +import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement; +import org.apache.cloudstack.utils.redfish.RedfishClient; + +public class RedfishWrapper { + + public RedfishClient.RedfishResetCmd parsePowerCommand(OutOfBandManagement.PowerOperation operation) { + if (operation == null) { + throw new IllegalStateException("Invalid power operation requested"); + } + switch (operation) { + case ON: + return RedfishClient.RedfishResetCmd.On; + case OFF: + return RedfishClient.RedfishResetCmd.GracefulShutdown; + case CYCLE: + return RedfishClient.RedfishResetCmd.PowerCycle; + case RESET: + return RedfishClient.RedfishResetCmd.ForceRestart; + case SOFT: + return RedfishClient.RedfishResetCmd.GracefulShutdown; + case STATUS: + throw new IllegalStateException(String.format("%s is not a valid Redfish Reset command [%s]", operation)); + default: + throw new IllegalStateException(String.format("Redfish does not support operation [%s]", operation)); + } + } + + public OutOfBandManagement.PowerState parseRedfishPowerStateToOutOfBand(RedfishClient.RedfishPowerState redfishPowerState) { + if (redfishPowerState == null) { + throw new IllegalStateException("Invalid power state [null]"); + } + + switch (redfishPowerState) { + case On: + return OutOfBandManagement.PowerState.On; + case Off: + return OutOfBandManagement.PowerState.Off; + case PoweringOn: + return OutOfBandManagement.PowerState.On; + case PoweringOff: + return OutOfBandManagement.PowerState.Off; + default: + return OutOfBandManagement.PowerState.Unknown; + } + } +} diff --git a/plugins/outofbandmanagement-drivers/redfish/src/main/resources/META-INF/cloudstack/redfish/module.properties b/plugins/outofbandmanagement-drivers/redfish/src/main/resources/META-INF/cloudstack/redfish/module.properties new file mode 100644 index 000000000000..8e701693d218 --- /dev/null +++ b/plugins/outofbandmanagement-drivers/redfish/src/main/resources/META-INF/cloudstack/redfish/module.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +name=redfish +parent=outofbandmanagement diff --git a/plugins/outofbandmanagement-drivers/redfish/src/main/resources/META-INF/cloudstack/redfish/spring-redfish-context.xml b/plugins/outofbandmanagement-drivers/redfish/src/main/resources/META-INF/cloudstack/redfish/spring-redfish-context.xml new file mode 100644 index 000000000000..44833e8f1e2e --- /dev/null +++ b/plugins/outofbandmanagement-drivers/redfish/src/main/resources/META-INF/cloudstack/redfish/spring-redfish-context.xml @@ -0,0 +1,33 @@ + + + + + + + diff --git a/plugins/pom.xml b/plugins/pom.xml index 296012e62133..18d48db4928c 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -16,8 +16,7 @@ specific language governing permissions and limitations under the License. --> - + 4.0.0 cloudstack-plugins Apache CloudStack Plugin POM @@ -109,6 +108,7 @@ outofbandmanagement-drivers/ipmitool outofbandmanagement-drivers/nested-cloudstack + outofbandmanagement-drivers/redfish storage/image/default storage/image/s3 @@ -129,7 +129,7 @@ user-authenticators/plain-text user-authenticators/saml2 user-authenticators/sha256salted - + org.apache.cloudstack @@ -219,4 +219,4 @@ - + \ No newline at end of file diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 29f428a4f02b..76521ffd2bba 100755 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -17662,6 +17662,10 @@ id: 'nestedcloudstack', description: 'nested-cloudstack - controls host that is a VM in a parent cloudstack (testing purposes only)' }); + items.push({ + id: 'redfish', + description: 'redfish - controls host using a redfish java client' + }); args.response.success({ data: items }); 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 new file mode 100644 index 000000000000..d32455a12e7d --- /dev/null +++ b/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishClient.java @@ -0,0 +1,378 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack.utils.redfish; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; + +import com.cloud.utils.nio.TrustAllManager; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.protocol.HTTP; +import org.apache.log4j.Logger; + +import com.cloud.utils.net.NetUtils; +import com.google.common.net.InternetDomainName; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * Provides support to a set of REST requests that can be sent to a Redfish Server.
+ * RedfishClient allows to gather the server Power State, and execute Reset + * actions such as 'On', 'ForceOff', 'GracefulShutdown', 'GracefulRestart' etc. + */ +public class RedfishClient { + + private static final Logger LOGGER = Logger.getLogger(RedfishClient.class); + + private String username; + private String password; + private boolean useHttps; + private boolean ignoreSsl; + + 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"; + private final static String ACCEPT = "accept"; + private final static String ODATA_ID = "@odata.id"; + private final static String MEMBERS = "Members"; + private final static String EXPECTED_HTTP_STATUS = "2XX"; + + /** + * Redfish Command type:
+ * ComputerSystemReset: execute Redfish reset commands ({@link RedfishResetCmd}).
+ * GetSystemId: get the system ID.
+ * GetPowerState: used for get the system power state.
+ */ + public enum + RedfishCmdType { + ComputerSystemReset, GetSystemId, GetPowerState + } + + /** + * Redfish System Power State:
+ * Off: The state is powered Off.
+ * On: The state is powered On.
+ * PoweringOff: A temporary state between On and Off.
+ * PoweringOn: A temporary state between Off and On. + */ + public enum RedfishPowerState { + On, Off, PoweringOn, PoweringOff + } + + /** + *
    + *
  • ForceOff: Turn the unit off immediately (non-graceful shutdown). + *
  • ForceOn: Turn the unit on immediately. + *
  • ForceRestart: Perform an immediate (non-graceful) shutdown, + * followed by a restart. + *
  • GracefulRestart: Perform a graceful shutdown followed by a restart + * of the system. + *
  • GracefulShutdown: Perform a graceful shutdown and power off. + *
  • Nmi: Generate a Diagnostic Interrupt (usually an NMI on x86 + * systems) to cease normal operations, perform diagnostic actions and typically + * halt the system. + *
  • On: Turn the unit on. + *
  • PowerCycle: Perform a power cycle of the unit. + *
  • PushPowerButton: Simulate the pressing of the physical power + * button on this unit. + *
+ */ + public enum RedfishResetCmd { + ForceOff, ForceOn, ForceRestart, GracefulRestart, GracefulShutdown, Nmi, On, PowerCycle, PushPowerButton + } + + public RedfishClient(String username, String password, boolean useHttps, boolean ignoreSsl) { + this.username = username; + this.password = password; + this.useHttps = useHttps; + this.ignoreSsl = ignoreSsl; + } + + protected String buildRequestUrl(String hostAddress, RedfishCmdType cmd, String resourceId) { + String urlHostAddress = validateAddressAndPrepareForUrl(hostAddress); + String requestPath = getRequestPathForCommand(cmd, resourceId); + + if (useHttps) { + return String.format("https://%s/%s", urlHostAddress, requestPath); + } else { + return String.format("http://%s/%s", urlHostAddress, requestPath); + } + } + + /** + * Executes a GET request for the given URL address. + */ + protected HttpResponse executeGetRequest(String url) { + URIBuilder builder = null; + HttpGet httpReq = null; + + try { + builder = new URIBuilder(url); + httpReq = new HttpGet(builder.build());; + httpReq.addHeader(ACCEPT, APPLICATION_JSON); + String encoding = basicAuth(username, password); + httpReq.addHeader("Authorization", encoding); + } catch (URISyntaxException e) { + throw new RedfishException(String.format("Failed to create URI for GET request [URL: %s] due to exception.", url), e); + } + + HttpClient client = null; + if (ignoreSsl) { + try { + client = ignoreSSLCertValidator(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new RedfishException(String.format("Failed to handle SSL Cert validator on GET request [URL: %s] due to exception.", url), e); + } + } else { + client = HttpClientBuilder.create().build(); + } + try { + return client.execute(httpReq); + } catch (IOException e) { + throw new RedfishException(String.format("Failed to execute GET request [URL: %s] due to exception.", url), e); + } + } + + private static String basicAuth(String username, String password) { + return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); + } + + /** + * Executes a POST request for the given URL address and Json object. + */ + private HttpResponse executePostRequest(String url, JsonObject jsonToSend) { + HttpPost httpReq = null; + try { + URIBuilder builder = new URIBuilder(url); + httpReq = new HttpPost(builder.build()); + httpReq.addHeader(ACCEPT, APPLICATION_JSON); + httpReq.addHeader(HTTP.CONTENT_TYPE, APPLICATION_JSON); + String encoding = basicAuth(username, password); + httpReq.addHeader("Authorization", encoding); + httpReq.setEntity(new StringEntity(jsonToSend.toString())); + } catch (URISyntaxException | UnsupportedEncodingException e) { + throw new RedfishException(String.format("Failed to create URI for POST request [URL: %s] due to exception.", url), e); + } + + HttpClient client = null; + if (ignoreSsl) { + try { + client = ignoreSSLCertValidator(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new RedfishException(String.format("Failed to handle SSL Cert validator on POST request [URL: %s] due to exception.", url), e); + } + } else { + client = HttpClientBuilder.create().build(); + } + + try { + return client.execute(httpReq); + } catch (IOException e) { + throw new RedfishException(String.format("Failed to execute POST request [URL: %s] due to exception.", url, e)); + } + } + + /** + * Returns the proper URL path for the given Redfish command ({@link RedfishCmdType}). + */ + private String getRequestPathForCommand(RedfishCmdType cmd, String resourceId) { + switch (cmd) { + case GetSystemId: + return SYSTEMS_URL_PATH; + case GetPowerState: + 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); + 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); + default: + throw new RedfishException(String.format("Redfish client does not support command '%s'.", cmd)); + } + } + + /** + * Validates the host address. It needs to be either a valid host domain name, or a valid IP address (IPv6 or IPv4). + */ + protected String validateAddressAndPrepareForUrl(String hostAddress) { + if (NetUtils.isValidIp6(hostAddress)) { + return String.format("[%s]", hostAddress); + } else if (NetUtils.isValidIp4(hostAddress)) { + return hostAddress; + } else if (InternetDomainName.isValid(hostAddress)) { + return hostAddress; + } else { + throw new RedfishException(String.format("Redfish host address '%s' is not a valid IPv4 or IPv6 address", hostAddress)); + } + } + + /** + * Sends a POST request for a ComputerSystem with the Reset command ({@link RedfishResetCmd}, e.g. RedfishResetCmd.GracefulShutdown).

+ * URL example: https://[host address]/redfish/v1/Systems/[System ID]/Actions/ComputerSystem.Reset + */ + public void executeComputerSystemReset(String hostAddress, RedfishResetCmd resetCommand) { + String systemId = getSystemId(hostAddress); + String url = buildRequestUrl(hostAddress, RedfishCmdType.ComputerSystemReset, systemId); + JsonObject resetType = new JsonObject(); + resetType.addProperty(REDFISH_RESET_TYPE, resetCommand.toString()); + + CloseableHttpResponse response = (CloseableHttpResponse)executePostRequest(url, resetType); + + 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)); + } + LOGGER.debug(String.format("Sending ComputerSystem.Reset Command '%s' to host '%s' with request '%s %s'", resetCommand, hostAddress, HttpPost.METHOD_NAME, url)); + } + + /** + * Returns the System ID. Used when sending Computer System requests (e.g. ComputerSystem.Reset request). + */ + public String getSystemId(String hostAddress) { + String url = buildRequestUrl(hostAddress, RedfishCmdType.GetSystemId, null); + CloseableHttpResponse response = (CloseableHttpResponse)executeGetRequest(url); + + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + throw new RedfishException(String.format("Failed to get System ID for host '%s' with request '%s: %s'. HTTP status code expected '%s' but it got '%s'.", hostAddress, + HttpGet.METHOD_NAME, url, HttpStatus.SC_OK, statusCode)); + } + + String systemId = processGetSystemIdResponse(response); + + LOGGER.debug(String.format("Retrieved System ID '%s' with request '%s: %s'", systemId, HttpGet.METHOD_NAME, url)); + + return systemId; + } + + /** + * Processes the response of request GET System ID as a JSON object. + */ + protected String processGetSystemIdResponse(CloseableHttpResponse response) { + InputStream in; + String jsonString; + try { + in = response.getEntity().getContent(); + BufferedReader streamReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + jsonString = streamReader.readLine(); + } catch (UnsupportedOperationException | IOException e) { + throw new RedfishException("Failed to process system Response", e); + } + + // 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(); + String[] arrayOfStrings = StringUtils.split(jsonObjectAsString, '/'); + + return arrayOfStrings[arrayOfStrings.length - 1]; + } + + /** + * Returns the Redfish system Power State ({@link RedfishPowerState}). + */ + public RedfishPowerState getSystemPowerState(String hostAddress) { + String systemId = getSystemId(hostAddress); + + String url = buildRequestUrl(hostAddress, RedfishCmdType.GetPowerState, systemId); + CloseableHttpResponse response = (CloseableHttpResponse)executeGetRequest(url); + + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + 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, HttpStatus.SC_OK, statusCode)); + } + + RedfishPowerState powerState = processGetSystemRequestResponse(response); + LOGGER.debug(String.format("Retrieved System power state '%s' with request '%s: %s'", powerState, HttpGet.METHOD_NAME, url)); + return powerState; + } + + /** + * Process 'ComputerSystem' GET request response and return as a RedfishPowerState + */ + protected RedfishPowerState processGetSystemRequestResponse(CloseableHttpResponse response) { + InputStream in; + String jsonString = null; + try { + in = response.getEntity().getContent(); + BufferedReader streamReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + + jsonString = streamReader.readLine(); + String powerState = new JsonParser().parse(jsonString).getAsJsonObject().get(POWER_STATE).getAsString(); + return RedfishPowerState.valueOf(powerState); + } catch (UnsupportedOperationException | IOException e) { + throw new RedfishException("Failed to process system response due to exception", e); + } + } + + /** + * Ignores SSL certififcation validator. + */ + private CloseableHttpClient ignoreSSLCertValidator() throws NoSuchAlgorithmException, KeyManagementException { + TrustManager[] trustAllCerts = new TrustManager[]{new TrustAllManager()}; + + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); + + HostnameVerifier allHostsValid = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, allHostsValid); + return HttpClientBuilder.create().setSSLSocketFactory(socketFactory).build(); + } +} diff --git a/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishException.java b/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishException.java new file mode 100644 index 000000000000..0ff6fc42aa8d --- /dev/null +++ b/utils/src/main/java/org/apache/cloudstack/utils/redfish/RedfishException.java @@ -0,0 +1,31 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack.utils.redfish; + +public class RedfishException extends RuntimeException { + + public RedfishException(String message) { + super(message); + } + + public RedfishException(String message, Exception e) { + super(message, e); + } + +} 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 new file mode 100644 index 000000000000..552f1874a4f0 --- /dev/null +++ b/utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java @@ -0,0 +1,163 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack.utils.redfish; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) public class RedfishClientTest { + + private static final String USERNAME = "user"; + 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"; + + RedfishClient redfishClientspy = Mockito.spy(new RedfishClient(USERNAME, PASSWORD, true, true)); + + @Test(expected = RedfishException.class) + public void validateAddressAndPrepareForUrlTestExpect() { + redfishClientspy.validateAddressAndPrepareForUrl("1:1:2:3:1"); + redfishClientspy.validateAddressAndPrepareForUrl("1"); + redfishClientspy.validateAddressAndPrepareForUrl("hostname"); + redfishClientspy.validateAddressAndPrepareForUrl(oobAddress); + } + + @Test + public void validateAddressAndPrepareForUrlTestDomainName() { + String result = redfishClientspy.validateAddressAndPrepareForUrl(oobAddress); + Assert.assertEquals(oobAddress, result); + } + + @Test + public void validateAddressAndPrepareForUrlTestIpv4() { + String ipv4 = "192.168.0.123"; + String result = redfishClientspy.validateAddressAndPrepareForUrl(ipv4); + Assert.assertEquals(ipv4, result); + } + + @Test + public void validateAddressAndPrepareForUrlTestIpv6() { + String ipv6 = "100::ffff:ffff:ffff:ffff"; + String expected = "[" + ipv6 + "]"; + String result = redfishClientspy.validateAddressAndPrepareForUrl(ipv6); + Assert.assertEquals(expected, result); + } + + @Test + public void buildRequestUrlTestHttpsGetSystemId() { + RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, true, false); + String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.GetSystemId, systemId); + String expected = String.format("https://%s/redfish/v1/Systems/", oobAddress, systemId); + Assert.assertEquals(expected, result); + } + + @Test + public void buildRequestUrlTestGetSystemId() { + RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, false, false); + String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.GetSystemId, systemId); + String expected = String.format("http://%s/redfish/v1/Systems/", oobAddress, systemId); + Assert.assertEquals(expected, result); + } + + @Test + public void buildRequestUrlTestHttpsComputerSystemReset() { + RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, true, false); + 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); + Assert.assertEquals(expected, result); + } + + @Test + public void buildRequestUrlTestComputerSystemReset() { + RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, false, false); + 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); + Assert.assertEquals(expected, result); + } + + @Test + public void buildRequestUrlTestHttpsGetPowerState() { + RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, true, false); + String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.GetPowerState, systemId); + String expected = String.format("https://%s/redfish/v1/Systems/%s", oobAddress, systemId); + Assert.assertEquals(expected, result); + } + + @Test + public void buildRequestUrlTestGetPowerState() { + RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, false, false); + String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.GetPowerState, systemId); + String expected = String.format("http://%s/redfish/v1/Systems/%s", oobAddress, systemId); + Assert.assertEquals(expected, result); + } + + @Test + public void getSystemPowerStateTest() { + Mockito.doReturn(systemId).when(redfishClientspy).getSystemId(Mockito.anyString()); + mockResponse(HttpStatus.SC_OK); + RedfishClient.RedfishPowerState expectedState = RedfishClient.RedfishPowerState.On; + Mockito.doReturn(expectedState).when(redfishClientspy).processGetSystemRequestResponse(Mockito.any(CloseableHttpResponse.class)); + + RedfishClient.RedfishPowerState result = redfishClientspy.getSystemPowerState(oobAddress); + + Assert.assertEquals(expectedState, result); + } + + @Test(expected = RedfishException.class) + public void getSystemPowerStateTestHttpStatusNotOk() { + Mockito.doReturn(systemId).when(redfishClientspy).getSystemId(Mockito.anyString()); + mockResponse(HttpStatus.SC_BAD_REQUEST); + redfishClientspy.getSystemPowerState(oobAddress); + } + + private CloseableHttpResponse mockResponse(int httpStatusCode) { + StatusLine statusLine = Mockito.mock(StatusLine.class); + Mockito.doReturn(httpStatusCode).when(statusLine).getStatusCode(); + CloseableHttpResponse response = Mockito.mock(CloseableHttpResponse.class); + Mockito.doReturn(statusLine).when(response).getStatusLine(); + Mockito.doReturn(response).when(redfishClientspy).executeGetRequest(Mockito.anyString()); + return response; + } + + @Test + public void getSystemIdTest() { + CloseableHttpResponse mockedResponse = mockResponse(HttpStatus.SC_OK); + Mockito.doReturn(mockedResponse).when(redfishClientspy).executeGetRequest(Mockito.anyString()); + Mockito.doReturn(systemId).when(redfishClientspy).processGetSystemIdResponse(Mockito.any(CloseableHttpResponse.class)); + + String result = redfishClientspy.getSystemId(oobAddress); + + Assert.assertEquals(systemId, result); + } + + @Test(expected = RedfishException.class) + public void getSystemIdTestHttpStatusNotOk() { + CloseableHttpResponse mockedResponse = mockResponse(HttpStatus.SC_UNAUTHORIZED); + Mockito.doReturn(mockedResponse).when(redfishClientspy).executeGetRequest(Mockito.anyString()); + redfishClientspy.getSystemId(oobAddress); + } + +} From 9749136b808238800c085597d9461c0b99e1d61d Mon Sep 17 00:00:00 2001 From: Gabriel Brascher Date: Wed, 24 Jun 2020 16:01:16 -0300 Subject: [PATCH 2/3] Address reviewer: enhance log message on validateAddressAndPrepareForUrl --- .../java/org/apache/cloudstack/utils/redfish/RedfishClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d32455a12e7d..f711ab81b61d 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 @@ -249,7 +249,7 @@ protected String validateAddressAndPrepareForUrl(String hostAddress) { } else if (InternetDomainName.isValid(hostAddress)) { return hostAddress; } else { - throw new RedfishException(String.format("Redfish host address '%s' is not a valid IPv4 or IPv6 address", hostAddress)); + throw new RedfishException(String.format("Redfish host address '%s' is not a valid IPv4/IPv6 address nor a valid domain name.", hostAddress)); } } From cda4182f68f73be34fda566bd5458dadc73552ce Mon Sep 17 00:00:00 2001 From: Gabriel Brascher Date: Fri, 3 Jul 2020 14:18:16 -0300 Subject: [PATCH 3/3] Extract duplicated lines into method. --- .../utils/redfish/RedfishClient.java | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 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 f711ab81b61d..8166b703a023 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 @@ -43,6 +43,7 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; @@ -149,36 +150,15 @@ protected String buildRequestUrl(String hostAddress, RedfishCmdType cmd, String protected HttpResponse executeGetRequest(String url) { URIBuilder builder = null; HttpGet httpReq = null; - try { builder = new URIBuilder(url); - httpReq = new HttpGet(builder.build());; - httpReq.addHeader(ACCEPT, APPLICATION_JSON); - String encoding = basicAuth(username, password); - httpReq.addHeader("Authorization", encoding); + httpReq = new HttpGet(builder.build()); } catch (URISyntaxException e) { throw new RedfishException(String.format("Failed to create URI for GET request [URL: %s] due to exception.", url), e); } - HttpClient client = null; - if (ignoreSsl) { - try { - client = ignoreSSLCertValidator(); - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new RedfishException(String.format("Failed to handle SSL Cert validator on GET request [URL: %s] due to exception.", url), e); - } - } else { - client = HttpClientBuilder.create().build(); - } - try { - return client.execute(httpReq); - } catch (IOException e) { - throw new RedfishException(String.format("Failed to execute GET request [URL: %s] due to exception.", url), e); - } - } - - private static String basicAuth(String username, String password) { - return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); + prepareHttpRequestBasicAuth(httpReq); + return executeHttpRequest(url, httpReq); } /** @@ -189,15 +169,36 @@ private HttpResponse executePostRequest(String url, JsonObject jsonToSend) { try { URIBuilder builder = new URIBuilder(url); httpReq = new HttpPost(builder.build()); - httpReq.addHeader(ACCEPT, APPLICATION_JSON); httpReq.addHeader(HTTP.CONTENT_TYPE, APPLICATION_JSON); - String encoding = basicAuth(username, password); - httpReq.addHeader("Authorization", encoding); httpReq.setEntity(new StringEntity(jsonToSend.toString())); } catch (URISyntaxException | UnsupportedEncodingException e) { throw new RedfishException(String.format("Failed to create URI for POST request [URL: %s] due to exception.", url), e); } + prepareHttpRequestBasicAuth(httpReq); + return executeHttpRequest(url, httpReq); + } + + /** + * Prepare http request to accept JSON and basic authentication + */ + private void prepareHttpRequestBasicAuth(HttpRequestBase httpReq) { + httpReq.addHeader(ACCEPT, APPLICATION_JSON); + String encoding = basicAuth(username, password); + httpReq.addHeader("Authorization", encoding); + } + + /** + * Encodes 'username:password' into 64-base encoded String + */ + private static String basicAuth(String username, String password) { + return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); + } + + /** + * Executes Http request according to URL and HttpRequestBase (e.g. HttpGet, HttpPost) + */ + private HttpResponse executeHttpRequest(String url, HttpRequestBase httpReq) { HttpClient client = null; if (ignoreSsl) { try {