ProcessManagerClass.php 10.3 KB
<?php
/**
 * @class ProcessManagerClass
 * @brief Process manager
 * @details
 */
class ProcessManagerClass
{
	private $processManagerFilePath;

	/*
	 * @brief Constructor
	*/
	function __construct($processManagerFilePath)
	{
		$this->processManagerFilePath = $processManagerFilePath;
	}

	/*
	 * @brief Run a process
	*/
	public function runProcess($cmd, $runningPath, $envArray, $postProcessCmd, $batchEnabled)
	{
		$process = new ProcessClass($cmd, $postProcessCmd);

		if (!$process->run($runningPath, $envArray))
			return array("success" => false, "message" => "Cannot run the process");

		$res = $this->concurrentAccessProcessManagerFile(array($this,'addProcessInProcessManagerFile'),$process);

		if (!$res['success'])
		{
			$process->stop();
			return $res;
		}
		
		//Save process id in running path
		chdir($runningPath);
		file_put_contents("process_id", $res['result']['id'].PHP_EOL);

		$process->waitEndOfProcess(array($this,'waitEndOfProcess'),$batchEnabled);

		return $this->getProcessInfo($res['result']['id'],true);
	}
	
	/*
	 * @brief Kill a process
	 */
	public function killProcess($id)
	{
		$res = $this->concurrentAccessProcessManagerFile(array($this,'killProcessFromId'),$id);
		return $res;
	}
	
	/*
	 * @brief Get info about a process
	*/
	public function getProcessInfo($id,$update)
	{
		$res = $this->concurrentAccessProcessManagerFile(array($this,'getProcessInfoFromId'),array('id' => $id, 'update' => $update));

		if (!$res['success'])
			return $res;

		return $res['result'];
	}

	/*
	 * @brief Delete a process
	*/
	public function deleteProcess($id)
	{
		$res = $this->concurrentAccessProcessManagerFile(array($this,'deleteProcessFromId'),$id);
		return $res;
	}

	/*
	 * @brief Wait the end of the execution (or the timeout) of a process.
	*/
	public function waitEndOfProcess($process, $batchEnabled)
	{
		if ($batchEnabled)
		if (time() - $process->getRunningStart() > KernelConfigClass::getTimeToBatchMode())
			return false;
		return true;
	}

	/*
	 * @brief Add a process in the manager file
	*/
	private function addProcessInProcessManagerFile($dom, $process) {
		$processId = "process_".CommonClass::generateRandomString(6).'_'.time().'_'.$process->getPID();
		$processNode = $dom->createElement("process");
		$processNode->setAttribute('xml:id', $processId);
		$dom->documentElement->appendChild($processNode);
		$this->updateProcessInProcessNode($dom, $processNode, $process);
		$dom->save($this->processManagerFilePath);
		return $this->getProcessInfoFromNode($processNode);
	}

	/*
	 * @brief Protection for concurrent access to the manager file
	*/
	private function concurrentAccessProcessManagerFile($callback, $additionalParams)
	{
		$lockFile = $this->processManagerFilePath.".lockfile";

		$fp = fopen($lockFile, "w+");

		if ($fp === false)
			return array('success' => false, 'message' => 'Cannot open process manager lock file');

		$res = true;

		if (flock($fp, LOCK_EX))
		{
			if (!file_exists($this->processManagerFilePath))
				$res = $this->createProcessManagerFile();

			if ($res)
			{
				$dom = new DOMDocument("1.0","UTF-8");
				$dom->preserveWhiteSpace = false;
				$dom->formatOutput = true;
				$res = $dom->load($this->processManagerFilePath);
				if ($res)
					$func_res = call_user_func($callback,$dom,$additionalParams);
			}
		}
		else
			$res = false;

		fclose($fp);

		if ($res)
			return array('success' => true, 'result' => $func_res);

		return array('success' => false, 'message' => 'Error during the concurrent access of the process manager file');
	}

	/*
	 * @brief Create a new manager file
	*/
	private function createProcessManagerFile() {
		$dom = new DOMDocument("1.0","UTF-8");
		$dom->preserveWhiteSpace = false;
		$dom->formatOutput = true;
		$rootNode = $dom->createElement("processlist");
		$dom->appendChild($rootNode);
		return $dom->save($this->processManagerFilePath);
	}

	/*
	 * @brief Update process info if a node of the manager file
	*/
	private function updateProcessInProcessNode($dom, $processNode, $process) {
		$this->updateProcessStatusToNode($dom,$processNode,"cmd",$process->getCommand());
		$this->updateProcessStatusToNode($dom,$processNode,"outputfile",$process->getOutputFile());
		$this->updateProcessStatusToNode($dom,$processNode,"exitcodefile",$process->getExitCodeFile());
		$this->updateProcessStatusToNode($dom,$processNode,"processfile",$process->getProcessFile());
		$this->updateProcessStatusToNode($dom,$processNode,"exitcode",$process->getExitCode());
		$this->updateProcessStatusToNode($dom,$processNode,"PID",$process->getPID());
		$this->updateProcessStatusToNode($dom,$processNode,"runningpath",$process->getRunningPath());
		$this->updateProcessStatusToNode($dom,$processNode,"runningstart",$process->getRunningStart());
		$this->updateProcessStatusToNode($dom,$processNode,"isrunning",$process->isRunning() ? "true" : "false");
		$this->updateProcessStatusToNode($dom,$processNode,"lastupdate",time());
	}

