asynctask.class.inc.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <?php
  2. // Copyright (C) 2010-2012 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Persistent classes (internal): user defined actions
  20. *
  21. * @copyright Copyright (C) 2010-2012 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. class ExecAsyncTask implements iBackgroundProcess
  25. {
  26. public function GetPeriodicity()
  27. {
  28. return 2; // seconds
  29. }
  30. public function Process($iTimeLimit)
  31. {
  32. $sNow = date('Y-m-d H:i:s');
  33. // Criteria: planned, and expected to occur... ASAP or in the past
  34. $sOQL = "SELECT AsyncTask WHERE (status = 'planned') AND (ISNULL(planned) OR (planned < '$sNow'))";
  35. $iProcessed = 0;
  36. while (time() < $iTimeLimit)
  37. {
  38. // Next one ?
  39. $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array(), null, 1 /* limit count */);
  40. $oTask = $oSet->Fetch();
  41. if (is_null($oTask))
  42. {
  43. // Nothing to be done
  44. break;
  45. }
  46. $iProcessed++;
  47. if ($oTask->Process())
  48. {
  49. $oTask->DBDelete();
  50. }
  51. }
  52. return "processed $iProcessed tasks";
  53. }
  54. }
  55. /**
  56. * A
  57. *
  58. * @package iTopORM
  59. */
  60. abstract class AsyncTask extends DBObject
  61. {
  62. public static function Init()
  63. {
  64. $aParams = array
  65. (
  66. "category" => "core/cmdb",
  67. "key_type" => "autoincrement",
  68. "name_attcode" => array('created'),
  69. "state_attcode" => "",
  70. "reconc_keys" => array(),
  71. "db_table" => "priv_async_task",
  72. "db_key_field" => "id",
  73. "db_finalclass_field" => "realclass",
  74. "display_template" => "",
  75. );
  76. MetaModel::Init_Params($aParams);
  77. // Null is allowed to ease the migration from iTop 2.0.2 and earlier, when the status did not exist, and because the default value is not taken into account in the SQL definition
  78. // The value is set from null to planned in the setup program
  79. MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('planned,running,idle,error'), "sql"=>"status", "default_value"=>"planned", "is_null_allowed"=>true, "depends_on"=>array())));
  80. MetaModel::Init_AddAttribute(new AttributeDateTime("created", array("allowed_values"=>null, "sql"=>"created", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  81. MetaModel::Init_AddAttribute(new AttributeDateTime("started", array("allowed_values"=>null, "sql"=>"started", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  82. MetaModel::Init_AddAttribute(new AttributeDateTime("planned", array("allowed_values"=>null, "sql"=>"planned", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  83. MetaModel::Init_AddAttribute(new AttributeExternalKey("event_id", array("targetclass"=>"Event", "jointype"=> "", "allowed_values"=>null, "sql"=>"event_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
  84. MetaModel::Init_AddAttribute(new AttributeInteger("remaining_retries", array("allowed_values"=>null, "sql"=>"remaining_retries", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
  85. }
  86. /**
  87. * Every is fine
  88. */
  89. const OK = 0;
  90. /**
  91. * The task no longer exists
  92. */
  93. const DELETED = 1;
  94. /**
  95. * The task is already being executed
  96. */
  97. const ALREADY_RUNNING = 2;
  98. /**
  99. * The current process requests the ownership on the task.
  100. * In case the task can be accessed concurrently, this function can be overloaded to add a critical section.
  101. * The function must not block the caller if another process is already owning the task
  102. *
  103. * @return integer A code among OK/DELETED/ALREADY_RUNNING.
  104. */
  105. public function MarkAsRunning()
  106. {
  107. try
  108. {
  109. $this->Set('status', 'running');
  110. $this->Set('started', time());
  111. $this->DBUpdate();
  112. return self::OK;
  113. }
  114. catch(Exception $e)
  115. {
  116. // Corrupted task !! (for example: "Failed to reload object")
  117. IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage().' - fatal error, deleting the task.');
  118. if ($this->Get('event_id') != 0)
  119. {
  120. $oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
  121. $oEventLog->Set('message', 'Failed, corrupted data: '.$e->getMessage());
  122. $oEventLog->DBUpdate();
  123. }
  124. $this->DBDelete();
  125. return self::DELETED;
  126. }
  127. }
  128. public function GetRetryDelay()
  129. {
  130. $iRetryDelay = 600;
  131. $aRetries = MetaModel::GetConfig()->Get('async_task_retries', array());
  132. if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
  133. {
  134. $aConfig = $aRetries[get_class($this)];
  135. $iRetryDelay = $aConfig['retry_delay'];
  136. }
  137. return $iRetryDelay;
  138. }
  139. public function GetMaxRetries()
  140. {
  141. $iMaxRetries = 0;
  142. $aRetries = MetaModel::GetConfig()->Get('async_task_retries', array());
  143. if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
  144. {
  145. $aConfig = $aRetries[get_class($this)];
  146. $iMaxRetries = $aConfig['max_retries'];
  147. }
  148. }
  149. protected function OnInsert()
  150. {
  151. $this->Set('created', time());
  152. $this->Set('remaining_retries', $this->GetMaxRetries());
  153. }
  154. /**
  155. * @return boolean True if the task record can be deleted
  156. */
  157. public function Process()
  158. {
  159. // By default: consider that the task is not completed
  160. $bRet = false;
  161. // Attempt to take the ownership
  162. $iStatus = $this->MarkAsRunning();
  163. if ($iStatus == self::OK)
  164. {
  165. try
  166. {
  167. $sStatus = $this->DoProcess();
  168. if ($this->Get('event_id') != 0)
  169. {
  170. $oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
  171. $oEventLog->Set('message', $sStatus);
  172. $oEventLog->DBUpdate();
  173. }
  174. $bRet = true;
  175. }
  176. catch(Exception $e)
  177. {
  178. $iRemaining = $this->Get('remaining_retries');
  179. if ($iRemaining > 0)
  180. {
  181. $iRetryDelay = $this->GetRetryDelay();
  182. IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage().' - remaining retries: '.$iRemaining.' - next retry in '.$iRetryDelay.'s');
  183. $this->Set('remaining_retries', $iRemaining - 1);
  184. $this->Set('status', 'planned');
  185. $this->Set('started', null);
  186. $this->Set('planned', time() + $iRetryDelay);
  187. $this->DBUpdate();
  188. }
  189. else
  190. {
  191. IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage());
  192. }
  193. }
  194. }
  195. else
  196. {
  197. // Already done or being handled by another process... skip...
  198. $bRet = false;
  199. }
  200. return $bRet;
  201. }
  202. abstract public function DoProcess();
  203. }
  204. /**
  205. * An email notification
  206. *
  207. * @package iTopORM
  208. */
  209. class AsyncSendEmail extends AsyncTask
  210. {
  211. public static function Init()
  212. {
  213. $aParams = array
  214. (
  215. "category" => "core/cmdb",
  216. "key_type" => "autoincrement",
  217. "name_attcode" => "created",
  218. "state_attcode" => "",
  219. "reconc_keys" => array(),
  220. "db_table" => "priv_async_send_email",
  221. "db_key_field" => "id",
  222. "db_finalclass_field" => "",
  223. "display_template" => "",
  224. );
  225. MetaModel::Init_Params($aParams);
  226. MetaModel::Init_InheritAttributes();
  227. MetaModel::Init_AddAttribute(new AttributeInteger("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>Email::ORIGINAL_FORMAT, "is_null_allowed"=>false, "depends_on"=>array())));
  228. MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  229. MetaModel::Init_AddAttribute(new AttributeText("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  230. MetaModel::Init_AddAttribute(new AttributeLongText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  231. // Display lists
  232. // MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'test_recipient', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance', 'trigger_list')); // Attributes to be displayed for the complete details
  233. // MetaModel::Init_SetZListItems('list', array('name', 'status', 'to', 'subject')); // Attributes to be displayed for a list
  234. // Search criteria
  235. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  236. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  237. }
  238. static public function AddToQueue(EMail $oEMail, $oLog)
  239. {
  240. $oNew = MetaModel::NewObject(__class__);
  241. if ($oLog)
  242. {
  243. $oNew->Set('event_id', $oLog->GetKey());
  244. }
  245. $oNew->Set('to', $oEMail->GetRecipientTO(true /* string */));
  246. $oNew->Set('subject', $oEMail->GetSubject());
  247. // $oNew->Set('version', 1);
  248. // $sMessage = serialize($oEMail);
  249. $oNew->Set('version', 2);
  250. $sMessage = $oEMail->SerializeV2();
  251. $oNew->Set('message', $sMessage);
  252. $oNew->DBInsert();
  253. }
  254. public function DoProcess()
  255. {
  256. $sMessage = $this->Get('message');
  257. $iVersion = (int) $this->Get('version');
  258. switch($iVersion)
  259. {
  260. case Email::FORMAT_V2:
  261. $oEMail = Email::UnSerializeV2($sMessage);
  262. break;
  263. case Email::ORIGINAL_FORMAT:
  264. $oEMail = unserialize($sMessage);
  265. break;
  266. default:
  267. return 'Unknown version of the serialization format: '.$iVersion;
  268. }
  269. $iRes = $oEMail->Send($aIssues, true /* force synchro !!!!! */);
  270. switch ($iRes)
  271. {
  272. case EMAIL_SEND_OK:
  273. return "Sent";
  274. case EMAIL_SEND_PENDING:
  275. return "Bug - the email should be sent in synchronous mode";
  276. case EMAIL_SEND_ERROR:
  277. return "Failed: ".implode(', ', $aIssues);
  278. }
  279. }
  280. }
  281. ?>