PuppetInvoker.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. <?php
  2. include_once "../util/Logger.php";
  3. include_once "PuppetConfigs.php";
  4. include_once "DBReader.php";
  5. include_once "genmanifest/generateManifest.php";
  6. include_once "genmanifest/RoleDependencies.php";
  7. define("KICKFAILED", "nokick");
  8. define("KICKSENT", "kicked");
  9. define("FAILEDNODES", "failed");
  10. define("SUCCESSFULLNODES", "success");
  11. define("TIMEDOUTNODES", "timedoutnodes");
  12. define("PREV_KICK_RUNNING", "prev_kick_running");
  13. class PuppetInvoker {
  14. private $parallel;
  15. private $reportDir;
  16. private $kickTimeout;
  17. private $logger;
  18. function __construct($db) {
  19. $this->reportDir = $GLOBALS["puppetReportsDir"];
  20. $this->kickTimeout = $GLOBALS["puppetKickTimeout"];
  21. $this->parallel = $GLOBALS["puppetMaxParallelKicks"];
  22. $this->db = $db;
  23. $this->logger = new HMCLogger("PuppetInvoker");
  24. }
  25. private function executeAndGetOutput($cmd) {
  26. $handle = popen ($cmd . " 2>&1", 'r');
  27. $output = "";
  28. if ($handle) {
  29. while(! feof ($handle)) {
  30. $read = fgets ($handle);
  31. $output = $output . $read;
  32. }
  33. pclose ($handle);
  34. }
  35. return $output;
  36. }
  37. private function sendKick($nodes, $txnId, &$failedNodes,
  38. &$successNodes, &$prevKickRunningNodes) {
  39. $cmd = "";
  40. $cmd = $cmd . "puppet kick ";
  41. foreach ($nodes as $n) {
  42. $cmd = $cmd . " --host " . $n;
  43. }
  44. $p = 10;
  45. if (count($nodes) < $p) {
  46. $p = count($nodes);
  47. }
  48. $cmd = $cmd . " --parallel " . $p;
  49. $this->logger->log_trace("Kick command: " . $cmd);
  50. $output = $this->executeAndGetOutput($cmd);
  51. $this->logger->log_trace("Kick response begins ===========");
  52. $this->logger->log_trace($output);
  53. $this->logger->log_trace("Kick response ends ===========");
  54. foreach ($nodes as $kNode) {
  55. $regExSuccess = "/". $kNode . " .* exit code 0/";
  56. $regExRunning = "/". $kNode . ".* is already running/";
  57. if (preg_match($regExSuccess, $output)>0) {
  58. $successNodes[] = $kNode;
  59. } else if (preg_match($regExRunning, $output)>0) {
  60. $this->logger->log_info($kNode . "previous kick still running, will continue to wait");
  61. $prevKickRunningNodes[] = $kNode;
  62. } else {
  63. $this->logger->log_info($kNode . ": Kick failed");
  64. $failedNodes[] = $kNode;
  65. }
  66. }
  67. }
  68. private function kickPuppetAsync($nodes, $txnId, &$kickFailedNodes, &$kickSuccessNodes,
  69. &$prevKickRunningNodes) {
  70. $nodeListToKick = array();
  71. $index = 0;
  72. foreach($nodes as $n) {
  73. if ($index < 10) {
  74. $nodeListToKick[] = $n;
  75. $index++;
  76. } else {
  77. $this->sendKick($nodeListToKick, $txnId, $kickFailedNodes,
  78. $kickSuccessNodes, $prevKickRunningNodes);
  79. $nodeListToKick = array();
  80. $nodeListToKick[] = $n;
  81. $index = 1;
  82. }
  83. }
  84. if ($index > 0) {
  85. $this->sendKick($nodeListToKick, $txnId, $kickFailedNodes,
  86. $kickSuccessNodes, $prevKickRunningNodes);
  87. }
  88. }
  89. private function getInfoFromDb($clusterName, $nodes, $components) {
  90. $dbReader = new DBReader($this->db);
  91. $hostInfo = $dbReader->getHostNames($clusterName);
  92. $configInfo = $dbReader->getAllConfigs($clusterName);
  93. $hostRolesStates = $dbReader->getHostRolesStates($clusterName, $nodes, $components);
  94. $hostAttributes = $dbReader->getAllHostAttributes($clusterName);
  95. return array($hostInfo, $configInfo, $hostRolesStates, $hostAttributes);
  96. }
  97. public function kickPuppet($nodes, $txnObj, $clusterName, $nodesComponents,
  98. $globalOptions = array()) {
  99. //Get host config from db
  100. $txnId = $txnObj->toString();
  101. $components = array_keys($nodesComponents);
  102. $infoFromDb = $this->getInfoFromDb($clusterName, $nodes, $components);
  103. $hostInfo = $infoFromDb[0];
  104. $configInfo = $infoFromDb[1];
  105. $hostRolesStates = $infoFromDb[2];
  106. $hostAttributes = $infoFromDb[3];
  107. //Treat globalOpts as configs only
  108. if (!empty($globalOptions)) {
  109. foreach ($globalOptions as $key => $value) {
  110. $configInfo[$key] = $value;
  111. }
  112. }
  113. $response = $this->genKickWait($nodes, $txnId, $clusterName, $hostInfo,
  114. $configInfo, $hostRolesStates, $hostAttributes, $GLOBALS["puppetManifestDir"],
  115. $GLOBALS["puppetKickVersionFile"], $GLOBALS["DRYRUN"]);
  116. return $response;
  117. }
  118. public function kickServiceCheck($serviceCheckNodes, $txnObj, $clusterName) {
  119. $txnId = $txnObj->toString();
  120. $hostRolesStates = array();
  121. $roleDependencies = new RoleDependencies();
  122. $nodeList = array();
  123. foreach($serviceCheckNodes as $service => $node) {
  124. $rs = $roleDependencies->getServiceCheckRole($service);
  125. if (!isset($rs)) {
  126. $this->logger->log_error("No service check defined for service "
  127. . $service);
  128. continue;
  129. }
  130. $nodeList[] = $node;
  131. if (!isset($hostRolesStates[$node])) {
  132. $hostRolesStates[$node] = array();
  133. }
  134. $hostRolesStates[$node][$rs] = array();
  135. }
  136. $nodesToKick = array_unique($nodeList);
  137. $dbReader = new DBReader($this->db);
  138. $hostInfo = $dbReader->getHostNames($clusterName);
  139. $configInfo = $dbReader->getAllConfigs($clusterName);
  140. $hostAttributes = $dbReader->getAllHostAttributes($clusterName);
  141. $response = $this->genKickWait($nodesToKick, $txnId, $clusterName, $hostInfo,
  142. $configInfo, $hostRolesStates, $hostAttributes, $GLOBALS["puppetManifestDir"],
  143. $GLOBALS["puppetKickVersionFile"], $GLOBALS["DRYRUN"]);
  144. return $response;
  145. }
  146. private function writeVersionFile($versionFile, $txnId) {
  147. $fh = fopen($versionFile, "w");
  148. fwrite($fh, $txnId);
  149. fclose($fh);
  150. }
  151. private function createGenKickWaitResponse($kickFailedNodes, $failureResponseNodes,
  152. $timedoutNodes, $successfullNodes, $allNodes) {
  153. $result = 0;
  154. $error = "";
  155. if ( (count($allNodes) > 0) && (count($kickFailedNodes) == count($allNodes)) ) {
  156. $result = -1;
  157. $error = "All kicks failed";
  158. }
  159. $failedNodes = array_merge($failureResponseNodes, $timedoutNodes);
  160. $response = array (
  161. "result" => $result,
  162. "error" => $error,
  163. KICKFAILED => $kickFailedNodes,
  164. FAILEDNODES => $failedNodes,
  165. SUCCESSFULLNODES => $successfullNodes,
  166. TIMEDOUTNODES => $timedoutNodes
  167. );
  168. $stringToLog = print_r($response, TRUE);
  169. $this->logger->log_info("Response of genKickWait: \n" . $stringToLog);
  170. return $response;
  171. }
  172. /**
  173. *This is public only for testing, don't use this method directly
  174. */
  175. public function genKickWait($nodes, $txnId, $clusterId, $hostInfo,
  176. $configInfo, $hostRolesStates, $hostAttributes, $manifestDir, $versionFile,
  177. $dryRun) {
  178. $kickFailedNodes = array();
  179. $failureResponseNodes = array();
  180. $kickedNodes = array();
  181. $timedoutNodes = array();
  182. $successfullNodes = array();
  183. if (empty($nodes)) {
  184. return $this->createGenKickWaitResponse($kickFailedNodes, $failureResponseNodes,
  185. $timedoutNodes, $successfullNodes, $nodes);
  186. }
  187. //Add manifest loader
  188. copy($GLOBALS["manifestloaderFile"], $GLOBALS["manifestloaderDestinationDir"] . "/site.pp");
  189. //Generate manifest
  190. $modulesDir = $GLOBALS["puppetModulesDirectory"];
  191. ManifestGenerator::generateManifest($manifestDir, $hostInfo,
  192. $configInfo, $hostRolesStates, $hostAttributes, $modulesDir);
  193. //Write version file
  194. $this->writeVersionFile($versionFile, $txnId);
  195. if ($dryRun) {
  196. $successfullNodes = $nodes;
  197. return $this->createGenKickWaitResponse($kickFailedNodes, $failureResponseNodes,
  198. $timedoutNodes, $successfullNodes, $nodes);
  199. }
  200. $numRekicks = 1;
  201. $maxRekicks = 3;
  202. $nodesToKick = $nodes;
  203. while ($numRekicks <= $maxRekicks && (count($nodesToKick) > 0)) {
  204. $newKickedNodes = array();
  205. $prevKickRunningNodes = array();
  206. $this->logger->log_info("Kick attempt (" . $numRekicks . "/" . $maxRekicks . ")");
  207. $result = $this->kickPuppetAsync($nodesToKick, $txnId, $kickFailedNodes,
  208. $newKickedNodes, $prevKickRunningNodes);
  209. $kickedNodes = array_merge($kickedNodes, $newKickedNodes);
  210. $nodesToWait = array_merge($newKickedNodes, $prevKickRunningNodes);
  211. $timedoutNodes = array();
  212. $this->waitForResults($nodesToWait, $txnId, $successfullNodes,
  213. $failureResponseNodes, $timedoutNodes);
  214. if (count($prevKickRunningNodes) == 0) {
  215. $numRekicks = $numRekicks +1;
  216. }
  217. $nodesToKick = $timedoutNodes;
  218. sleep(1);
  219. }
  220. $sitePPFile = $manifestDir . "/site.pp";
  221. system("mv " . $sitePPFile . " " . $sitePPFile ."-".$txnId);
  222. // Delete version file, it will be generated next time.
  223. unlink($versionFile);
  224. $response = $this->createGenKickWaitResponse($kickFailedNodes, $failureResponseNodes,
  225. $timedoutNodes, $successfullNodes, $nodes);
  226. return $response;
  227. }
  228. private function waitForResults($nodes, $txnId, &$successfullNodes,
  229. &$failureResponseNodes, &$timedoutNodes) {
  230. $doneNodes = array();
  231. $startTime = time();
  232. $this->logger->log_info("Waiting for results from "
  233. . implode(",", $nodes));
  234. while (true) {
  235. foreach ($nodes as $n) {
  236. if (isset($doneNodes[$n])) {
  237. continue;
  238. }
  239. $fileName = $this->getReportFilePattern($n, $txnId);
  240. if (file_exists($fileName)) {
  241. $doneNodes[$n] = 1;
  242. }
  243. }
  244. $this->logger->log_info(count($doneNodes) . " out of " . count($nodes)
  245. . " nodes have reported");
  246. if (count($doneNodes) >= count($nodes)) {
  247. ##All nodes kicked have reported back
  248. break;
  249. }
  250. $currTime = time();
  251. if ($currTime - $startTime > $this->kickTimeout) {
  252. $this->logger->log_warn("Kick timed out, waited "
  253. . $this->kickTimeout . " seconds");
  254. break;
  255. }
  256. sleep(5);
  257. }
  258. ##Get result from each node
  259. foreach ($nodes as $n) {
  260. $fileName = $this->getReportFilePattern($n, $txnId);
  261. if (file_exists($fileName)) {
  262. $r = file_get_contents($fileName);
  263. if ((preg_match("/status: changed/", $r) > 0) ||
  264. (preg_match("/status: unchanged/", $r)) > 0) {
  265. $successfullNodes[] = $n;
  266. } else {
  267. $failureResponseNodes[] = $n;
  268. }
  269. } else {
  270. $timedoutNodes[] = $n;
  271. }
  272. }
  273. }
  274. /**
  275. * Returns the summary reports collected from nodes.
  276. * The function does not wait for any reports, it just
  277. * returns the available ones.
  278. * $nodes : Array of nodes to collect reports from.
  279. * $txnObj : The transaction for which reports are needed.
  280. * returns array keyed by nodes. For each node, the entry is
  281. * also an array with 3 keys:
  282. * overall: overall status which can be changed, unchanged
  283. * of failed. Both changed and unchanged can be
  284. * considered as successful.
  285. * finishtime: Time when the kick processing was completed
  286. * at the node.
  287. * message: This is a little trimmed version of actual reports
  288. * sent by the nodes.
  289. */
  290. public function getReports($nodes, $txnObj) {
  291. $txnId = $txnObj->toString();
  292. $reports = array();
  293. foreach ($nodes as $n) {
  294. $filename = $this->getReportFilePattern($n, $txnId);
  295. if (file_exists($filename)) {
  296. $r = file_get_contents($filename);
  297. $reports[$n]["reportfile"] = $filename;
  298. if (preg_match("/status: changed/", $r) > 0) {
  299. $reports[$n]["overall"] = "CHANGED";
  300. } else if (preg_match("/status: unchanged/", $r) > 0) {
  301. $reports[$n]["overall"] = "UNCHANGED";
  302. } else {
  303. $reports[$n]["overall"] = "FAILED";
  304. }
  305. $finishtime = array();
  306. $count = preg_match_all("/time: (.*)/", $r, $finishtime);
  307. if ($count !== FALSE && $count > 0 ) {
  308. $reports[$n]["finishtime"] = $finishtime[1][$count-1];
  309. }
  310. $messages = array();
  311. $count = preg_match_all("/message: (.*)/", $r, $messages);
  312. $reports[$n]["message"] = array();
  313. if ($count !== FALSE && $count > 0) {
  314. for ($i = 0; $i < $count; $i++ ) {
  315. $reports[$n]["message"][] = $messages[1][$i];
  316. }
  317. }
  318. }
  319. }
  320. return $reports;
  321. }
  322. private function getReportFilePattern($node, $txnId) {
  323. $reportFile = $this->reportDir . "/" . $txnId . "/" . $node;
  324. return $reportFile;
  325. }
  326. }
  327. ?>