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

org.rhq.plugins.apache.ApacheVirtualHostServiceComponent Maven / Gradle / Ivy

There is a newer version: 4.13.0
Show newest version
/*
 * RHQ Management Platform
 * Copyright (C) 2005-2013 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package org.rhq.plugins.apache;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

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

import org.rhq.augeas.AugeasComponent;
import org.rhq.augeas.node.AugeasNode;
import org.rhq.augeas.tree.AugeasTree;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.ConfigurationUpdateStatus;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.domain.measurement.calltime.CallTimeData;
import org.rhq.core.domain.resource.CreateResourceStatus;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.pluginapi.configuration.ConfigurationFacet;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.inventory.CreateChildResourceFacet;
import org.rhq.core.pluginapi.inventory.CreateResourceReport;
import org.rhq.core.pluginapi.inventory.DeleteResourceFacet;
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.util.ResponseTimeConfiguration;
import org.rhq.core.pluginapi.util.ResponseTimeLogParser;
import org.rhq.plugins.apache.mapping.ApacheAugeasMapping;
import org.rhq.plugins.apache.parser.ApacheDirective;
import org.rhq.plugins.apache.parser.ApacheDirectiveTree;
import org.rhq.plugins.apache.util.AugeasNodeSearch;
import org.rhq.plugins.apache.util.AugeasNodeValueUtil;
import org.rhq.plugins.apache.util.ConfigurationTimestamp;
import org.rhq.plugins.apache.util.PluginUtility;
import org.rhq.plugins.apache.util.RuntimeApacheConfiguration;
import org.rhq.plugins.www.snmp.SNMPException;
import org.rhq.plugins.www.snmp.SNMPSession;
import org.rhq.plugins.www.snmp.SNMPValue;
import org.rhq.plugins.www.util.WWWUtils;

/**
 * @author Ian Springer
 * @author Lukas Krejci
 */
