blob: 5a153ffbe4f6afbe92374077d9976d9674133a04 [file] [log] [blame]
<?php
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
include_once '../util/Logger.php';
include_once '../conf/Config.inc';
include_once "../util/lock.php";
include_once '../db/HMCDBAccessor.php';
include_once "../util/HMCTxnUtils.php";
/**
* Interface between UI and install framework layer.
*/
class HMC {
private $dbHandle;
private $dbPath;
private $logger;
private $clusterName;
private $command;
function __construct($dbPath, $clusterName) {
$this->dbPath = $dbPath;
$this->clusterName = $clusterName;
$this->logger = new HMCLogger("HMC");
$this->dbHandle = new HMCDBAccessor($dbPath);
if (!isset($GLOBALS["PHP_EXEC_PATH"])) {
$GLOBALS["PHP_EXEC_PATH"] = "/usr/bin/php";
}
// TODO
if (!isset($GLOBALS["CLUSTERMAIN_PATH"])) {
$GLOBALS["CLUSTERMAIN_PATH"] = "ClusterMain.php";
}
$this->command = $GLOBALS["PHP_EXEC_PATH"]
. " " . $GLOBALS["CLUSTERMAIN_PATH"];
}
/**
* Function to deploy and start HDP across the whole cluster
* Runs in the background
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function deployHDP() {
$action = "deployHDP";
$msg = "Deploying cluster";
$args = " -c " . $this->clusterName
. " -d " . $this->dbPath
. " -a deploy ";
return $this->internalTrigger($action, $msg, $args);
}
/**
* Function to deploy all the required rpms and start all required
* services on a given node
* Runs in the background
* @param array $nodes Hostnames of the nodes to be deployed
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function deployNodes($nodes) {
$action = "deployNodes";
$nodeList = implode(",", $nodes);
$msg = "Deploying on nodes, list=" . $nodeList;
$args = " -c " . $this->clusterName
. " -d " . $this->dbPath
. " -a deployNode ";
foreach ($nodes as $node) {
$args .= " -n " . $node;
}
return $this->internalTrigger($action, $msg, $args);
}
/**
* Function to start all the services in order.
* Runs in the background.
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function startAllServices() {
$action = "startAllServices";
$msg = "Starting all services";
$args = " -c " . $this->clusterName
. " -d " . $this->dbPath
. " -a startAll ";
return $this->internalTrigger($action, $msg, $args);
}
/**
* Function to stop all the services in order.
* Runs in the background.
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function stopAllServices() {
$action = "stopAllServices";
$msg = "Stopping all services";
$args = " -c " . $this->clusterName
. " -d " . $this->dbPath
. " -a stopAll ";
return $this->internalTrigger($action, $msg, $args);
}
/**
* NOT SUPPORTED
* Function to start given services.
* Runs in the background.
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function startServices($services) {
$action = "startServices";
$svcList = implode(",", $services);
$msg = "Starting services, list=".$svcList;
$args = " -c " . $this->clusterName
. " -d " . $this->dbPath
. " -a start ";
foreach ($services as $svc) {
$args .= " -s " . $svc;
}
return $this->internalTrigger($action, $msg, $args);
}
/**
* NOT SUPPORTED
* Function to stop given services.
* Runs in the background.
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function stopServices($services) {
$action = "stopServices";
$svcList = implode(",", $services);
$msg = "Stopping services, list=".$svcList;
$args = " -c " . $this->clusterName
. " -d " . $this->dbPath
. " -a stop ";
foreach ($services as $svc) {
$args .= " -s " . $svc;
}
return $this->internalTrigger($action, $msg, $args);
}
/**
* NOT SUPPORTED
* Function to start a given service and all the services it depends upon to
* start successfully in the required order.
* Runs in the background.
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function startDependentServices($service) {
// generate a txn id which is returned back to the UI for progress
// monitoring
// use popen/pclose to start a background process
// background script:
// generate site.pp to start service and dependent services in required order
// update progress, make logs accessible
error_log("Start dependent services is not supported");
exit (1);
}
/**
* NOT SUPPORTED
* Function to stop a given service and all the services that depend upon it
* in the required order.
* Runs in the background.
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function stopDependentServices($service) {
// generate a txn id which is returned back to the UI for progress
// monitoring
// use popen/pclose to start a background process
// background script:
// generate site.pp to stop service and dependent services in required order
// update progress, make logs accessible
error_log("Stop dependent services is not supported");
exit (1);
}
/**
* NOT SUPPORTED
* Function to reconfigure a cluster by first stopping the whole cluster,
* re-pushing new configs to all nodes and restarting all services.
* Runs in the background.
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function reconfigureHDP() {
// generate a txn id which is returned back to the UI for progress
// monitoring
// use popen/pclose to start a background process
// background script:
// generate site.pp to stop all services in required order
// update progress, make logs accessible
// generate site.pp with new configs
// update progress, make logs accessible
// generate site.pp to start all services in required order
// update progress, make logs accessible
error_log("Re-configuring is not supported");
exit (1);
}
/**
* Function to reconfigure services by first stopping the services and the
* required dependencies, re-pushing new configs to required nodes and
* restarting all the required services.
* Runs in the background.
* @param array services to re-configure
* @return mixed
* txn_id: transaction id to refer to to get progress updates and logs
* array ( "txn_id" => $txn_id );
*/
public function reconfigureServices($services) {
$action = "reconfigureServices";
$svcList = implode(",", $services);
$msg = "Reconfiguring services, list=".$svcList;
$args = " -c " . $this->clusterName
. " -d " . $this->dbPath
. " -a reconfigure ";
foreach ($services as $svc) {
$args .= " -s " . $svc;
}
return $this->internalTrigger($action, $msg, $args);
}
/**
* Get progress update for a given transaction.
* @param txnId Transaction Id
* @return mixed
* array ( "txn_id" => $txn_id,
* "result" => 0,
* "processRunning" => bool,
* "subTxns" => array (
* array (
* "subTxnId" =>
* "parentSubTxnId" =>
* "state" =>
* "description" =>
* )
* )
* )
*/
public function getProgress($txnId) {
// given the txn id generated for one of the supported actions, provide
// the current progress update
// this could also be a trigger to update the current state if needed
$response = array ( "result" => 0, "error" => "");
$txnInfo = $this->dbHandle->getTransactionStatusInfo($this->clusterName,
$txnId);
if ($txnInfo === FALSE || $txnInfo["result"] != 0) {
return $txnInfo;
}
$pidInfo = json_decode($txnInfo["pidInfo"], true);
$procRunning = HMCTxnUtils::checkTxnProcessStatus($pidInfo);
$response["processRunning"] = $procRunning;
$subTxnInfo = $this->dbHandle->getAllSubTransactionsInfo($this->clusterName,
$txnId);
if ($subTxnInfo === FALSE || $subTxnInfo["result"] != 0) {
return $subTxnInfo;
}
$orderedSubTxns = $this->orderSubTxns($subTxnInfo["subTxns"]);
$response["txnId"] = $txnId;
$response["subTxns"] = array();
foreach($orderedSubTxns as $sTxnId => $sTxn) {
if (isset($sTxn["opStatus"])) {
$sTxn["opStatus"] = json_decode($sTxn["opStatus"], true);
} else {
$sTxn["opStatus"] = array();
}
array_push($response["subTxns"], $sTxn);
}
return $response;
}
public function orderSubTxns($subTxns) {
$subTxnCount = count($subTxns);
$parentIndexedSubTxns = array();
$allSubTxns = array();
foreach ($subTxns as $subTxn) {
$allSubTxns[$subTxn["subTxnId"]] = $subTxn;
}
foreach ($subTxns as $subTxn) {
if (!isset($parentIndexedSubTxns[$subTxn["parentSubTxnId"]])) {
$parentIndexedSubTxns[$subTxn["parentSubTxnId"]] = array();
}
$parentIndexedSubTxns[$subTxn["parentSubTxnId"]][$subTxn["subTxnId"]] = $subTxn;
}
$rankedSubTxns = array();
$currentRank = 0;
$rankedIndexes = array();
$this->_orderSubTxns($rankedSubTxns, $rankedIndexes,
$currentRank, $allSubTxns, $parentIndexedSubTxns, $subTxns);
assert(count($rankedSubTxns) == $subTxnCount);
return $rankedSubTxns;
}
private function _orderSubTxns(&$rankedSubTxns, &$rankedIndexes,
&$currentRank, &$allSubTxns, $parentIndexedSubTxns, $subTxnsToOrder) {
$maxIndex = 0;
$indexedSubTxns = array();
foreach ($subTxnsToOrder as $subTxn) {
if ($subTxn["subTxnId"] > $maxIndex) {
$maxIndex = $subTxn["subTxnId"];
}
$indexedSubTxns[$subTxn["subTxnId"]] = $subTxn;
}
for ($i = 0; $i <= $maxIndex; ++$i) {
if (!isset($indexedSubTxns[$i])) {
continue;
}
$subTxn = $indexedSubTxns[$i];
$parentSubTxnId = $subTxn["parentSubTxnId"];
if (isset($parentIndexedSubTxns[$i])
&& is_array($parentIndexedSubTxns[$i])
&& count($parentIndexedSubTxns[$i]) > 0) {
ksort($parentIndexedSubTxns[$i]);
foreach ($parentIndexedSubTxns[$i] as $index => $pSubTxn) {
$pSubTxnId = $pSubTxn["subTxnId"];
$ppSubTxnId = $pSubTxn["parentSubTxnId"];
if (!isset($rankedIndexes[$pSubTxnId])) {
$this->_orderSubTxns($rankedSubTxns, $rankedIndexes,
$currentRank, $allSubTxns, $parentIndexedSubTxns,
$parentIndexedSubTxns[$ppSubTxnId]);
}
if (!isset($rankedIndexes[$pSubTxnId])) {
$pSubTxn["rank"] = $currentRank;
$rankedSubTxns[$currentRank] = $pSubTxn;
$rankedIndexes[$pSubTxn["subTxnId"]] = TRUE;
++$currentRank;
}
}
}
if (!isset($rankedIndexes[$i])) {
$subTxn["rank"] = $currentRank;
$rankedSubTxns[$currentRank] = $subTxn;
$rankedIndexes[$i] = TRUE;
++$currentRank;
}
}
return $rankedSubTxns;
}
/**
* Get logs for a given transaction
* @param string $txnId Transaction Id
*/
public function getLogs($txnId) {
// get the logs for a given transaction triggered earlier.
// not sure if we want to support filtering by log level for now
$response = array ( "result" => 0, "error" => "");
$txnInfo = $this->dbHandle->getTransactionStatusInfo($this->clusterName,
$txnId);
if ($txnInfo === FALSE || $txnInfo["result"] != 0) {
return $txnInfo;
}
$subTxnInfo = $this->dbHandle->getAllSubTransactionsInfo($this->clusterName,
$txnId);
if ($subTxnInfo === FALSE || $subTxnInfo["result"] != 0) {
return $subTxnInfo;
}
$puppetInvoker = new PuppetInvoker($this->dbPath);
$response["txnId"] = $txnId;
$response["subTxns"] = array();
foreach ($subTxnInfo["subTxns"] as $subTxnId => $subTxn) {
$nodes = array();
$response["subTxns"][$subTxn["subTxnId"]] =
array( "nodeReport" => array(),
"nodeLogs" => array());
if (!isset($subTxn["opStatus"])) {
continue;
}
$opStatus = json_decode($subTxn["opStatus"], true);
if (!isset($opStatus["nodeReport"])) {
continue;
}
$nodeReport = $opStatus["nodeReport"];
$response["subTxns"][$subTxn["subTxnId"]]["nodeReport"] = $nodeReport;
$keys = array ( "PUPPET_KICK_FAILED", "PUPPET_OPERATION_FAILED",
"PUPPET_OPERATION_SUCCEEDED" );
foreach ($keys as $key) {
if (isset($nodeReport[$key])
&& is_array($nodeReport[$key])) {
$nodes = array_merge($nodes, $nodeReport[$key]);
}
}
$transaction = new Transaction($txnId, $subTxn["subTxnId"],
$subTxn["parentSubTxnId"]);
$puppetLogs = $puppetInvoker->getReports($nodes, $transaction);
$this->logger->log_debug("Got puppet reports for transaction=" .$transaction->toString()
. ", nodes=" . print_r($nodes, true)
. ", logs=" . print_r($puppetLogs, true));
$response["subTxns"][$subTxn["subTxnId"]]["nodeLogs"] = $puppetLogs;
}
return $response;
}
private function internalTrigger($action, $msg, $args) {
$this->logger->log_info("HMC triggering action=" . $action . ", " . $msg);
$response = array ( "result" => 0, "error" => "");
$statusInfo = array ("function" => "HMC::$action",
"action" => $msg);
$txnId = HMCTxnUtils::createNewTransaction($this->dbHandle,
$this->clusterName, $statusInfo);
if ($txnId === FALSE) {
$error = "Failed to create a new transaction, action=" . $action;
$this->logger->log_error($error);
return array ("result" => 1, "error" => $error);
}
$response["txnId"] = $txnId;
$args .= " -x " . $txnId;
$this->logger->log_debug("Triggering background process"
. ", clusterName=" . $this->clusterName
. ", txnId=" . $txnId
. ", command=" . $this->command
. ", args=" . $args);
$backgroundPid = HMCTxnUtils::execBackgroundProcess($this->dbHandle,
$this->clusterName, $txnId, $this->command, $args, "");
if ($backgroundPid === FALSE) {
$error = "Failed to trigger background process, action=" . $action;
$this->logger->log_error($error);
$response["result"] = 1;
$response["error"] = $error;
}
return $response;
}
public function uninstallHDP($wipeoutData = FALSE) {
$this->logger->log_info("Triggering uninstall"
. ", clusterName=" . $this->clusterName
);
$action = "uninstallHDP";
$msg = "Uninstalling cluster, wipeout=" . $wipeoutData;
$args = " -c " . $this->clusterName
. " -d " . $this->dbPath;
if (!$wipeoutData) {
$args .= " -a uninstallAll ";
} else {
$args .= " -a wipeout ";
}
return $this->internalTrigger($action, $msg, $args);
}
}
?>