/******************************************************************************* * =========================================================== * Ankush : Big Data Cluster Management Solution * =========================================================== * * (C) Copyright 2014, by Impetus Technologies * * This is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License (LGPL v3) as * published by the Free Software Foundation; * * This software is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this software; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ******************************************************************************/ package com.impetus.ankush.common.framework; import java.io.IOException; import java.net.Socket; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Semaphore; import net.neoremind.sshxcute.core.Result; import net.neoremind.sshxcute.core.SSHExec; import net.neoremind.sshxcute.exception.TaskExecFailException; import net.neoremind.sshxcute.task.CustomTask; import net.neoremind.sshxcute.task.impl.ExecCommand; import org.springframework.util.StringUtils; import com.impetus.ankush.AppStoreWrapper; import com.impetus.ankush.common.domain.Cluster; import com.impetus.ankush.common.exception.AnkushException; import com.impetus.ankush.common.scripting.impl.ExecSudoCommand; import com.impetus.ankush.common.service.GenericManager; import com.impetus.ankush.common.utils.ParserUtil; import com.impetus.ankush.common.utils.SSHUtils; import com.impetus.ankush.common.utils.validator.PortValidator; import com.impetus.ankush2.agent.AgentDeployer; import com.impetus.ankush2.constant.Constant; import com.impetus.ankush2.framework.config.ClusterConfig; import com.impetus.ankush2.logger.AnkushLogger; /** * @author hokam * */ public class ClusterPreValidator { private static final String CONNECTED = "connected"; private static final String JPS_PROCESS_LIST = "jpsProcessList"; private static final String REQUIRE_TTY_DISABLED = "requireTTYDisabled"; private static final String WGET_EXISTS = "wgetExists"; private static final String TAR_EXISTS = "tarExists"; // private static final String UNZIP_EXISTS = "unzipExists"; // private static final String ZIP_EXISTS = "zipExists"; private static final String JPS_EXISTS = "jpsExists"; private static final String NO_LOOPBACK = "noLoopback"; private static final String IS_SUDO_USER = "isSudoUser"; private static final String IS_DISK_FREE = "isDiskFree"; private static final String PORT_AVAILABILITY = "portAvailability"; private static final String FIREWALL_DISABLED = "firewallDisabled"; private static final String AGENT_METADATA_NOT_EXISTS = "agentMetadataNotExists"; private static final String AGENT_NOT_RUNNING = "agentNotRunning"; private static final String CONNECTION_STATUS = "Connection Status"; private static final String WARNING = "Warning"; private static final String OK = "Ok"; private static final String CRITICAL = "Critical"; /** The logger. */ private static AnkushLogger logger = new AnkushLogger( ClusterPreValidator.class); public Map validate(final Map params) { final LinkedHashMap result = new LinkedHashMap(); if (notContainsKey(params, "nodePorts", result)) { return result; } if (params.containsKey(Constant.Keys.CLUSTERID)) { // Get cluster manager GenericManager<Cluster, Long> clusterManager = AppStoreWrapper .getManager(Constant.Manager.CLUSTER, Cluster.class); // get cluster id string String clusterIdStr = (String) params.get(Constant.Keys.CLUSTERID); // convert cluster id string into long value. Long clusterId = ParserUtil.getLongValue(clusterIdStr, 0); // Get the cluster object from database. Cluster cluster = clusterManager.get(clusterId); ClusterConfig clusterConfig = cluster.getClusterConfig(); // set username params.put(Constant.Keys.USERNAME, clusterConfig.getAuthConf() .getUsername()); String pass = clusterConfig.getAuthConf().getPassword(); if (pass != null && !pass.isEmpty()) { params.put(Constant.Keys.PASSWORD, pass); } else { params.put(Constant.Keys.PRIVATEKEY, clusterConfig .getAuthConf().getPrivateKey()); } params.put(Constant.Agent.Key.AGENT_INSTALL_DIR, clusterConfig.getAgentInstallDir()); } else { if (notContainsKey(params, Constant.Keys.USERNAME, result)) { return result; } if (notContainsKey(params, Constant.Keys.PASSWORD, result)) { return result; } if (notContainsKey(params, Constant.Keys.PRIVATEKEY, result)) { return result; } } final String username = (String) params.get(Constant.Keys.USERNAME); final String password = (String) params.get(Constant.Keys.PASSWORD); final String privateKey = (String) params.get(Constant.Keys.PRIVATEKEY); final Map nodePorts = (Map) params.get("nodePorts"); Set<String> nodes = nodePorts.keySet(); final boolean authUsingPassword = (password != null && !password .isEmpty()); final String authInfo = authUsingPassword ? password : privateKey; try { final Semaphore semaphore = new Semaphore(nodes.size()); for (final String hostname : nodes) { semaphore.acquire(); AppStoreWrapper.getExecutor().execute(new Runnable() { @Override public void run() { SSHExec conn = null; LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(); try { conn = SSHUtils.connectToNode(hostname, username, password, privateKey); map = getValidationMap(params, username, password, nodePorts, authUsingPassword, authInfo, hostname, conn); } catch (Exception e) { map.put("error", new Status("Error", "Unable to perform validations", CRITICAL)); logger.error(e.getMessage(), e); } finally { // setting overall status. map.put("status", getOverAllStatus((Collection) map.values())); // putting map against node. result.put(hostname, map); if (semaphore != null) { semaphore.release(); } if (conn != null) { conn.disconnect(); } } } }); } semaphore.acquire(nodes.size()); result.put("status", true); } catch (Exception e) { String message = e.getMessage(); if (message == null) { message = "Unable to validate nodes"; } result.put("error", Collections.singletonList(message)); result.put("status", false); logger.error(e.getMessage(), e); } return result; } /** * Method to get over all status of the node validations. * * @param collection * @return */ private Status getOverAllStatus(Collection<Status> collection) { List<String> statusValues = new ArrayList<String>(); String overallStatus = OK; for (Status status : collection) { statusValues.add(status.getStatus()); } if (statusValues.contains(CRITICAL)) { overallStatus = CRITICAL; } else if (statusValues.contains(WARNING)) { overallStatus = WARNING; } return new Status("Overall Status", "", overallStatus); } /** * Method to check process lists. * * @param username * @param hostname * @param authUsingPassword * @param authInfo * @return */ private Status checkProcessList(String username, String hostname, boolean authUsingPassword, String authInfo) { String processList = ""; String message = null; try { processList = SSHUtils.getCommandOutput("jps", hostname, username, authInfo, authUsingPassword); } catch (Exception e) { message = e.getMessage(); logger.error(message, e); } List<String> processes = null; if ((processList == null) || processList.isEmpty()) { processes = new ArrayList<String>(); } else { processes = new ArrayList<String>(Arrays.asList(processList .split("\n"))); } List<String> processLists = new ArrayList<String>(); for (String process : processes) { if (!(process.contains("Jps") || process .contains(AgentDeployer.ANKUSH_SERVER_PROCSS_NAME))) { String[] processArray = process.split("\\ "); if (processArray.length == 2) { processLists.add(process.split("\\ ")[1]); } } } if (processLists.isEmpty()) { return new Status("JPS process list", OK); } else { message = StringUtils .collectionToCommaDelimitedString(processLists) + " already running"; return new Status("JPS process list", message, WARNING); } } /** * Method to check command existence. * * @param conn * @param command * @return */ private Status checkCommandExistence(SSHExec conn, String command) { boolean taskStatus = SSHUtils .getCommandStatus(conn, "which " + command); String label = command.substring(0, 1).toUpperCase() + command.substring(1) + " existence"; if (taskStatus) { return new Status(label, OK); } else { return new Status(label, command + " command not found", CRITICAL); } } /** * Method to check port availability * * @param nodePorts * @param hostname * @return */ private Status checkPortAvailability(final Map nodePorts, final String hostname) { String message = new String(); for (Object port : (List) nodePorts.get(hostname)) { PortValidator validator = new PortValidator(hostname, port.toString()); boolean taskStatus = validator.validate(); if (!taskStatus) { message = message + port.toString() + ", "; } } if (message.isEmpty()) { return new Status("Port availability", OK); } else { return new Status("Port availability", message.substring(0, message.length() - 2) + " already in use/blocked", CRITICAL); } } /** * Method to check key existence * * @param params * @param key * @param result * @return */ private boolean notContainsKey(Map params, String key, Map result) { if (params.containsKey(key)) { return false; } else { result.put("error", "Missing " + key); return true; } } /** * Method to check .ankush/agent folder existence. * * @param conn * @return */ private Status checkDotAnkushAgentFolder(SSHExec conn, String agentInstallDir) { // checking .ankush folder existance. boolean taskStatus = !SSHUtils.getCommandStatus(conn, "ls " + agentInstallDir + ".ankush/agent"); if (taskStatus) { return new Status("AnkushAgent directory existence", OK); } else { return new Status("AnkushAgent directory existence", "AnkushAgent found installed on this node", WARNING); } } /** * Check Agent process status.l * * @param conn * @return */ private Status checkAgentProcessStatus(SSHExec conn) { String message = "AnkushAgent found running on this node"; boolean taskStatus = !SSHUtils.getCommandStatus(conn, "jps | grep AnkushAgent"); if (taskStatus) { return new Status("AnkushAgent service running", OK); } else { return new Status("AnkushAgent service running", message, WARNING); } } /** * Check etc hosts. * * @param hostname * the hostname * @param username * the username * @param authInfo * the auth info * @param authUsingPassword * the auth using password * @return true, if successful */ public Status checkLoopbackAddress(SSHExec conn, String hostname) { Result res = null; // requires tty check by executing a sudo command boolean status = false; String message = "Invalid /etc/hosts configuration, please remove the loopback IP (127.0.x.x) mapping with " + hostname + "."; CustomTask checkLoopBackTask = new ExecCommand("egrep '^127.0.*" + hostname + "|^::1*" + hostname + "' " + Constant.LinuxEnvFiles.ETC_HOSTS); try { if (conn != null) { status = (conn.exec(checkLoopBackTask).rc != 0); } } catch (TaskExecFailException e) { logger.error(e.getMessage(), e); message = e.getMessage(); if (message == null) { message = "Failed to validate loopback address"; } } if (status) { return new Status("Loopback address", OK); } else { return new Status("Loopback address", message, CRITICAL); } } /** * Check requires tty. * * @param hostname * the hostname * @param username * the username * @param password * the password * @param privateKey * the private key * @return true, if successful */ public Status checkRequiresTTY(SSHExec conn, String password) { Result res = null; // requires tty check by executing a sudo command boolean status = false; String message = "Requiretty is enabled. Unable to get tty session on machine."; CustomTask ttyTask = new ExecSudoCommand(password, "grep 'requiretty' /etc/sudoers"); try { if (conn != null) { res = conn.exec(ttyTask); } else { status = false; } } catch (Exception e) { message = e.getMessage(); if (message == null) { message = "Failed to validate requires tty"; } logger.error(e.getMessage(), e); } // error msg contains tty to run boolean isTtyEnabled = res.error_msg.contains("tty to run"); status = !isTtyEnabled || res.error_msg.isEmpty(); if (status) { return new Status("Require TTY", OK); } else { return new Status("Require TTY", message, WARNING); } } /** * Check sudoers. * * @param hostname * the hostname * @param username * the username * @param authInfo * the auth info * @param authUsingPassword * the auth using password * @return true, if successful */ public Status checkSudoers(SSHExec conn, String password) { // requires tty check by executing a sudo command CustomTask ttyTask = new ExecSudoCommand(password, "ls"); boolean status = false; String message = "The user is not having admin credentials"; // if connected. if (conn != null) { logger.debug(CONNECTED); Result rs; try { rs = conn.exec(ttyTask); status = (rs.rc == 0); } catch (Exception e) { message = e.getMessage(); if (message == null) { message = "Failed to validate sudo user"; } status = false; } } if (status) { return new Status("Sudo user", OK); } else { return new Status("Sudo user", message, WARNING); } } public Status checkFreeDisk(SSHExec conn, String password, String agentHome) { String errMsg = "Could not check free disk availability @ " + agentHome + "."; String diskSpaceKey = "AnkushAgent Install Dir Disk Space"; Long availability; boolean status = true; try { // task to check free disk apace CustomTask freeDisk = new ExecCommand("df " + agentHome); Result result = conn.exec(freeDisk); // If filesystem name is long, then the command will output details // in more than one line, so splitting the output on the basis of // new line so as to remove the first line containing column labels List<Object> details = new ArrayList(Arrays.asList(result.sysout .split("\n"))); // Removing the column labels from command output details.remove(0); // joining elements in lists with tab String filesystemDetail = org.apache.commons.lang3.StringUtils .join(details, "\t"); // Considering that a directory is mounted only on a single // filesystem, so throwing exception if the size of the list created // using space as token is other than 6 if (new ArrayList(Arrays.asList(filesystemDetail.trim().split( "\\s+"))).size() != 6) { throw new AnkushException(errMsg); } // Getting the disk availability details availability = Long.valueOf(new ArrayList(Arrays .asList(filesystemDetail.split("\\s+"))).get(3).toString()); // if disk space available is less than 50 MB, then setting status // as critical if (availability < 51200L) { status = false; } } catch (Exception e) { return new Status(diskSpaceKey, errMsg, CRITICAL); } if (status) { return new Status(diskSpaceKey, "Sufficent disk space available @ " + agentHome + ". Space Availability : " + (availability / 1024) + " MB", OK); } else { return new Status(diskSpaceKey, "Insufficent disk space available @ " + agentHome + ". Space Availability : " + (availability / 1024) + " MB", CRITICAL); } } /** * Check iptables. * * @param hostname * the hostname * @return true, if successful */ // public Status checkIptables(String hostname) { // Status value = null; // boolean status = false; // String message = "Firewall is not disabled"; // String command = "nmap -P0 " + hostname; // ByteArrayOutputStream baos = new ByteArrayOutputStream(); // // try { // CommandExecutor.exec(command, baos, null); // String result = baos.toString(); // value = new Status("Firewall", OK); // status = result.contains("closed") && result.contains("open") // && !result.contains("filtered ports"); // } catch (IOException e) { // message = e.getMessage(); // logger.error(e.getMessage(), e); // } catch (Exception e) { // message = e.getMessage(); // logger.error(e.getMessage(), e); // } // if (message == null) { // message = "Failed to validate firewall"; // } // // if (status) { // value = new Status("Firewall", OK); // } else { // value = new Status("Firewall", message, CRITICAL); // } // return value; // } /** * Check iptables. * * @param hostname * the hostname * @return true, if successful */ public Status checkIptables(String hostname) { Status value = null; String message = "Firewall is not disabled"; Socket socket = null; // Random port to validate Integer port = 4511; try { // Creating a socket condition socket = new Socket(hostname, port); // if socket connection created, then firewall is disabled value = new Status("Firewall", OK); } catch (Exception e) { // if exception type is NoRouteToHostException , then firewall is // enabled , else if exception type is ConnectException, then // firewall is disabled but no process is running on the port , else // unable to validate firewall if (e instanceof java.net.NoRouteToHostException) { value = new Status("Firewall", message, CRITICAL); } else if (e instanceof java.net.ConnectException) { value = new Status("Firewall", OK); } else { value = new Status("Firewall", "Failed to validate firewall", CRITICAL); } } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { } } } return value; } private LinkedHashMap<String, Object> getValidationMap(Map params, String username, String password, Map nodePorts, boolean authUsingPassword, String authInfo, String hostname, SSHExec conn) { LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(); if (conn != null) { // connection status. map.put(CONNECTED, new Status(CONNECTION_STATUS, "Connection established to node", OK)); // Getting Ankush agent process status. logger.debug("getting AnkushAgent process status"); map.put(AGENT_NOT_RUNNING, checkAgentProcessStatus(conn)); logger.debug("Checking .ankush folder existance"); // checking .ankush folder existance. map.put(AGENT_METADATA_NOT_EXISTS, checkDotAnkushAgentFolder(conn, String.valueOf(params .get(Constant.Agent.Key.AGENT_INSTALL_DIR)))); // cheking ip tables. logger.debug("checking ip tables." + params); // taskStatus = checkIptables(hostname); map.put(FIREWALL_DISABLED, checkIptables(hostname)); // checking port availability. map.put(PORT_AVAILABILITY, checkPortAvailability(nodePorts, hostname)); // checking require tty. Status requiretty = checkRequiresTTY(conn, password); logger.debug("checking require tty." + params); map.put(REQUIRE_TTY_DISABLED, requiretty); Status isSudoUser = checkSudoers(conn, password); if (requiretty.getStatus().equals(WARNING)) { isSudoUser.setMessage(requiretty.getMessage()); } // checking free disk space. logger.debug("checking free disk space." + params); Status isDiskFree = checkFreeDisk(conn, password, String.valueOf(params .get(Constant.Agent.Key.AGENT_INSTALL_DIR))); map.put(IS_DISK_FREE, isDiskFree); // checking sudoers. logger.debug("checking sudoers." + params); map.put(IS_SUDO_USER, isSudoUser); // checking etc hosts. logger.debug("checking etc hosts." + params); map.put(NO_LOOPBACK, checkLoopbackAddress(conn, hostname)); // checking wget. logger.debug("checking checking wget." + params); map.put(WGET_EXISTS, checkCommandExistence(conn, "wget")); // checking tar. logger.debug("checking checking tar." + params); map.put(TAR_EXISTS, checkCommandExistence(conn, "tar")); // checking unzip. // logger.debug("checking checking unzip." + params); // map.put(UNZIP_EXISTS, checkCommandExistence(conn, "unzip")); // checking zip. // logger.debug("checking checking zip." + params); // map.put(ZIP_EXISTS, checkCommandExistence(conn, "zip")); // checking jps. logger.debug("checking checking jps." + params); map.put(JPS_EXISTS, checkCommandExistence(conn, "jps")); // getting running jps process list. logger.debug("getting jps process list"); map.put(JPS_PROCESS_LIST, checkProcessList(username, hostname, authUsingPassword, authInfo)); } else { map.put(CONNECTED, new Status(CONNECTION_STATUS, "Failed to connect to node", CRITICAL)); } return map; } class Status { private String label; private String message = "No Conflict Detected"; private String status; public Status(String label, String message, String status) { super(); this.label = label; this.message = message; this.status = status; } public Status(String label, String status) { super(); this.label = label; this.status = status; } /** * @return the label */ public String getLabel() { return label; } /** * @param label * the label to set */ public void setLabel(String label) { this.label = label; } /** * @return the message */ public String getMessage() { return message; } /** * @param message * the message to set */ public void setMessage(String message) { this.message = message; } /** * @return the status */ public String getStatus() { return status; } /** * @param status * the status to set */ public void setStatus(String status) { this.status = status; } } }