/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ package org.opensearch.performanceanalyzer.rest; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.security.InvalidParameterException; import java.util.List; import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.opensearch.performanceanalyzer.AppContext; import org.opensearch.performanceanalyzer.commons.rca.Version; import org.opensearch.performanceanalyzer.commons.stats.metrics.StatExceptionCode; import org.opensearch.performanceanalyzer.metrics.MetricsRestUtil; import org.opensearch.performanceanalyzer.rca.framework.core.Stats; import org.opensearch.performanceanalyzer.rca.framework.util.SQLiteQueryUtils; import org.opensearch.performanceanalyzer.rca.persistence.Persistable; /** * Request handler that supports querying RCAs * *

To dump all RCA related tables from SQL : curl --url * "localhost:9600/_plugins/_performanceanalyzer/rca?all" -XGET * *

To get response for all the available RCA, use: curl --url * "localhost:9600/_plugins/_performanceanalyzer/rca" -XGET * *

To get response for a specific RCA, use: curl --url * "localhost:9600/_plugins/_performanceanalyzer/rca?name=HighHeapUsageClusterRca" -XGET * *

For temperature profiles, one can get the local node temperature using a request url as: curl * "localhost:9600/_plugins/_performanceanalyzer/rca?name=NodeTemperatureRca&local=true" * *

The cluster level RCA can only be queried from the elected cluster_manager using this rest * API: curl "localhost:9600/_plugins/_performanceanalyzer/rca?name=ClusterTemperatureRca" * *

curl "localhost:9600/_plugins/_performanceanalyzer/rca?name=NodeTemperatureRca&local=true"|jq * { "NodeTemperatureRca": [ { "rca_name": "NodeTemperatureRca", "timestamp": 1589592178829, * "state": "unknown", "CompactNodeSummary": [ { "node_id": "v9_TNhEeSP2Q3DJO8fd6BA", * "host_address": "172.17.0.2", "CPU_Utilization_mean": 0, "CPU_Utilization_total": * 0.0310837676896351, "CPU_Utilization_num_shards": 2, "Heap_AllocRate_mean": 0, * "Heap_AllocRate_total": 4021355.87442904, "Heap_AllocRate_num_shards": 2, * "IO_READ_SYSCALL_RATE_mean": 0, "IO_READ_SYSCALL_RATE_total": 0, * "IO_READ_SYSCALL_RATE_num_shards": 0, "IO_WriteSyscallRate_mean": 0, "IO_WriteSyscallRate_total": * 0, "IO_WriteSyscallRate_num_shards": 0 } ] } ] } */ public class QueryRcaRequestHandler extends MetricsHandler implements HttpHandler { private static final Logger LOG = LogManager.getLogger(QueryRcaRequestHandler.class); private static final String DUMP_ALL = "all"; private static final String VERSION_PARAM = "v"; private static final String LOCAL_PARAM = "local"; private static final String VERSION_RESPONSE_PROPERTY = "version"; public static final String NAME_PARAM = "name"; private Persistable persistable; private MetricsRestUtil metricsRestUtil; private AppContext appContext; public QueryRcaRequestHandler(final AppContext appContext) { this.appContext = appContext; metricsRestUtil = new MetricsRestUtil(); } @Override public void handle(HttpExchange exchange) throws IOException { String requestMethod = exchange.getRequestMethod(); if (requestMethod.equalsIgnoreCase("GET")) { LOG.debug("RCA Query handler called."); exchange.getResponseHeaders().set("Content-Type", "application/json"); try { synchronized (this) { String query = exchange.getRequestURI().getQuery(); if (query != null && query.equalsIgnoreCase(VERSION_PARAM)) { sendResponse(exchange, getVersion(), HttpURLConnection.HTTP_OK); return; } // first check if we want to dump all SQL tables for debugging purpose if (query != null && query.equals(DUMP_ALL)) { sendResponse(exchange, dumpAllRcaTables(), HttpURLConnection.HTTP_OK); } else { Map params = getParamsMap(query); if (isLocalTemperatureProfileRequest(params)) { handleLocalRcaRequest(params, exchange); } else { handleClusterRcaRequest(params, exchange); } } } } catch (InvalidParameterException e) { LOG.error( (Supplier) () -> new ParameterizedMessage( "QueryException {} ExceptionCode: {}.", e.toString(), StatExceptionCode.REQUEST_ERROR.toString()), e); String response = "{\"error\":\"" + e.getMessage() + "\"}"; sendResponse(exchange, response, HttpURLConnection.HTTP_BAD_REQUEST); } catch (Exception e) { LOG.error( (Supplier) () -> new ParameterizedMessage( "QueryException {} ExceptionCode: {}.", e.toString(), StatExceptionCode.REQUEST_ERROR.toString()), e); String response = "{\"error\":\"" + e.toString() + "\"}"; sendResponse(exchange, response, HttpURLConnection.HTTP_INTERNAL_ERROR); } } else { exchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, -1); } exchange.close(); } private void handleClusterRcaRequest(Map params, HttpExchange exchange) throws IOException { // check if we are querying from elected cluster_manager if (!validNodeRole()) { JsonObject errResponse = new JsonObject(); errResponse.addProperty("error", "Node being queried is not elected cluster_manager."); sendResponse(exchange, errResponse.toString(), HttpURLConnection.HTTP_BAD_REQUEST); return; } List rcaList = metricsRestUtil.parseArrayParam(params, NAME_PARAM, true); // query all cluster level RCAs if no RCA is specified in name. if (rcaList.isEmpty()) { // rcaList = SQLiteQueryUtils.getClusterLevelRca(); rcaList = persistable.getAllPersistedRcas(); } else if (!validParams(rcaList)) { JsonObject errResponse = new JsonObject(); JsonArray errReason = new JsonArray(); SQLiteQueryUtils.getClusterLevelRca().forEach(errReason::add); errResponse.addProperty("error", "Invalid RCA."); errResponse.add("valid_cluster_rca", errReason); sendResponse(exchange, errResponse.toString(), HttpURLConnection.HTTP_BAD_REQUEST); return; } String response = getRcaData(persistable, rcaList).toString(); sendResponse(exchange, response, HttpURLConnection.HTTP_OK); } private boolean isLocalTemperatureProfileRequest(final Map params) { final List temperatureProfileRcas = SQLiteQueryUtils.getTemperatureProfileRcas(); if (params.containsKey(LOCAL_PARAM)) { if (!Boolean.parseBoolean(params.get(LOCAL_PARAM))) { return false; } String requestedRca = params.get(NAME_PARAM); return temperatureProfileRcas.contains(requestedRca); } return false; } private void handleLocalRcaRequest( final Map params, final HttpExchange exchange) throws IOException { String rcaRequested = params.get(NAME_PARAM); if (rcaRequested == null || rcaRequested.isEmpty()) { JsonObject errorResponse = new JsonObject(); errorResponse.addProperty("error", "name parameter is empty or null"); sendResponse(exchange, errorResponse.toString(), HttpURLConnection.HTTP_BAD_REQUEST); return; } try { if (Stats.getInstance().getMutedGraphNodes().contains(rcaRequested)) { JsonObject errorResponse = new JsonObject(); StringBuilder builder = new StringBuilder(); builder.append("The Rca '") .append(rcaRequested) .append( "' is muted. Consider removing it " + "from the rca.conf's 'muted-rcas' list"); errorResponse.addProperty("error", builder.toString()); sendResponse( exchange, errorResponse.toString(), HttpURLConnection.HTTP_BAD_REQUEST); } else { String response = getTemperatureProfileRca(persistable, rcaRequested).toString(); sendResponse(exchange, response, HttpURLConnection.HTTP_OK); } } catch (Exception ex) { JsonObject errorResponse = new JsonObject(); errorResponse.addProperty("error", ex.getMessage()); sendResponse(exchange, errorResponse.toString(), HttpURLConnection.HTTP_BAD_REQUEST); } } // check whether RCAs are cluster level RCAs private boolean validParams(List rcaList) { return rcaList.stream().allMatch(SQLiteQueryUtils::isClusterLevelRca); } // check if we are querying from elected cluster_manager private boolean validNodeRole() { return appContext.getMyInstanceDetails().getIsClusterManager(); } private JsonElement getRcaData(Persistable persistable, List rcaList) { LOG.debug("RCA: in getRcaData"); JsonObject jsonObject = new JsonObject(); if (persistable != null) { rcaList.forEach(rca -> jsonObject.add(rca, persistable.read(rca))); } return jsonObject; } private JsonElement getTemperatureProfileRca(final Persistable persistable, String rca) { JsonObject responseJson = new JsonObject(); if (persistable != null) { responseJson.add(rca, persistable.read(rca)); } return responseJson; } private String dumpAllRcaTables() { String jsonResponse = ""; if (persistable != null) { jsonResponse = persistable.read(); } return jsonResponse; } public void sendResponse(HttpExchange exchange, String response, int status) throws IOException { try (OutputStream os = exchange.getResponseBody()) { exchange.sendResponseHeaders(status, response.length()); os.write(response.getBytes()); } catch (Exception e) { response = e.toString(); exchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, response.length()); } } public synchronized void setPersistable(Persistable persistable) { this.persistable = persistable; } /** * Gets the current RCA framework version. * * @return The version number as a json string. */ public String getVersion() { final JsonObject versionObject = new JsonObject(); versionObject.addProperty(VERSION_RESPONSE_PROPERTY, Version.getRcaVersion()); return versionObject.toString(); } }