public class ApacheVirtualHostServiceComponent implements ResourceComponent, MeasurementFacet,
    ConfigurationFacet, DeleteResourceFacet, CreateChildResourceFacet {

    private static final Log LOG = LogFactory.getLog(ApacheVirtualHostServiceComponent.class);

    public static final String URL_CONFIG_PROP = "url";
    public static final String MAIN_SERVER_RESOURCE_KEY = "MainServer";

    public static final String RESPONSE_TIME_LOG_FILE_CONFIG_PROP =
        ResponseTimeConfiguration.RESPONSE_TIME_LOG_FILE_CONFIG_PROP;
    public static final String RESPONSE_TIME_URL_EXCLUDES_CONFIG_PROP =
        ResponseTimeConfiguration.RESPONSE_TIME_URL_EXCLUDES_CONFIG_PROP;
    public static final String RESPONSE_TIME_URL_TRANSFORMS_CONFIG_PROP =
        ResponseTimeConfiguration.RESPONSE_TIME_URL_TRANSFORMS_CONFIG_PROP;

    public static final String SERVER_NAME_CONFIG_PROP = "ServerName";

    private static final String RESPONSE_TIME_METRIC = "ResponseTime";
    /** Multiply by 1/1000 to convert logged response times, which are in microseconds, to milliseconds. */
    private static final double RESPONSE_TIME_LOG_TIME_MULTIPLIER = 0.001;

    private ResourceContext resourceContext;
    private URL url;
    private ResponseTimeLogParser logParser;

    private ConfigurationTimestamp lastConfigurationTimeStamp = new ConfigurationTimestamp();
    private int snmpWwwServiceIndex = -1;

    public static final String RESOURCE_TYPE_NAME = "Apache Virtual Host";

    public void start(ResourceContext resourceContext) throws Exception {
        this.resourceContext = resourceContext;
        Configuration pluginConfig = this.resourceContext.getPluginConfiguration();
        String url = pluginConfig.getSimple(URL_CONFIG_PROP).getStringValue();
        if (url != null) {
            try {
                this.url = new URL(url);
                if (this.url.getPort() == 0) {
                    throw new InvalidPluginConfigurationException(
                        "The 'url' connection property is invalid - 0 is not a valid port; please change the value to the "
                            + "port this virtual host is listening on. NOTE: If the 'url' property was set this way "
                            + "after autodiscovery, you most likely did not include the port in the ServerName directive for "
                            + "this virtual host in httpd.conf.");
                }
            } catch (MalformedURLException e) {
                throw new Exception("Value of '" + URL_CONFIG_PROP + "' connection property ('" + url
                    + "') is not a valid URL.");
            }
        }

        ResponseTimeConfiguration responseTimeConfig = new ResponseTimeConfiguration(pluginConfig);
        File logFile = responseTimeConfig.getLogFile();
        if (logFile != null) {
            this.logParser = new ResponseTimeLogParser(logFile, RESPONSE_TIME_LOG_TIME_MULTIPLIER);
            this.logParser.setExcludes(responseTimeConfig.getExcludes());
            this.logParser.setTransforms(responseTimeConfig.getTransforms());
        }
    }

    public void stop() {
        this.resourceContext = null;
        this.url = null;
    }

    public AvailabilityType getAvailability() {
        if (url != null) {
            int timeout = PluginUtility.getAvailabilityFacetTimeout();
            return WWWUtils.isAvailable(url, timeout) ? AvailabilityType.UP : AvailabilityType.DOWN;
        } else {
            try {
                //we don't need the SNMP connection to figure out the index on which the SNMP
                //module would report this vhost. So first, let's check if that index is valid
                //(i.e. check that the vhost is actually still present in the apache configuration)                                
                if (getWwwServiceIndex() < 1) {
                    return AvailabilityType.DOWN;
                }

                //ok, so the vhost is present. Now let's just ping the SNMP module to see
                //if it is reachable and base our availability on that...
                SNMPSession snmpSession = resourceContext.getParentResourceComponent().getSNMPSession();

                return snmpSession.ping() ? AvailabilityType.UP : AvailabilityType.DOWN;
            } catch (Exception e) {
                LOG.debug("Determining the availability of the vhost [" + resourceContext.getResourceKey()
                    + "] using SNMP failed.", e);
                return AvailabilityType.DOWN;
            }
        }
    }

    public Configuration loadResourceConfiguration() throws Exception {
        if (!isAugeasEnabled()) {
            throw new IllegalStateException(ApacheServerComponent.CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
        }

        ApacheServerComponent parent = resourceContext.getParentResourceComponent();

        AugeasComponent comp = getAugeas();
        try {
            AugeasTree tree = comp.getAugeasTree(ApacheServerComponent.AUGEAS_HTTP_MODULE_NAME);
            ConfigurationDefinition resourceConfigDef =
                resourceContext.getResourceType().getResourceConfigurationDefinition();

            ApacheAugeasMapping mapping = new ApacheAugeasMapping(tree);
            return mapping.updateConfiguration(getNode(tree), resourceConfigDef);
        } finally {
            comp.close();
        }
    }

    public void updateResourceConfiguration(ConfigurationUpdateReport report) {
        if (!isAugeasEnabled()) {
            report.setStatus(ConfigurationUpdateStatus.FAILURE);
            report.setErrorMessage(ApacheServerComponent.CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
            return;
        }

        AugeasComponent comp = getAugeas();
        AugeasTree tree = null;
        try {
            tree = comp.getAugeasTree(ApacheServerComponent.AUGEAS_HTTP_MODULE_NAME);
            ConfigurationDefinition resourceConfigDef =
                resourceContext.getResourceType().getResourceConfigurationDefinition();
            ApacheAugeasMapping mapping = new ApacheAugeasMapping(tree);
            AugeasNode virtHostNode = getNode(tree);
            mapping.updateAugeas(virtHostNode, report.getConfiguration(), resourceConfigDef);
            tree.save();

            report.setStatus(ConfigurationUpdateStatus.SUCCESS);
            LOG.info("Apache configuration was updated");

            finishConfigurationUpdate(report);
        } catch (Exception e) {
            if (tree != null) {
                String message = "Augeas failed to save configuration " + tree.summarizeAugeasError();
                report.setErrorMessage(message);
                LOG.error(message);
            } else {
                report.setErrorMessageFromThrowable(e);
                LOG.error("Augeas failed to save configuration", e);
            }
            report.setStatus(ConfigurationUpdateStatus.FAILURE);
        } finally {
            comp.close();
        }
    }

    public void deleteResource() throws Exception {
        if (!isAugeasEnabled()) {
            throw new IllegalStateException(ApacheServerComponent.CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
        }

        ApacheServerComponent parent = resourceContext.getParentResourceComponent();

        if (MAIN_SERVER_RESOURCE_KEY.equals(resourceContext.getResourceKey())) {
            throw new IllegalArgumentException(
                "Cannot delete the virtual host representing the main server configuration.");
        }

        AugeasComponent comp = getAugeas();

        try {
            AugeasTree tree = comp.getAugeasTree(ApacheServerComponent.AUGEAS_HTTP_MODULE_NAME);
            AugeasNode myNode = getNode(tree);

            tree.removeNode(myNode, true);
            tree.save();

            deleteEmptyFile(tree, myNode);
            conditionalRestart();
        } catch (IllegalStateException e) {
            //this means we couldn't find the augeas node for this vhost.
            //that error can be safely ignored in this situation.
        } finally {
            comp.close();
        }
    }

    public void getValues(MeasurementReport report, Set schedules) throws Exception {
        SNMPSession snmpSession = this.resourceContext.getParentResourceComponent().getSNMPSession();
        int primaryIndex = getWwwServiceIndex();
        boolean ping = snmpSession.ping();
        boolean snmpMetricsSupported = primaryIndex >= 0 && ping;

        if (LOG.isDebugEnabled()) {
            if (snmpMetricsSupported) {
                LOG.debug("SNMP metrics collection supported for VirtualHost service #" + primaryIndex);
            } else {
                LOG.debug("SNMP metrics collection unsupported for VirtualHost: primaryIndex[" + primaryIndex
                    + "], session[" + snmpSession + "], ping[" + ping + "]");
            }
        }

        for (MeasurementScheduleRequest schedule : schedules) {
            String metricName = schedule.getName();
            if (metricName.equals(RESPONSE_TIME_METRIC)) {
                if (this.logParser != null) {
                    try {
                        CallTimeData callTimeData = new CallTimeData(schedule);
                        this.logParser.parseLog(callTimeData);
                        report.addData(callTimeData);
                    } catch (Exception e) {
                        LOG.error("Failed to retrieve HTTP call-time data.", e);
                    }
                } else {
                    LOG.error("The '" + RESPONSE_TIME_METRIC + "' metric is enabled for resource '"
                        + this.resourceContext.getResourceKey() + "', but no value is defined for the '"
                        + RESPONSE_TIME_LOG_FILE_CONFIG_PROP + "' connection property.");
                    // TODO: Communicate this error back to the server for display in the GUI.
                }
            } else {
                if (snmpMetricsSupported) {
                    // Assume anything else is an SNMP metric.
                    try {
                        collectSnmpMetric(report, primaryIndex, snmpSession, schedule);
                    } catch (SNMPException e) {
                        LOG.error("An error occurred while attempting to collect an SNMP metric.", e);
                    }
                }
            }
        }

        LOG.info("Collected " + report.getDataCount() + " metrics for VirtualHost "
            + this.resourceContext.getResourceKey() + ".");
    }

    public CreateResourceReport createResource(CreateResourceReport report) {
        if (!isAugeasEnabled()) {
            report.setStatus(CreateResourceStatus.FAILURE);
            report.setErrorMessage(ApacheServerComponent.CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
            return report;
        }

        ResourceType resourceType = report.getResourceType();
        AugeasComponent comp = null;
        try {
            comp = getAugeas();
            if (resourceType.equals(getDirectoryResourceType())) {
                Configuration resourceConfiguration = report.getResourceConfiguration();
                Configuration pluginConfiguration = report.getPluginConfiguration();

                String directoryName = report.getUserSpecifiedResourceName();

                //fill in the plugin configuration

                //get the directive index
                AugeasTree tree = comp.getAugeasTree(ApacheServerComponent.AUGEAS_HTTP_MODULE_NAME);
                AugeasNode myNode = getNode(tree);
                List directories = myNode.getChildByLabel(" nodes = parentNode.getChildByLabel(" allVhosts = new ArrayList();

        RuntimeApacheConfiguration.walkRuntimeConfig(new RuntimeApacheConfiguration.NodeVisitor() {
            public void visitOrdinaryNode(AugeasNode node) {
                if (" snmpValues = snmpSession.getColumn(mibName);

            // NOTE: We assume SNMPValue's are returned in index-order.
            snmpValue = snmpValues.get(primaryIndex - 1);
        } else {
            // it's a request or response metric (e.g. "wwwRequestInRequests.GET" or "wwwResponseOutResponses.200")
            mibName = metricName.substring(0, dotIndex);
            String mibSecondaryIndex = metricName.substring(dotIndex + 1);
            String oid;
            try {
                Integer.parseInt(mibSecondaryIndex);
                oid = mibSecondaryIndex;
            } catch (NumberFormatException e) {
                // OID must be encoded as a string (e.g. 3.71.69.84 == "GET") - decode it
                oid = convertStringToOid(mibSecondaryIndex);
            }

            boolean found = false;
            Map table = snmpSession.getTable(mibName, primaryIndex);
            if (table != null) {
                snmpValue = table.get(oid);
                if (snmpValue != null) {
                    found = true;
                }
            }

            if (!found) {
                LOG.error("Entry '" + oid + "' not found for " + mibName + "[" + primaryIndex + "].");
                LOG.error("Table:\n" + table);
                return;
            }
        }

        LOG.debug("Collected SNMP metric [" + metricName + "], value = " + snmpValue);

        boolean valueIsTimestamp = false;
        ApacheServerComponent.addSnmpMetricValueToReport(report, schedule, snmpValue, valueIsTimestamp);
    }

    private String convertStringToOid(String string) {
        String oid;
        StringBuilder strBuf = new StringBuilder();
        strBuf.append(string.length()); // first digit in OID is the length of the string
        for (int i = 0; i < string.length(); i++) {
            // remaining digits are the integer values of each of the characters in the string
            strBuf.append('.').append((byte) string.charAt(i));
        }

        oid = strBuf.toString();
        return oid;
    }

    public static int getWwwServiceIndex(ApacheServerComponent parent, String resourceKey) {
        //figure out the servername and addresses of this virtual host
        //from the resource key.
        String vhostServerName = null;
        String[] vhostAddressStrings = null;
        int pipeIdx = resourceKey.indexOf('|');
        if (pipeIdx >= 0) {
            vhostServerName = resourceKey.substring(0, pipeIdx);
            if (vhostServerName.isEmpty()) {
                vhostServerName = null;
            }
        }
        vhostAddressStrings = resourceKey.substring(pipeIdx + 1).split(" ");

        int foundIdx = 0;

        //only look for the vhost entry if the vhost we're looking for isn't the main server
        if (!MAIN_SERVER_RESOURCE_KEY.equals(vhostAddressStrings[0])) {
            ApacheDirectiveTree tree = parent.parseRuntimeConfiguration(false);

            //find the vhost entry the resource key represents
            List vhosts = tree.search("/ serverNames = vhost.getChildByName("ServerName");
                String serverName = serverNames.size() > 0 ? serverNames.get(0).getValuesAsString() : null;

                List addrs = vhost.getValues();

                boolean serverNamesMatch =
                    (serverName == null && vhostServerName == null)
                        || (serverName != null && serverName.equals(vhostServerName));
                boolean addrsMatch = true;

                if (addrs.size() != vhostAddressStrings.length) {
                    addrsMatch = false;
                } else {
                    for (int i = 0; i < vhostAddressStrings.length; ++i) {
                        if (!addrs.contains(vhostAddressStrings[i])) {
                            addrsMatch = false;
                            break;
                        }
                    }
                }

                if (serverNamesMatch && addrsMatch) {
                    break;
                }

                ++foundIdx;
            }

            if (foundIdx == vhosts.size()) {
                LOG.debug("The virtual host with resource key [" + resourceKey
                    + "] doesn't seem to be present in the apache configuration anymore.");
                return -1;
            } else {
                //httpd vhosts are internally (in httpd internal data structures) ordered like this:
                //1) the main server entry is always first
                //2) all the vhosts are ordered from the last to appear in the joined config files to the first one

                //we now have an index to the list of the vhosts in the order they are defined.
                //so let's swap it over.
                //just subtracting from the size will give us the "room" for the first index
                //being the main host. In another words the below subtraction is correct even though
                //you might think there's a 1-off bug there.
                foundIdx = vhosts.size() - foundIdx;
            }
        }

        //the snmp indices are 1-based
        return foundIdx + 1;
    }

    /**
     * @return the index of the virtual host that identifies it in SNMP
     * @throws Exception on SNMP error
     */
    private int getWwwServiceIndex() {
        ConfigurationTimestamp currentTimestamp =
            resourceContext.getParentResourceComponent().getConfigurationTimestamp();
        if (!lastConfigurationTimeStamp.equals(currentTimestamp)) {
            snmpWwwServiceIndex = -1;
            //don't go through this configuration again even if we fail further below.. we'd fail again.
            lastConfigurationTimeStamp = currentTimestamp;

            //configuration has changed. re-read the service index of this virtual host            
            snmpWwwServiceIndex =
                getWwwServiceIndex(resourceContext.getParentResourceComponent(), resourceContext.getResourceKey());
        }
        return snmpWwwServiceIndex;
    }

    private ResourceType getDirectoryResourceType() {
        return resourceContext.getResourceType().getChildResourceTypes().iterator().next();
    }

    public boolean isAugeasEnabled() {
        ApacheServerComponent parent = resourceContext.getParentResourceComponent();
        return parent.isAugeasEnabled();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy