mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-23 02:28:44 +02:00
New capability for CRON: handle tasks scheduled at given date/time (as opposed to a task being executed more or less continuously).
SVN:trunk[2816]
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -17,6 +17,19 @@
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* interface iProcess
|
||||
* Something that can be executed
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
interface iProcess
|
||||
{
|
||||
public function Process($iUnixTimeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* interface iBackgroundProcess
|
||||
* Any extension that must be called regularly to be executed in the background
|
||||
@@ -25,10 +38,30 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
interface iBackgroundProcess
|
||||
interface iBackgroundProcess extends iProcess
|
||||
{
|
||||
/*
|
||||
Gives the repetition rate in seconds
|
||||
@returns integer
|
||||
*/
|
||||
public function GetPeriodicity();
|
||||
public function Process($iUnixTimeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* interface iScheduledProcess
|
||||
* A variant of process that must be called at specific times
|
||||
*
|
||||
* @copyright Copyright (C) 2013 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
interface iScheduledProcess extends iProcess
|
||||
{
|
||||
/*
|
||||
Gives the exact time at which the process must be run next time
|
||||
@returns DateTime
|
||||
*/
|
||||
public function GetNextOccurrence();
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -418,7 +418,7 @@ class ApplicationInstaller
|
||||
|
||||
protected static function DoBackup($sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sBackupFile, $sSourceConfigFile)
|
||||
{
|
||||
$oBackup = new DBBackup($sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix);
|
||||
$oBackup = new SetupDBBackup($sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix);
|
||||
$sZipFile = $oBackup->MakeName($sBackupFile);
|
||||
$oBackup->CreateZip($sZipFile, $sSourceConfigFile);
|
||||
}
|
||||
@@ -867,3 +867,16 @@ class ApplicationInstaller
|
||||
MetaModel::ResetCache();
|
||||
}
|
||||
}
|
||||
|
||||
class SetupDBBackup extends DBBackup
|
||||
{
|
||||
protected function LogInfo($sMsg)
|
||||
{
|
||||
SetupPage::log('Info - '.$sMsg);
|
||||
}
|
||||
|
||||
protected function LogError($sMsg)
|
||||
{
|
||||
SetupPage::log('Error - '.$sMsg);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,14 @@ class BackupException extends Exception
|
||||
|
||||
class DBBackup
|
||||
{
|
||||
// To be overriden depending on the expected usages
|
||||
protected function LogInfo($sMsg)
|
||||
{
|
||||
}
|
||||
protected function LogError($sMsg)
|
||||
{
|
||||
}
|
||||
|
||||
protected $sDBHost;
|
||||
protected $iDBPort;
|
||||
protected $sDBUser;
|
||||
@@ -72,6 +80,16 @@ class DBBackup
|
||||
$this->sDBSubName = $sDBSubName;
|
||||
}
|
||||
|
||||
protected $sMySQLBinDir = '';
|
||||
/**
|
||||
* Create a normalized backup name, depending on the current date/time and Database
|
||||
* @param sNameSpec string Name and path, eventually containing itop placeholders + time formatting specs
|
||||
*/
|
||||
public function SetMySQLBinDir($sMySQLBinDir)
|
||||
{
|
||||
$this->sMySQLBinDir = $sMySQLBinDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a normalized backup name, depending on the current date/time and Database
|
||||
* @param sNameSpec string Name and path, eventually containing itop placeholders + time formatting specs
|
||||
@@ -92,7 +110,7 @@ class DBBackup
|
||||
// Note: the file is created by tempnam and might not be writeable by another process (Windows/IIS)
|
||||
// (delete it before spawning a process)
|
||||
$sDataFile = tempnam(SetupUtils::GetTmpDir(), 'itop-');
|
||||
SetupPage::log("Info - Data file: '$sDataFile'");
|
||||
$this->LogInfo("Data file: '$sDataFile'");
|
||||
|
||||
if (is_null($sSourceConfigFile))
|
||||
{
|
||||
@@ -147,7 +165,9 @@ class DBBackup
|
||||
$sTables = implode(' ', $aEscapedTables);
|
||||
}
|
||||
|
||||
$sMySQLBinDir = utils::ReadParam('mysql_bindir', '', true);
|
||||
$this->LogInfo("Starting backup of $this->sDBHost/$this->sDBName(suffix:'$this->sDBSubName')");
|
||||
|
||||
$sMySQLBinDir = utils::ReadParam('mysql_bindir', $this->sMySQLBinDir, true);
|
||||
if (empty($sMySQLBinDir))
|
||||
{
|
||||
$sMySQLDump = 'mysqldump';
|
||||
@@ -173,17 +193,17 @@ class DBBackup
|
||||
$sCommandDisplay = "$sMySQLDump --opt --default-character-set=utf8 --add-drop-database --single-transaction --host=$sHost $sPortOption --user=xxxxx --password=xxxxx --result-file=$sTmpFileName $sDBName $sTables";
|
||||
|
||||
// Now run the command for real
|
||||
SetupPage::log("Info - Executing command: $sCommandDisplay");
|
||||
$this->LogInfo("Executing command: $sCommandDisplay");
|
||||
$aOutput = array();
|
||||
$iRetCode = 0;
|
||||
exec($sCommand, $aOutput, $iRetCode);
|
||||
foreach($aOutput as $sLine)
|
||||
{
|
||||
SetupPage::log("Info - mysqldump said: $sLine");
|
||||
$this->LogInfo("mysqldump said: $sLine");
|
||||
}
|
||||
if ($iRetCode != 0)
|
||||
{
|
||||
SetupPage::log("Error - retcode=".$iRetCode."\n");
|
||||
$this->LogError("retcode=".$iRetCode."\n");
|
||||
throw new BackupException("Failed to execute mysqldump. Return code: $iRetCode. Check the log file '".realpath(APPROOT.'/log/setup.log')."' for more information.");
|
||||
}
|
||||
}
|
||||
@@ -210,17 +230,17 @@ class DBBackup
|
||||
|
||||
if ($oZip->close())
|
||||
{
|
||||
SetupPage::log("Info - Archive: $sZipArchiveFile created");
|
||||
$this->LogInfo("Archive: $sZipArchiveFile created");
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupPage::log("Error - Failed to save zip archive: $sZipArchiveFile");
|
||||
$this->LogError("Failed to save zip archive: $sZipArchiveFile");
|
||||
throw new BackupException("Failed to save zip archive: $sZipArchiveFile");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupPage::log("Error - Failed to create zip archive: $sZipArchiveFile.");
|
||||
$this->LogError("Failed to create zip archive: $sZipArchiveFile.");
|
||||
throw new BackupException("Failed to create zip archive: $sZipArchiveFile.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,26 +61,43 @@ function UsageAndExit($oP)
|
||||
exit -2;
|
||||
}
|
||||
|
||||
function RunTask($oBackgroundProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
|
||||
function RunTask($oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
|
||||
{
|
||||
try
|
||||
{
|
||||
$oNow = new DateTime();
|
||||
$fStart = microtime(true);
|
||||
$sMessage = $oBackgroundProcess->Process($iTimeLimit);
|
||||
$sMessage = $oProcess->Process($iTimeLimit);
|
||||
$fDuration = microtime(true) - $fStart;
|
||||
$oTask->ComputeDurations($fDuration);
|
||||
$oTask->Set('latest_run_date', $oNow->format('Y-m-d H:i:s'));
|
||||
$oPlannedStart = new DateTime($oTask->Get('latest_run_date'));
|
||||
// Let's assume that the task was started exactly when planned so that the schedule does no shift each time
|
||||
// this allows to schedule a task everyday "around" 11:30PM for example
|
||||
$oPlannedStart->modify('+'.$oBackgroundProcess->GetPeriodicity().' seconds');
|
||||
$oEnd = new DateTime();
|
||||
if ($oPlannedStart->format('U') < $oEnd->format('U'))
|
||||
if ($oTask->Get('total_exec_count') == 0)
|
||||
{
|
||||
// Huh, next planned start is already in the past, shift it of the periodicity !
|
||||
$oPlannedStart = $oEnd->modify('+'.$oBackgroundProcess->GetPeriodicity().' seconds');
|
||||
// First execution
|
||||
$oTask->Set('first_run_date', $oNow->format('Y-m-d H:i:s'));
|
||||
}
|
||||
$oTask->ComputeDurations($fDuration); // does increment the counter and compute statistics
|
||||
$oTask->Set('latest_run_date', $oNow->format('Y-m-d H:i:s'));
|
||||
|
||||
$oRefClass = new ReflectionClass(get_class($oProcess));
|
||||
if ($oRefClass->implementsInterface('iScheduledProcess'))
|
||||
{
|
||||
// Schedules process do repeat at specific moments
|
||||
$oPlannedStart = $oProcess->GetNextOccurrence();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Background processes do repeat periodically
|
||||
$oPlannedStart = new DateTime($oTask->Get('latest_run_date'));
|
||||
// Let's assume that the task was started exactly when planned so that the schedule does no shift each time
|
||||
// this allows to schedule a task everyday "around" 11:30PM for example
|
||||
$oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds');
|
||||
$oEnd = new DateTime();
|
||||
if ($oPlannedStart->format('U') < $oEnd->format('U'))
|
||||
{
|
||||
// Huh, next planned start is already in the past, shift it of the periodicity !
|
||||
$oPlannedStart = $oEnd->modify('+'.$oProcess->GetPeriodicity().' seconds');
|
||||
}
|
||||
}
|
||||
|
||||
$oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s'));
|
||||
$oTask->DBUpdate();
|
||||
}
|
||||
@@ -91,8 +108,7 @@ function RunTask($oBackgroundProcess, BackgroundTask $oTask, $oStartDate, $iTime
|
||||
return $sMessage;
|
||||
}
|
||||
|
||||
// Known limitation - the background process periodicity is NOT taken into account
|
||||
function CronExec($oP, $aBackgroundProcesses, $bVerbose)
|
||||
function CronExec($oP, $aProcesses, $bVerbose)
|
||||
{
|
||||
$iStarted = time();
|
||||
$iMaxDuration = MetaModel::GetConfig()->Get('cron_max_execution_time');
|
||||
@@ -103,6 +119,26 @@ function CronExec($oP, $aBackgroundProcesses, $bVerbose)
|
||||
$oP->p("Planned duration = $iMaxDuration seconds");
|
||||
}
|
||||
|
||||
// Reset the next planned execution to take into account new settings
|
||||
$oSearch = new DBObjectSearch('BackgroundTask');
|
||||
$oTasks = new DBObjectSet($oSearch);
|
||||
while($oTask = $oTasks->Fetch())
|
||||
{
|
||||
$sTaskClass = $oTask->Get('class_name');
|
||||
$oRefClass = new ReflectionClass($sTaskClass);
|
||||
if ($oRefClass->implementsInterface('iScheduledProcess'))
|
||||
{
|
||||
if ($bVerbose)
|
||||
{
|
||||
$oP->p("Resetting the next run date for $sTaskClass");
|
||||
}
|
||||
$oProcess = $aProcesses[$sTaskClass];
|
||||
$oNextOcc = $oProcess->GetNextOccurrence();
|
||||
$oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s'));
|
||||
$oTask->DBUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
$iCronSleep = MetaModel::GetConfig()->Get('cron_sleep');
|
||||
|
||||
$oSearch = new DBObjectSearch('BackgroundTask');
|
||||
@@ -114,46 +150,47 @@ function CronExec($oP, $aBackgroundProcesses, $bVerbose)
|
||||
{
|
||||
$aTasks[$oTask->Get('class_name')] = $oTask;
|
||||
}
|
||||
foreach ($aBackgroundProcesses as $oBackgroundProcess)
|
||||
foreach ($aProcesses as $oProcess)
|
||||
{
|
||||
$sTaskClass = get_class($oBackgroundProcess);
|
||||
$sTaskClass = get_class($oProcess);
|
||||
$oNow = new DateTime();
|
||||
if (!array_key_exists($sTaskClass, $aTasks))
|
||||
{
|
||||
// New entry, let's create a new BackgroundTask record and run the task immediately
|
||||
// New entry, let's create a new BackgroundTask record, and plan the first execution
|
||||
$oTask = new BackgroundTask();
|
||||
$oTask->Set('class_name', get_class($oBackgroundProcess));
|
||||
$oTask->Set('first_run_date', $oNow->format('Y-m-d H:i:s'));
|
||||
$oTask->Set('class_name', get_class($oProcess));
|
||||
$oTask->Set('total_exec_count', 0);
|
||||
$oTask->Set('min_run_duration', 99999.999);
|
||||
$oTask->Set('max_run_duration', 0);
|
||||
$oTask->Set('average_run_duration', 0);
|
||||
$oTask->Set('next_run_date', $oNow->format('Y-m-d H:i:s')); // in case of crash...
|
||||
$oRefClass = new ReflectionClass($sTaskClass);
|
||||
if ($oRefClass->implementsInterface('iScheduledProcess'))
|
||||
{
|
||||
$oNextOcc = $oProcess->GetNextOccurrence();
|
||||
$oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s'));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Background processes do start asap, i.e. "now"
|
||||
$oTask->Set('next_run_date', $oNow->format('Y-m-d H:i:s'));
|
||||
}
|
||||
if ($bVerbose)
|
||||
{
|
||||
$oP->p('Creating record for: '.$sTaskClass);
|
||||
$oP->p('First execution planned at: '.$oTask->Get('next_run_date'));
|
||||
}
|
||||
$oTask->DBInsert();
|
||||
if ($bVerbose)
|
||||
{
|
||||
$oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=40s", ' '.$sTaskClass.' (first run) '));
|
||||
}
|
||||
$sMessage = RunTask($oBackgroundProcess, $oTask, $oNow, $iTimeLimit);
|
||||
if ($bVerbose)
|
||||
{
|
||||
if(!empty($sMessage))
|
||||
{
|
||||
$oP->p("$sTaskClass: $sMessage");
|
||||
}
|
||||
$oEnd = new DateTime();
|
||||
$oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=40s", ' '.$sTaskClass.' '));
|
||||
}
|
||||
$aTasks[$oTask->Get('class_name')] = $oTask;
|
||||
}
|
||||
else if( ($aTasks[$sTaskClass]->Get('status') == 'active') && ($aTasks[$sTaskClass]->Get('next_run_date') <= $oNow->format('Y-m-d H:i:s')))
|
||||
|
||||
if( ($aTasks[$sTaskClass]->Get('status') == 'active') && ($aTasks[$sTaskClass]->Get('next_run_date') <= $oNow->format('Y-m-d H:i:s')))
|
||||
{
|
||||
$oTask = $aTasks[$sTaskClass];
|
||||
// Run the task and record its next run time
|
||||
if ($bVerbose)
|
||||
{
|
||||
$oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=40s", ' '.$sTaskClass.' '));
|
||||
}
|
||||
$sMessage = RunTask($oBackgroundProcess, $aTasks[$sTaskClass], $oNow, $iTimeLimit);
|
||||
$sMessage = RunTask($oProcess, $aTasks[$sTaskClass], $oNow, $iTimeLimit);
|
||||
if ($bVerbose)
|
||||
{
|
||||
if(!empty($sMessage))
|
||||
@@ -204,10 +241,14 @@ function DisplayStatus($oP)
|
||||
}
|
||||
$oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+');
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Main
|
||||
//
|
||||
|
||||
set_time_limit(0); // Some background actions may really take long to finish (like backup)
|
||||
|
||||
if (utils::IsModeCLI())
|
||||
{
|
||||
$oP = new CLIPage("iTop - CRON");
|
||||
@@ -260,21 +301,28 @@ if (!UserRights::IsAdministrator())
|
||||
exit -1;
|
||||
}
|
||||
|
||||
if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE))
|
||||
{
|
||||
$oP->p("A database maintenance is ongoing (read-only mode even for admins).");
|
||||
$oP->Output();
|
||||
exit -1;
|
||||
}
|
||||
|
||||
|
||||
// Enumerate classes implementing BackgroundProcess
|
||||
//
|
||||
$aBackgroundProcesses = array();
|
||||
$aProcesses = array();
|
||||
foreach(get_declared_classes() as $sPHPClass)
|
||||
{
|
||||
$oRefClass = new ReflectionClass($sPHPClass);
|
||||
$oExtensionInstance = null;
|
||||
if ($oRefClass->implementsInterface('iBackgroundProcess'))
|
||||
if ($oRefClass->implementsInterface('iProcess'))
|
||||
{
|
||||
if (is_null($oExtensionInstance))
|
||||
{
|
||||
$oExecInstance = new $sPHPClass;
|
||||
}
|
||||
$aBackgroundProcesses[$sPHPClass] = $oExecInstance;
|
||||
$aProcesses[$sPHPClass] = $oExecInstance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +332,7 @@ $bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */);
|
||||
if ($bVerbose)
|
||||
{
|
||||
$aDisplayProcesses = array();
|
||||
foreach ($aBackgroundProcesses as $oExecInstance)
|
||||
foreach ($aProcesses as $oExecInstance)
|
||||
{
|
||||
$aDisplayProcesses[] = get_class($oExecInstance);
|
||||
}
|
||||
@@ -318,7 +366,7 @@ elseif ($res === '1')
|
||||
// The current session holds the lock
|
||||
try
|
||||
{
|
||||
CronExec($oP, $aBackgroundProcesses, $bVerbose);
|
||||
CronExec($oP, $aProcesses, $bVerbose);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user