	/*
	 * @brief Extract process info from a node
	*/
	private function getProcessInfoFromNode($processNode)
	{
		return array(
				"id"           => $processNode->getAttribute("xml:id"),
				"cmd"          => $this->getNodeValueFromNode($processNode, "cmd"),
				"outputfile"   => $this->getNodeValueFromNode($processNode, "outputfile"),
				"exitcodefile" => $this->getNodeValueFromNode($processNode, "exitcodefile"),
				"processfile"  => $this->getNodeValueFromNode($processNode, "processfile"),
				"exitcode"     => $this->getNodeValueFromNode($processNode, "exitcode"),
				"PID"          => $this->getNodeValueFromNode($processNode, "PID"),
				"runningpath"  => $this->getNodeValueFromNode($processNode, "runningpath"),
				"runningstart" => $this->getNodeValueFromNode($processNode, "runningstart"),
				"isrunning"    => ($this->getNodeValueFromNode($processNode, "isrunning") == "true"),
				"iskilled"     => ($this->getNodeValueFromNode($processNode, "iskilled") == "true"),
				"lastupdate"   => $this->getNodeValueFromNode($processNode, "lastupdate")
		);
	}

	/*
	 * @brief Get a node by tag name and by a given parent node
	*/
	private function getNodeFromNode($parentNode, $name) {
		$nodes = $parentNode->getElementsByTagName($name);
		if ($nodes->length > 0)
			return $nodes->item(0);
		return NULL;
	}

	/*
	 * @brief Get the value of a node by tag name and by a given parent node
	*/
	private function getNodeValueFromNode($parentNode, $name) {
		$node = $this->getNodeFromNode($parentNode, $name);
		if ($node == NULL)
			return NULL;
		return $node->nodeValue;
	}

	/*
	 * @brief Update a process info in a process node
	*/
	private function updateProcessStatusToNode($dom, $parentNode, $name, $value) {
		$node = $this->getNodeFromNode($parentNode, $name);
		if ($node == NULL) {
			$node = $dom->createElement($name);
			$parentNode->appendChild($node);
		}
		$node->nodeValue = htmlspecialchars($value);
		return $node;
	}

	/*
	 * @brief Create a Process from node
	*/
	private function getProcessFromNode($processNode) {
		$cmd = $this->getNodeValueFromNode($processNode, "cmd");
		if ($cmd == NULL)
			return NULL;
		$process = new ProcessClass($cmd);
		if ($process === false)
			return NULL;
		$processInfo = $this->getProcessInfoFromNode($processNode);
		$outputFile   = $processInfo["outputfile"];
		$processFile  = $processInfo["processfile"];
		$exitCodeFile = $processInfo["exitcodefile"];
		$pid          = $processInfo["PID"];
		$runningPath  = $processInfo["runningpath"];
		$runningStart = $processInfo["runningstart"];
		$process->init($outputFile, $exitCodeFile, $processFile, $pid, $runningPath, $runningStart);
		return $process;
	}

	// Next element sibling for DOMElement
	function fNextEltSibling($node) {
		while ($node && ($node = $node->nextSibling)) {
			if ($node instanceof DOMElement) {
				break;
			}
		}
		return $node;
	}


	/*
	 * @brief Get information about a process
	*/
	private function getProcessInfoFromId($dom, $args)
	{
		$id = $args['id'];
		$update = $args['update'];
		 
		$processNodes = $dom->documentElement->getElementsByTagName("process");
		$processNode = $processNodes->item(0);
		while ($processNode)
		{
			if ($processNode->getAttribute('xml:id') == $id)
			{
				if ($update)
				{
					$process = $this->getProcessFromNode($processNode);
					if (!isset($process))
						return array('success' => false, 'message' => 'Error to retrieve process info');

					$this->updateProcessInProcessNode($dom, $processNode, $process);
					
					$dom->save($this->processManagerFilePath);
				}
					
				$processInfo = $this->getProcessInfoFromNode($processNode);
					
				return array('success' => true, 'result' => $processInfo);
			}
			$processNode = $this->fNextEltSibling($processNode);
		}

		return array('success' => false, 'message' => 'Cannot get process from id');
	}

	/*
	 * @brief Delete a process
	*/
	private function deleteProcessFromId($dom, $id)
	{
		$processNodes = $dom->documentElement->getElementsByTagName("process");
		$processNode = $processNodes->item(0);
		while ($processNode)
		{
			if ($processNode->getAttribute('xml:id') == $id)
			{
				$process = $this->getProcessFromNode($processNode);
				if (!isset($process))
					return array('success' => false, 'message' => 'Error to retrieve process info');

				$process->delete();
					
				$dom->documentElement->removeChild($processNode);
					
				$dom->save($this->processManagerFilePath);

				return array('success' => true);
			}
			$processNode = $this->fNextEltSibling($processNode);
		}

		return array('success' => false, 'message' => 'Cannot get process from id');
	}
	
	/*
	 * @brief Kill a process
	*/
	private function killProcessFromId($dom, $id)
	{
		$processNodes = $dom->documentElement->getElementsByTagName("process");
		$processNode = $processNodes->item(0);
		while ($processNode)
		{
			if ($processNode->getAttribute('xml:id') == $id)
			{
				$process = $this->getProcessFromNode($processNode);
				if (!isset($process))
					return array('success' => false, 'message' => 'Error to retrieve process info');
	
				$this->updateProcessStatusToNode($dom,$processNode,"isrunning","false");
				$this->updateProcessStatusToNode($dom,$processNode,"iskilled","true");
				
				if ($process->stop())
				{
					$dom->save($this->processManagerFilePath);
	
					return array('success' => true);
				}
				return array('success' => false, 'message' => 'Cannot process from id');
			}
			$processNode = $this->fNextEltSibling($processNode);
		}
	
		return array('success' => false, 'message' => 'Cannot kill process from id '.$id);
	}
}

?>