Service.php 20 KB

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