Package org.rioproject.impl.exec

Source Code of org.rioproject.impl.exec.ServiceExecutor

/*
* Copyright 2008 the original author or authors.
*
* 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 org.rioproject.impl.exec;

import com.sun.jini.config.Config;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;
import org.rioproject.exec.ExecDescriptor;
import org.rioproject.servicebean.ServiceBean;
import org.rioproject.servicebean.ServiceBeanContext;
import org.rioproject.deploy.ServiceBeanInstantiationException;
import org.rioproject.impl.jmx.JMXConnectionUtil;
import org.rioproject.impl.jmx.JMXUtil;
import org.rioproject.impl.servicebean.ServiceElementUtil;
import org.rioproject.opstring.ServiceElement;
import org.rioproject.sla.SLA;
import org.rioproject.system.ComputeResourceUtilization;
import org.rioproject.system.MeasuredResource;
import org.rioproject.system.SystemWatchID;
import org.rioproject.system.capability.PlatformCapability;
import org.rioproject.impl.system.measurable.SigarHelper;
import org.rioproject.impl.system.measurable.cpu.CPU;
import org.rioproject.impl.system.measurable.cpu.ProcessCPUHandler;
import org.rioproject.impl.system.measurable.memory.Memory;
import org.rioproject.impl.system.measurable.memory.ProcessMemoryMonitor;
import org.rioproject.watch.WatchDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.MBeanServerConnection;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Provides support to execute an external service. If the external service is
* a Java Virtual Machine, this utility will try to attach to the Java Virtual
* Machine using the
* <a href="http://java.sun.com/javase/6/docs/technotes/guides/attach/index.html">
* JMX Attach API</a>. This is only possible using Java 6 or greater.
* If the runtime is Java 5, external service utilization monitoring is not
* provided.
*
* <p>In order to obtain the process identifier required to attach to
* the exec'd JVM, the external process may create a pid file, containing the
* process identifier of the process. If this is the case, then configuring the
* deployment to declare the path of the produced pid file can be done.
*
* <p>If the exec'd process does not create a pid file,
* <a href="http://www.hyperic.com/products/sigar.html">SIGAR</a>
* is used to traverse the process tree to identify the parent process that
* exec'd the child JVM. If the parent process can be identified
* (see {@link org.rioproject.impl.system.measurable.SigarHelper#matchChild(int, String[])} the
* <tt>ServiceExecutor</tt> will attach to the JVM, and monitor CPU and Memory
* utilization. <a href="http://www.hyperic.com/products/sigar.html">SIGAR</a>
* is also used to monitor the real memory used by the exec'd JVM.
*
* <p>This class also provides configuration support for the following entries:
*
* <ul>
* <li><span
* style="font-weight: bold; font-family: courier new,courier,monospace;">shellTemplate</span><br
*style="font-weight: bold; font-family: courier new,courier,monospace;">
* <table style="text-align: left; width: 100%;" border="0"
* cellpadding="2" cellspacing="2">
* <tbody>
* <tr>
* <td
* style="vertical-align: top; text-align: right; font-weight: bold;">
* Type:<br>
* </td>
* <td style="vertical-align: top;">String<br>
* </td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; text-align: right; font-weight: bold;">
* Default:<br>
* </td>
* <td style="vertical-align: top;"><span
* style="font-family: monospace;">exec-template.sh</span><br>
* </td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; text-align: right; font-weight: bold;">
* Description:<br>
* </td>
* <td style="vertical-align: top;">The template to use for
* generating a script to exec a command. The script template must be
* loadable as a resource, and must provide the following token that get
* replaced by runtime values:<br>
* <br>
* <span style="font-family: monospace; font-weight: bold;">${command}</span>
* The command to execute. This token eirs placed by the command to
* execute.<br>
* <span style="font-family: monospace;">$<span
* style="font-weight: bold;">{pidFile}</span></span> Rio creates a file
* that stores the pid of the executed command. This token is replaced by
* the name of the pid file to create<br>
* <span style="font-family: monospace; font-weight: bold;">${commandLine}</span>
* This token is replaced by the actual command line that is created to
* 'exec' the command above. Inoput arguments, standard error and output
* are also part of the created command line<br>
* </td>
* </tr>
* </tbody>
* </table>
* </li>
* </ul>
* <ul>
* <li>
* <span style="font-weight: bold; font-family: courier new,courier,monospace;">pidFileWaitTime</span>
* <br style="font-weight: bold; font-family: courier new,courier,monospace;">
* <table style="text-align: left; width: 100%;" border="0" cellpadding="2" cellspacing="2">
* <tbody>
* <tr>
* <td style="vertical-align: top; text-align: right; font-weight: bold;">
* Type:<br>
* </td>
* <td style="vertical-align: top;">int<br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top; text-align: right; font-weight: bold;">
* Default:<br>
* </td>
* <td style="vertical-align: top;"><span style="font-family: monospace;">60 </span><br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top; text-align: right; font-weight: bold;">
* Description:<br>
* </td>
* <td style="vertical-align: top;">
* The amount of time to wait for an exec'd service to create a pid file.
* The value is in seconds, where 60 seconds is the default. The minimum
* value for this property is 5 seconds, the maximum allowed 5 minutes.<br>
* <br>
* This value represents the maximum amount of time to wait. If the pid file is
* created prior to the timeout value, the utility proceeds immediately.<br>
* </td>
* </tr>
* </tbody>
* </table>
* </li>
* </ul>
*
* @author Dennis Reedy
*/
public class ServiceExecutor {
    private ServiceBean serviceBean;
    private ProcessManager processManager;
    private ExecDescriptor execDescriptor;
    private String shellTemplate;
    private SigarHelper sigar;
    private Memory memory;
    private CPU cpu;
    private long actualPID=-1;
    private MBeanServerConnection mbsc;
    private ServiceBeanContext context;
    private Configuration config;
    private static final String COMPONENT = ServiceExecutor.class.getPackage().getName();
    private int pidFileWaitTime = 60; // number of seconds
    static final Logger logger = LoggerFactory.getLogger(COMPONENT);

