/* * RHQ Management Platform * Copyright (C) 2005-2014 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.modules.plugins.jbossas7; import static org.rhq.core.pluginapi.bundle.BundleHandoverResponse.FailureType.EXECUTION; import static org.rhq.core.pluginapi.bundle.BundleHandoverResponse.FailureType.INVALID_ACTION; import static org.rhq.core.pluginapi.bundle.BundleHandoverResponse.FailureType.MISSING_PARAMETER; import static org.rhq.core.util.StringUtil.isBlank; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; 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.jetbrains.annotations.NotNull; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.ConfigurationUpdateStatus; import org.rhq.core.domain.configuration.PropertyList; 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.MeasurementDataTrait; import org.rhq.core.domain.measurement.MeasurementReport; import org.rhq.core.domain.measurement.MeasurementScheduleRequest; import org.rhq.core.domain.resource.CreateResourceStatus; import org.rhq.core.pluginapi.bundle.BundleHandoverRequest; import org.rhq.core.pluginapi.bundle.BundleHandoverResponse; import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport; import org.rhq.core.pluginapi.inventory.CreateResourceReport; 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.modules.plugins.jbossas7.json.Address; import org.rhq.modules.plugins.jbossas7.json.CompositeOperation; import org.rhq.modules.plugins.jbossas7.json.Operation; import org.rhq.modules.plugins.jbossas7.json.PROPERTY_VALUE; import org.rhq.modules.plugins.jbossas7.json.Result; /** * Component class for AS7 host and domain controllers. * * @author Heiko W. Rupp */ public class HostControllerComponent<T extends ResourceComponent<?>> extends BaseServerComponent<T> implements MeasurementFacet, OperationFacet { private static final Log LOG = LogFactory.getLog(HostControllerComponent.class); private static final String DOMAIN_CONFIG_TRAIT = "domain-config-file"; private static final String HOST_CONFIG_TRAIT = "host-config-file"; private static final String DOMAIN_HOST_TRAIT = "domain-host-name"; private static final String DOMAIN_NAME_TRAIT = "domain-name"; private static final String DOMAIN_TEMP_DIR_TRAIT = "domain-temp-dir"; private static final String PROCESS_TYPE_DC = "Domain Controller"; private boolean domainController; // determines whether this HC is also DC @Override protected AS7Mode getMode() { return AS7Mode.DOMAIN; } @Override public void start(ResourceContext<T> resourceContext) throws Exception { super.start(resourceContext); setDomainController(PROCESS_TYPE_DC.equals(getProcessTypeAttrValue())); } @Override protected void onAvailGoesUp() { super.onAvailGoesUp(); setDomainController(PROCESS_TYPE_DC.equals(getProcessTypeAttrValue())); } @Override public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> requests) throws Exception { Set<MeasurementScheduleRequest> leftovers = new HashSet<MeasurementScheduleRequest>(requests.size()); for (MeasurementScheduleRequest request : requests) { String requestName = request.getName(); if (requestName.equals(DOMAIN_CONFIG_TRAIT) || requestName.equals(HOST_CONFIG_TRAIT)) { collectConfigTrait(report, request); } else if (requestName.equals(DOMAIN_HOST_TRAIT)) { MeasurementDataTrait data = new MeasurementDataTrait(request, findASDomainHostName()); report.addData(data); } else if (requestName.equals(DOMAIN_NAME_TRAIT)) { MeasurementDataTrait data = new MeasurementDataTrait(request, readAttribute("name")); report.addData(data); } else { leftovers.add(request); // handled below } } super.getValues(report, leftovers); } @Override public OperationResult invokeOperation(String name, Configuration parameters) throws Exception { OperationResult operationResult; if (name.equals("start")) { operationResult = startServer(); } else if (name.equals("restart")) { operationResult = restartServer(parameters); } else if (name.equals("executeCommands") || name.equals("executeScript")) { return runCliCommand(parameters); } else if (name.equals("setupCli")) { return setupCli(parameters); } else if (name.equals("shutdown")) { // This is a bit trickier, as it needs to be executed on the level on /host=xx String domainHost = getASHostName(); if (domainHost.isEmpty()) { OperationResult result = new OperationResult(); result.setErrorMessage("No domain host found - can not continue"); operationResult = result; } Operation op = new Operation("shutdown", "host", domainHost); Result res = getASConnection().execute(op); operationResult = postProcessResult(name, res); if (waitUntilDown()) { operationResult.setSimpleResult("Success"); } else { operationResult.setErrorMessage("Was not able to shut down the server."); } } else if (name.equals("installRhqUser")) { operationResult = installManagementUser(parameters, pluginConfiguration); } else { // Defer other stuff to the base component for now operationResult = super.invokeOperation(name, parameters); } context.getAvailabilityContext().requestAvailabilityCheck(); return operationResult; } @Override public CreateResourceReport createResource(CreateResourceReport report) { // If Content is to be deployed, call the deployContent method if (report.getPackageDetails() != null) return super.deployContent(report); String targetTypeName = report.getResourceType().getName(); Operation op; String resourceName = report.getUserSpecifiedResourceName(); Configuration rc = report.getResourceConfiguration(); Address targetAddress; // Dispatch according to child type if (targetTypeName.equals("ServerGroup")) { targetAddress = new Address(); // Server groups are at / level targetAddress.add("server-group", resourceName); op = new Operation("add", targetAddress); String profile = rc.getSimpleValue("profile", ""); if (profile.isEmpty()) { report.setErrorMessage("No profile given"); report.setStatus(CreateResourceStatus.FAILURE); return report; } op.addAdditionalProperty("profile", profile); String socketBindingGroup = rc.getSimpleValue("socket-binding-group", ""); if (socketBindingGroup.isEmpty()) { report.setErrorMessage("No socket-binding-group given"); report.setStatus(CreateResourceStatus.FAILURE); return report; } op.addAdditionalProperty("socket-binding-group", socketBindingGroup); PropertySimple offset = rc.getSimple("socket-binding-port-offset"); if (offset != null && offset.getStringValue() != null) op.addAdditionalProperty("socket-binding-port-offset", offset.getIntegerValue()); PropertySimple jvm = rc.getSimple("jvm"); if (jvm != null) { op.addAdditionalProperty("jvm", jvm.getStringValue()); } } else if (targetTypeName.equals(BaseComponent.MANAGED_SERVER)) { String targetHost = rc.getSimpleValue("hostname", null); if (targetHost == null) { report.setErrorMessage("No domain host given"); report.setStatus(CreateResourceStatus.FAILURE); return report; } targetAddress = new Address("host", targetHost); targetAddress.add("server-config", resourceName); op = new Operation("add", targetAddress); String socketBindingGroup = rc.getSimpleValue("socket-binding-group", ""); if (socketBindingGroup.isEmpty()) { report.setErrorMessage("No socket-binding-group given"); report.setStatus(CreateResourceStatus.FAILURE); return report; } op.addAdditionalProperty("socket-binding-group", socketBindingGroup); String autostartS = rc.getSimpleValue("auto-start", "false"); boolean autoStart = Boolean.valueOf(autostartS); op.addAdditionalProperty("auto-start", autoStart); String portS = rc.getSimpleValue("socket-binding-port-offset", "0"); int portOffset = Integer.parseInt(portS); op.addAdditionalProperty("socket-binding-port-offset", portOffset); String serverGroup = rc.getSimpleValue("group", null); if (serverGroup == null) { report.setErrorMessage("No server group given"); report.setStatus(CreateResourceStatus.FAILURE); return report; } op.addAdditionalProperty("group", serverGroup); } else if (targetTypeName.equals("JVM-Definition")) { return super.createResource(report); } else { throw new IllegalArgumentException("Don't know yet how to create instances of " + targetTypeName); } Result res = getASConnection().execute(op); if (res.isSuccess()) { if (targetTypeName.equals(BaseComponent.MANAGED_SERVER)) { report.setResourceKey(ManagedASDiscovery.createResourceKey(rc.getSimpleValue("hostname"), report.getUserSpecifiedResourceName())); } else { report.setResourceKey(targetAddress.getPath()); } report.setResourceName(resourceName); report.setStatus(CreateResourceStatus.SUCCESS); if (targetTypeName.equals("ServerGroup")) { PropertyList sysProperties = rc.getList("*2"); if (sysProperties != null && !sysProperties.getList().isEmpty()) { // because AS7 does not allow us to pass system properties while creating server-group we must do it now ConfigurationUpdateReport rep = new ConfigurationUpdateReport(rc); ConfigurationDefinition configDef = report.getResourceType().getResourceConfigurationDefinition(); ConfigurationWriteDelegate delegate = new ConfigurationWriteDelegate(configDef, getASConnection(), targetAddress); delegate.updateResourceConfiguration(rep); if (ConfigurationUpdateStatus.FAILURE.equals(rep.getStatus())) { report.setStatus(CreateResourceStatus.FAILURE); report.setErrorMessage("Failed to additionally configure server group: " + rep.getErrorMessage()); } } } } else { report.setErrorMessage(res.getFailureDescription()); report.setStatus(CreateResourceStatus.FAILURE); report.setException(res.getRhqThrowable()); } return report; } /** * Handles content handed over during a bundle deployment.<br> * <br> * This component supports the following actions:<br> * <br> * <strong>action = deployment: deploys the content to a server group</strong><br> * <br> * Required parameters:<br> * <ul> * <li>serverGroup: The name of the server group this deployment should be deployed to</li> * </ul> * <br> * Optional parameters:<br> * <ul> * <li>runtimeName: Runtime name of the uploaded file (e.g. 'my.war'); if not present, the file name is used</li> * </ul> * <br> * <strong>action = execute-script: executes a server CLI script</strong><br> * <br> * Optional parameters:<br> * <ul> * <li>waitTime (in seconds): how long to wait for completion; defaults to an hour</li> * <li>killOnTimeout (true/false): should the CLI process be killed if timeout is reached; defaults to false</li> * </ul> * * @param handoverRequest handover parameters and context * @return a report object indicating success or failure */ @Override public BundleHandoverResponse handleContent(BundleHandoverRequest handoverRequest) { try { if (handoverRequest.getAction().equals("deployment")) { return handleDeployment(handoverRequest); } if (handoverRequest.getAction().equals("execute-script")) { return handleExecuteScript(handoverRequest); } return BundleHandoverResponse.failure(INVALID_ACTION); } catch (Exception e) { return BundleHandoverResponse.failure(EXECUTION, "Unexpected handover failure", e); } } private BundleHandoverResponse handleDeployment(BundleHandoverRequest handoverRequest) { String serverGroup = handoverRequest.getParams().get("serverGroup"); if (isBlank(serverGroup)) { return BundleHandoverResponse.failure(MISSING_PARAMETER, "serverGroup parameter is missing"); } // make sure our server is UP. We need to check it, because this handover // could happen right after "execute-script" handover, which could have reloaded the server // @see https://bugzilla.redhat.com/show_bug.cgi?id=1252930 Integer timeout = BUNDLE_HANDOVER_SERVER_CHECK_TIMEOUT; String waitForServer = handoverRequest.getParams().get(BUNDLE_HANDOVER_SERVER_CHECK_TIMEOUT_PARAM); if(waitForServer != null && waitForServer.length() > 0) { try { timeout = Integer.valueOf(waitForServer); } catch(NumberFormatException e) { return BundleHandoverResponse.failure(EXECUTION, "Given server timeout parameter is not a valid number: " + waitForServer); } } if (!ensureServerUp(timeout)) { // Value 0 disables the check return BundleHandoverResponse.failure(EXECUTION, "Failed to upload deployment content, " + this.context.getResourceDetails() + " is currently not responding or " + AvailabilityType.DOWN); } HandoverContentUploader contentUploader = new HandoverContentUploader(handoverRequest, getASConnection()); boolean uploaded = contentUploader.upload(); if (!uploaded) { return contentUploader.getFailureResponse(); } String filename = contentUploader.getFilename(); String runtimeName = contentUploader.getRuntimeName(); String hash = contentUploader.getHash(); Redeployer redeployer = new Redeployer(runtimeName, hash, getASConnection()); if (redeployer.deploymentExists()) { Result result = redeployer.redeployOnServer(); if (result.isRolledBack()) { return BundleHandoverResponse.failure(EXECUTION, result.getFailureDescription()); } return BundleHandoverResponse.success(); } // TODO use Deployer Operation addDeploymentStep = new Operation("add", "deployment", filename); List<Object> addDeploymentContentProperty = new ArrayList<Object>(1); Map<String, Object> contentValues = new HashMap<String, Object>(); contentValues.put("hash", new PROPERTY_VALUE("BYTES_VALUE", hash)); addDeploymentContentProperty.add(contentValues); addDeploymentStep.addAdditionalProperty("content", addDeploymentContentProperty); addDeploymentStep.addAdditionalProperty("name", filename); addDeploymentStep.addAdditionalProperty("runtime-name", runtimeName); Address serverGroupDeploymentAddress = new Address(); serverGroupDeploymentAddress.add("server-group", serverGroup); serverGroupDeploymentAddress.add("deployment", filename); Operation addToServerGroupStep = new Operation("add", serverGroupDeploymentAddress); addToServerGroupStep.addAdditionalProperty("runtime-name", runtimeName); addToServerGroupStep.addAdditionalProperty("enabled", true); CompositeOperation compositeOperation = new CompositeOperation(); compositeOperation.addStep(addDeploymentStep); compositeOperation.addStep(addToServerGroupStep); Result result = getASConnection().execute(compositeOperation, 300); if (!result.isSuccess()) { return BundleHandoverResponse.failure(EXECUTION, result.getFailureDescription()); } else { return BundleHandoverResponse.success(); } } private String getProcessTypeAttrValue() { try { return readAttribute(new Address("/"), "process-type"); } catch (Exception e) { LOG.warn("Unable to detect HostController's process-type", e); return null; } } public synchronized boolean isDomainController() { return domainController; } public synchronized void setDomainController(boolean domainController) { this.domainController = domainController; } @NotNull @Override protected Address getEnvironmentAddress() { return new Address("host=" + getASHostName() + ",core-service=host-environment"); } @NotNull @Override protected Address getHostAddress() { return new Address("host=" + getASHostName()); } @NotNull @Override protected String getBaseDirAttributeName() { return "domain-base-dir"; } @NotNull @Override protected String getConfigDirAttributeName() { return "domain-config-dir"; } @Override protected String getTempDirAttributeName() { return DOMAIN_TEMP_DIR_TRAIT; } }