/******************************************************************************* * Copyright (c) 2011 GigaSpaces Technologies Ltd. All rights reserved * * 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.cloudifysource.usm; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.lang.math.NumberUtils; import org.cloudifysource.dsl.utils.ServiceUtils; import org.cloudifysource.utilitydomain.admin.TimedAdmin; import org.hyperic.sigar.FileInfo; import org.hyperic.sigar.ProcState; import org.hyperic.sigar.Sigar; import org.hyperic.sigar.SigarException; import org.jini.rio.boot.ServiceClassLoader; import org.openspaces.admin.esm.ElasticServiceManager; import org.openspaces.admin.gsc.GridServiceContainer; import org.openspaces.admin.gsm.GridServiceManager; import org.openspaces.admin.pu.ProcessingUnit; import org.openspaces.admin.space.Space; import org.openspaces.pu.container.support.ResourceApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; import com.gigaspaces.internal.sigar.SigarHolder; /********* * Utility class for the USM package. * * @author barakme * */ public final class USMUtils { // private static final int POST_SYNC_PROCESS_SLEEP_INTERVAL = 500; private USMUtils() { // private constructor to prevent instantiation. } private static java.util.logging.Logger logger = java.util.logging.Logger.getLogger(USMUtils.class.getName()); private static TimedAdmin timedAdmin; /******** * Marks a file as executable by the operating system. On windows, always returns true. Otherwise, attempts will be * made to mark the file as executable using Java 6 API, if possible. If not, will attempt to use sigar. If this * fails, a chmod command will be executed. * * @param sigar * an instance of sigar. * @param file * the file to make as executable. * @return true if the file was successfully marked as executable, false otherwise. * @throws USMException * in case of an error. */ public static boolean markFileAsExecutable(final Sigar sigar, final File file) throws USMException { if (!file.exists()) { return false; } if (ServiceUtils.isWindows()) { return true; // not used in windows } if (USMUtils.markFileAsExecutableUsingJdk(file)) { return true; } if (USMUtils.isFileExecutableSigar(file)) { return true; } logger.info("File " + file + " is not executable according to sigar"); return USMUtils.markFileAsExecutableUsingChmod(file); } private static boolean markFileAsExecutableUsingJdk(final File file) throws USMException { // TODO - refactor this - just catch exception on the whole block logger.fine("Attempting to set file as executable using REFLECTION"); Method method = null; try { method = File.class.getMethod("setExecutable", new Class[] { Boolean.TYPE }); } catch (final SecurityException e) { throw new USMException("Failed to check if file " + file.getAbsolutePath() + " is executable", e); } catch (final NoSuchMethodException e) { // ignore - this can happen } if (method == null) { logger.warning("setExecutable not supported by JDK. " + "GigaSpaces Cloudify should be executed on Java Version >= 1.6"); return false; } try { final boolean retval = (Boolean) method.invoke(file, true); logger.fine("File " + file + " was marked as executable"); if (!retval) { logger.warning("File: " + file + " is not executable. " + "Attempt to mark it as executable has failed. " + "Execution of this command may not be possible"); } return retval; } catch (final IllegalArgumentException e) { throw new USMException("Failed to make file " + file.getAbsolutePath() + " as executable", e); } catch (final IllegalAccessException e) { throw new USMException("Failed to make file " + file.getAbsolutePath() + " as executable", e); } catch (final InvocationTargetException e) { throw new USMException("Failed to make file " + file.getAbsolutePath() + " as executable", e); } } private static boolean isFileExecutableSigar(final File file) throws USMException { long mode; try { logger.info("Checking if file " + file + " is executable using sigar"); mode = SigarHolder.getSigar().getFileInfo(file.getAbsolutePath()).getMode(); } catch (final SigarException e) { throw new USMException("Failed to check if file " + file.getAbsolutePath() + " is executable", e); } final long executable = mode & FileInfo.MODE_UEXECUTE; return executable > 0; } private static boolean markFileAsExecutableUsingChmod(final File file) throws USMException { try { logger.info("File " + file + " will be marked as executable using chmod command"); final Process p = Runtime.getRuntime().exec(new String[] { "chmod", "+x", file.getAbsolutePath() }); final int result = p.waitFor(); logger.info("chmod command finished with result = " + result); if (result != 0) { final String msg = "chmod command did not terminate successfully. File " + file + " may not be executable!"; logger.warning(msg); } return result == 0; } catch (final IOException e) { throw new USMException("Failed to execute chmod command: chmod +x " + file.getAbsolutePath(), e); } catch (final InterruptedException e) { throw new USMException("Failed to execute chmod command on file " + file, e); } } /******* * Returns true if current process is a GSC. * * @param ctx * the spring application context. * @return . */ public static boolean isRunningInGSC(final ApplicationContext ctx) { return ctx.getClassLoader() instanceof ServiceClassLoader; } /************* * Return's the working directory of a PU. * * @param ctx * the spring application context. * @return the working directory. */ public static File getPUWorkDir(final ApplicationContext ctx) { File puWorkDir = null; if (isRunningInGSC(ctx)) { // running in GSC final ServiceClassLoader scl = (ServiceClassLoader) ctx.getClassLoader(); final URL url = scl.getSlashPath(); URI uri; try { uri = url.toURI(); } catch (final URISyntaxException e) { throw new IllegalArgumentException("Failed to create URI from URL: " + url, e); } puWorkDir = new File(uri); } else { final ResourceApplicationContext rac = (ResourceApplicationContext) ctx; try { final Field resourcesField = rac.getClass().getDeclaredField("resources"); final boolean accessibleBefore = resourcesField.isAccessible(); resourcesField.setAccessible(true); final Resource[] resources = (Resource[]) resourcesField.get(rac); for (final Resource resource : resources) { // find META-INF/spring/pu.xml final File file = resource.getFile(); if (file.getName().equals("pu.xml") && file.getParentFile().getName().equals("spring") && file.getParentFile().getParentFile().getName().equals("META-INF")) { puWorkDir = resource.getFile().getParentFile().getParentFile().getParentFile(); break; } } resourcesField.setAccessible(accessibleBefore); } catch (final Exception e) { throw new IllegalArgumentException("Could not find pu.xml in the ResourceApplicationContext", e); } if (puWorkDir == null) { throw new IllegalArgumentException("Could not find pu.xml in the ResourceApplicationContext"); } } if (!puWorkDir.exists()) { throw new IllegalStateException("Could not find PU work dir at: " + puWorkDir); } final File puExtDir = new File(puWorkDir, "ext"); if (!puExtDir.exists()) { throw new IllegalStateException("Could not find PU ext dir at: " + puExtDir); } return puWorkDir; } /** * Returns a cached instance of TimedAdmin, creates it if necessary. * @return a cached instance of TimedAdmin */ public static synchronized TimedAdmin getTimedAdmin() { if (timedAdmin != null) { logger.fine("using a cached instance of TimedAdmin"); return timedAdmin; } logger.fine("creating a new instance of TimedAdmin"); timedAdmin = new TimedAdmin(); // useful for unit tests timedAdmin.discoverUnmanagedSpaces(); // Don't discover GridServiceAgent objects, not required for the USM and could potentially generate a lot // of objects in memory and multiple network connections final Class[] discoveryServices = new Class[] { GridServiceManager.class, GridServiceContainer.class, ElasticServiceManager.class, ProcessingUnit.class, Space.class }; timedAdmin.setDiscoveryServices(discoveryServices); timedAdmin.setStatisticsHistorySize(0); return timedAdmin; } /********* * Returns the list of parent processes, starting by the child pid and ending with the current pid. * * @param childPid * the list starting point. * @param myPid * the curennt process pid. * @return the pid list. * @throws USMException * in case of an error. */ public static List<Long> getProcessParentChain(final long childPid, final long myPid) throws USMException { final LinkedList<Long> list = new LinkedList<Long>(); if (childPid == 0) { logger.warning("The child PID is 0 - this usually means that this USM instance had a problem " + "during startup. Please check the logs for more details."); return list; } long currentPid = childPid; while (currentPid != myPid) { list.add(currentPid); final long ppid = USMUtils.getParentPid(currentPid); if (ppid == 0) { logger.severe("Attempt to create a process chain from child process " + childPid + " this process(" + myPid + "). Process " + childPid + " is not a descendant of this process. Only process " + childPid + " will be included in the chain"); list.clear(); list.add(childPid); return list; } currentPid = ppid; } return list; } /****** * Returns the parent pid for the given pid. * * @param pid * the process pid. * @return the parent pid for the given pid. * @throws USMException * in case of an error. */ public static long getParentPid(final long pid) throws USMException { ProcState procState = null; procState = USMUtils.getProcState(pid); if (procState == null) { return 0; } return procState.getPpid(); } /********* * Checks, using Sigar, is a given process is alive. * * @param pid * the process pid. * @return true if the process is alive (i.e. not stopped or zombie). * @throws USMException * in case of an error. */ public static boolean isProcessAlive(final long pid) throws USMException { final ProcState procState = USMUtils.getProcState(pid); return (procState != null && procState.getState() != ProcState.STOP && procState.getState() != ProcState.ZOMBIE); } private static ProcState getProcState(final long pid) throws USMException { final Sigar sigar = SigarHolder.getSigar(); ProcState procState = null; try { procState = sigar.getProcState(pid); } catch (final SigarException e) { if ("No such process".equals(e.getMessage())) { return null; } throw new USMException("Failed to check if process with PID: " + pid + " is alive. Error was: " + e.getMessage(), e); } return procState; } /*********** * Given a map, returns a new map where the values are numbers, matching entries from the original list which were * numbers or strings that can be parsed as numbers. Other entries are discarded. * * @param map * the original map. * @return the numbers map. */ // converts a map of type <String, Object> to a Map of <String, Number> public static Map<String, Number> convertMapToNumericValues(final Map<String, Object> map) { final Map<String, Number> returnMap = new HashMap<String, Number>(); for (final Map.Entry<String, Object> entryObject : map.entrySet()) { if (entryObject.getValue() instanceof Number) { returnMap.put(entryObject.getKey(), (Number) entryObject.getValue()); } else if (entryObject.getValue() instanceof String) { if (NumberUtils.isNumber((String) entryObject.getValue())) { final Number number = NumberUtils.createNumber((String) entryObject.getValue()); returnMap.put(entryObject.getKey(), number); } } else { logger.info("monitor value is expected to be numeric: " + entryObject.getValue().toString()); } } return returnMap; } /********** * Closes the cached admin instance. * */ public static synchronized void shutdownAdmin() { timedAdmin.close(); logger.info("USM timed Admin instance was shut down"); } /********* * Returns the exit code of the given process handle, or null if the process has not terminated. * * @param processToCheck * the process. * @return the exit code, or null. */ public static Integer getProcessExitCode(final Process processToCheck) { if (processToCheck == null) { return null; } try { int val = processToCheck.exitValue(); return val; } catch (final Exception e) { return null; } } }