HMC.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. <?php
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. */
  19. include_once '../util/Logger.php';
  20. include_once '../conf/Config.inc';
  21. include_once "../util/lock.php";
  22. include_once '../db/HMCDBAccessor.php';
  23. include_once "../util/HMCTxnUtils.php";
  24. /**
  25. * Interface between UI and install framework layer.
  26. */
  27. class HMC {
  28. private $dbHandle;
  29. private $dbPath;
  30. private $logger;
  31. private $clusterName;
  32. private $command;
  33. function __construct($dbPath, $clusterName) {
  34. $this->dbPath = $dbPath;
  35. $this->clusterName = $clusterName;
  36. $this->logger = new HMCLogger("HMC");
  37. $this->dbHandle = new HMCDBAccessor($dbPath);
  38. if (!isset($GLOBALS["PHP_EXEC_PATH"])) {
  39. $GLOBALS["PHP_EXEC_PATH"] = "/usr/bin/php";
  40. }
  41. // TODO
  42. if (!isset($GLOBALS["CLUSTERMAIN_PATH"])) {
  43. $GLOBALS["CLUSTERMAIN_PATH"] = "ClusterMain.php";
  44. }
  45. $this->command = $GLOBALS["PHP_EXEC_PATH"]
  46. . " " . $GLOBALS["CLUSTERMAIN_PATH"];
  47. }
  48. /**
  49. * Function to deploy and start HDP across the whole cluster
  50. * Runs in the background
  51. * @return mixed
  52. * txn_id: transaction id to refer to to get progress updates and logs
  53. * array ( "txn_id" => $txn_id );
  54. */
  55. public function deployHDP() {
  56. $action = "deployHDP";
  57. $msg = "Deploying cluster";
  58. $args = " -c " . $this->clusterName
  59. . " -d " . $this->dbPath
  60. . " -a deploy ";
  61. return $this->internalTrigger($action, $msg, $args);
  62. }
  63. /**
  64. * Function to deploy all the required rpms and start all required
  65. * services on a given node
  66. * Runs in the background
  67. * @param array $nodes Hostnames of the nodes to be deployed
  68. * @return mixed
  69. * txn_id: transaction id to refer to to get progress updates and logs
  70. * array ( "txn_id" => $txn_id );
  71. */
  72. public function deployNodes($nodes) {
  73. $action = "deployNodes";
  74. $nodeList = implode(",", $nodes);
  75. $msg = "Deploying on nodes, list=" . $nodeList;
  76. $args = " -c " . $this->clusterName
  77. . " -d " . $this->dbPath
  78. . " -a deployNode ";
  79. foreach ($nodes as $node) {
  80. $args .= " -n " . $node;
  81. }
  82. return $this->internalTrigger($action, $msg, $args);
  83. }
  84. /**
  85. * Function to start all the services in order.
  86. * Runs in the background.
  87. * @return mixed
  88. * txn_id: transaction id to refer to to get progress updates and logs
  89. * array ( "txn_id" => $txn_id );
  90. */
  91. public function startAllServices() {
  92. $action = "startAllServices";
  93. $msg = "Starting all services";
  94. $args = " -c " . $this->clusterName
  95. . " -d " . $this->dbPath
  96. . " -a startAll ";
  97. return $this->internalTrigger($action, $msg, $args);
  98. }
  99. /**
  100. * Function to stop all the services in order.
  101. * Runs in the background.
  102. * @return mixed
  103. * txn_id: transaction id to refer to to get progress updates and logs
  104. * array ( "txn_id" => $txn_id );
  105. */
  106. public function stopAllServices() {
  107. $action = "stopAllServices";
  108. $msg = "Stopping all services";
  109. $args = " -c " . $this->clusterName
  110. . " -d " . $this->dbPath
  111. . " -a stopAll ";
  112. return $this->internalTrigger($action, $msg, $args);
  113. }
  114. /**
  115. * NOT SUPPORTED
  116. * Function to start given services.
  117. * Runs in the background.
  118. * @return mixed
  119. * txn_id: transaction id to refer to to get progress updates and logs
  120. * array ( "txn_id" => $txn_id );
  121. */
  122. public function startServices($services) {
  123. $action = "startServices";
  124. $svcList = implode(",", $services);
  125. $msg = "Starting services, list=".$svcList;
  126. $args = " -c " . $this->clusterName
  127. . " -d " . $this->dbPath
  128. . " -a start ";
  129. foreach ($services as $svc) {
  130. $args .= " -s " . $svc;
  131. }
  132. return $this->internalTrigger($action, $msg, $args);
  133. }
  134. /**
  135. * NOT SUPPORTED
  136. * Function to stop given services.
  137. * Runs in the background.
  138. * @return mixed
  139. * txn_id: transaction id to refer to to get progress updates and logs
  140. * array ( "txn_id" => $txn_id );
  141. */
  142. public function stopServices($services) {
  143. $action = "stopServices";
  144. $svcList = implode(",", $services);
  145. $msg = "Stopping services, list=".$svcList;
  146. $args = " -c " . $this->clusterName
  147. . " -d " . $this->dbPath
  148. . " -a stop ";
  149. foreach ($services as $svc) {
  150. $args .= " -s " . $svc;
  151. }
  152. return $this->internalTrigger($action, $msg, $args);
  153. }
  154. /**
  155. * NOT SUPPORTED
  156. * Function to start a given service and all the services it depends upon to
  157. * start successfully in the required order.
  158. * Runs in the background.
  159. * @return mixed
  160. * txn_id: transaction id to refer to to get progress updates and logs
  161. * array ( "txn_id" => $txn_id );
  162. */
  163. public function startDependentServices($service) {
  164. // generate a txn id which is returned back to the UI for progress
  165. // monitoring
  166. // use popen/pclose to start a background process
  167. // background script:
  168. // generate site.pp to start service and dependent services in required order
  169. // update progress, make logs accessible
  170. error_log("Start dependent services is not supported");
  171. exit (1);
  172. }
  173. /**
  174. * NOT SUPPORTED
  175. * Function to stop a given service and all the services that depend upon it
  176. * in the required order.
  177. * Runs in the background.
  178. * @return mixed
  179. * txn_id: transaction id to refer to to get progress updates and logs
  180. * array ( "txn_id" => $txn_id );
  181. */
  182. public function stopDependentServices($service) {
  183. // generate a txn id which is returned back to the UI for progress
  184. // monitoring
  185. // use popen/pclose to start a background process
  186. // background script:
  187. // generate site.pp to stop service and dependent services in required order
  188. // update progress, make logs accessible
  189. error_log("Stop dependent services is not supported");
  190. exit (1);
  191. }
  192. /**
  193. * NOT SUPPORTED
  194. * Function to reconfigure a cluster by first stopping the whole cluster,
  195. * re-pushing new configs to all nodes and restarting all services.
  196. * Runs in the background.
  197. * @return mixed
  198. * txn_id: transaction id to refer to to get progress updates and logs
  199. * array ( "txn_id" => $txn_id );
  200. */
  201. public function reconfigureHDP() {
  202. // generate a txn id which is returned back to the UI for progress
  203. // monitoring
  204. // use popen/pclose to start a background process
  205. // background script:
  206. // generate site.pp to stop all services in required order
  207. // update progress, make logs accessible
  208. // generate site.pp with new configs
  209. // update progress, make logs accessible
  210. // generate site.pp to start all services in required order
  211. // update progress, make logs accessible
  212. error_log("Re-configuring is not supported");
  213. exit (1);
  214. }
  215. /**
  216. * Function to reconfigure services by first stopping the services and the
  217. * required dependencies, re-pushing new configs to required nodes and
  218. * restarting all the required services.
  219. * Runs in the background.
  220. * @param array services to re-configure
  221. * @return mixed
  222. * txn_id: transaction id to refer to to get progress updates and logs
  223. * array ( "txn_id" => $txn_id );
  224. */
  225. public function reconfigureServices($services) {
  226. $action = "reconfigureServices";
  227. $svcList = implode(",", $services);
  228. $msg = "Reconfiguring services, list=".$svcList;
  229. $args = " -c " . $this->clusterName
  230. . " -d " . $this->dbPath
  231. . " -a reconfigure ";
  232. foreach ($services as $svc) {
  233. $args .= " -s " . $svc;
  234. }
  235. return $this->internalTrigger($action, $msg, $args);
  236. }
  237. /**
  238. * Get progress update for a given transaction.
  239. * @param txnId Transaction Id
  240. * @return mixed
  241. * array ( "txn_id" => $txn_id,
  242. * "result" => 0,
  243. * "processRunning" => bool,
  244. * "subTxns" => array (
  245. * array (
  246. * "subTxnId" =>
  247. * "parentSubTxnId" =>
  248. * "state" =>
  249. * "description" =>
  250. * )
  251. * )
  252. * )
  253. */
  254. public function getProgress($txnId) {
  255. // given the txn id generated for one of the supported actions, provide
  256. // the current progress update
  257. // this could also be a trigger to update the current state if needed
  258. $response = array ( "result" => 0, "error" => "");
  259. $txnInfo = $this->dbHandle->getTransactionStatusInfo($this->clusterName,
  260. $txnId);
  261. if ($txnInfo === FALSE || $txnInfo["result"] != 0) {
  262. return $txnInfo;
  263. }
  264. $pidInfo = json_decode($txnInfo["pidInfo"], true);
  265. $procRunning = HMCTxnUtils::checkTxnProcessStatus($pidInfo);
  266. $response["processRunning"] = $procRunning;
  267. $subTxnInfo = $this->dbHandle->getAllSubTransactionsInfo($this->clusterName,
  268. $txnId);
  269. if ($subTxnInfo === FALSE || $subTxnInfo["result"] != 0) {
  270. return $subTxnInfo;
  271. }
  272. $orderedSubTxns = $this->orderSubTxns($subTxnInfo["subTxns"]);
  273. $response["txnId"] = $txnId;
  274. $response["subTxns"] = array();
  275. foreach($orderedSubTxns as $sTxnId => $sTxn) {
  276. if (isset($sTxn["opStatus"])) {
  277. $sTxn["opStatus"] = json_decode($sTxn["opStatus"], true);
  278. } else {
  279. $sTxn["opStatus"] = array();
  280. }
  281. array_push($response["subTxns"], $sTxn);
  282. }
  283. return $response;
  284. }
  285. public function orderSubTxns($subTxns) {
  286. $subTxnCount = count($subTxns);
  287. $parentIndexedSubTxns = array();
  288. $allSubTxns = array();
  289. foreach ($subTxns as $subTxn) {
  290. $allSubTxns[$subTxn["subTxnId"]] = $subTxn;
  291. }
  292. foreach ($subTxns as $subTxn) {
  293. if (!isset($parentIndexedSubTxns[$subTxn["parentSubTxnId"]])) {
  294. $parentIndexedSubTxns[$subTxn["parentSubTxnId"]] = array();
  295. }
  296. $parentIndexedSubTxns[$subTxn["parentSubTxnId"]][$subTxn["subTxnId"]] = $subTxn;
  297. }
  298. $rankedSubTxns = array();
  299. $currentRank = 0;
  300. $rankedIndexes = array();
  301. $this->_orderSubTxns($rankedSubTxns, $rankedIndexes,
  302. $currentRank, $allSubTxns, $parentIndexedSubTxns, $subTxns);
  303. assert(count($rankedSubTxns) == $subTxnCount);
  304. return $rankedSubTxns;
  305. }
  306. private function _orderSubTxns(&$rankedSubTxns, &$rankedIndexes,
  307. &$currentRank, &$allSubTxns, $parentIndexedSubTxns, $subTxnsToOrder) {
  308. $maxIndex = 0;
  309. $indexedSubTxns = array();
  310. foreach ($subTxnsToOrder as $subTxn) {
  311. if ($subTxn["subTxnId"] > $maxIndex) {
  312. $maxIndex = $subTxn["subTxnId"];
  313. }
  314. $indexedSubTxns[$subTxn["subTxnId"]] = $subTxn;
  315. }
  316. for ($i = 0; $i <= $maxIndex; ++$i) {
  317. if (!isset($indexedSubTxns[$i])) {
  318. continue;
  319. }
  320. $subTxn = $indexedSubTxns[$i];
  321. $parentSubTxnId = $subTxn["parentSubTxnId"];
  322. if (isset($parentIndexedSubTxns[$i])
  323. && is_array($parentIndexedSubTxns[$i])
  324. && count($parentIndexedSubTxns[$i]) > 0) {
  325. ksort($parentIndexedSubTxns[$i]);
  326. foreach ($parentIndexedSubTxns[$i] as $index => $pSubTxn) {
  327. $pSubTxnId = $pSubTxn["subTxnId"];
  328. $ppSubTxnId = $pSubTxn["parentSubTxnId"];
  329. if (!isset($rankedIndexes[$pSubTxnId])) {
  330. $this->_orderSubTxns($rankedSubTxns, $rankedIndexes,
  331. $currentRank, $allSubTxns, $parentIndexedSubTxns,
  332. $parentIndexedSubTxns[$ppSubTxnId]);
  333. }
  334. if (!isset($rankedIndexes[$pSubTxnId])) {
  335. $pSubTxn["rank"] = $currentRank;
  336. $rankedSubTxns[$currentRank] = $pSubTxn;
  337. $rankedIndexes[$pSubTxn["subTxnId"]] = TRUE;
  338. ++$currentRank;
  339. }
  340. }
  341. }
  342. if (!isset($rankedIndexes[$i])) {
  343. $subTxn["rank"] = $currentRank;
  344. $rankedSubTxns[$currentRank] = $subTxn;
  345. $rankedIndexes[$i] = TRUE;
  346. ++$currentRank;
  347. }
  348. }
  349. return $rankedSubTxns;
  350. }
  351. /**
  352. * Get logs for a given transaction
  353. * @param string $txnId Transaction Id
  354. */
  355. public function getLogs($txnId) {
  356. // get the logs for a given transaction triggered earlier.
  357. // not sure if we want to support filtering by log level for now
  358. $response = array ( "result" => 0, "error" => "");
  359. $txnInfo = $this->dbHandle->getTransactionStatusInfo($this->clusterName,
  360. $txnId);
  361. if ($txnInfo === FALSE || $txnInfo["result"] != 0) {
  362. return $txnInfo;
  363. }
  364. $subTxnInfo = $this->dbHandle->getAllSubTransactionsInfo($this->clusterName,
  365. $txnId);
  366. if ($subTxnInfo === FALSE || $subTxnInfo["result"] != 0) {
  367. return $subTxnInfo;
  368. }
  369. $puppetInvoker = new PuppetInvoker($this->dbPath);
  370. $response["txnId"] = $txnId;
  371. $response["subTxns"] = array();
  372. foreach ($subTxnInfo["subTxns"] as $subTxnId => $subTxn) {
  373. $nodes = array();
  374. $response["subTxns"][$subTxn["subTxnId"]] =
  375. array( "nodeReport" => array(),
  376. "nodeLogs" => array());
  377. if (!isset($subTxn["opStatus"])) {
  378. continue;
  379. }
  380. $opStatus = json_decode($subTxn["opStatus"], true);
  381. if (!isset($opStatus["nodeReport"])) {
  382. continue;
  383. }
  384. $nodeReport = $opStatus["nodeReport"];
  385. $response["subTxns"][$subTxn["subTxnId"]]["nodeReport"] = $nodeReport;
  386. $keys = array ( "PUPPET_KICK_FAILED", "PUPPET_OPERATION_FAILED",
  387. "PUPPET_OPERATION_SUCCEEDED" );
  388. foreach ($keys as $key) {
  389. if (isset($nodeReport[$key])
  390. && is_array($nodeReport[$key])) {
  391. $nodes = array_merge($nodes, $nodeReport[$key]);
  392. }
  393. }
  394. $transaction = new Transaction($txnId, $subTxn["subTxnId"],
  395. $subTxn["parentSubTxnId"]);
  396. $puppetLogs = $puppetInvoker->getReports($nodes, $transaction);
  397. $this->logger->log_debug("Got puppet reports for transaction=" .$transaction->toString()
  398. . ", nodes=" . print_r($nodes, true)
  399. . ", logs=" . print_r($puppetLogs, true));
  400. $response["subTxns"][$subTxn["subTxnId"]]["nodeLogs"] = $puppetLogs;
  401. }
  402. return $response;
  403. }
  404. private function internalTrigger($action, $msg, $args) {
  405. $this->logger->log_info("HMC triggering action=" . $action . ", " . $msg);
  406. $response = array ( "result" => 0, "error" => "");
  407. $statusInfo = array ("function" => "HMC::$action",
  408. "action" => $msg);
  409. $txnId = HMCTxnUtils::createNewTransaction($this->dbHandle,
  410. $this->clusterName, $statusInfo);
  411. if ($txnId === FALSE) {
  412. $error = "Failed to create a new transaction, action=" . $action;
  413. $this->logger->log_error($error);
  414. return array ("result" => 1, "error" => $error);
  415. }
  416. $response["txnId"] = $txnId;
  417. $args .= " -x " . $txnId;
  418. $this->logger->log_debug("Triggering background process"
  419. . ", clusterName=" . $this->clusterName
  420. . ", txnId=" . $txnId
  421. . ", command=" . $this->command
  422. . ", args=" . $args);
  423. $backgroundPid = HMCTxnUtils::execBackgroundProcess($this->dbHandle,
  424. $this->clusterName, $txnId, $this->command, $args, "");
  425. if ($backgroundPid === FALSE) {
  426. $error = "Failed to trigger background process, action=" . $action;
  427. $this->logger->log_error($error);
  428. $response["result"] = 1;
  429. $response["error"] = $error;
  430. }
  431. return $response;
  432. }
  433. public function uninstallHDP($wipeoutData = FALSE) {
  434. $this->logger->log_info("Triggering uninstall"
  435. . ", clusterName=" . $this->clusterName
  436. );
  437. $action = "uninstallHDP";
  438. $msg = "Uninstalling cluster, wipeout=" . $wipeoutData;
  439. $args = " -c " . $this->clusterName
  440. . " -d " . $this->dbPath;
  441. if (!$wipeoutData) {
  442. $args .= " -a uninstallAll ";
  443. } else {
  444. $args .= " -a wipeout ";
  445. }
  446. return $this->internalTrigger($action, $msg, $args);
  447. }
  448. }
  449. ?>