All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rhq.plugins.script.ScriptServerComponent Maven / Gradle / Ivy

Go to download

A plugin for managing resources that have command line executables or scripts as their management interface.

There is a newer version: 4.13.0
Show newest version
/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.plugins.script;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.core.domain.measurement.MeasurementDataTrait;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.measurement.MeasurementFacet;
import org.rhq.core.pluginapi.operation.OperationFacet;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.core.system.ProcessExecution;
import org.rhq.core.system.ProcessExecutionResults;
import org.rhq.core.system.SystemInfo;
import org.rhq.core.util.exception.ThrowableUtil;

/**
 * Represents a managed resource whose management interface is a command line executable or script,
 * sometimes referred to as the "CLI" (command line interface).
 *
 * @author John Mazzitelli
 */
public class ScriptServerComponent implements ResourceComponent, MeasurementFacet, OperationFacet {

    private final Log log = LogFactory.getLog(ScriptServerComponent.class);

    private static final long DEFAULT_MAX_WAIT_TIME = 3600000L;

    protected static final String PLUGINCONFIG_EXECUTABLE = "executable";
    protected static final String PLUGINCONFIG_WORKINGDIR = "workingDirectory";
    protected static final String PLUGINCONFIG_ENVVARS = "environmentVariables";
    protected static final String PLUGINCONFIG_ENVVAR_NAME = "name";
    protected static final String PLUGINCONFIG_ENVVAR_VALUE = "value";
    protected static final String PLUGINCONFIG_AVAIL_EXECUTE_CHECK = "availabilityExecuteCheck";
    protected static final String PLUGINCONFIG_AVAIL_EXITCODE_REGEX = "availabilityExitCodeRegex";
    protected static final String PLUGINCONFIG_AVAIL_OUTPUT_REGEX = "availabilityOutputRegex";
    protected static final String PLUGINCONFIG_AVAIL_ARGS = "availabilityArguments";
    protected static final String PLUGINCONFIG_VERSION_ARGS = "versionArguments";
    protected static final String PLUGINCONFIG_VERSION_REGEX = "versionRegex";
    protected static final String PLUGINCONFIG_FIXED_VERSION = "fixedVersion";
    protected static final String PLUGINCONFIG_DESC_ARGS = "descriptionArguments";
    protected static final String PLUGINCONFIG_DESC_REGEX = "descriptionRegex";
    protected static final String PLUGINCONFIG_FIXED_DESC = "fixedDescription";

    protected static final String OPERATION_PARAM_ARGUMENTS = "arguments";
    protected static final String OPERATION_PARAM_WAIT_TIME = "waitTime";
    protected static final String OPERATION_PARAM_CAPTURE_OUTPUT = "captureOutput";
    protected static final String OPERATION_PARAM_KILL_ON_TIMEOUT = "killOnTimeout";
    protected static final String OPERATION_RESULT_EXITCODE = "exitCode";
    protected static final String OPERATION_RESULT_OUTPUT = "output";

    protected static final String METRIC_PROPERTY_ARGUMENTS = "arguments";
    protected static final String METRIC_PROPERTY_REGEX = "regex";
    protected static final String METRIC_PROPERTY_EXITCODE = "exitcode";

    private Configuration resourceConfiguration;
    private ResourceContext resourceContext;

    public void start(ResourceContext context) {
        if (log.isDebugEnabled()) {
            log.debug("Script Server started: " + context.getPluginConfiguration());
        }

        this.resourceContext = context;
    }

    public void stop() {
        if (log.isDebugEnabled()) {
            log.debug("Script Server stopped: " + this.resourceContext.getPluginConfiguration());
        }
    }

    public AvailabilityType getAvailability() {
        boolean result = checkAvailability();
        return result ? AvailabilityType.UP : AvailabilityType.DOWN;
    }

