/* * 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.BufferedReader; import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import org.ourgrid.common.executor.config.ExecutorConfiguration; import org.ourgrid.common.util.TempFileManager; 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 use SWAN / XEN 's mechanisms to involke Linux native * commands. Unlike Regular Linux implementation of executor, SWANExecutor has a * couple of limitations: i) dirName on execute() must be the playpen. ii) only * one command can be executed per time. SWANExecutor may run a little slow. * It's the trade-off for security. */ public class SWANExecutor implements Executor { private static final long serialVersionUID = 33L; /** The working directory. For SWAN it must me the playpen. */ private File dir; /** The file that will contain the standard output of the execution. */ private File stdOutput; /** The file that will contain the error output of the execution. */ private File errorOutput; /** The file that will contain the exit value of the execution. */ private File exitValue; /* * This is where the handle will be stored. Since SWANExecutor suports only * one instance of execution per time, It does NOT use the same * implementation as LinuxExecutor, where multiple handles can be issued. */ /** Used to limit to a single execution. The handle is unique. */ private ExecutorHandle theOnlyHandle = new IntegerExecutorHandle( 1 ); /** * The handle (also unique) for the execution of the script that involkes * the three stages os SWAN. */ private ExecutorHandle stagesHandle; /** * An instance of LinuxExecutor, used to assist the native linux command * invokation. It's needed to run the scripts used by SWAN. */ private LinuxExecutor linExecutor; /** Sets if the execution (limited to one in SWAN) is being used or not */ private boolean singleExecutionInUse = false; /** Sets if the process has been killed or not */ private boolean processKilled = false; /** * Logger to store log messages */ private transient final CommuneLogger logger; /** * A protected constructor to be accessible only by the * <code>ExecutorFactory</code>. * @param logger */ protected SWANExecutor( CommuneLogger logger ) { // An Executor should only be constructed by ExecutorFactory; this.logger = logger; this.linExecutor = new LinuxExecutor(logger); } /** * A protected constructor to be used only by tests. */ protected SWANExecutor( LinuxExecutor testLinuxExecutor, File stdOutput, File errorOutput, File exitValue, CommuneLogger logger ) { this.linExecutor = testLinuxExecutor; this.stdOutput = stdOutput; this.errorOutput = errorOutput; this.exitValue = exitValue; this.logger = logger; } public void prepareAllocation() throws ExecutorException { // TODO Auto-generated method stub } /** * This method implements the command execution feature for SWAN * environment. <br> * One of SWAN's limitation is that it can run only one execution per time. * Multiple executions are not allowed. A getResult() or a kill() must be * done before starting a new execution. * * @param command The command must be executed * @param dirName The execution root directory following the Linux name * convention. It must be the playpen. * @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 SWAN * environment. <br> * One of SWAN's limitation is that it can run only one execution per time. * Multiple executions are not allowed. A getResult() or a kill() must be * done before starting a new execution. * * @param dirName The execution root directory following the Linux name * convention. It must be the playpen. * @param command The command must be executed * @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 ); } logger.debug( "Requested to execute command: " + command + ", Dir Name: " + dirName ); /* * The var singleExecutionInUse is false if the single execution is not * running and the result has been gotten or if the process has been * killed. */ if ( singleExecutionInUse ) { logger.debug( "Unable to execute command: " + command + " because " + "Swan's single execution is unavailable (busy)." ); throw new ExecutorException( "SwanExecutor: There is already one execution in progress. GetResult() or Kill() must be done before another execute()", new UnsupportedOperationException( "Swan only supports one execution per time." ) ); } /* * Since a new execution is being started, reset the flag processKilled, * and set singleExecutionInUse=true. Also set the working directory. */ processKilled = false; singleExecutionInUse = true; /* SWANExecutor is now performmng its only execution. */ dir = new File( dirName ); /* setting the working directory. Remember that it must be the playpen.*/ try { /* * Creating the files that will contain the outputs... The files are * created in the user's playpen and will be copyed to dom1 through * stagein. The outputs will be stored, the files will be staged out * back to the playpen, their contents will be saved and they will * be deleted. */ stdOutput = TempFileManager.createTempFile( "stdOutput_", ".txt", dir ); errorOutput = TempFileManager.createTempFile( "stdError_", ".txt", dir ); exitValue = TempFileManager.createTempFile( "exitValue_", ".txt", dir ); /* * A script created to run the command passed as a param. This is * the script that will be executed in dom1. */ File script = createScript( command, dirName, envVars ); /* The initvm command to be executed */ String initVMCmd = "swan_initvm -p " + dirName + " -c \"sh " + script.getName() + " >" + stdOutput.getName() + " 2>" + errorOutput.getName() + " \" "; /* * A new script iscreated. createSWANStagesScript() creates a script * that contains the stagein, initVM and stageout commands. The * dirName and the whole initVM command are passed as params. */ File swanStagesScript = createSWANStagesScript( dirName, initVMCmd, envVars ); /* * script with the swan_initvm command */ // Now, run the script containing the three stages of SWAN. stagesHandle = linExecutor.execute( dirName, "sh " + swanStagesScript.getName() ); } catch ( Exception e ) { throw new ExecutorException( command, e ); } return theOnlyHandle; } /** * Returns the result of a execution specified by a handle. Due to SWAN * limitations, there is only one execution per time, and its result must be * returned (or a kill has to be called) before a new one takes place. * * @param handle the command handle * @return ExecutorResult StdOut, StdErr and exitValue from kill command. */ public ExecutorResult getResult( ExecutorHandle handle ) throws ExecutorException { logger.debug( "Getting result of execution... Handle: " + handle ); /* The ExecutorResult to be returned. */ ExecutorResult result = null; /* * If there is no execution running (or done) to have it's result * gotten, it's an error. */ if ( !singleExecutionInUse ) { logger.error( "Tried to get result of an inexistent execution." ); return null; } /* * Waits for the end of the execution of stagesScripts. This is where * execution hangs. */ ExecutorResult stagesResult = linExecutor.getResult( stagesHandle ); if ( stagesResult.getExitValue() == 0 ) { logger.debug( "stagesScript finished successfully." ); } else { logger.debug( "stagesScript did NOT finish successfully." ); } /* * Checks if process has been killed. If so, it returns the result of * the execution of the stagesScript. If not, continue with normal * execution - catch the output! */ if ( processKilled ) { logger.debug( "Process killed." ); processKilled = false; return stagesResult; } result = this.catchOutputFromFile(); /* * Checks again if process has been killed. It is done because a kill * might been involked while catchOutputFromFile was running. In this * case, It's possible that we have the right results, but since It's * been killed the exitValue must be not equals to 0 */ if ( processKilled ) { logger.debug( "Process killed while catching output from files." ); result.setExitValue( 1 ); } /* resets the flag, so execute can be involked again. */ singleExecutionInUse = false; logger.debug( "Finished getResult. Single Execution released." ); return result; } /** * Kills command that was issued via an execute method * * @param handle the command handle * @throws ExecutorException when there is a problem while changing .the * permissions of a file. */ public void kill( ExecutorHandle handle ) throws ExecutorException { /* * Checks if the single execution is being used. If It's not, makes no * sense killing it. If it happens, either an error occurred or the API * is being misused. */ if ( !singleExecutionInUse ) { logger.error( "Tried to kill an inexistent execution." ); return; } try { /* Try to kill the process executing stagesScript */ logger.debug( "Kill requested. Will try to kill stagesScript now." ); linExecutor.kill( stagesHandle ); /* * Executes swan_killvm to run XEN shutdown commands and unmount the * partition. */ logger.debug( "Executing swan_killvm to conclude kill process." ); ExecutorHandle killHandle = linExecutor.execute( dir.getPath(), "swan_killvm" ); ExecutorResult killResult = linExecutor.getResult( killHandle ); if ( killResult.getExitValue() != 0 ) { logger.error( "Error while executing swan_killvm." ); } /* Flag up processKilled and release the execution. */ processKilled = true; singleExecutionInUse = false; logger.debug( "Kill executed successfully." ); } catch ( Exception e ) { logger.error( "Exception", e ); } } public void finishExecution() throws ExecutorException { // TODO Auto-generated method stub } /** * Changes the permissions of an especifc file * * @param file The file which permissions will be changed * @param modeStr The new permitions.<br> * Example: "123" = "--x-w--wx" */ /* * The files permissions must be changed on the playpen, outside the VM that * executes the native commands. When a stagein is executed, the file * permissions will become the same in the VM. So the implementation is the * same as Linux Executor's. */ public void chmod( File file, String modeStr ) throws ExecutorException { linExecutor.chmod( file, modeStr ); } /** * Creates a file "SWANStagesScript" containing the commands used by SWAN. * * @param dirName The path of the directory where the file will be created. * @param command The command line that will be put an the file. * @param envVars A map containing eh enviroment vars * @return a File representing the script created */ private File createSWANStagesScript( String dirName, String command, Map envVars ) throws IOException { /* the file of SWANStagesScript to be returned */ File temp = new File( dirName, "SWANStagesScript" ); /* delete the file if it already exists */ if ( temp.exists() ) { temp.delete(); } BufferedWriter writerTemp = new BufferedWriter( new FileWriter( temp ) ); String storageDir = ( String ) envVars.get( "STORAGE" ); if ( storageDir == null ) { storageDir = "."; } writerTemp.write( "swan_stagein -p " + dirName + " -s " + storageDir + " &&" ); writerTemp.newLine(); writerTemp.write( command + " &&" ); writerTemp.newLine(); writerTemp.write( "swan_stageout -d " + dirName ); writerTemp.flush(); writerTemp.close(); temp.deleteOnExit(); return temp; } /** * Creates an auxiliary file (script) containing the command to be executed. * * @param command The command to be run * @param dirName The path of the directory where the file will be created. * @return a File representing the script created */ protected File createScript( String command, String dirName ) throws ExecutorException { return createScript( command, dirName, null ); } /** * Creates an auxiliary file (script) containing the command to be executed. * * @param command The command to be run * @param dirName The path of the directory where the file will be created. * @param envVars A map (var name, value) with the environment variables * used by the command * @return a File representing the script created */ protected File createScript( String command, String dirName, Map envVars ) throws ExecutorException { File dir = new File( dirName ); // The execution directory File commandFile = null; // The abstraction for the command boolean isScript = false; // Indicate if the command is already an // script // Check if dir is not null. Convert to "." to void problems if ( dirName == null ) { dirName = "."; } else 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 */ try { commandFile = new File( dir, command ); if ( commandFile.exists() && commandFile.canRead() ) { FileInputStream commandFIS = new FileInputStream( commandFile ); DataInputStream commandDIS = new DataInputStream( commandFIS ); if ( commandDIS.readChar() == '#' && commandDIS.readChar() == '!' ) { isScript = true; } /** * Close to avoid the "Too many open files" message and release * resources */ commandFIS.close(); /** * Close to avoid the "Too many open files" message and release * resources */ commandDIS.close(); } } catch ( FileNotFoundException e ) { throw new ExecutorException( command, new FileNotFoundException( commandFile.getAbsolutePath() ) ); } catch ( IOException e ) { throw new ExecutorException( command, e ); } File temp; BufferedWriter writerTemp; Iterator keys; String theKey; String exportCommand = "export "; /** * Try create the temporary script in fact. */ try { temp = TempFileManager.createTempFile( "broker", ".tmp", dir ); /* A writer to produce the script commands */ writerTemp = new BufferedWriter( new FileWriter( temp ) ); /* If the command does not need any kind of environment variables */ if ( envVars != null ) { /* If the map has some environment variable to export */ if ( !envVars.isEmpty() ) { /* Gets an iterator to the keys in the Map */ keys = envVars.keySet().iterator(); if ( keys.hasNext() ) { while ( keys.hasNext() ) { theKey = ( String ) keys.next(); if ( theKey.equals( "PLAYPEN" ) ) { writerTemp.write( theKey + "=\'" + "/playpen/" + "\'" ); } else { if ( theKey.equals( "STORAGE" ) ) { writerTemp.write( theKey + "=\'" + "/mgstorage/" + "\'" ); } else { writerTemp.write( theKey + "=\'" + envVars.get( theKey ) + "\'" ); } } writerTemp.newLine(); exportCommand = exportCommand + " " + theKey; } writerTemp.write( exportCommand ); writerTemp.newLine(); } } } writerTemp.write( "PATH=$PATH:$STORAGE:." ); writerTemp.newLine(); exportCommand = exportCommand + " PATH"; writerTemp.write( exportCommand ); writerTemp.newLine(); if ( isScript ) { writerTemp.write( "sh " ); } // put the command in the script redirecting its output to files. // Ex. "ls -la > outputfile 2>errorfile" writerTemp.write( command ); writerTemp.newLine(); writerTemp.write( "echo $? >" + exitValue.getName() ); writerTemp.flush(); writerTemp.close(); return temp; } catch ( IOException ioe ) { throw new ExecutorException( ioe ); } } /** * Creates and returns an instance of ExcecutorResult that will contain the * informations about the finished process. It reads the files containing * the outputs and creates the result. * * @return An instance of ExecutorResult describing the result of <i>process</i> * execution. */ private ExecutorResult catchOutputFromFile( ) { // create an instance of Executor result information class ExecutorResult result = new ExecutorResult(); // The StringBuffers that will contain the outputs from the files StringBuffer outputResult = new StringBuffer(); StringBuffer outputErrorResult = new StringBuffer(); StringBuffer exitValueResult = new StringBuffer(); try { // CATCHING STANDARD OUTPUT String line = null; // Read the whole file and put it on the stringBuffer BufferedReader stdOutputBR = new BufferedReader( new FileReader( dir.getPath() + "/" + stdOutput.getName() ) ); while ( ( line = stdOutputBR.readLine() ) != null && !processKilled ) { outputResult.append( line + "\n" ); } stdOutputBR.close(); // CATCHING ERROR OUTPUT line = null; // Read the whole file and put it on the stringBuffer BufferedReader errorOutputBR = new BufferedReader( new FileReader( dir.getPath() + "/" + errorOutput.getName() ) ); while ( ( line = errorOutputBR.readLine() ) != null && !processKilled ) { outputErrorResult.append( line + "\n" ); } errorOutputBR.close(); // CATCHING EXIT VALUE line = null; // Read the whole file and put it on the stringBuffer BufferedReader exitValueBR = new BufferedReader( new FileReader( dir.getPath() + "/" + exitValue.getName() ) ); while ( ( line = exitValueBR.readLine() ) != null && !processKilled ) { exitValueResult.append( line ); } exitValueBR.close(); // Delete the output files staged out from the VM File file = new File( dir.getPath() + "/" + stdOutput.getName() ); if ( file.delete() == false ) { logger.error( "Could not delete " + stdOutput.getName() ); } file = new File( dir.getPath() + "/" + errorOutput.getName() ); if ( file.delete() == false ) { logger.error( "Could not delete " + errorOutput.getName() ); } file = new File( dir.getPath() + "/" + exitValue.getName() ); if ( file.delete() == false ) { logger.error( "Could not delete " + exitValue.getName() ); } } catch ( IOException ioe ) { logger.error( ioe.getMessage() ); } try { result.setExitValue( Integer.parseInt( exitValueResult.toString() ) ); } catch ( NumberFormatException nfe ) { logger .debug( "Failed parsing exitValueResult.toString() to int. It is normal if the process has been killed " ); } result.setStdout( outputResult.toString() ); result.setStderr( outputErrorResult.toString() ); return result; } public void setConfiguration(ExecutorConfiguration executorConfiguratrion) { // 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 } public void finishPrepareAllocation() throws ExecutorException { // TODO Auto-generated method stub } @Override public void shutdown() throws ExecutorException { // TODO Auto-generated method stub } }// SWANExecutor