PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/package/app/app/batch/batches/KBatchBase.class.php

https://bitbucket.org/pandaos/kaltura
PHP | 764 lines | 603 code | 65 blank | 96 comment | 11 complexity | bf6f1042d1373d0ce373fc19563c0019 MD5 | raw file
Possible License(s): AGPL-3.0, GPL-3.0, BSD-3-Clause, LGPL-2.1, GPL-2.0, LGPL-3.0, JSON, MPL-2.0-no-copyleft-exception, Apache-2.0
  1. <?php
  2. /**
  3. * @package Scheduler
  4. */
  5. require_once ("bootstrap.php");
  6. /**
  7. * Will be used as the base class for all the batch classes.
  8. *
  9. * @package Scheduler
  10. */
  11. abstract class KBatchBase extends KRunableClass implements IKalturaLogger
  12. {
  13. /**
  14. * @var KalturaClient
  15. */
  16. protected $kClient = null;
  17. /**
  18. * @var KalturaConfiguration
  19. */
  20. protected $kClientConfig = null;
  21. /**
  22. * @var boolean
  23. */
  24. protected $isUnitTest = false;
  25. /**
  26. * @var resource
  27. */
  28. protected $monitorHandle = null;
  29. /**
  30. * @return KalturaBatchJobType
  31. */
  32. protected abstract function getJobType();
  33. /**
  34. * @param KalturaBatchJob $job
  35. * @return KalturaBatchJob
  36. */
  37. protected abstract function exec(KalturaBatchJob $job);
  38. protected function init()
  39. {
  40. $this->saveQueueFilter($this->getJobType());
  41. }
  42. /* (non-PHPdoc)
  43. * @see KRunableClass::run()
  44. */
  45. public function run($jobs = null)
  46. {
  47. KalturaLog::info("Batch is running");
  48. if($this->taskConfig->isInitOnly())
  49. return $this->init();
  50. if(is_null($jobs))
  51. $jobs = $this->kClient->batch->getExclusiveJobs($this->getExclusiveLockKey(), $this->taskConfig->maximumExecutionTime, 1, $this->getFilter(), $this->getJobType());
  52. KalturaLog::info(count($jobs) . " jobs to perform");
  53. if(! count($jobs) > 0)
  54. {
  55. KalturaLog::info("Queue size: 0 sent to scheduler");
  56. $this->saveSchedulerQueue($this->getJobType());
  57. return null;
  58. }
  59. foreach($jobs as &$job)
  60. $job = $this->exec($job, $job->data);
  61. return $jobs;
  62. }
  63. /**
  64. * @param int $jobId
  65. * @param KalturaBatchJob $job
  66. * @return KalturaBatchJob
  67. */
  68. protected function updateExclusiveJob($jobId, KalturaBatchJob $job)
  69. {
  70. return $this->kClient->batch->updateExclusiveJob($jobId, $this->getExclusiveLockKey(), $job);
  71. }
  72. /**
  73. * @param KalturaBatchJob $job
  74. * @return KalturaBatchJob
  75. */
  76. protected function freeExclusiveJob(KalturaBatchJob $job)
  77. {
  78. $response = $this->kClient->batch->freeExclusiveJob($job->id, $this->getExclusiveLockKey(), $this->getJobType(), false);
  79. KalturaLog::info("Queue size: $response->queueSize sent to scheduler");
  80. $this->saveSchedulerQueue($this->getJobType(), $response->queueSize);
  81. return $response->job;
  82. }
  83. /**
  84. * @return int
  85. * @throws Exception
  86. */
  87. public static function getType()
  88. {
  89. throw new Exception("getType must be overidden");
  90. }
  91. /**
  92. * @param boolean $unitTest
  93. */
  94. public function setUnitTest($unitTest)
  95. {
  96. $this->isUnitTest = $unitTest;
  97. }
  98. /**
  99. * @return KalturaClient
  100. */
  101. protected function getClient()
  102. {
  103. return $this->kClient;
  104. }
  105. protected function impersonate($partnerId)
  106. {
  107. $this->kClientConfig->partnerId = $partnerId;
  108. $this->kClient->setConfig($this->kClientConfig);
  109. }
  110. protected function unimpersonate()
  111. {
  112. $this->kClientConfig->partnerId = $this->taskConfig->getPartnerId();
  113. $this->kClient->setConfig($this->kClientConfig);
  114. }
  115. /**
  116. * @return KalturaBatchJobFilter
  117. */
  118. protected function getFilter()
  119. {
  120. $filter = new KalturaBatchJobFilter();
  121. if($this->taskConfig->filter)
  122. $filter = $this->taskConfig->filter;
  123. if ($this->taskConfig->minCreatedAtMinutes && is_numeric($this->taskConfig->minCreatedAtMinutes))
  124. {
  125. $minCreatedAt = time() - ($this->taskConfig->minCreatedAtMinutes * 60);
  126. $filter->createdAtLessThanOrEqual = $minCreatedAt;
  127. }
  128. return $filter;
  129. }
  130. protected function getSchedulerId()
  131. {
  132. return $this->taskConfig->getSchedulerId();
  133. }
  134. protected function getSchedulerName()
  135. {
  136. return $this->taskConfig->getSchedulerName();
  137. }
  138. protected function getId()
  139. {
  140. return $this->taskConfig->id;
  141. }
  142. protected function getIndex()
  143. {
  144. return $this->taskConfig->getTaskIndex();
  145. }
  146. protected function getName()
  147. {
  148. return $this->taskConfig->name;
  149. }
  150. protected function getConfigHostName()
  151. {
  152. return $this->taskConfig->getHostName();
  153. }
  154. /**
  155. * @return KalturaExclusiveLockKey
  156. */
  157. protected function getExclusiveLockKey()
  158. {
  159. $lockKey = new KalturaExclusiveLockKey();
  160. $lockKey->schedulerId = $this->getSchedulerId();
  161. $lockKey->workerId = $this->getId();
  162. $lockKey->batchIndex = $this->getIndex();
  163. return $lockKey;
  164. }
  165. /**
  166. * @param KalturaBatchJob $job
  167. */
  168. protected function onFree(KalturaBatchJob $job)
  169. {
  170. $this->onJobEvent($job, KBatchEvent::EVENT_JOB_FREE);
  171. }
  172. /**
  173. * @param KalturaBatchJob $job
  174. */
  175. protected function onUpdate(KalturaBatchJob $job)
  176. {
  177. $this->onJobEvent($job, KBatchEvent::EVENT_JOB_UPDATE);
  178. }
  179. /**
  180. *
  181. */
  182. protected function onBatchUp()
  183. {
  184. $this->onEvent(KBatchEvent::EVENT_BATCH_UP);
  185. }
  186. /**
  187. *
  188. */
  189. protected function onBatchDown()
  190. {
  191. $this->onEvent(KBatchEvent::EVENT_BATCH_DOWN);
  192. }
  193. /**
  194. * @param string $file
  195. * @param int $size
  196. * @param int $event_id
  197. */
  198. protected function onFileEvent($file, $size, $event_id)
  199. {
  200. $event = new KBatchEvent();
  201. $event->value_1 = $size;
  202. $event->value_2 = $file;
  203. $this->onEvent($event_id, $event);
  204. }
  205. /**
  206. * @param int $event_id
  207. * @param KBatchEvent $event
  208. */
  209. protected function onEvent($event_id, KBatchEvent $event = null)
  210. {
  211. if(is_null($event))
  212. $event = new KBatchEvent();
  213. $event->batch_client_version = "1.0";
  214. $event->batch_event_time = time();
  215. $event->batch_event_type_id = $event_id;
  216. $event->batch_session_id = $this->sessionKey;
  217. $event->batch_id = $this->getIndex();
  218. $event->batch_name = $this->getName();
  219. $event->section_id = $this->getId();
  220. $event->batch_type = $this->getType();
  221. $event->location_id = $this->getSchedulerId();
  222. $event->host_name = $this->getSchedulerName();
  223. KDwhClient::send($event);
  224. }
  225. /**
  226. * @param KalturaBatchJob $job
  227. * @param int $event_id
  228. */
  229. protected function onJobEvent(KalturaBatchJob $job, $event_id)
  230. {
  231. $event = new KBatchEvent();
  232. $event->partner_id = $job->partnerId;
  233. $event->entry_id = $job->entryId;
  234. $event->bulk_upload_id = $job->bulkJobId;
  235. $event->batch_parant_id = $job->parentJobId;
  236. $event->batch_root_id = $job->rootJobId;
  237. $event->batch_status = $job->status;
  238. $event->batch_progress = $job->progress;
  239. $this->onEvent($event_id, $event);
  240. }
  241. /**
  242. * @param KSchedularTaskConfig $taskConfig
  243. */
  244. public function __construct($taskConfig = null)
  245. {
  246. parent::__construct($taskConfig);
  247. KalturaLog::debug('This batch index: ' . $this->getIndex());
  248. KalturaLog::debug('This session key: ' . $this->sessionKey);
  249. $this->kClientConfig = new KalturaConfiguration($this->taskConfig->getPartnerId());
  250. $this->kClientConfig->setLogger($this);
  251. $this->kClientConfig->serviceUrl = $this->taskConfig->getServiceUrl();
  252. $this->kClientConfig->curlTimeout = $this->taskConfig->getCurlTimeout();
  253. $this->kClientConfig->clientTag = 'batch: ' . $this->taskConfig->getSchedulerName();
  254. $this->kClient = new KalturaClient($this->kClientConfig);
  255. //$ks = $this->kClient->session->start($secret, "user-2", KalturaSessionType::ADMIN);
  256. $ks = $this->createKS();
  257. $this->kClient->setKs($ks);
  258. KDwhClient::setFileName($this->taskConfig->getDwhPath());
  259. $this->onBatchUp();
  260. KScheduleHelperManager::saveRunningBatch($this->taskConfig->getCommandsDir(), $this->getName(), $this->getIndex());
  261. }
  262. /**
  263. * @return string
  264. */
  265. private function createKS()
  266. {
  267. $partnerId = $this->taskConfig->getPartnerId();
  268. $sessionType = KalturaSessionType::ADMIN;
  269. $puserId = 'batchUser';
  270. $privileges = '';
  271. $adminSecret = $this->taskConfig->getSecret();
  272. $expiry = 60 * 60 * 24 * 30; // 30 days
  273. $rand = rand(0, 32000);
  274. $rand = microtime(true);
  275. $expiry = time() + $expiry;
  276. $masterPartnerId = $this->taskConfig->getPartnerId();
  277. $additionalData = null;
  278. $fields = array($partnerId, '', $expiry, $sessionType, $rand, $puserId, $privileges, $masterPartnerId, $additionalData);
  279. $str = implode(";", $fields);
  280. $salt = $adminSecret;
  281. $hashed_str = $this->hash($salt, $str) . "|" . $str;
  282. $decoded_str = base64_encode($hashed_str);
  283. return $decoded_str;
  284. }
  285. /**
  286. * @param string $salt
  287. * @param string $str
  288. * @return string
  289. */
  290. private function hash($salt, $str)
  291. {
  292. return sha1($salt . $str);
  293. }
  294. /**
  295. * @param string $localPath
  296. * @return string
  297. */
  298. protected function translateLocalPath2Shared($localPath)
  299. {
  300. $search = array();
  301. $replace = array();
  302. if(!is_null($this->taskConfig->baseLocalPath) || !is_null($this->taskConfig->baseSharedPath))
  303. {
  304. $search[] = $this->taskConfig->baseLocalPath;
  305. $replace[] = $this->taskConfig->baseSharedPath;
  306. }
  307. if(!is_null($this->taskConfig->baseTempLocalPath) || !is_null($this->taskConfig->baseTempSharedPath))
  308. {
  309. $search[] = $this->taskConfig->baseTempLocalPath;
  310. $replace[] = $this->taskConfig->baseTempSharedPath;
  311. }
  312. $search[] = '\\';
  313. $replace[] = '/';
  314. return str_replace($search, $replace, $localPath);
  315. }
  316. /**
  317. * @param string $sharedPath
  318. * @return string
  319. */
  320. protected function translateSharedPath2Local($sharedPath)
  321. {
  322. $search = array();
  323. $replace = array();
  324. if(!is_null($search) || !is_null($replace))
  325. {
  326. $search = $this->taskConfig->baseSharedPath;
  327. $replace = $this->taskConfig->baseLocalPath;
  328. }
  329. return str_replace($search, $replace, $sharedPath);
  330. }
  331. /**
  332. * @param array $files array(0 => array('name' => [name], 'path' => [path], 'size' => [size]), 1 => array('name' => [name], 'path' => [path], 'size' => [size]))
  333. * @return string
  334. */
  335. protected function checkFilesArrayExist(array $files)
  336. {
  337. foreach($files as $file)
  338. if(!$this->checkFileExists($file['path'], $file['size']))
  339. return false;
  340. return true;
  341. }
  342. protected static function foldersize($path)
  343. {
  344. if(!file_exists($path)) return 0;
  345. if(is_file($path)) return filesize($path);
  346. $ret = 0;
  347. foreach(glob($path."/*") as $fn)
  348. $ret += KBatchBase::foldersize($fn);
  349. return $ret;
  350. }
  351. /**
  352. * @param string $file
  353. * @param int $size
  354. * @return bool
  355. */
  356. protected function checkFileExists($file, $size = null)
  357. {
  358. if($this->isUnitTest)
  359. return true;
  360. // If this is not a file but a directory, certain operations should be done diffrently:
  361. // - size calcultions
  362. // - the response from the client (to check the client size beaviour)
  363. $directorySync = is_dir($file);
  364. KalturaLog::info("Check File Exists[$file] size[$size]");
  365. if(! $size)
  366. {
  367. clearstatcache();
  368. if($directorySync)
  369. $size=KBatchBase::foldersize($file);
  370. else
  371. $size = filesize($file);
  372. if(! $size)
  373. return false;
  374. }
  375. $retries = ($this->taskConfig->fileExistReties ? $this->taskConfig->fileExistReties : 1);
  376. $interval = ($this->taskConfig->fileExistInterval ? $this->taskConfig->fileExistInterval : 5);
  377. while($retries > 0)
  378. {
  379. $check = $this->kClient->batch->checkFileExists($file, $size);
  380. // In case of directorySync - do not check client sizeOk - to be revised
  381. if($check->exists && ($check->sizeOk || $directorySync))
  382. {
  383. $this->onFileEvent($file, $size, KBatchEvent::EVENT_FILE_EXISTS);
  384. return true;
  385. }
  386. $this->onFileEvent($file, $size, KBatchEvent::EVENT_FILE_DOESNT_EXIST);
  387. sleep($interval);
  388. $retries --;
  389. }
  390. return false;
  391. }
  392. public function __destruct()
  393. {
  394. $this->onBatchDown();
  395. KScheduleHelperManager::unlinkRunningBatch($this->taskConfig->getCommandsDir(), $this->getName(), $this->getIndex());
  396. }
  397. /**
  398. * @param string $jobType
  399. * @param boolean $isCloser
  400. * @return KalturaWorkerQueueFilter
  401. */
  402. public function getQueueFilter($jobType, $isCloser = false)
  403. {
  404. $filter = $this->getFilter();
  405. $filter->jobTypeEqual = $jobType;
  406. if($isCloser)
  407. {
  408. $filter->statusEqual = KalturaBatchJobStatus::ALMOST_DONE;
  409. }
  410. else
  411. {
  412. $filter->statusIn = KalturaBatchJobStatus::PENDING . ',' . KalturaBatchJobStatus::RETRY;
  413. }
  414. $workerQueueFilter = new KalturaWorkerQueueFilter();
  415. $workerQueueFilter->schedulerId = $this->getSchedulerId();
  416. $workerQueueFilter->workerId = $this->getId();
  417. $workerQueueFilter->filter = $filter;
  418. $workerQueueFilter->jobType = $jobType;
  419. return $workerQueueFilter;
  420. }
  421. /**
  422. * @param int $jobType
  423. * @param boolean $isCloser
  424. */
  425. public function saveQueueFilter($jobType, $isCloser = false)
  426. {
  427. $filter = $this->getQueueFilter($jobType, $isCloser);
  428. $dir = $this->taskConfig->getQueueFiltersDir();
  429. $type = $this->taskConfig->name;
  430. $res = self::createDir($dir);
  431. if(! $res)
  432. return;
  433. $path = "$dir/$type.flt";
  434. KalturaLog::debug("Saving filter to $path: " . print_r($filter, true));
  435. KScheduleHelperManager::saveFilter($path, $filter);
  436. }
  437. /**
  438. * @param int $jobType
  439. * @param int $size
  440. * @param boolean $isCloser
  441. */
  442. public function saveSchedulerQueue($jobType, $size = null, $isCloser = false)
  443. {
  444. if(is_null($size))
  445. {
  446. $workerQueueFilter = $this->getQueueFilter($jobType, $isCloser);
  447. $size = $this->kClient->batch->getQueueSize($workerQueueFilter);
  448. }
  449. $queueStatus = new KalturaBatchQueuesStatus();
  450. $queueStatus->workerId = $this->getId();
  451. $queueStatus->jobType = $jobType;
  452. $queueStatus->size = $size;
  453. $this->saveSchedulerCommands(array($queueStatus));
  454. }
  455. /**
  456. * @param array $commands
  457. */
  458. public function saveSchedulerCommands(array $commands)
  459. {
  460. $dir = $this->taskConfig->getCommandsDir();
  461. $type = $this->taskConfig->type;
  462. $res = self::createDir($dir);
  463. if(! $res)
  464. return;
  465. $path = "$dir/$type.cmd";
  466. KScheduleHelperManager::saveCommands($path, $commands);
  467. }
  468. /**
  469. * @param string $path
  470. * @param int $rights
  471. * @return NULL|string
  472. */
  473. public static function createDir($path, $rights = 0777)
  474. {
  475. if(! is_dir($path))
  476. {
  477. if(! file_exists($path))
  478. {
  479. KalturaLog::info("Creating temp directory [$path]");
  480. mkdir($path, $rights, true);
  481. }
  482. else
  483. {
  484. // already exists but not a directory
  485. KalturaLog::err("Cannot create temp directory [$path] due to an error. Please fix and restart");
  486. return null;
  487. }
  488. }
  489. return $path;
  490. }
  491. /**
  492. * @return KalturaBatchJob
  493. */
  494. protected function newEmptyJob()
  495. {
  496. return new KalturaBatchJob();
  497. }
  498. /**
  499. * @param KalturaBatchJob $job
  500. * @param string $msg
  501. * @param int $status
  502. * @param int $progress
  503. * @param unknown_type $data
  504. * @param boolean $remote
  505. * @return KalturaBatchJob
  506. */
  507. protected function updateJob(KalturaBatchJob $job, $msg, $status, $progress = null, KalturaJobData $data = null, $remote = null)
  508. {
  509. $updateJob = $this->newEmptyJob();
  510. if(! is_null($msg))
  511. {
  512. $updateJob->message = $msg;
  513. $updateJob->description = $job->description . "\n$msg";
  514. }
  515. $updateJob->status = $status;
  516. $updateJob->progress = $progress;
  517. $updateJob->data = $data;
  518. $updateJob->lastWorkerRemote = $remote;
  519. KalturaLog::info("job[$job->id] status: [$status] msg : [$msg]");
  520. if($this->isUnitTest)
  521. return $job;
  522. $job = $this->updateExclusiveJob($job->id, $updateJob);
  523. if($job instanceof KalturaBatchJob)
  524. $this->onUpdate($job);
  525. return $job;
  526. }
  527. /**
  528. * @param KalturaBatchJob $job
  529. * @param int $errType
  530. * @param int $errNumber
  531. * @param string $msg
  532. * @param int $status
  533. * @param KalturaJobData $data
  534. * @return KalturaBatchJob
  535. */
  536. protected function closeJob(KalturaBatchJob $job, $errType, $errNumber, $msg, $status, $data = null)
  537. {
  538. if(! is_null($errType))
  539. KalturaLog::err($msg);
  540. $updateJob = $this->newEmptyJob();
  541. if(! is_null($msg))
  542. {
  543. $updateJob->message = $msg;
  544. $updateJob->description = $job->description . "\n$msg";
  545. }
  546. if($status == KalturaBatchJobStatus::FINISHED)
  547. $updateJob->progress = 100;
  548. $updateJob->status = $status;
  549. $updateJob->errType = $errType;
  550. $updateJob->errNumber = $errNumber;
  551. $updateJob->data = $data;
  552. KalturaLog::info("job[$job->id] status: [$status] msg : [$msg]");
  553. if($this->isUnitTest)
  554. {
  555. $job->status = $updateJob->status;
  556. $job->message = $updateJob->message;
  557. $job->description = $updateJob->description;
  558. $job->errType = $updateJob->errType;
  559. $job->errNumber = $updateJob->errNumber;
  560. return $job;
  561. }
  562. $job = $this->updateExclusiveJob($job->id, $updateJob);
  563. if($job instanceof KalturaBatchJob)
  564. $this->onUpdate($job);
  565. $this->onUpdate($job);
  566. KalturaLog::info("Free job[$job->id]");
  567. $job = $this->freeExclusiveJob($job);
  568. if($job instanceof KalturaBatchJob)
  569. $this->onFree($job);
  570. return $job;
  571. }
  572. protected function getMonitorPath()
  573. {
  574. return 'killer/KBatchKillerExe.php';
  575. }
  576. protected function startMonitor(array $files)
  577. {
  578. if($this->monitorHandle && is_resource($this->monitorHandle))
  579. return;
  580. $killConfig = new KBatchKillerConfig();
  581. $killConfig->pid = getmypid();
  582. $killConfig->maxIdleTime = $this->taskConfig->getMaxIdleTime();
  583. $killConfig->sleepTime = $this->taskConfig->getMaxIdleTime() / 2;
  584. /*
  585. Do not run killer process w/out set config->maxIdle
  586. */
  587. if($killConfig->maxIdleTime<=0 || is_null($killConfig->maxIdleTime) ) {
  588. KalturaLog::info(__METHOD__.': The MaxIdleTime is not set properly. The Killer job will not run');
  589. return;
  590. }
  591. $killConfig->files = $files;
  592. //$killConfig->files = array("/root/anatol/0_phxt8hsa.api.log");
  593. $killConfig->sessionKey = $this->sessionKey;
  594. $killConfig->batchIndex = $this->getIndex();
  595. $killConfig->batchName = $this->getName();
  596. $killConfig->workerId = $this->getId();
  597. $killConfig->workerType = $this->getType();
  598. $killConfig->schedulerId = $this->getSchedulerId();
  599. $killConfig->schedulerName = $this->getSchedulerName();
  600. $killConfig->dwhPath = $this->taskConfig->getDwhPath();
  601. $phpPath = 'php'; // TODO - get it from somewhere
  602. $killerPath = $this->getMonitorPath();
  603. $killerPathStr = base64_encode(serialize($killConfig));
  604. $cmdLine = "$phpPath $killerPath $killerPathStr";
  605. $descriptorspec = array(); // stdin is a pipe that the child will read from
  606. $other_options = array('suppress_errors' => FALSE, 'bypass_shell' => FALSE);
  607. KalturaLog::debug("Killer config:\n" . print_r($killConfig, true));
  608. KalturaLog::debug("Now executing [$cmdLine]");
  609. KalturaLog::info('Starting monitor');
  610. $this->monitorHandle = proc_open($cmdLine, $descriptorspec, $pipes, null, null, $other_options);
  611. }
  612. protected function stopMonitor()
  613. {
  614. if(!$this->monitorHandle || !is_resource($this->monitorHandle))
  615. return;
  616. KalturaLog::info('Stoping monitor');
  617. $status = proc_get_status($this->monitorHandle);
  618. if($status['running'] == true)
  619. {
  620. proc_terminate($this->monitorHandle, 9); //9 is the SIGKILL signal
  621. proc_close($this->monitorHandle);
  622. $pid = $status['pid'];
  623. if(function_exists('posix_kill'))
  624. {
  625. posix_kill($pid, 9);
  626. }
  627. else
  628. {
  629. exec("kill -9 $pid", $output); // for linux
  630. //exec("taskkill -F -PID $pid", $output); // for windows
  631. }
  632. }
  633. $this->monitorHandle = null;
  634. }
  635. function log($message)
  636. {
  637. KalturaLog::log($message);
  638. }
  639. }