    /**
     * Executes the CLI and based on the measurement property, collects the appropriate measurement value.
     * This supports measurement data and traits.
     * A metric property can be a string that is assumed the arguments to pass to the CLI. However, if
     * the property is in the form "{arguments}|regex", the arguments are passed to the CLI and the metric
     * value is taken from the regex match. Examples of metric properties:
     *  
     * 
    *
  • "-a --arg2=123" - passes that string as the arguments list, the metric value is the output of the CLI
  • *
  • "{}|exitcode" - no arguments are passed to the CLI and the metric value is the exit code of the CLI
  • *
  • "{-a}|[lL]abel(\p{Digit}+)[\r\n]*" - "-a" is the argument passed to the CLI and the metric value is the digits following the label of the output
  • *
  • "{}|[ABC]+" - no arguments are passed and the value is the output of the CLI assuming it matches the regex
  • *
  • "{--foobar}|foobar (.*) blah" - passes "--foobar" as the argument and the metric value is the string that matches the regex group
  • *
* * @see MeasurementFacet#getValues(MeasurementReport, Set) */ public void getValues(MeasurementReport report, Set requests) { Map exeResultsCache = new HashMap(); for (MeasurementScheduleRequest request : requests) { String metricPropertyName = request.getName(); boolean dataMustBeNumeric; if (request.getDataType() == DataType.MEASUREMENT) { dataMustBeNumeric = true; } else if (request.getDataType() == DataType.TRAIT) { dataMustBeNumeric = false; } else { log.error("Plugin does not support metric [" + metricPropertyName + "] of type [" + request.getDataType() + "]"); continue; } try { // determine how to execute the CLI for this metric Map argsRegex = parseMetricProperty(metricPropertyName); Object dataValue; if (argsRegex == null) { continue; // this metric's property was invalid, skip this one and move on to the next } String arguments = argsRegex.get(METRIC_PROPERTY_ARGUMENTS); String regex = argsRegex.get(METRIC_PROPERTY_REGEX); boolean valueIsExitCode = METRIC_PROPERTY_EXITCODE.equals(regex); // if we already executed it with the same arguments, don't bother doing it again ProcessExecutionResults exeResults = exeResultsCache.get((arguments == null) ? "" : arguments); if (exeResults == null) { boolean captureOutput = !valueIsExitCode; // don't need output if we need to just check exit code exeResults = executeExecutable(arguments, DEFAULT_MAX_WAIT_TIME, captureOutput); exeResultsCache.put((arguments == null) ? "" : arguments, exeResults); } // don't report a metric value if the CLI failed to execute if (exeResults.getError() != null) { log.error("Cannot collect CLI metric [" + metricPropertyName + "]. Cause: " + ThrowableUtil.getAllMessages(exeResults.getError())); continue; } // set dataValue to the appropriate value based on how the metric property defined it if (valueIsExitCode) { dataValue = exeResults.getExitCode(); if (dataValue == null) { log.error("Could not determine exit code for metric property [" + metricPropertyName + "] - metric will not be collected"); continue; } } else if (regex != null) { final String output = exeResults.getCapturedOutput(); if (output == null) { log.error("Could not get output for metric property [" + metricPropertyName + "] -- metric will not be collected"); continue; } Pattern pattern = Pattern.compile(regex); Matcher match = pattern.matcher(output); if (match.find()) { if (match.groupCount() > 0) { dataValue = match.group(1); } else { dataValue = output; } } else { log.error("Output did not match metric property [" + metricPropertyName + "] - metric will not be collected: " + truncateString(output)); continue; } } else { dataValue = exeResults.getCapturedOutput(); if (dataValue == null) { log.error("Could not get output for metric property [" + metricPropertyName + "] - metric will not be collected"); continue; } } // add the metric value to the measurement report if (dataMustBeNumeric) { Double numeric = Double.parseDouble(dataValue.toString().trim()); report.addData(new MeasurementDataNumeric(request, numeric.doubleValue())); } else { report.addData(new MeasurementDataTrait(request, dataValue.toString().trim())); } } catch (Exception e) { log.error("Failed to obtain measurement [" + metricPropertyName + "]. Cause: " + e); } } exeResultsCache.clear(); // help out the garbage collector, since this could be alot of data exeResultsCache = null; return; } /** * Given a metric property name, this parses it into its different pieces and returns a * map of the different tokens. The format of the metric property one of the following: *
     * arguments
     * {arguments}|regex
     * {arguments}|exitcode
     * 
* * where "arguments" is the empty or non-empty string of the arguments to pass to the CLI executable, * regex is a empty or non-empty regular expresssion string to match the output (if there is a matching * group in the regex, its value will be used as the metric value, not the full output of the executable), * exitcode is the literal string "exitcode" to indicate that the exit code value is to be used as the metric value. * * @param metricPropertyName the name of the property in the metric descriptor * @return map containing the pieces that have been parsed out - the keys are * either {@link #METRIC_PROPERTY_ARGUMENTS} or {@link #METRIC_PROPERTY_REGEX}. * The map will be null if the property name is invalid for some reason. * A map entry will not exist if it was not specified in the property name. */ protected Map parseMetricProperty(String metricPropertyName) { HashMap map = new HashMap(); if (metricPropertyName != null && metricPropertyName.length() > 0) { if (!metricPropertyName.startsWith("{")) { map.put(METRIC_PROPERTY_ARGUMENTS, metricPropertyName); // the property is entirely the arguments } else { String[] argsRegex = metricPropertyName.substring(1).split("\\}\\|", 2); if (argsRegex.length != 2) { log.error("Invalid metric property [" + metricPropertyName + "] - metric will not be collected"); return null; } if (!isValidRegularExpression(argsRegex[1])) { log.error("Invalid regex [" + argsRegex[1] + "] for metric property [" + metricPropertyName + "] - metric will not be collected"); return null; } if (argsRegex[0].length() > 0) { map.put(METRIC_PROPERTY_ARGUMENTS, argsRegex[0]); } if (argsRegex[1].length() > 0) { map.put(METRIC_PROPERTY_REGEX, argsRegex[1]); } } } return map; } /** * Invokes the CLI executable. User can tell us what arguments to pass to the executable. * The result includes the output of the process as well as the exit code. * * @see OperationFacet#invokeOperation(String, Configuration) */ public OperationResult invokeOperation(String name, Configuration configuration) throws Exception { OperationResult result = new OperationResult(); String arguments = configuration.getSimpleValue(OPERATION_PARAM_ARGUMENTS, null); String waitTimeStr = configuration.getSimpleValue(OPERATION_PARAM_WAIT_TIME, null); String captureOutputStr = configuration.getSimpleValue(OPERATION_PARAM_CAPTURE_OUTPUT, null); String killOnTimeoutStr = configuration.getSimpleValue(OPERATION_PARAM_KILL_ON_TIMEOUT, null); long waitTime; boolean captureOutput; boolean killOnTimeout; if (waitTimeStr != null) { try { waitTime = Long.parseLong(waitTimeStr); waitTime *= 1000L; // the parameter is specified in seconds, but we need it in milliseconds } catch (NumberFormatException e) { throw new NumberFormatException("Wait time parameter value is invalid: " + waitTimeStr); } } else { waitTime = DEFAULT_MAX_WAIT_TIME; } if (captureOutputStr != null) { captureOutput = Boolean.parseBoolean(captureOutputStr); } else { captureOutput = true; } if (killOnTimeoutStr != null) { killOnTimeout = Boolean.parseBoolean(killOnTimeoutStr); } else { killOnTimeout = true; } ProcessExecutionResults exeResults = executeExecutable(arguments, waitTime, captureOutput, killOnTimeout); Integer exitcode = exeResults.getExitCode(); String output = exeResults.getCapturedOutput(); Throwable error = exeResults.getError(); if (error != null) { result.setErrorMessage(ThrowableUtil.getAllMessages(error)); } Configuration resultsConfig = result.getComplexResults(); if (exitcode != null) { resultsConfig.put(new PropertySimple(OPERATION_RESULT_EXITCODE, exitcode)); } if (output != null) { resultsConfig.put(new PropertySimple(OPERATION_RESULT_OUTPUT, output)); } return result; } protected ResourceContext getResourcContext() { return this.resourceContext; } /** * Executes the CLI executable with the given arguments. * * Same as {@link #executeExecutable(String, long, boolean, boolean)} with 'killOnTimeout' being true. * * @return the results of the execution * * @throws InvalidPluginConfigurationException */ protected ProcessExecutionResults executeExecutable(String args, long wait, boolean captureOutput) { return executeExecutable(args, wait, captureOutput, true); } /** * Executes the CLI executable with the given arguments. * * @param args the arguments to send to the executable (may be null) * @param wait the maximum time in milliseconds to wait for the process to execute; 0 means do not wait * @param captureOutput if true, the executables output will be captured and returned * @param killOnTimeout if true and if 'wait' is greater than 0, the process will be killed if it times out * * @return the results of the execution * * @throws InvalidPluginConfigurationException */ protected ProcessExecutionResults executeExecutable(String args, long wait, boolean captureOutput, boolean killOnTimeout) throws InvalidPluginConfigurationException { SystemInfo sysInfo = this.resourceContext.getSystemInformation(); Configuration pluginConfig = this.resourceContext.getPluginConfiguration(); ProcessExecutionResults results = executeExecutable(sysInfo, pluginConfig, args, wait, captureOutput, killOnTimeout); if (log.isDebugEnabled()) { logDebug("CLI results: exitcode=[" + results.getExitCode() + "]; error=[" + results.getError() + "]; output=" + truncateString(results.getCapturedOutput())); } return results; } // This is protected static so the discovery component can use it. protected static ProcessExecutionResults executeExecutable(SystemInfo sysInfo, Configuration pluginConfig, String args, long wait, boolean captureOutput) throws InvalidPluginConfigurationException { return executeExecutable(sysInfo, pluginConfig, args, wait, captureOutput, true); } private static ProcessExecutionResults executeExecutable(SystemInfo sysInfo, Configuration pluginConfig, String args, long wait, boolean captureOutput, boolean killOnTimeout) throws InvalidPluginConfigurationException { ProcessExecution processExecution = getProcessExecutionInfo(pluginConfig); if (args != null) { processExecution.setArguments(args.split(" ")); } processExecution.setCaptureOutput(captureOutput); processExecution.setWaitForCompletion(wait); processExecution.setKillOnTimeout(killOnTimeout); ProcessExecutionResults results = sysInfo.executeProcess(processExecution); return results; } private boolean checkAvailability() throws InvalidPluginConfigurationException { String executable; boolean availExecuteCheck = false; String availArgs = null; String availExitCodeRegex = null; String availOutputRegex = null; // determine how we are to consider the CLI available by looking at the plugin configuration try { Configuration pc = this.resourceContext.getPluginConfiguration(); executable = pc.getSimpleValue(PLUGINCONFIG_EXECUTABLE, null); if (executable == null) { throw new Exception("Missing executable in plugin configuraton"); } PropertySimple availExecuteCheckProp = pc.getSimple(PLUGINCONFIG_AVAIL_EXECUTE_CHECK); PropertySimple availArgsProp = pc.getSimple(PLUGINCONFIG_AVAIL_ARGS); PropertySimple availExitCodeRegexProp = pc.getSimple(PLUGINCONFIG_AVAIL_EXITCODE_REGEX); PropertySimple availOutputRegexProp = pc.getSimple(PLUGINCONFIG_AVAIL_OUTPUT_REGEX); if (availExecuteCheckProp != null && availExecuteCheckProp.getBooleanValue() != null) { availExecuteCheck = availExecuteCheckProp.getBooleanValue().booleanValue(); } if (availArgsProp != null && availArgsProp.getStringValue() != null) { availArgs = availArgsProp.getStringValue(); } if (availExitCodeRegexProp != null && availExitCodeRegexProp.getStringValue() != null) { availExitCodeRegex = availExitCodeRegexProp.getStringValue(); } if (availOutputRegexProp != null && availOutputRegexProp.getStringValue() != null) { availOutputRegex = availOutputRegexProp.getStringValue(); } } catch (Exception e) { throw new InvalidPluginConfigurationException("Cannot get avail plugin config. Cause: " + e); } // first, make sure the executable actually exists File executableFile = new File(executable); if (!executableFile.exists()) { if (log.isDebugEnabled()) { logDebug("The executable [" + executable + "] does not exist - resource is considered DOWN"); } return false; } // if we need to check the exit code or output, execute the CLI now if (availExecuteCheck || availExitCodeRegex != null || availOutputRegex != null) { ProcessExecutionResults results = executeExecutable(availArgs, 3000L, (availOutputRegex != null)); // if we get some error while trying to run the executable, immediately consider the resource down if (results.getError() != null) { if (log.isDebugEnabled()) { logDebug("CLI execution encountered an error, resource is considered DOWN: " + ThrowableUtil.getAllMessages(results.getError())); } return false; } // if the exit code is used to determine availability, check it now if (availExitCodeRegex != null) { if (results.getExitCode() == null) { if (log.isDebugEnabled()) { logDebug("Cannot get exit code, resource is considered DOWN"); } return false; } boolean exitcodeMatches = results.getExitCode().toString().matches(availExitCodeRegex); if (!exitcodeMatches) { if (log.isDebugEnabled()) { logDebug("CLI exit code=[" + results.getExitCode() + "] != [" + availExitCodeRegex + "]. DOWN"); } return false; } } // if the output is used to determine availability, check it now if (availOutputRegex != null) { String output = results.getCapturedOutput(); if (output == null) { output = ""; } boolean outputMatches = output.matches(availOutputRegex); if (!outputMatches) { if (log.isDebugEnabled()) { logDebug("CLI output [" + truncateString(output) + "] did not match regex [" + availOutputRegex + "], resource is considered DOWN"); } return false; } } } // everything passes, resource is UP return true; } /** * Truncate a string so it is short, usually for display or logging purposes. * * @param output the output to trim * @return the trimmed output */ private String truncateString(String output) { String outputToLog = output; if (outputToLog != null && outputToLog.length() > 100) { outputToLog = outputToLog.substring(0, 100) + "..."; } return outputToLog; } private static ProcessExecution getProcessExecutionInfo(Configuration pluginConfig) throws InvalidPluginConfigurationException { PropertySimple executableProp = pluginConfig.getSimple(PLUGINCONFIG_EXECUTABLE); PropertySimple workingDirProp = pluginConfig.getSimple(PLUGINCONFIG_WORKINGDIR); PropertyList envvarsProp = pluginConfig.getList(PLUGINCONFIG_ENVVARS); String executable = null; String workingDir = null; Map envvars = null; if (executableProp == null) { throw new InvalidPluginConfigurationException("Missing required plugin config: " + PLUGINCONFIG_EXECUTABLE); } else { executable = executableProp.getStringValue(); if (executable == null || executable.length() == 0) { throw new InvalidPluginConfigurationException("Bad plugin config: " + PLUGINCONFIG_EXECUTABLE); } } if (workingDirProp != null) { workingDir = workingDirProp.getStringValue(); if (workingDir != null && workingDir.length() == 0) { workingDir = null; // empty string is as good as unsetting it (i.e. making it null) } } if (envvarsProp != null) { try { // we want envvars to be null if there are no envvars defined, so the agent env is passed // but if there are 1 or more envvars in our config, then we define our envvars list List listOfMaps = envvarsProp.getList(); if (listOfMaps != null && listOfMaps.size() > 0) { envvars = new HashMap(); for (Property envvarMap : listOfMaps) { PropertySimple name = (PropertySimple) ((PropertyMap) envvarMap).get(PLUGINCONFIG_ENVVAR_NAME); PropertySimple value = (PropertySimple) ((PropertyMap) envvarMap) .get(PLUGINCONFIG_ENVVAR_VALUE); envvars.put(name.getStringValue(), value.getStringValue()); } } } catch (Exception e) { throw new InvalidPluginConfigurationException("Bad plugin config: " + PLUGINCONFIG_ENVVARS + ". Cause: " + e); } } ProcessExecution processExecution = new ProcessExecution(executable); processExecution.setEnvironmentVariables(envvars); processExecution.setWorkingDirectory(workingDir); return processExecution; } private boolean isValidRegularExpression(String regex) { try { Pattern.compile(regex); return true; } catch (Exception e) { return false; } } private void logDebug(String msg) { log.debug("[" + this.resourceContext.getResourceKey() + "]: " + msg); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy