/*
* Copyright (C) 2008 Universidade Federal de Campina Grande
*
* This file is part of OurGrid.
*
* OurGrid is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.ourgrid.common.executor;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.ourgrid.common.util.CommonUtils;
import org.ourgrid.common.util.TempFileManager;
import org.ourgrid.reqtrace.Req;
import br.edu.ufcg.lsd.commune.container.logging.CommuneLogger;
/**
* This class is the concrete implementation of Executor interface that provides
* the platform dependent command execution. The reationale behind this
* implementation is to implement Linux native command invocation.
*/
public class LinuxExecutor extends VanillaExecutor {
private static final long serialVersionUID = 33L;
/**
* execute permission.
*/
static int XPerm = 1;
/**
* write permission.
*/
static int WPerm = 2;
/**
* read permission.
*/
static int RPerm = 4;
private static final String MYPIDS = ".mypids";
/**
* A protected constructor to be accessible only by the
* <code>ExecutorFactory</code>.
*/
@Req("REQ004")
protected LinuxExecutor( CommuneLogger logger ) {
// An Executor should only be constructed by ExecutorFactory;
super(logger);
}
/**
* @see Executor#chmod(File, String)
*/
public void chmod( File file, String modeStr ) throws ExecutorException {
/* The linux command for changing the permissions of files */
String cmd = "chmod";
/* Octal representation of native file permissions */
int mode;
try {
mode = Integer.parseInt( modeStr );
} catch ( NumberFormatException nfe ) {
throw new ExecutorException( "The permissions are invalid: " + modeStr, nfe );
}
if ( ( mode < 0 ) || ( mode > 7777 ) ) {
throw new ExecutorException( "The permissions are invalid: " + mode );
}
/* Verify if the the file object is null */
if ( file == null ) {
throw new ExecutorException( cmd, new FileNotFoundException( "'null'" ) );
}
/* Verify if the file exists */
if ( !( file.exists() ) ) {
throw new ExecutorException( cmd + " " + file.toString(),
new FileNotFoundException( file.getAbsolutePath() ) );
}
/*
* The command line that must be executed to change the permissions of
* the file
*/
String commandLine = "";
/* Compose the command line */
commandLine = cmd + " " + mode + " " + file.toString();
getLogger().debug( "Changing file permissions like " + commandLine);
/* execute the command */
ExecutorResult result = this.getResult( this.execute( file.getParent(), commandLine ) );
if ( result.getExitValue() != 0 ) {
throw new ExecutorException( "Could not change the file permissions. " + result.getStderr() );
}
}
/**
* This method implements the command execution feature for Linux
* environment.
*
* @param command The command must be executed
* @param dirName The execution root directory following the Linux name
* convention
* @return A handle that identifies this execution
* @throws ExecutorException If the command could not be executed.
*/
public ExecutorHandle execute( String dirName, String command ) throws ExecutorException {
return this.execute( dirName, command, new LinkedHashMap() );
}
/**
* This method implements the command execution feature for Linux
* environment.
*
* @param command The command must be executed
* @param dirName The execution root directory following the Linux name
* convention
* @param envVars A map (var name, value) with the environment variables
* used by the command
* @return A handle that identifies this execution
* @throws ExecutorException If the command could not be executed.
*/
public ExecutorHandle execute( String dirName, String command, Map envVars ) throws ExecutorException {
if ( dirName == null || command == null ) {
throw new ExecutorException( "Invalid parameters: " + dirName + ", " + command );
}
/*
* The native process abstract representation. For Linux
*/
Process process;
/* The handle for this execution. */
ExecutorHandle handle;
/* Get a new one */
handle = this.getNextHandle();
/* An script created to provide shell facilities for command execution */
File script = createScript( command, dirName, envVars, handle );
getLogger().debug( "About to invoke sh " + script.getPath() + " command: " + command );
File mypidFile = new File( getMypidFileName( dirName, handle ) );
if ( mypidFile.exists() ) {
mypidFile.delete();
}
try {
/* Invoke the native method of command executino */
process = Runtime.getRuntime().exec( "sh " + script.getPath() );
/** Register one more */
this.includeInProcesses( handle, process, dirName );
} catch ( IOException e ) {
throw new ExecutorException( command, e );
}
mypidFile.deleteOnExit();
// script.deleteOnExit();
return handle;
}
/**
* Gets the mypids file name
*/
private String getMypidFileName( String dirName, ExecutorHandle handle ) {
String fileName = dirName + File.separator + LinuxExecutor.MYPIDS + "." + handle.toString();
return fileName;
}
/**
* Adds a process into the set of the ones which results were not collected
* yet.
*
* @param handle The handle for the process
* @param process The process to be included at the group
* @param command
* @param dirName The directory where the process was started.
*/
protected synchronized void includeInProcesses( ExecutorHandle handle, Process process, String dirName ) {
HandleEntry hEntry = new HandleEntry( handle, process, dirName );
addHandleEntry(handle, hEntry);
}
/**
* Creates a script file to execute the command passed as paramether. This
* script will execute the command into the directory and will export the
* environment variables at the map passed as arguments.
*
* @param command The command to be executed.
* @param dirName Where the command has to be executed.
* @param envVars The environment variables to be exported and used by the
* command.
* @param handle The process handle.
* @return A script file that has the functionalities described here.
* @throws ExecutorException If the directory passed is not valid (does not
* exists) and if could not create the script file for any I/O
* problem..
*/
protected File createScript( String command, String dirName, Map envVars, ExecutorHandle handle )
throws ExecutorException {
/* Check if dir is not null. Convert to "." to void problems */
if ( dirName == null ) {
dirName = ".";
}
getLogger().debug( "Creating script on dir..." + dirName + " for command " + command );
/* The execution root directory */
File dir = new File( dirName );
getLogger().debug( "Will create file on dir " + dir + " is Directory: " + dir.isDirectory() );
/* The abstraction for the command */
File commandFile = null;
/* Indicate if the command is already an script */
boolean isScript = false;
if ( !dirName.equals( "." ) && !dir.isDirectory() ) {
throw new ExecutorException( command, new FileNotFoundException( dir.getAbsolutePath() ) );
}
/*
* try to figure out if command is already a script note that this is
* incomplete as it only works if the script is in dirName
*/
DataInputStream commandDIS = null;
try {
getLogger().debug( "Will create file on dir " + dir + " command: " + command );
commandFile = new File( dir, command );
if ( commandFile.exists() && commandFile.canRead() ) {
commandDIS = new DataInputStream( new FileInputStream( commandFile ) );
if ( commandDIS.readChar() == '#' && commandDIS.readChar() == '!' ) {
isScript = true;
}
}
} catch ( FileNotFoundException e ) {
throw new ExecutorException( command, new FileNotFoundException( commandFile.getAbsolutePath() ) );
} catch ( IOException e ) {
throw new ExecutorException( command, e );
} finally {
/**
* Close to avoid the "Too many open files" message and release
* resources
*/
if ( commandDIS != null ) {
try {
commandDIS.close();
} catch ( IOException e1 ) {
}
}
}
File temp;
BufferedWriter writerTemp = null;
Iterator keys;
String theKey;
String exportCommand = "export ";
/**
* Try create the temporary script in fact.
*/
try {
temp = TempFileManager.createTempFile( "ourgrid", ".tmp" );
/* A writer to produce the script commands */
writerTemp = new BufferedWriter( new FileWriter( temp ) );
/* Write the PID of process that represents the script */
writerTemp.write( "echo $$ >> " + getMypidFileName( dirName, handle ) );
writerTemp.newLine();
if ( envVars != null ) {
/* Gets an iterator to the keys in the Map */
keys = envVars.keySet().iterator();
while ( keys.hasNext() ) {
theKey = ( String ) keys.next();
writerTemp.write( theKey + "=\'" + envVars.get( theKey ) + "\'" );
writerTemp.newLine();
exportCommand = exportCommand + " " + theKey;
}
if ( envVars.get( "STORAGE" ) != null ) {
writerTemp.write( "PATH=$PATH:$STORAGE:$PLAYPEN:." );
writerTemp.newLine();
exportCommand = exportCommand + " PATH";
}
// this test is only for not writing blank lines in the script
if ( !envVars.isEmpty() ) {
writerTemp.write( exportCommand );
writerTemp.newLine();
}
}
writerTemp.write( "cd " + dirName );
writerTemp.newLine();
if ( isScript ) {
writerTemp.write( "sh " );
}
writerTemp.write( command );
writerTemp.newLine();
return temp;
} catch ( IOException ioe ) {
throw new ExecutorException( ioe );
} finally {
if ( writerTemp != null ) {
try {
writerTemp.close();
} catch ( IOException e1 ) {
}
}
}
}
/**
* @see Executor#kill(ExecutorHandle)
*/
public void kill( ExecutorHandle handle ) throws ExecutorException {
if ( handle != null ) {
if(getHandleEntries().containsKey( handle )){
String dirName = getDirName( handle );
// script that kills a process and all its children
String lsCommand = "ls -l " + this.getMypidFileName( dirName, handle );
Runtime runtime = Runtime.getRuntime();
Process process;
int MAX_RETRIES = 5;
try {
for ( int retries = 0; retries < MAX_RETRIES; retries++ ) {
process = runtime.exec( lsCommand );
if ( process.waitFor() != 0 ) {
Thread.sleep( 1000 );
}
}
} catch ( Exception e ) {
e.printStackTrace();
}
String killCommand = "reckill() { for pid in `pgrep -P $1`; do reckill $pid; done; kill -9 $1; }; for realPID in `cat "
+ this.getMypidFileName( dirName, handle )
+ "`; do for pid2 in `pgrep -P $realPID`; do reckill $pid2; done; done";
Map envVars = CommonUtils.createMap();
ExecutorHandle killHandle = this.execute( dirName, killCommand, envVars );
this.getResult( killHandle );
getLogger().debug( "Command " + killCommand + " has been executed." );
}else{
getLogger().debug( "Command kill for handle " + handle.toString()
+ " is not necessary because this process is already finished." );
}
}
}
/**
* @see Executor#getResult(ExecutorHandle)
*/
public ExecutorResult getResult( ExecutorHandle handle ) throws ExecutorException {
ExecutorResult result = null;
Process processToWait = null;
try {
/* Get the reference to the process will return ther result */
processToWait = this.getProcess( handle );
/* get the process output */
result = this.catchOutput( processToWait );
removeFromProcesses( handle );
} catch ( InterruptedException e ) {
throw new ExecutorException( "Cannot get the result of command execution.", e );
}
return result;
}
public void finishExecution() throws ExecutorException {
// TODO Auto-generated method stub
}
/**
* This method provides a synchronized access to the Map containning the
* Processes.
*
* @param handle An identificator for the Process in the Map.
* @return The dirName where the process was started.
*/
private synchronized String getDirName( ExecutorHandle handle ) {
HandleEntry handleEntry = getHandleEntries().get( handle );
if ( handleEntry == null )
return null;
return handleEntry.getDirName();
}
public void prepareAllocation() throws ExecutorException {
// TODO Auto-generated method stub
}
public void killCommand(ExecutorHandle handle) throws ExecutorException {
// TODO Auto-generated method stub
}
public void killPreparingAllocation() throws ExecutorException {
// TODO Auto-generated method stub
}
public void finishCommandExecution(ExecutorHandle handle) throws ExecutorException {
// TODO Auto-generated method stub
}
@Override
public void shutdown() throws ExecutorException {
// TODO Auto-generated method stub
}
}