    public ServiceExecutor() {
        sigar = SigarHelper.getInstance();
    }

    public void setServiceBeanContext(final ServiceBeanContext context)
        throws ServiceBeanInstantiationException, IOException, ConfigurationException {
        this.context = context;
        this.config = context.getConfiguration();
        try {
            shellTemplate = (String)context.getConfiguration().getEntry(COMPONENT,
                                                                        "shellTemplate",
                                                                        String.class,
                                                                        null);
        } catch (ConfigurationException e) {
            logger.warn("Cannot get shell template from configuration, continue with default");
        }
        try {
            pidFileWaitTime = Config.getIntEntry(context.getConfiguration(),
                                                 COMPONENT,
                                                 "pidFileWaitTime",
                                                 60,    //default is 1 minute
                                                 5,     //minimum of 5 second wait
                                                 60 * 5); // max of 5 minute wait
        } catch(ConfigurationException e) {
            logger.warn("Getting pidFileWaitTime, using default", e);
        }
        execDescriptor = context.getServiceElement().getExecDescriptor();
        if(execDescriptor==null)
            throw new ServiceBeanInstantiationException("An ExecDescriptor is required " +
                                                "by the ServiceExecutor," +
                                                " unable to proceed.");

        String cmdLine = getCommandLine(execDescriptor);
        if(!cmdLine.startsWith(File.separator)) {
            PlatformCapability[] pCaps =
                context.getComputeResourceManager().getMatchedPlatformCapabilities();
            boolean matched = false;
            for(PlatformCapability pCap : pCaps) {
                if(pCap.getPath()!=null) {
                    File toExec = new File(pCap.getPath(), cmdLine);
                    if(toExec.exists() && toExec.canRead()) {
                        matched = true;
                        if(logger.isInfoEnabled()) {
                            logger.info("Adding PlatformCapability PATH [{}] to declared command line [{}]",
                                        pCap.getPath(), cmdLine);
                        }
                       
                        execDescriptor = Util.extendCommandLine(pCap.getPath(), execDescriptor);
                        break;
                    }
                }
            }
            if(!matched) {
                throw new ServiceBeanInstantiationException(
                    "ExecDescriptor with command line " +
                    "["+execDescriptor.getCommandLine()+"] " +
                    "cannot " +
                    "be executed, no associated PlatformCapability " +
                    "found");
            }
        } else {
            if(logger.isInfoEnabled()) {
                logger.info("Using command line [{}]",  execDescriptor.getCommandLine());
            }
        }

        File toExec = new File(execDescriptor.getCommandLine());
        if(!toExec.exists())
            throw new ServiceBeanInstantiationException("The command line ["+
                                                        execDescriptor.getCommandLine()+
                                                        "] can not be found, " +
                                                        "unable to continue. Check " +
                                                        "that the directory structure " +
                                                        "matches that as found on the " +
                                                        "executing platform. If the " +
                                                        "ServiceExec is a result of " +
                                                        "software downloading, make sure " +
                                                        "that the installation is " +
                                                        "correct and that downloaded " +
                                                        "software has been extracted");
        exec();
        processManager.manage();
        String pidFileName = execDescriptor.getPidFile();
        if(pidFileName!=null) {
            logger.info("Try to obtain actual pid of exec'd process using pid file: "+pidFileName);
            long waited = 0;
            while(waited < pidFileWaitTime) {
                File pidFile = new File(pidFileName);
                if(pidFile.exists()) {
                    actualPID = readPidFromFile(pidFile);
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    logger.warn("Waiting for pid file to appear, abort wait", e);
                    break;
                }
                waited++;
            }
        }
        if(actualPID==-1) {
            long managedPID = processManager.getPid();
            long waited = 0;
            long t0 = System.currentTimeMillis();
            while(waited < 5) {
                String[] ids;
                try {
                    ids = VirtualMachineHelper.listIDs();
                } catch (Exception e) {
                    logger.error("Cannot load the Attach API", e);
                    break;
                }
                StringBuilder s = new StringBuilder();
                for(int i=0; i<ids.length; i++) {
                    if(i>0)
                        s.append(", ");
                    s.append(ids[i]);
                }
                System.out.println("JMX pids: ["+s.toString()+"]");
                long[] pids = new long[ids.length];
                for(int i=0; i<ids.length; i++)
                    pids[i] = new Long(ids[i]);

                /* First check to see if the actualPID is in the list of JMX managed pids */
                for(long pid : pids) {
                    if(pid==managedPID) {
                        actualPID = managedPID;
                        long t1 = System.currentTimeMillis();
                        System.out.println("Time waiting for process to be under JMX management: "+(t1/t0)+" milliseconds");
                        break;
                    }
                }
                if(actualPID!=-1)
                    break;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    logger.warn("Waiting for pid to appear under JMX management, abort wait", e);
                    break;
                }
                waited++;
            }
        }
        if(sigar!=null) {
            if(actualPID==-1) {
                logger.info("Try to obtain actual pid of exec'd process using SIGAR");
                String[] ids = new String[0];
                try {
                    ids = VirtualMachineHelper.listIDs();
                } catch (Exception e) {
                    logger.error("Cannot load the Attach API", e);
                }
                actualPID = sigar.matchChild(processManager.getPid(), ids);
            }
        } else {
            if(actualPID==-1)
                logger.warn("No SIGAR support available, unable to obtain PID");
        }
        if(actualPID!=-1) {
            logger.info("PID of exec'd process obtained: "+actualPID);
            try {
                mbsc = JMXConnectionUtil.attach(Long.toString(actualPID));
                logger.info("JMX Attach succeeded to exec'd JVM with pid: "+actualPID);
                createSystemWatches();
                setThreadDeadlockDetector(context.getServiceElement());
                checkWatchDescriptors(context.getServiceElement());
            } catch(Exception e) {
                logger.warn("Could not attach to the exec'd JVM with PID: " +
                            actualPID +
                            ", continue service execution", e);
            }
        } else {
            logger.info("Could not obtain actual PID of exec'd process, " +
                        "process cpu and java memory utilization are not available");
        }

    }

    public ComputeResourceUtilization getComputeResourceUtilization() {
        List<MeasuredResource> mRes;
        if(memory!=null && cpu!=null) {
            mRes =  new ArrayList<MeasuredResource>();           
            mRes.add(memory.getMeasuredResource());
            mRes.add(cpu.getMeasuredResource());
        } else {
            mRes = Collections.unmodifiableList(new ArrayList<MeasuredResource>());
        }
        return new ComputeResourceUtilization("", "", "", mRes);
    }

    private void setThreadDeadlockDetector(final ServiceElement elem) {
        ServiceElementUtil.setThreadDeadlockDetector(elem, mbsc);
    }

    private void createSystemWatches() {
        if(sigar!=null) {
            int pidToUse = (int)(actualPID==-1?processManager.getPid():actualPID);
            memory = new Memory(config);
            ProcessMemoryMonitor memMonitor =
                (ProcessMemoryMonitor)memory.getMeasurableMonitor();
            memMonitor.setPID(pidToUse);
            if(mbsc!=null) {
                MemoryMXBean memBean =
                    getPlatformMXBeanProxy(mbsc,
                                           ManagementFactory.MEMORY_MXBEAN_NAME,
                                           MemoryMXBean.class);
                memMonitor.setMXBean(memBean);
            } else {
                memMonitor.setMXBean(null);
            }

            memory.start();

            cpu = new CPU(config, SystemWatchID.PROC_CPU, true);
            ProcessCPUHandler cpuMonitor = (ProcessCPUHandler)cpu.getMeasurableMonitor();
            cpuMonitor.setPID(pidToUse);
            OperatingSystemMXBean opSys =
                getPlatformMXBeanProxy(mbsc,
                                       ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME,
                                       OperatingSystemMXBean.class);
            cpuMonitor.setMXBean(opSys);
            RuntimeMXBean runtime =
                getPlatformMXBeanProxy(mbsc,
                                       ManagementFactory.RUNTIME_MXBEAN_NAME,
                                       RuntimeMXBean.class);
            cpuMonitor.setStartTime(runtime.getStartTime());
            cpu.start();

            context.getWatchRegistry().register(memory, cpu);
        }
    }

    private <T> T getPlatformMXBeanProxy(final MBeanServerConnection mbsc,
                                         final String name,
                                         final Class<T> mxBeanInterface) {
        return JMXUtil.getPlatformMXBeanProxy(mbsc, name, mxBeanInterface);
    }

    private void checkWatchDescriptors(ServiceElement elem) {
        SLA[] slas = elem.getServiceLevelAgreements().getServiceSLAs();
        for(SLA sla : slas) {
            WatchDescriptor[] wDescs = sla.getWatchDescriptors();
            for(WatchDescriptor wDesc : wDescs) {
                if(wDesc.getObjectName()!=null)
                    wDesc.setMBeanServerConnection(mbsc);
            }
        }
    }

    private String getCommandLine(final ExecDescriptor exec) {
        String cmd;
        if(exec.getWorkingDirectory()!=null) {
            String wd = exec.getWorkingDirectory();
            if(wd.endsWith(File.separator))
                cmd = wd + exec.getCommandLine();
            else
                cmd = wd + File.separator + exec.getCommandLine();
        } else {
            cmd = exec.getCommandLine();
        }

        return cmd;
    }

    @SuppressWarnings("unused")
    public void setExecDescriptor(final ExecDescriptor execDescriptor) {
        if (execDescriptor == null)
            throw new IllegalArgumentException("ExecDescriptor is null");
        this.execDescriptor = execDescriptor;
    }

    @SuppressWarnings("unused")
    public void setServiceBean(final ServiceBean serviceBean) {
        this.serviceBean = serviceBean;
    }

    @SuppressWarnings("unused")
    public void preDestroy() {
        terminate();
    }

    public ProcessManager exec() throws IOException {
        if(execDescriptor==null)
            throw new IllegalStateException("execDescriptor is not set");
        return exec(execDescriptor);
    }

    public ProcessManager exec(final ExecDescriptor exDesc) throws IOException {
        Shell shell = ShellFactory.createShell();
        if(shellTemplate!=null)
            shell.setShellTemplate(shellTemplate);

        processManager = shell.exec(exDesc);

        processManager.registerListener(new ProcessManager.Listener() {
            public void processTerminated(int pid) {
                if(logger.isDebugEnabled())
                    logger.debug("Process [{}] terminated", pid);
                if(serviceBean!=null)
                    serviceBean.destroy(true);
            }
        });
        return processManager;
    }

    /**
     * Close the shell and release all used resources
     */
    public synchronized void terminate() {
        if(memory!=null)
            memory.stop();
        if(cpu!=null)
            cpu.stop();
        if (processManager != null) {
            processManager.destroy(true);
        }
    }

    public long readPidFromFile(File f) throws IOException {
        long pid = -1;
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(f));
            String line = in.readLine().trim();
            pid = Long.valueOf(line);
        } finally {
            if(in!=null)
                in.close();
        }
        return pid;
    }

    public static void main(String[] args) {
        ExecDescriptor exDesc;
        try {
            if(args.length>0 && args[0].endsWith("config")) {
                Configuration config = ConfigurationProvider.getInstance(args);
                exDesc = (ExecDescriptor) config.getEntry(COMPONENT,
                                                          "descriptor",
                                                          ExecDescriptor.class,
                                                          null);
            } else {
                exDesc = new ExecDescriptor();
                exDesc.setCommandLine("${RIO_HOME}/bin/cybernode");
                exDesc.setWorkingDirectory("${RIO_HOME}/bin");
                exDesc.setStdErrFileName("${RIO_HOME}/logs/cybernode.log");
                exDesc.setStdOutFileName("${RIO_HOME}/logs/cybernode.log");
            }
            final ServiceExecutor svcExecutor = new ServiceExecutor();
            try {
                ProcessManager manager = svcExecutor.exec(exDesc);
                manager.manage();
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //svcExecutor.terminate();
                    }
                }).start();
                manager.getProcess().waitFor();
                System.out.println("Manager returned from waitFor()");
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (ConfigurationException e) {
            e.printStackTrace();
        }
    }
}
TOP

Related Classes of org.rioproject.impl.exec.ServiceExecutor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.