package org.ourgrid.common.executor.generic; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.ourgrid.common.executor.Executor; import org.ourgrid.common.executor.ExecutorException; import org.ourgrid.common.executor.ExecutorHandle; import org.ourgrid.common.executor.ExecutorResult; import org.ourgrid.common.executor.IntegerExecutorHandle; import org.ourgrid.common.executor.config.ExecutorConfiguration; import org.ourgrid.common.executor.config.GenericExecutorConfiguration; import org.ourgrid.common.util.CommonUtils; import org.ourgrid.common.util.StringUtil; import org.ourgrid.virt.OurVirt; import org.ourgrid.virt.exception.SnapshotAlreadyExistsException; import org.ourgrid.virt.model.ExecutionResult; import org.ourgrid.virt.model.HypervisorType; import org.ourgrid.virt.model.VirtualMachineStatus; import org.ourgrid.worker.WorkerConstants; import org.ourgrid.worker.business.dao.WorkerDAOFactory; import org.ourgrid.worker.utils.RandomNumberUtil; import br.edu.ufcg.lsd.commune.container.logging.CommuneLogger; public class GenericExecutor implements Executor { private static final Logger LOGGER = Logger.getLogger(GenericExecutor.class); private static final long serialVersionUID = 34L; private String vmName; private HypervisorType hypervisorType; private String snapshotName; private ExecutorConfiguration executorConfiguration; private Map<String, String> virtualMachineConfiguration; private OurVirt ourVirt; private String virtualMachinePlaypenPath; private String virtualMachineStoragePath; private String workerPlaypenPath; private String workerStoragePath; private static int nextHandle = 0; private ExecutorResult executorResult; public GenericExecutor(CommuneLogger logger) { this.ourVirt = new OurVirt(); } @Override public void setConfiguration(ExecutorConfiguration configuration) { LOGGER.debug("Setting up Generic Executor configuration."); executorConfiguration = configuration; vmName = executorConfiguration.getProperty( GenericExecutorConfiguration.VM_NAME.toString()); hypervisorType = HypervisorType.valueOf(executorConfiguration.getProperty( GenericExecutorConfiguration.VM_HYPERVISOR_TYPE.toString())); snapshotName = executorConfiguration.getProperty( GenericExecutorConfiguration.VM_SNAPSHOT_NAME.toString()); LOGGER.debug("Generic Executor virtual machine name: " + vmName + ", Hypervisor: "+hypervisorType); virtualMachineConfiguration = new HashMap<String, String>(); virtualMachineConfiguration.put("user", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_USER.toString())); virtualMachineConfiguration.put("password", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_PASSWORD.toString())); virtualMachineConfiguration.put("memory", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_MEMORY.toString())); virtualMachineConfiguration.put("os", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_OS.toString())); virtualMachineConfiguration.put("osversion", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_OS_VERSION.toString())); virtualMachineConfiguration.put("networktype", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_NETWORK_TYPE.toString())); virtualMachineConfiguration.put("networkadaptername", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_NETWORK_ADAPTER_NAME.toString())); virtualMachineConfiguration.put("pae.enabled", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_PAE_ENABLED.toString())); LOGGER.debug("Generic Worker Guest OS: " + executorConfiguration.getProperty( GenericExecutorConfiguration.VM_OS.toString())); virtualMachineConfiguration.put("disktype", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_DISK_TYPE.toString())); virtualMachineConfiguration.put("diskimagepath", executorConfiguration.getProperty( GenericExecutorConfiguration.VM_DISK_IMAGE_PATH.toString())); String timeout = executorConfiguration.getProperty( GenericExecutorConfiguration.VM_START_TIMEOUT.toString()); if (timeout != null) { virtualMachineConfiguration.put("starttimeout", timeout); } LOGGER.debug("Virtual machine configuration: " + virtualMachineConfiguration); workerPlaypenPath = executorConfiguration. getProperty(WorkerConstants.PROP_PLAYPEN_ROOT); workerStoragePath = executorConfiguration. getProperty(WorkerConstants.PROP_STORAGE_DIR); virtualMachinePlaypenPath = executorConfiguration. getProperty(GenericExecutorConfiguration.GUEST_PLAYPEN_PATH.toString()); virtualMachineStoragePath = executorConfiguration. getProperty(GenericExecutorConfiguration.GUEST_STORAGE_PATH.toString()); LOGGER.debug("Generic Worker established a connection to virtual machine." + "Storage (HOST): " + workerStoragePath + ", Storage (GUEST): " + virtualMachineStoragePath); LOGGER.debug("Generic Worker established a connection to virtual machine." + "Playpen (HOST): " + workerPlaypenPath + ", Playpen (GUEST): " + virtualMachinePlaypenPath); } @Override public void prepareAllocation() throws ExecutorException { LOGGER.debug("Generic Executor is preparing allocation."); try{ ourVirt.register(vmName, virtualMachineConfiguration); ourVirt.create(hypervisorType, vmName); restart(); } catch (Exception e) { try { ourVirt.stop(hypervisorType, vmName); } catch (Exception e1) { // Best effort } LOGGER.error(e); throw new ExecutorException("OurVirt: " + e.getMessage()); } } private void restart() throws Exception { VirtualMachineStatus status = ourVirt.status(hypervisorType, vmName); if (status != VirtualMachineStatus.RUNNING) { LOGGER.debug("Virtual machine [ " + vmName + " ]: Status: " + status); try { takeSnapshot(); } catch(SnapshotAlreadyExistsException snapshotAlreadyExistsException) { restoreSnapshot(); } catch (Exception e) { LOGGER.warn(e); } deleteSharedFolder(GenericExecutorConfiguration.PLAYPEN_SHARED_FOLDER); createSharedFolder(GenericExecutorConfiguration.PLAYPEN_SHARED_FOLDER, workerPlaypenPath, virtualMachinePlaypenPath); deleteSharedFolder(GenericExecutorConfiguration.STORAGE_SHARED_FOLDER); createSharedFolder(GenericExecutorConfiguration.STORAGE_SHARED_FOLDER, workerStoragePath, virtualMachineStoragePath); start(); mountSharedFolder(GenericExecutorConfiguration.PLAYPEN_SHARED_FOLDER, workerPlaypenPath, virtualMachinePlaypenPath); mountSharedFolder(GenericExecutorConfiguration.STORAGE_SHARED_FOLDER, workerStoragePath, virtualMachineStoragePath); } else { stop(status); restart(); } } private void stop(VirtualMachineStatus status) throws Exception { LOGGER.debug("Virtual machine [ " + vmName + " ]: was " + status +". Stopping."); ourVirt.stop(hypervisorType, vmName); } private void start() throws Exception { LOGGER.debug("Virtual machine [ " + vmName + " ]: Starting."); ourVirt.start(hypervisorType, vmName); } private void mountSharedFolder(String shareName, String hostPath, String guestPath) throws Exception { LOGGER.debug("Virtual machine [ " + vmName + " ]: Mounting shared folder " + shareName + ".\n" + "Host Path: " + hostPath + "\nGuest Path: " + guestPath); ourVirt.mountSharedFolder(hypervisorType, vmName, shareName, hostPath, guestPath); } private void restoreSnapshot() throws Exception { LOGGER.debug("Virtual machine [ " + vmName + " ]: " + "Has snapshot [ " + snapshotName + " ]. Restoring."); ourVirt.restoreSnapshot(hypervisorType, vmName, snapshotName); } private void takeSnapshot() throws Exception { LOGGER.debug("Virtual machine [ " + vmName + " ]: " + "Taking snapshot [ " + snapshotName + " ]."); ourVirt.takeSnapshot(hypervisorType, vmName, snapshotName); } private void createSharedFolder(String shareName, String hostPath, String guestPath) throws Exception { LOGGER.debug("Virtual machine [ " + vmName + " ]: Creating shared folder " + shareName+"."); ourVirt.createSharedFolder(hypervisorType, vmName, shareName, hostPath, guestPath); } private void deleteSharedFolder(String shareName) throws Exception { LOGGER.debug("Virtual machine [ " + vmName + " ]: Removing shared folder " + shareName + "."); ourVirt.deleteSharedFolder(hypervisorType, vmName, shareName); } @Override public ExecutorHandle execute(String dirName, String command, Map<String, String> envVars) throws ExecutorException { LOGGER.debug("Virtual machine [ " + vmName + " ]: Executing command \"" + command + "\"."); try { if (virtualMachineConfiguration.get("os").contains("windows")) { throw new UnsupportedOperationException("This guest OS is not supported."); } else { String playpenOnHost = WorkerDAOFactory.getInstance().getEnvironmentDAO().getPlaypenDir(); String playpenDirName = new File(playpenOnHost).getName(); String storageOnHost = WorkerDAOFactory.getInstance().getEnvironmentDAO().getStorageDir(); String storageDirName = new File(storageOnHost).getName(); File script = createExecScript(command, envVars, playpenDirName, storageDirName); String playpenOnGuest = virtualMachinePlaypenPath + '/' + playpenDirName; String scriptPath = playpenOnGuest + '/' + script.getName(); String outputRandom = random(); String outputPrefix = playpenOnGuest + "/" + outputRandom; ExecutionResult result = ourVirt.exec(hypervisorType, vmName, "/bin/bash -c \"/bin/bash " + scriptPath + " 2> " + outputPrefix + "-err 1> " + outputPrefix + "-out \""); this.executorResult = new ExecutorResult( result.getReturnValue(), readOutput(playpenOnHost + "/" + outputRandom + "-out"), readOutput(playpenOnHost + "/" + outputRandom + "-err") ); } } catch (Exception e) { LOGGER.error(e); throw new ExecutorException("OurVirt: " + e.getMessage()); } return new IntegerExecutorHandle(nextHandle++); } private static String readOutput(String file) throws IOException, FileNotFoundException { try { return IOUtils.toString(new FileInputStream(new File(file).getAbsolutePath())); } catch (Exception e) { LOGGER.warn(e); return ""; } } private static String random() { Long randomNumber = (long) (RandomNumberUtil.getRandom() * Long.MAX_VALUE); randomNumber = Long.signum(randomNumber) == -1 ? randomNumber * (-1) : randomNumber; return randomNumber.toString(); } private File createExecScript(String command, Map<String, String> envVars, String playpenPath, String storageDir) throws IOException { File script = new File(workerPlaypenPath + "/" + playpenPath + "/" + "exec.sh"); FileWriter scriptWriter = new FileWriter(script); String storagePathOnGuest = virtualMachineStoragePath + "/" + storageDir; String playpenPathOnGuest = virtualMachinePlaypenPath + "/" + playpenPath; scriptWriter.append("#!/bin/bash\n"); scriptWriter.append("[ -e \"" + storagePathOnGuest + "\" ] && [ -n \"`ls -A " + storagePathOnGuest + "`\" ]" + " && cp -r " + storagePathOnGuest + "/* " + playpenPathOnGuest + "\n"); scriptWriter.append("cd " + playpenPathOnGuest + "\n"); scriptWriter.append(StringUtil.replaceVariables(command, setGuestEnvVariables(envVars, playpenPath, storageDir))); scriptWriter.close(); return script; } private Map<String, String> setGuestEnvVariables(Map<String, String> envVars, String playpenPath, String storagePath) { Map<String, String> clone = CommonUtils.createSerializableMap(); clone.putAll(envVars); clone.remove(WorkerConstants.PROP_PLAYPEN_ROOT); clone.remove(WorkerConstants.PROP_STORAGE_DIR); clone.put(WorkerConstants.ENV_PLAYPEN, virtualMachinePlaypenPath + "/" + playpenPath); clone.put(WorkerConstants.ENV_STORAGE, virtualMachineStoragePath + "/" + storagePath); return clone; } @Override public ExecutorHandle execute(String dirName, String command) throws ExecutorException { return execute(dirName, command, new LinkedHashMap<String, String>()); } @Override public void shutdown() throws ExecutorException { try { VirtualMachineStatus status = ourVirt.status(hypervisorType, vmName); if (status == VirtualMachineStatus.RUNNING) { stop(status); } } catch (Exception e) { LOGGER.error(e); throw new ExecutorException(e.getMessage()); } } @Override public ExecutorResult getResult(ExecutorHandle handle) throws ExecutorException { LOGGER.debug("Virtual machine [ " + vmName + " ]: Retrieving result of last command executed."); return this.executorResult; } @Override public void killPreparingAllocation() throws ExecutorException { shutdown(); } @Override public void finishCommandExecution(ExecutorHandle handle) throws ExecutorException { } @Override public void chmod(File file, String perm) throws ExecutorException { } @Override public void killCommand(ExecutorHandle handle) throws ExecutorException { } @Override public void finishPrepareAllocation() throws ExecutorException { } }