Service.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  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 "../db/OrchestratorDB.php";
  20. include_once "../db/Transaction.php";
  21. include_once "../puppet/PuppetInvoker.php";
  22. include_once "State.php";
  23. include_once "ServiceComponent.php";
  24. /**
  25. * Service represents one of the main services deployed such as:
  26. * HDFS, MapReduce, ZooKeeper, HBase, HCatalog, Oozie
  27. */
  28. class Service {
  29. // Name of the service
  30. public $name;
  31. // Service state
  32. public $state;
  33. // Cluster Name
  34. public $clusterName;
  35. // Service dependencies
  36. public $dependencies;
  37. // Service dependents
  38. public $dependents;
  39. // Service components
  40. public $components;
  41. // Database
  42. public $db;
  43. // Puppet
  44. public $puppet;
  45. // logger
  46. private $logger;
  47. // current action being done
  48. private $currentAction;
  49. // whether all components are clients only or not
  50. private $isClientOnly;
  51. // Set state whether smoke tests are required to be run for client-only services
  52. // For client-components set smoke tests to be run only when install is done
  53. private $runClientSmokeTest;
  54. function __construct($clusterName, $serviceName, $serviceState,
  55. $odb, $puppet) {
  56. $this->name = $serviceName;
  57. $this->state = $serviceState;
  58. $this->clusterName = $clusterName;
  59. $this->db = $odb;
  60. $this->puppet = $puppet;
  61. $this->logger = new HMCLogger("Service: $serviceName ($clusterName)");
  62. $this->logger->log_debug("Service: $serviceName, $serviceState, $clusterName");
  63. $this->currentAction = "";
  64. $this->isClientOnly = NULL;
  65. $this->runClientSmokeTest = FALSE;
  66. }
  67. /**
  68. * Persist state into DB
  69. * @param State $state
  70. * @param Transaction $transaction
  71. * @param bool $dryRun
  72. * @param bool $persistTxn - FALSE in case of INSTALL only
  73. */
  74. function setState($state, $transaction, $dryRun, $persistTxn) {
  75. if ($persistTxn) {
  76. $txnProgress = getTransactionProgressFromState($state);
  77. // $desc = $this->name."-".$this->currentAction."-". TransactionProgress::$PROGRESS[$txnProgress];
  78. $desc = getActionDescription($this->name, $this->currentAction, TransactionProgress::$PROGRESS[$txnProgress]);
  79. if ($dryRun) {
  80. // $desc = $this->name."-".$this->currentAction."-PENDING";
  81. $desc = getActionDescription($this->name, $this->currentAction, "PENDING");
  82. }
  83. $result =
  84. $this->db->persistTransaction($transaction, State::$STATE[$state],
  85. $desc, TransactionProgress::$PROGRESS[$txnProgress],
  86. "SERVICE", $dryRun);
  87. if ($result['result'] !== 0) {
  88. $this->state == State::FAILED;
  89. $this->logger->log_error($this->name." - ".State::$STATE[$state]);
  90. $this->logger->log_error("Failed to persist transaction: " . $transaction->toString());
  91. $this->db->setServiceState($this, $state);
  92. return $result;
  93. }
  94. }
  95. if (!$dryRun) {
  96. $result = $this->db->setServiceState($this, $state);
  97. if ($result['result'] !== 0) {
  98. $this->state == State::FAILED;
  99. $this->logger->log_error("Failed to persist state for Service "
  100. . "$this->name - ".State::$STATE[$state] . " dryRun=$dryRun");
  101. $this->db->setServiceState($this, $state);
  102. return $result;
  103. }
  104. }
  105. $this->state = $state;
  106. $this->logger->log_info("$this->name - " . State::$STATE[$state]
  107. . " dryRun=$dryRun");
  108. return array("result" => 0, "error" => "");
  109. }
  110. /**
  111. * Uninstall the service.
  112. * @return mixed
  113. * array( "result" => 0, "error" => msg)
  114. */
  115. public function uninstall($transaction, $dryRun) {
  116. $this->currentAction = "UNINSTALL";
  117. // Check if it's already INSTALLED or STARTED
  118. if ($this->state === State::UNINSTALLED) {
  119. $this->logger->log_debug("Service $this->name is already UNINSTALLED!");
  120. return array("result" => 0, "error" => "");
  121. }
  122. // Ensure state is UNINSTALLED or FAILED
  123. if ($this->state !== State::STOPPED &&
  124. $this->state !== State::FAILED &&
  125. $this->state !== State::UNKNOWN &&
  126. $this->state != State::INSTALLED) {
  127. $this->logger->log_error("Service $this->name is not UNKNOWN or INSTALLED or FAILED or STOPPED! state=" . State::$STATE[$this->state]);
  128. return array("result" => -1, "error" => "Service $this->name is not INSTALLED or FAILED or STOPPED! state=" . State::$STATE[$this->state]);
  129. }
  130. // Note that we are about to UNINSTALL
  131. $result = $this->setState(State::UNINSTALLING, $transaction, $dryRun, FALSE);
  132. if ($result['result'] !== 0) {
  133. return $result;
  134. }
  135. // Mark each component as UNINSTALLED
  136. $result = $this->getComponents($transaction);
  137. if ($result["result"] !== 0) {
  138. return $result;
  139. }
  140. foreach ($this->components as $component) {
  141. $subTxn = $transaction->createSubTransaction();
  142. $s = $component->uninstall($subTxn, $dryRun);
  143. }
  144. // Done!
  145. return $this->setState(State::UNINSTALLED, $transaction, $dryRun, FALSE);
  146. }
  147. /**
  148. * Install & configure the service.
  149. * @return mixed
  150. * array( "result" => 0, "error" => msg)
  151. */
  152. public function install($transaction, $dryRun) {
  153. $this->currentAction = "INSTALL";
  154. // set flag to ensure smoke tests are run for client-only services
  155. // as we just installed or re-configured something
  156. $this->runClientSmokeTest = TRUE;
  157. // Check if it's already INSTALLED or STARTED
  158. if ($this->state === State::INSTALLED ||
  159. $this->state === State::STARTED) {
  160. $this->logger->log_debug("Service $this->name is already INSTALLED!");
  161. return array("result" => 0, "error" => "");
  162. }
  163. // Ensure state is UNINSTALLED or FAILED
  164. if ($this->state !== State::UNINSTALLED &&
  165. $this->state !== State::FAILED &&
  166. $this->state !== State::UNKNOWN &&
  167. $this->state !== State::STOPPED) {
  168. $this->logger->log_error("Service $this->name is not UNKNOWN or UNINSTALLED or FAILED or STOPPED! state=" . State::$STATE[$this->state]);
  169. return array("result" => -1, "error" => "Service $this->name is not UNINSTALLED or FAILED or STOPPED! state=" . State::$STATE[$this->state]);
  170. }
  171. // Ensure each dependent service is INSTALLED
  172. $result = $this->getDependencies($transaction);
  173. if ($result["result"] !== 0) {
  174. return $result;
  175. }
  176. foreach ($this->dependencies as $dep) {
  177. $subTxn = $transaction->createSubTransaction();
  178. $s = $dep->install($subTxn, $dryRun);
  179. $depResult = $s['result'];
  180. $depErrMsg = $s['error'];
  181. if ($depResult !== 0) {
  182. return array("result" => $depResult, "error" => "Failed to install $dep->name with $depResult (\'$depErrMsg\')");
  183. }
  184. }
  185. // Note that we are about to INSTALL
  186. $result = $this->setState(State::INSTALLING, $transaction, $dryRun, FALSE);
  187. if ($result['result'] !== 0) {
  188. return $result;
  189. }
  190. // Install self
  191. // TODO: Special case, don't use Puppet here!
  192. // Mark each component as INSTALLED
  193. $result = $this->getComponents($transaction);
  194. if ($result["result"] !== 0) {
  195. return $result;
  196. }
  197. foreach ($this->components as $component) {
  198. $subTxn = $transaction->createSubTransaction();
  199. $s = $component->install($subTxn, $dryRun);
  200. }
  201. // Done!
  202. return $this->setState(State::INSTALLED, $transaction, $dryRun, FALSE);
  203. }
  204. /**
  205. * Requires components to be set before calling this api.
  206. * @return boolean whether service has only client components
  207. */
  208. private function checkIsClientOnly() {
  209. if (isset($this->isClientOnly)
  210. && $this->isClientOnly != NULL) {
  211. return $this->isClientOnly;
  212. }
  213. $isClientOnly = TRUE;
  214. foreach ($this->components as $component) {
  215. if (!$component->isClient) {
  216. $isClientOnly = FALSE;
  217. break;
  218. }
  219. }
  220. $this->isClientOnly = $isClientOnly;
  221. return $isClientOnly;
  222. }
  223. /**
  224. * Start the service.
  225. * @return mixed
  226. * array( "result" => 0, "error" => msg)
  227. */
  228. public function start($transaction, $dryRun) {
  229. $this->currentAction = "START";
  230. $result = $this->getComponents($transaction);
  231. if ($result["result"] !== 0) {
  232. return $result;
  233. }
  234. // Ensure each dependent service is STARTED
  235. $result = $this->getDependencies($transaction);
  236. if ($result["result"] !== 0) {
  237. return $result;
  238. }
  239. foreach ($this->dependencies as $dep) {
  240. $s = $dep->start($transaction->createSubTransaction(), $dryRun, TRUE);
  241. $depResult = $s['result'];
  242. $depErrMsg = $s['error'];
  243. if ($depResult !== 0) {
  244. return array("result" => $depResult,
  245. "error" => "Failed to start $dep->name with $depResult (\'$depErrMsg\')");
  246. }
  247. }
  248. $this->checkIsClientOnly();
  249. $this->logger->log_debug("Service - " . $this->name . " - isClientOnly="
  250. . $this->isClientOnly
  251. . ", dryRun=" . $dryRun);
  252. $persistTxn = TRUE;
  253. $actualDryRun = $dryRun;
  254. if ($this->isClientOnly) {
  255. // this is to ensure that we do not persist the start sub-txn into the DB
  256. // also start-stop state does not make sense for a client-only service
  257. // we retain notion of START state in memory to ensure that we do not
  258. // kick the smoke test twice
  259. $persistTxn = FALSE;
  260. $dryRun = TRUE;
  261. }
  262. // Check if it's already STARTED
  263. if ($this->state === State::STARTED) {
  264. $this->logger->log_debug("Service $this->name is already STARTED!");
  265. return array("result" => 0, "error" => "");
  266. }
  267. // Ensure state is INSTALLED or STOPPED or FAILED
  268. if ($this->state !== State::INSTALLED
  269. && $this->state !== State::STARTING
  270. && $this->state !== State::STOPPING
  271. && $this->state !== State::STOPPED
  272. && $this->state !== State::FAILED) {
  273. $this->logger->log_error("Service $this->name is not INSTALLED or STOPPED or FAILED!");
  274. return array("result" => -1,
  275. "error" => "Service $this->name is not INSTALLED or STOPPED or FAILED!");
  276. }
  277. // Note that we are about to START
  278. $result = $this->setState(State::STARTING, $transaction, $dryRun, $persistTxn);
  279. if ($result['result'] !== 0) {
  280. $this->setState(State::FAILED, $transaction, $dryRun, $persistTxn);
  281. return $result;
  282. }
  283. if (!$this->isClientOnly) {
  284. // Start each component
  285. foreach ($this->components as $component) {
  286. $s = $component->start($transaction->createSubTransaction(), $dryRun);
  287. $cmpResult = $s['result'];
  288. $cmpErrMsg = $s['error'];
  289. if ($cmpResult !== 0) {
  290. $this->setState(State::FAILED, $transaction, $dryRun, $persistTxn);
  291. return array("result" => $cmpResult, "error" => "Failed to start $component->name with $cmpResult (\'$cmpErrMsg\')");
  292. }
  293. }
  294. }
  295. // Done!
  296. $result = $this->setState(State::STARTED, $transaction, $dryRun, $persistTxn);
  297. if ($result["result"] != 0) {
  298. $this->setState(State::FAILED, $transaction, $dryRun, $persistTxn);
  299. $this->logger->log_error("Failed to set state to STARTED with " . $result["error"]);
  300. return $result;
  301. }
  302. return $this->smoke($transaction->getNextSubTransaction(), $actualDryRun);
  303. }
  304. private function setSmokeProgress($transaction, $dryRun, $txnProgress) {
  305. $this->logger->log_debug("Setting smoke test progress for service="
  306. . $this->name . ", dryrun=" . $dryRun
  307. . ", progress=" . TransactionProgress::$PROGRESS[$txnProgress]);
  308. if ($dryRun) {
  309. $txnProgress = TransactionProgress::PENDING;
  310. }
  311. // $desc = $this->name."-SMOKETEST-".TransactionProgress::$PROGRESS[$txnProgress];
  312. $desc = getActionDescription($this->name, "SMOKE TEST", TransactionProgress::$PROGRESS[$txnProgress]);
  313. $result =
  314. $this->db->persistTransaction($transaction,
  315. TransactionProgress::$PROGRESS[$txnProgress],
  316. $desc, TransactionProgress::$PROGRESS[$txnProgress],
  317. "SERVICE-SMOKETEST", $dryRun);
  318. /*
  319. TODO error check later
  320. if ($result['result'] !== 0) {
  321. $this->state == State::FAILED;
  322. $this->logger->log_error($this->name." - ".State::$STATE[$state]);
  323. $this->db->setServiceState($this, $state);
  324. return $result;
  325. }
  326. */
  327. return array("result" => 0, "error" => "");
  328. }
  329. public function smoke($transaction, $dryRun) {
  330. $this->currentAction = "SMOKETEST";
  331. $this->checkIsClientOnly();
  332. if ($this->isClientOnly
  333. && !$this->runClientSmokeTest) {
  334. $this->logger->log_info("Skipping client-only service smoke tests"
  335. . " as nothing installed in this cycle");
  336. return array("result" => 0, "error" => "");
  337. }
  338. $result = $this->db->getServiceClientNode($this->name);
  339. if ($result == FALSE || $result["result"] != 0) {
  340. $this->logger->log_error("Failed to access db to get service-client node for $this->name");
  341. $this->setSmokeProgress($transaction, $dryRun,
  342. TransactionProgress::FAILED);
  343. return;
  344. }
  345. if (!is_array($result["nodes"]) || count($result["nodes"]) == 0 ) {
  346. $this->logger->log_warn("Cannot find service-client node for $this->name");
  347. // treating this as a no-op
  348. // TODO - should it be a failure instead?
  349. return array("result" => 0, "error" => "");
  350. }
  351. $clientNode = $result["nodes"][0];
  352. // set smoke starting state
  353. $this->setSmokeProgress($transaction, $dryRun,
  354. TransactionProgress::IN_PROGRESS);
  355. $result = $this->getComponents($transaction);
  356. if ($result["result"] !== 0) {
  357. return $result;
  358. }
  359. if (!$this->isClientOnly) {
  360. // Check if it's already STARTED
  361. // only in case service has non-client components
  362. if ($this->state !== State::STARTED) {
  363. $this->logger->log_debug("Service $this->name is not STARTED, cannot run smoke tests!");
  364. $this->setSmokeProgress($transaction, $dryRun, TransactionProgress::FAILED);
  365. return array("result" => -2, "error" =>
  366. "Service $this->name is not STARTED, cannot run smoke tests!");
  367. }
  368. }
  369. if (!$dryRun) {
  370. $result =
  371. $this->puppet->kickServiceCheck(
  372. array($this->name => $clientNode), $transaction,
  373. $this->clusterName
  374. );
  375. $this->logger->log_debug("Puppet kick response for smoketesting service on "
  376. . " cluster=" . $this->clusterName
  377. . ", service=" . $this->name
  378. . ", txn=" . $transaction->toString()
  379. . ", response=" . print_r($result, true));
  380. // handle puppet response
  381. $opStatus = array ( "nodeReport" =>
  382. array ( "PUPPET_KICK_FAILED" => $result[KICKFAILED],
  383. "PUPPET_OPERATION_FAILED" => $result[FAILEDNODES],
  384. "PUPPET_OPERATION_SUCCEEDED" => $result[SUCCESSFULLNODES]));
  385. $this->logger->log_info("Persisting puppet report for smoke testing "
  386. . $this->name);
  387. $this->db->persistTransactionOpStatus($transaction,
  388. json_encode($opStatus));
  389. if ($result["result"] != 0
  390. || count($result[SUCCESSFULLNODES]) != 1) {
  391. $this->logger->log_error("Service smoke check failed with "
  392. . print_r($result, true));
  393. $this->setState(State::FAILED, $transaction, $dryRun, TRUE);
  394. $this->setSmokeProgress($transaction, $dryRun,
  395. TransactionProgress::FAILED);
  396. return array("result" => -2, "error" =>
  397. "Service $this->name is not STARTED, smoke tests failed!");
  398. }
  399. }
  400. $this->setSmokeProgress($transaction, $dryRun,
  401. TransactionProgress::COMPLETED);
  402. return array("result" => 0, "error" => "");
  403. }
  404. /**
  405. * Stop the service.
  406. * @return mixed
  407. * array( "result" => 0, "error" => msg)
  408. */
  409. public function stop($transaction, $dryRun) {
  410. $this->currentAction = "STOP";
  411. $result = $this->getComponents($transaction);
  412. if ($result["result"] !== 0) {
  413. return $result;
  414. }
  415. // Ensure each dependent service is STOPPED
  416. $result = $this->getDependents($transaction);
  417. if ($result["result"] !== 0) {
  418. return $result;
  419. }
  420. foreach ($this->dependents as $dep) {
  421. $s = $dep->stop($transaction->createSubTransaction(), $dryRun);
  422. $depResult = $s['result'];
  423. $depErrMsg = $s['error'];
  424. if ($depResult !== 0) {
  425. return array("result" => $depResult, "error" => "Failed to stop $dep->name with $depResult (\'$depErrMsg\')");
  426. }
  427. }
  428. $this->checkIsClientOnly();
  429. if (!$this->isClientOnly) {
  430. // Check if it's already STOPPED
  431. if ($this->state === State::STOPPED) {
  432. $this->logger->log_info("Service $this->name is already STOPPED!");
  433. return array("result" => 0, "error" => "");
  434. }
  435. // Only stop if state is STARTED/STARTING/STOPPING/FAILED
  436. if ($this->state !== State::STARTED
  437. && $this->state !== State::STARTING
  438. && $this->state !== State::STOPPING
  439. && $this->state !== State::FAILED) {
  440. $this->logger->log_info("Service " . $this->name . " is not STARTED/STOPPING/FAILED!"
  441. . "Current state = " . State::$STATE[$this->state]
  442. . " - STOP is a no-op");
  443. return array("result" => 0, "error" => "");
  444. }
  445. // Note we are about to STOP
  446. $result = $this->setState(State::STOPPING, $transaction, $dryRun, TRUE);
  447. if ($result['result'] !== 0) {
  448. $this->setState(State::FAILED, $transaction, $dryRun, TRUE);
  449. return $result;
  450. }
  451. // Stop each component
  452. foreach ($this->components as $component) {
  453. $s = $component->stop($transaction->createSubTransaction(), $dryRun);
  454. $cmpResult = $s['result'];
  455. $cmpErrMsg = $s['error'];
  456. if ($cmpResult !== 0) {
  457. $this->setState(State::FAILED, $transaction, $dryRun, TRUE);
  458. return array("result" => $cmpResult, "error" => "Failed to stop $component->name with $cmpResult (\'$cmpErrMsg\')");
  459. }
  460. }
  461. // Done!
  462. return $this->setState(State::STOPPED, $transaction, $dryRun, TRUE);
  463. }
  464. return array("result" => 0, "error" => "");
  465. }
  466. private function getDependencies($transaction) {
  467. if (!isset($this->dependencies)) {
  468. $this->dependencies = $this->db->getServiceDependencies($this->name);
  469. }
  470. return $this->checkDBReturn($transaction, $this->dependencies);
  471. }
  472. private function getDependents($transaction) {
  473. if (!isset($this->dependents)) {
  474. $this->dependents = $this->db->getServiceDependents($this->name);
  475. }
  476. return $this->checkDBReturn($transaction, $this->dependents);
  477. }
  478. private function getComponents($transaction) {
  479. if (!isset($this->components)) {
  480. $this->components = $this->db->getServiceComponents($this->name);
  481. }
  482. return $this->checkDBReturn($transaction, $this->components);
  483. }
  484. private function checkDBReturn($transaction, $dbResult) {
  485. if ($dbResult === FALSE) {
  486. $trace = debug_backtrace();
  487. $this->logger->log_error("DB Error: " . $trace[1]["function"]);
  488. $this->setState(State::FAILED, $transaction, FALSE, TRUE);
  489. return array("result" => $dbResult, "error" => "Failed to update db for $this->name with $dbResult");
  490. }
  491. return array("result" => 0, "error" => "");
  492. }
  493. }
  494. ?>