AsyncJobs.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <?php
  2. Lib::loadClass('RouteBase');
  3. Lib::loadClass('Core_AsyncJobs');
  4. class Route_AsyncJobs extends RouteBase {
  5. function isInstalled() { return Core_AsyncJobs::isInstalled(); }
  6. function getSbinPath() { return realpath(APP_PATH_ROOT . "/../sbin/"); }
  7. function defaultAction() { UI::layout([ $this, 'defaultView' ]); }
  8. function defaultView() {
  9. echo UI::h('h1', [], "Async jobs (wps server)");
  10. if (!User::isAdmin()) throw new Exception("Access Denied");
  11. DBG::nicePrint(Config::getConfFile(), "Config::getConfFile()");
  12. try {
  13. Core_AsyncJobs::isInstalled();
  14. } catch (Exception $e) {
  15. UI::alert('danger', $e->getMessage());
  16. }
  17. echo UI::h('div', [ 'style' => "padding:12px 0; border-bottom:1px solid #ddd" ], [
  18. UI::hButtonPost("Check", [
  19. 'class' => "btn btn-default",
  20. 'title' => "check if async process manager is working",
  21. 'data' => [
  22. '_route' => 'AsyncJobs',
  23. '_postTask' => "pm2Check",
  24. ]
  25. ]),
  26. " ",
  27. UI::h('a', [
  28. 'href' => $this->getLink('list'),
  29. 'class' => "btn btn-default",
  30. ], "Lista"),
  31. " ",
  32. UI::hButtonPost("pm2 list", [
  33. 'class' => "btn btn-warning",
  34. 'data' => [
  35. '_route' => 'AsyncJobs',
  36. '_postTask' => "pm2List",
  37. ]
  38. ]),
  39. " ",
  40. UI::hButtonPost("pm2 kill", [
  41. 'class' => "btn btn-warning",
  42. 'data' => [
  43. '_route' => 'AsyncJobs',
  44. '_postTask' => "pm2Kill",
  45. ]
  46. ]),
  47. " ",
  48. UI::hButtonPost("pm2 run1", [
  49. 'class' => "btn btn-warning",
  50. 'data' => [
  51. '_route' => 'AsyncJobs',
  52. '_postTask' => "mp2Start1",
  53. ]
  54. ]),
  55. " ",
  56. UI::hButtonPost("pm2 run2", [
  57. 'class' => "btn btn-warning",
  58. 'data' => [
  59. '_route' => 'AsyncJobs',
  60. '_postTask' => "mp2Start2",
  61. ]
  62. ]),
  63. " ",
  64. UI::hButtonPost("pm2 run3", [
  65. 'class' => "btn btn-warning",
  66. 'data' => [
  67. '_route' => 'AsyncJobs',
  68. '_postTask' => "mp2Start3",
  69. ]
  70. ]),
  71. " ",
  72. UI::hButtonPost("pm2 delete stopped", [
  73. 'class' => "btn btn-warning",
  74. 'data' => [
  75. '_route' => 'AsyncJobs',
  76. '_postTask' => "mp2DeleteStopped",
  77. ]
  78. ]),
  79. " ",
  80. UI::hButtonPost("reinstall pm2-www", [
  81. 'class' => "btn btn-warning",
  82. 'data' => [
  83. '_route' => 'AsyncJobs',
  84. '_postTask' => "testReinstallPm2ByWWW",
  85. ]
  86. ]),
  87. " ",
  88. UI::hButtonPost("Test", [
  89. 'class' => "btn btn-warning",
  90. 'title' => "Test server permissions",
  91. 'data' => [
  92. '_route' => 'AsyncJobs',
  93. '_postTask' => "makeTests",
  94. ]
  95. ]),
  96. ]);
  97. $postTask = V::get('_postTask', '', $_POST);
  98. switch ($postTask) {
  99. case "pm2Check": UI::tryCatchView([ $this, 'pm2CheckPostTask' ]); break;
  100. case "pm2List": UI::tryCatchView([ $this, 'pm2ListPostTask' ]); break;
  101. case "pm2Kill": UI::tryCatchView([ $this, 'pm2KillPostTask' ]); break;
  102. case "mp2Start1": UI::tryCatchView([ $this, 'mp2Start1PostTask' ]); break;
  103. case "mp2Start2": UI::tryCatchView([ $this, 'mp2Start2PostTask' ]); break;
  104. case "mp2Start3": UI::tryCatchView([ $this, 'mp2Start3PostTask' ]); break;
  105. case "mp2DeleteStopped": UI::tryCatchView([ $this, 'mp2DeleteStoppedPostTask' ]); break;
  106. case "testReinstallPm2ByWWW": UI::tryCatchView([ $this, 'testReinstallPm2ByWWWPostTask' ]); break;
  107. case "makeTests": UI::tryCatchView([ $this, 'makeTestsPostTask' ]); break;
  108. }
  109. }
  110. function pm2CheckPostTask() {
  111. $cmd = "pm2 ping";
  112. V::exec($cmd, $out, $ret);
  113. echo "cmd: <code>{$cmd}</code><br>RETURN CODE: '{$ret}'<br><pre>OUTPUT:\n" . implode("\n", $out) . "</pre>";
  114. // expected: end($out) === "{ msg: 'pong' }"
  115. }
  116. function listAction() { UI::layout([ $this, 'listView' ]); }
  117. function listView() {
  118. echo UI::h('ol', [ 'class' => "breadcrumb", 'style' => "font-size:14px" ], [
  119. UI::h('li', [], [
  120. UI::h('i', [ 'class' => "glyphicon glyphicon-home" ]),
  121. " ",
  122. UI::h('a', [ 'href' => $this->getLink() ], "Async jobs"),
  123. ]),
  124. UI::h('li', [ 'class' => "active" ], [
  125. "Lista"
  126. ]),
  127. UI::h('a', [
  128. 'href' => $this->getLink('list'),
  129. 'class' => "pull-right btn btn-link",
  130. 'style' => "padding:0; line-height:1em",
  131. ], "odśwież"),
  132. ]);
  133. $postTask = V::get('_postTask', '', $_POST);
  134. switch ($postTask) {
  135. case "stopJob": UI::tryCatchView([ $this, 'stopJobPostTask' ]); break;
  136. case "startJob": UI::tryCatchView([ $this, 'startJobPostTask' ]); break;
  137. }
  138. echo UI::hTable([
  139. 'rows' => array_map([ $this, 'viewTableRowAsyncJob' ], Core_AsyncJobs::getSimpleList()),
  140. ]);
  141. }
  142. function stopJobPostTask() {
  143. $idJob = V::get('idJob', 0, $_POST, 'int'); // $jobInfo['pm_id'],
  144. $name = V::get('name', '', $_POST); // $jobInfo['name'],
  145. if ($idJob < 0) throw new Exception("Wrong param idJob");
  146. if ($idJob === 0 && $_POST['idJob'] !== '0') throw new Exception("Wrong param idJob 0");
  147. Core_AsyncJobs::stopJob($idJob);
  148. }
  149. function startJobPostTask() {
  150. $idJob = V::get('idJob', 0, $_POST, 'int'); // $jobInfo['pm_id'],
  151. $name = V::get('name', '', $_POST); // $jobInfo['name'],
  152. if ($idJob < 0) throw new Exception("Wrong param idJob");
  153. if ($idJob === 0 && $_POST['idJob'] !== '0') throw new Exception("Wrong param idJob 0");
  154. Core_AsyncJobs::startJob($idJob);
  155. }
  156. function pm2ListPostTask() {
  157. $cmd = "pm2 list";
  158. V::exec($cmd, $out, $ret);
  159. echo "cmd: <code>{$cmd}</code><br>RETURN CODE: '{$ret}'<br><pre>OUTPUT:\n" . implode("\n", $out) . "</pre>";
  160. $cmd = "pm2 jlist";
  161. V::exec($cmd, $out, $ret);
  162. echo "cmd: <code>{$cmd}</code><br>RETURN CODE: '{$ret}'<br><pre>OUTPUT:\n" . implode("\n", $out) . "</pre>";
  163. echo '<hr style="margin:24px auto">';
  164. $parsedJobList = Core_AsyncJobs::getFullList();
  165. DBG::nicePrint($parsedJobList, "\$parsedJobList");
  166. echo '<hr style="margin:24px auto">';
  167. UI::table([
  168. 'rows' => array_map([ $this, 'viewTableRowAsyncJob' ], Core_AsyncJobs::getSimpleList()),
  169. ]);
  170. }
  171. function viewTableRowAsyncJob($jobInfo) {
  172. return [
  173. 'name' => $jobInfo['name'],
  174. 'pid' => $jobInfo['pid'],
  175. 'pm_id' => $jobInfo['pm_id'],
  176. 'status' => $jobInfo['pm2_env.status'],
  177. 'memory' => $jobInfo['monit.memory'],
  178. 'cpu' => $jobInfo['monit.cpu'],
  179. '#' => UI::h(null, [], [
  180. UI::h('a', [
  181. 'href' => $this->getLink('jobLog', [
  182. 'idJob' => $jobInfo['pm_id'],
  183. 'name' => $jobInfo['name'],
  184. ]),
  185. 'class' => "btn btn-xs btn-warning",
  186. ], "logi"),
  187. " ",
  188. UI::hButtonPost("stop", [
  189. 'class' => "btn btn-xs btn-default",
  190. 'data' => [
  191. '_route' => 'AsyncJobs',
  192. '_postTask' => "stopJob",
  193. 'idJob' => $jobInfo['pm_id'],
  194. 'name' => $jobInfo['name'],
  195. ]
  196. ]),
  197. " ",
  198. UI::hButtonPost("start", [
  199. 'class' => "btn btn-xs btn-default",
  200. 'data' => [
  201. '_route' => 'AsyncJobs',
  202. '_postTask' => "startJob",
  203. 'idJob' => $jobInfo['pm_id'],
  204. 'name' => $jobInfo['name'],
  205. ]
  206. ]),
  207. ]),
  208. ];
  209. }
  210. function jobLogAction() { UI::layout([ $this, 'jobLogView' ]); }
  211. function jobLogView() {
  212. $idJob = V::get('idJob', 0, $_GET, 'int'); // $jobInfo['pm_id'],
  213. $name = V::get('name', '', $_GET); // $jobInfo['name'],
  214. echo UI::h('ol', [ 'class' => "breadcrumb", 'style' => "font-size:14px" ], [
  215. UI::h('li', [], [
  216. UI::h('i', [ 'class' => "glyphicon glyphicon-home" ]),
  217. " ",
  218. UI::h('a', [ 'href' => $this->getLink() ], "Async jobs"),
  219. ]),
  220. UI::h('li', [], [
  221. UI::h('a', [ 'href' => $this->getLink('list') ], "Lista"),
  222. ]),
  223. UI::h('li', [ 'class' => "active" ], [
  224. "Log dla '{$idJob}'",
  225. ]),
  226. ]);
  227. if ($idJob < 0) throw new Exception("Wrong param idJob");
  228. if ($idJob === 0 && $_GET['idJob'] !== '0') throw new Exception("Wrong param idJob 0");
  229. $out = Core_AsyncJobs::getJobLogs($idJob, 10);
  230. echo UI::h('div', [ 'style' => "background-color:#ddd; padding:4px 12px" ], "Logi dla zadania '{$idJob}'");
  231. echo UI::h('pre', [ 'style' => "border:1px solid #ddd; border-radius:0; padding:12px" ], $out);
  232. }
  233. function pm2KillPostTask() {
  234. $cmd = "pm2 kill";
  235. V::exec($cmd, $out, $ret);
  236. echo "cmd: <code>{$cmd}</code><br>RETURN CODE: '{$ret}'<br><pre>OUTPUT:\n" . implode("\n", $out) . "</pre>";
  237. }
  238. function mp2Start1PostTask() {
  239. $cmd = "pm2 start " . APP_PATH_ROOT . "/../sbin/test-sleep-loop.php --name='test-job-1'";
  240. V::exec($cmd . " 2>&1", $out, $ret);
  241. echo "cmd: <code>{$cmd}</code><br>RETURN CODE: '{$ret}'<br><pre>OUTPUT:\n" . implode("\n", $out) . "</pre>";
  242. }
  243. function mp2Start2PostTask() {
  244. $cmd = "pm2 start " . APP_PATH_ROOT . "/../sbin/test-sleep-loop.php --name='test-job-2'";
  245. V::exec($cmd . " 2>&1", $out, $ret);
  246. echo "cmd: <code>{$cmd}</code><br>RETURN CODE: '{$ret}'<br><pre>OUTPUT:\n" . implode("\n", $out) . "</pre>";
  247. }
  248. function mp2Start3PostTask() {
  249. $jobName = implode("&", [
  250. "ant=default_db.in7_dziennik_koresp/test-bash",
  251. "task=test-loop",
  252. "ns=default_db:IN7_DZIENNIK_KORESP",
  253. "pk=66263",
  254. ]);
  255. $jobNamespace = "p5";
  256. $cmd = implode(" ", [
  257. "pm2 start",
  258. APP_PATH_ROOT . "/../sbin/test-sleep-loop.php",
  259. "--name='{$jobName}'",
  260. "--namespace='{$jobNamespace}'",
  261. ]);
  262. V::exec($cmd . " 2>&1", $out, $ret);
  263. echo "cmd: <code>{$cmd}</code><br>RETURN CODE: '{$ret}'<br><pre>OUTPUT:\n" . implode("\n", $out) . "</pre>";
  264. }
  265. function mp2DeleteStoppedPostTask() {
  266. Core_AsyncJobs::deleteStopped();
  267. UI::alert("DONE");
  268. }
  269. function makeTestsPostTask() {
  270. $seBinPath = $this->getSbinPath();
  271. $listTest = []; // [ [ ret_code, cmd ], ... ]
  272. $listTest[] = [ 0, "cd '{$seBinPath}' && bash ./se.sh test-exit-0" ];
  273. $listTest[] = [ 1, "cd '{$seBinPath}' && bash ./se.sh test-exit-1" ];
  274. $listTest[] = [ 1, "cd '{$seBinPath}' && bash ./se.sh test-sudo" ];
  275. $listTest[] = [ 0, "cd '{$seBinPath}' && bash ./se.sh --sudo test-sudo" ];
  276. // $cmd = "cd '{$seBinPath}' && bash se.sh install-pm2-www"; // DBG: require root, add prefix 'sudo--'
  277. // $cmd = "cd '{$seBinPath}' && bash ./se.sh --sudo install-pm2-www";
  278. // $cmd = "cd '{$seBinPath}' && bash se.sh install-pm2-www"; // expected Permission denied - missing sudo
  279. // $cmd = "cd '{$seBinPath}' && bash se.sh sudo "; // expected Missing script name + usage
  280. // $cmd = "cd '{$seBinPath}' && bash se.sh non-existing-script"; // expecte Module not exists + usage
  281. // $cmd = "cd '{$seBinPath}' && bash se.sh "; // expected usage
  282. foreach ($listTest as $idx => $test) {
  283. list($expected, $cmd) = $test;
  284. $out = [];
  285. V::exec($cmd = "{$cmd} 2>&1", $out, $ret);
  286. echo UI::h('details', [], [
  287. UI::h('summary', [], "test {$idx} " . (($expected === $ret) ? "SUCCESS" : "FAILED")),
  288. UI::h('div', [], [
  289. UI::h('pre', [], "RET({$ret}). OUTPUT:" . "\n" . implode("\n", $out)),
  290. UI::h('div', [ 'class' => "alert alert-info" ], "test ({$ret})" . "\n<br>" . implode("\n<br>", $out) ),
  291. ($expected === $ret)
  292. ? UI::h('div', [ 'class' => 'alert alert-success' ], "OK: script return as expected!")
  293. : UI::h('div', [ 'class' => 'alert alert-danger' ], "FAIL: script return '{$ret}' but expected '{$expected}'!")
  294. ,
  295. ]),
  296. ]);
  297. }
  298. }
  299. function testReinstallPm2ByWWWPostTask() {
  300. $seBinPath = $this->getSbinPath();
  301. $out = [];
  302. $cmd = "cd '{$seBinPath}' && bash ./se.sh --sudo install-pm2-www";
  303. V::exec($cmd = "{$cmd} 2>&1", $out, $ret);
  304. echo UI::h('pre', [], "RET({$ret}). OUTPUT:" . "\n" . implode("\n", $out));
  305. if (0 !== $ret) throw new Exception( (empty($out)) ? "Error: install pm2-www failed! ({$ret})" : implode("\n<br>", $out) );
  306. if (!file_exists('/usr/local/bin/pm2-www')) {
  307. throw new Exception("Nie udało się zainstalować pm2-www");
  308. } else {
  309. throw new AlertSuccessException("Zainstalowano pm2-www");
  310. }
  311. }
  312. }