hmci/src/main/java/biz/nellemann/hmci/HmcClient.java

385 lines
14 KiB
Java

/*
* Copyright 2020 Mark Nellemann <mark.nellemann@gmail.com>
*
* Licensed 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 biz.nellemann.hmci;
import biz.nellemann.hmci.Configuration.HmcObject;
import okhttp3.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
class HmcClient {
private final static Logger log = LoggerFactory.getLogger(HmcClient.class);
private final MediaType MEDIA_TYPE_IBM_XML_LOGIN = MediaType.parse("application/vnd.ibm.powervm.web+xml; type=LogonRequest");
private final String hmcId;
private final String baseUrl;
private final String username;
private final String password;
protected Integer responseErrors = 0;
protected String authToken;
private final OkHttpClient client;
HmcClient(HmcObject configHmc) {
this.hmcId = configHmc.name;
this.baseUrl = configHmc.url;
this.username = configHmc.username;
this.password = configHmc.password;
Boolean unsafe = configHmc.unsafe;
if(unsafe) {
this.client = getUnsafeOkHttpClient();
} else {
this.client = new OkHttpClient();
}
}
@Override
public String toString() {
return hmcId + " (" + baseUrl + ")";
}
/**
* Logon to the HMC and get an authentication token for further requests.
*/
synchronized void login() throws Exception {
log.debug("Connecting to HMC - " + baseUrl);
StringBuilder payload = new StringBuilder();
payload.append("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>");
payload.append("<LogonRequest xmlns='http://www.ibm.com/xmlns/systems/power/firmware/web/mc/2012_10/' schemaVersion='V1_0'>");
payload.append("<UserID>").append(username).append("</UserID>");
payload.append("<Password>").append(password).append("</Password>");
payload.append("</LogonRequest>");
try {
URL url = new URL(String.format("%s/rest/api/web/Logon", baseUrl));
Request request = new Request.Builder()
.url(url)
.addHeader("Accept", "application/vnd.ibm.powervm.web+xml; type=LogonResponse")
.addHeader("X-Audit-Memento", "hmci")
.put(RequestBody.create(payload.toString(), MEDIA_TYPE_IBM_XML_LOGIN))
.build();
Response response = client.newCall(request).execute();
String responseBody = Objects.requireNonNull(response.body()).string();
if (!response.isSuccessful()) {
log.warn("login() - Unexpected response: " + response.code());
throw new IOException("Unexpected code: " + response);
}
Document doc = Jsoup.parse(responseBody);
authToken = doc.select("X-API-Session").text();
log.debug("login() - Auth Token: " + authToken);
} catch (MalformedURLException e) {
log.error("login() - URL Error: " + e.getMessage());
throw e;
}
}
/**
* Logoff from the HMC and remove any session
*
*/
synchronized void logoff() throws IOException {
if(authToken == null) {
return;
}
URL absUrl = new URL(String.format("%s/rest/api/web/Logon", baseUrl));
Request request = new Request.Builder()
.url(absUrl)
.addHeader("Content-Type", "application/vnd.ibm.powervm.web+xml; type=LogonRequest")
.addHeader("X-API-Session", authToken)
.delete()
.build();
try {
client.newCall(request).execute();
} catch (IOException e) {
log.warn("logoff() error: " + e.getMessage());
} finally {
authToken = null;
}
}
/**
* Return Map of ManagedSystems seen by this HMC
*
* @return Map of system-id and ManagedSystem
*/
Map<String, ManagedSystem> getManagedSystems() throws Exception {
URL url = new URL(String.format("%s/rest/api/uom/ManagedSystem", baseUrl));
String responseBody = getResponse(url);
Map<String,ManagedSystem> managedSystemsMap = new HashMap<>();
// Do not try to parse empty response
if(responseBody == null || responseBody.isEmpty() || responseBody.length() <= 1) {
responseErrors++;
return managedSystemsMap;
}
try {
Document doc = Jsoup.parse(responseBody);
Elements managedSystems = doc.select("ManagedSystem|ManagedSystem"); // doc.select("img[src$=.png]");
for(Element el : managedSystems) {
ManagedSystem system = new ManagedSystem(
hmcId,
el.select("Metadata > Atom > AtomID").text(),
el.select("SystemName").text(),
el.select("MachineTypeModelAndSerialNumber > MachineType").text(),
el.select("MachineTypeModelAndSerialNumber > Model").text(),
el.select("MachineTypeModelAndSerialNumber > SerialNumber").text()
);
managedSystemsMap.put(system.id, system);
log.debug("getManagedSystems() - Found system: " + system);
}
} catch(Exception e) {
log.warn("getManagedSystems() - xml parse error", e);
}
return managedSystemsMap;
}
/**
* Return Map of LogicalPartitions seen by a ManagedSystem on this HMC
* @param system a valid ManagedSystem
* @return Map of partition-id and LogicalPartition
*/
Map<String, LogicalPartition> 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);
Map<String, LogicalPartition> partitionMap = new HashMap<String, LogicalPartition>();
// Do not try to parse empty response
if(responseBody == null || responseBody.isEmpty() || responseBody.length() <= 1) {
responseErrors++;
return partitionMap;
}
try {
Document doc = Jsoup.parse(responseBody);
Elements logicalPartitions = doc.select("LogicalPartition|LogicalPartition"); // doc.select("img[src$=.png]");
for(Element el : logicalPartitions) {
LogicalPartition logicalPartition = new LogicalPartition(
el.select("PartitionUUID").text(),
el.select("PartitionName").text(),
el.select("PartitionType").text(),
system
);
partitionMap.put(logicalPartition.id, logicalPartition);
log.debug("getLogicalPartitionsForManagedSystem() - Found partition: " + logicalPartition);
}
} catch(Exception e) {
log.warn("getLogicalPartitionsForManagedSystem() - xml parse error", e);
}
return partitionMap;
}
/**
* Parse XML feed to get PCM Data in JSON format
* @param system a valid ManagedSystem
* @return JSON string with PCM data for this ManagedSystem
*/
String getPcmDataForManagedSystem(ManagedSystem system) throws Exception {
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 jsonBody = null;
// Do not try to parse empty response
if(responseBody == null || responseBody.isEmpty() || responseBody.length() <= 1) {
responseErrors++;
log.warn("getPcmDataForManagedSystem() - empty response");
return null;
}
try {
Document doc = Jsoup.parse(responseBody);
Element entry = doc.select("feed > entry").first();
Element link = entry.select("link[href]").first();
if(link.attr("type").equals("application/json")) {
String href = link.attr("href");
log.debug("getPcmDataForManagedSystem() - json url: " + href);
jsonBody = getResponse(new URL(href));
}
} catch(Exception e) {
log.warn("getPcmDataForManagedSystem() - xml parse error", e);
}
return jsonBody;
}
/**
* Parse XML feed to get PCM Data in JSON format
* @param partition a valid LogicalPartition
* @return JSON string with PCM data for this LogicalPartition
*/
String getPcmDataForLogicalPartition(LogicalPartition partition) throws Exception {
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 jsonBody = null;
// Do not try to parse empty response
if(responseBody == null || responseBody.isEmpty() || responseBody.length() <= 1) {
responseErrors++;
log.warn("getPcmDataForLogicalPartition() - empty response");
return null;
}
try {
Document doc = Jsoup.parse(responseBody);
Element entry = doc.select("feed > entry").first();
Element link = entry.select("link[href]").first();
if(link.attr("type").equals("application/json")) {
String href = link.attr("href");
log.debug("getPcmDataForLogicalPartition() - json url: " + href);
jsonBody = getResponse(new URL(href));
}
} catch(Exception e) {
log.warn("getPcmDataForLogicalPartition() - xml parse error", e);
}
return jsonBody;
}
/**
* Return a Response from the HMC
* @param url to get Response from
* @return Response body string
*/
private String getResponse(URL url) throws Exception {
log.debug("getResponse() - " + url.toString());
Request request = new Request.Builder()
.url(url)
.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
.addHeader("X-API-Session", authToken)
.get().build();
Response response = client.newCall(request).execute();
String body = Objects.requireNonNull(response.body()).string();
if (!response.isSuccessful()) {
response.close();
if(response.code() == 401) {
log.warn("getResponse() - 401 - login and retry.");
authToken = null;
login();
return null;
}
log.error("getResponse() - Unexpected response: " + response.code());
throw new IOException("getResponse() - Unexpected response: " + response.code());
}
return body;
}
/**
* Provide an unsafe (ignoring SSL problems) OkHttpClient
*
* @return unsafe OkHttpClient
*/
private static OkHttpClient getUnsafeOkHttpClient() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
builder.hostnameVerifier((hostname, session) -> true);
return builder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}