From 98044859aeaaf7cad965ae03c106df8af38ed730 Mon Sep 17 00:00:00 2001 From: Mark Nellemann Date: Thu, 28 Jan 2021 19:53:45 +0100 Subject: [PATCH 1/2] Option to enable power and thermal readings through the HCM REST API. --- README.md | 4 +- doc/hmci.service | 2 + .../java/biz/nellemann/hmci/HmcInstance.java | 18 ++-- .../biz/nellemann/hmci/HmcRestClient.java | 85 +++++++++++++++++++ src/test/resources/system-preferences.xml | 42 +++++++++ 5 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 src/test/resources/system-preferences.xml diff --git a/README.md b/README.md index 7f40f03..153964e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Metrics includes: - *Managed Systems* - the physical Power servers - *Logical Partitions* - the virtualized servers running AIX, Linux and IBM-i (AS/400) - *Virtual I/O Servers* - the i/o partition(s) taking care of network and storage -- *Energy* - power consumption and temperatures +- *Energy* - power consumption and temperatures (needs to be enabled and not available for E880, E980) ![architecture](https://bitbucket.org/mnellemann/hmci/downloads/HMCi.png) @@ -57,7 +57,7 @@ Below are screenshots of the provided Grafana dashboards (found in the **doc/** ### Naming collision -You can't have partitions on different HMC's with the same name, as these cannot be distinguished when metrics are +You can't have partitions (or Virtual I/O Servers) on different Systems with the same name, as these cannot be distinguished when metrics are written to InfluxDB (which uses the name as key). ### Renaming partitions diff --git a/doc/hmci.service b/doc/hmci.service index bf34023..d7b7038 100644 --- a/doc/hmci.service +++ b/doc/hmci.service @@ -2,6 +2,8 @@ Description=HMC Insights Service [Service] +#User=nobody +#Group=nogroup TimeoutStartSec=0 Restart=always ExecStart=/opt/hmci/bin/hmci diff --git a/src/main/java/biz/nellemann/hmci/HmcInstance.java b/src/main/java/biz/nellemann/hmci/HmcInstance.java index 6c1062e..48ac246 100644 --- a/src/main/java/biz/nellemann/hmci/HmcInstance.java +++ b/src/main/java/biz/nellemann/hmci/HmcInstance.java @@ -110,6 +110,8 @@ class HmcInstance implements Runnable { log.debug("discover() - " + hmcId); + Map tmpPartitions = new HashMap<>(); + try { hmcRestClient.logoff(); hmcRestClient.login(); @@ -119,19 +121,16 @@ class HmcInstance implements Runnable { if(!systems.containsKey(systemId)) { systems.put(systemId, system); log.info("discover() - Found ManagedSystem: " + system + " @" + hmcId); + hmcRestClient.enableEnergyMonitoring(system); } // Get LPAR's for this system try { - hmcRestClient.getLogicalPartitionsForManagedSystem(system).forEach((partitionId, partition) -> { - - // Add to list of known partitions - if(!partitions.containsKey(partitionId)) { - partitions.put(partitionId, partition); - log.info("discover() - Found LogicalPartition: " + partition + " @" + hmcId); - } - - }); + hmcRestClient.getLogicalPartitionsForManagedSystem(system).forEach(tmpPartitions::put); + if(!tmpPartitions.isEmpty()) { + partitions.clear(); + tmpPartitions.forEach(partitions::put); + } } catch (Exception e) { log.warn("discover() - getLogicalPartitions", e); } @@ -142,6 +141,7 @@ class HmcInstance implements Runnable { log.warn("discover() - getManagedSystems: " + e.getMessage()); } + } diff --git a/src/main/java/biz/nellemann/hmci/HmcRestClient.java b/src/main/java/biz/nellemann/hmci/HmcRestClient.java index 49f2428..a39e24d 100644 --- a/src/main/java/biz/nellemann/hmci/HmcRestClient.java +++ b/src/main/java/biz/nellemann/hmci/HmcRestClient.java @@ -19,6 +19,8 @@ import okhttp3.*; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.nodes.Entities; +import org.jsoup.parser.Parser; import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -347,6 +349,54 @@ public class HmcRestClient { } + void enableEnergyMonitoring(ManagedSystem system) { + + log.debug("enableEnergyMonitoring() - " + system.id); + try { + URL url = new URL(String.format("%s/rest/api/pcm/ManagedSystem/%s/preferences", baseUrl, system.id)); + String responseBody = getResponse(url); + String jsonBody = null; + + // Do not try to parse empty response + if(responseBody == null || responseBody.isEmpty() || responseBody.length() <= 1) { + responseErrors++; + log.warn("enableEnergyMonitoring() - empty response"); + return; + } + + Document doc = Jsoup.parse(responseBody, "", Parser.xmlParser()); + doc.outputSettings().escapeMode(Entities.EscapeMode.xhtml); + doc.outputSettings().prettyPrint(false); + doc.outputSettings().charset("US-ASCII"); + Element entry = doc.select("feed > entry").first(); + Element link1 = entry.select("EnergyMonitoringCapable").first(); + Element link2 = entry.select("EnergyMonitorEnabled").first(); + + if(link1.text().equals("true")) { + log.debug("enableEnergyMonitoring() - EnergyMonitoringCapable == true"); + if(link2.text().equals("false")) { + //log.warn("enableEnergyMonitoring() - EnergyMonitorEnabled == false"); + link2.text("true"); + + Document content = Jsoup.parse(doc.select("Content").first().html(), "", Parser.xmlParser()); + content.outputSettings().escapeMode(Entities.EscapeMode.xhtml); + content.outputSettings().prettyPrint(false); + content.outputSettings().charset("UTF-8"); + String updateXml = content.outerHtml(); + + sendPostRequest(url, updateXml); + } + } else { + log.warn("enableEnergyMonitoring() - EnergyMonitoringCapable == false"); + } + + } catch (Exception e) { + log.warn("enableEnergyMonitoring() - Exception: " + e.getMessage()); + } + } + + + /** * Return a Response from the HMC * @param url to get Response from @@ -385,6 +435,41 @@ public class HmcRestClient { + + public String sendPostRequest(URL url, String payload) throws Exception { + + log.debug("sendPostRequest() - " + url.toString()); + + RequestBody requestBody; + if(payload != null) { + //log.debug("sendPostRequest() - payload: " + payload); + requestBody = RequestBody.create(payload, MediaType.get("application/xml")); + } else { + requestBody = RequestBody.create("", null); + } + + + Request request = new Request.Builder() + .url(url) + //.addHeader("Content-Type", "application/xml") + .addHeader("content-type", "application/xml") + .addHeader("X-API-Session", authToken) + .post(requestBody).build(); + + Response response = client.newCall(request).execute(); + String body = Objects.requireNonNull(response.body()).string(); + + if (!response.isSuccessful()) { + response.close(); + log.warn(body); + log.error("sendPostRequest() - Unexpected response: " + response.code()); + throw new IOException("sendPostRequest() - Unexpected response: " + response.code()); + } + + log.debug("sendPostRequest() - response: " + body); + return body; + } + /** * Provide an unsafe (ignoring SSL problems) OkHttpClient * diff --git a/src/test/resources/system-preferences.xml b/src/test/resources/system-preferences.xml new file mode 100644 index 0000000..78712b5 --- /dev/null +++ b/src/test/resources/system-preferences.xml @@ -0,0 +1,42 @@ + + d93d97ab-757a-38ed-afe7-013c51cf60c9 + Performance and Capacity Monitoring Preferences + + + + + e40c41c0-4aff-472d-85f2-715e11ed1a74 + 2021-01-28T13:13:23.636+01:00 + Performance and Capacity Monitoring Preferences + 2021-01-28T13:13:23.636+01:00 + + IBM Power Systems Management Console + + + + + + d93d97ab-757a-38ed-afe7-013c51cf60c9 + 1611836003635 + + + P750-8408-E8D-SN211D04V + + + + + 8408 + E8D + 211D04V + + false + true + true + false + true + false + + + + + From 92079a13d29269aae162b80fd95967616e1ad7fb Mon Sep 17 00:00:00 2001 From: Mark Nellemann Date: Fri, 29 Jan 2021 09:44:29 +0100 Subject: [PATCH 2/2] Bump version and rename of method. --- gradle.properties | 2 +- .../biz/nellemann/hmci/HmcRestClient.java | 34 ++++++++++++------- .../nellemann/hmci/HmcRestClientTest.groovy | 4 +-- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7501f79..22d40c4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ id = hmci group = biz.nellemann.hmci -version = 1.1.2 +version = 1.1.3 diff --git a/src/main/java/biz/nellemann/hmci/HmcRestClient.java b/src/main/java/biz/nellemann/hmci/HmcRestClient.java index a39e24d..c284b7f 100644 --- a/src/main/java/biz/nellemann/hmci/HmcRestClient.java +++ b/src/main/java/biz/nellemann/hmci/HmcRestClient.java @@ -162,7 +162,7 @@ public class HmcRestClient { Map getManagedSystems() throws Exception { URL url = new URL(String.format("%s/rest/api/uom/ManagedSystem", baseUrl)); - String responseBody = getResponse(url); + String responseBody = sendGetRequest(url); Map managedSystemsMap = new HashMap<>(); // Do not try to parse empty response @@ -202,7 +202,7 @@ public class HmcRestClient { */ Map getLogicalPartitionsForManagedSystem(ManagedSystem system) throws Exception { URL url = new URL(String.format("%s/rest/api/uom/ManagedSystem/%s/LogicalPartition", baseUrl, system.id)); - String responseBody = getResponse(url); + String responseBody = sendGetRequest(url); Map partitionMap = new HashMap<>(); // Do not try to parse empty response @@ -242,7 +242,7 @@ public class HmcRestClient { log.debug("getPcmDataForManagedSystem() - " + system.id); URL url = new URL(String.format("%s/rest/api/pcm/ManagedSystem/%s/ProcessedMetrics?NoOfSamples=1", baseUrl, system.id)); - String responseBody = getResponse(url); + String responseBody = sendGetRequest(url); String jsonBody = null; // Do not try to parse empty response @@ -260,7 +260,7 @@ public class HmcRestClient { if(link.attr("type").equals("application/json")) { String href = link.attr("href"); log.debug("getPcmDataForManagedSystem() - json url: " + href); - jsonBody = getResponse(new URL(href)); + jsonBody = sendGetRequest(new URL(href)); } } catch(Exception e) { @@ -280,7 +280,7 @@ public class HmcRestClient { log.debug(String.format("getPcmDataForLogicalPartition() - %s @ %s", partition.id, partition.system.id)); URL url = new URL(String.format("%s/rest/api/pcm/ManagedSystem/%s/LogicalPartition/%s/ProcessedMetrics?NoOfSamples=1", baseUrl, partition.system.id, partition.id)); - String responseBody = getResponse(url); + String responseBody = sendGetRequest(url); String jsonBody = null; // Do not try to parse empty response @@ -298,7 +298,7 @@ public class HmcRestClient { if(link.attr("type").equals("application/json")) { String href = link.attr("href"); log.debug("getPcmDataForLogicalPartition() - json url: " + href); - jsonBody = getResponse(new URL(href)); + jsonBody = sendGetRequest(new URL(href)); } } catch(Exception e) { @@ -319,7 +319,7 @@ public class HmcRestClient { log.debug("getPcmDataForEnergy() - " + systemEnergy.system.id); URL url = new URL(String.format("%s/rest/api/pcm/ManagedSystem/%s/ProcessedMetrics?Type=Energy&NoOfSamples=1", baseUrl, systemEnergy.system.id)); - String responseBody = getResponse(url); + String responseBody = sendGetRequest(url); String jsonBody = null; //log.info(responseBody); @@ -338,7 +338,7 @@ public class HmcRestClient { if(link.attr("type").equals("application/json")) { String href = link.attr("href"); log.debug("getPcmDataForEnergy() - json url: " + href); - jsonBody = getResponse(new URL(href)); + jsonBody = sendGetRequest(new URL(href)); } } catch(Exception e) { @@ -349,12 +349,16 @@ public class HmcRestClient { } + /** + * Set EnergyMonitorEnabled preference to true, if possible. + * @param system + */ void enableEnergyMonitoring(ManagedSystem system) { log.debug("enableEnergyMonitoring() - " + system.id); try { URL url = new URL(String.format("%s/rest/api/pcm/ManagedSystem/%s/preferences", baseUrl, system.id)); - String responseBody = getResponse(url); + String responseBody = sendGetRequest(url); String jsonBody = null; // Do not try to parse empty response @@ -402,7 +406,7 @@ public class HmcRestClient { * @param url to get Response from * @return Response body string */ - private String getResponse(URL url) throws Exception { + private String sendGetRequest(URL url) throws Exception { log.debug("getResponse() - " + url.toString()); @@ -434,8 +438,13 @@ public class HmcRestClient { } - - + /** + * Send a POST request with a payload (can be null) to the HMC + * @param url + * @param payload + * @return + * @throws Exception + */ public String sendPostRequest(URL url, String payload) throws Exception { log.debug("sendPostRequest() - " + url.toString()); @@ -470,6 +479,7 @@ public class HmcRestClient { return body; } + /** * Provide an unsafe (ignoring SSL problems) OkHttpClient * diff --git a/src/test/groovy/biz/nellemann/hmci/HmcRestClientTest.groovy b/src/test/groovy/biz/nellemann/hmci/HmcRestClientTest.groovy index 2f8f08c..5b4c4f4 100644 --- a/src/test/groovy/biz/nellemann/hmci/HmcRestClientTest.groovy +++ b/src/test/groovy/biz/nellemann/hmci/HmcRestClientTest.groovy @@ -73,7 +73,7 @@ class HmcRestClientTest extends Specification { mockServer.enqueue(new MockResponse().setBody(testJson)) when: - String jsonString = hmc.getResponse(new URL(mockServer.url("/rest/api/pcm/ProcessedMetrics/ManagedSystem_e09834d1-c930-3883-bdad-405d8e26e166_20200807T122600+0200_20200807T122600+0200_30.json") as String)) + String jsonString = hmc.sendGetRequest(new URL(mockServer.url("/rest/api/pcm/ProcessedMetrics/ManagedSystem_e09834d1-c930-3883-bdad-405d8e26e166_20200807T122600+0200_20200807T122600+0200_30.json") as String)) then: jsonString.contains('"uuid": "e09834d1-c930-3883-bdad-405d8e26e166"') @@ -87,7 +87,7 @@ class HmcRestClientTest extends Specification { mockServer.enqueue(new MockResponse().setBody(testJson)) when: - String jsonString = hmc.getResponse(new URL(mockServer.url("/rest/api/pcm/ProcessedMetrics/LogicalPartition_2DE05DB6-8AD5-448F-8327-0F488D287E82_20200807T123730+0200_20200807T123730+0200_30.json") as String)) + String jsonString = hmc.sendGetRequest(new URL(mockServer.url("/rest/api/pcm/ProcessedMetrics/LogicalPartition_2DE05DB6-8AD5-448F-8327-0F488D287E82_20200807T123730+0200_20200807T123730+0200_30.json") as String)) then: jsonString.contains('"uuid": "b597e4da-2aab-3f52-8616-341d62153559"')