org.apache.ambari.server.bootstrap.BSRunner.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ambari.server.bootstrap.BSRunner.java

Source

/**
 * 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.
 */
package org.apache.ambari.server.bootstrap;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.apache.ambari.server.bootstrap.BootStrapStatus.BSStat;
import org.apache.ambari.server.utils.ShellCommandUtil;
import org.apache.ambari.server.utils.ShellCommandUtil.Result;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author ncole
 *
 */
class BSRunner extends Thread {
    private static Log LOG = LogFactory.getLog(BSRunner.class);

    private static final String DEFAULT_USER = "root";
    private static final String SCRIPTS_DIR = "/var/lib/tbds-server/resources/scripts";

    private boolean finished = false;
    private SshHostInfo sshHostInfo;
    private File bootDir;
    private String bsScript;
    private File requestIdDir;
    private File sshKeyFile;
    private File passwordFile;
    private int requestId;
    private String agentSetupScript;
    private String agentSetupPassword;
    private String ambariHostname;
    private boolean verbose;
    private BootStrapImpl bsImpl;
    private final String clusterOsFamily;
    private String projectVersion;
    private int serverPort;

    public BSRunner(BootStrapImpl impl, SshHostInfo sshHostInfo, String bootDir, String bsScript,
            String agentSetupScript, String agentSetupPassword, int requestId, long timeout, String hostName,
            boolean isVerbose, String clusterOsFamily, String projectVersion, int serverPort) {
        this.requestId = requestId;
        this.sshHostInfo = sshHostInfo;
        this.bsScript = bsScript;
        this.bootDir = new File(bootDir);
        this.requestIdDir = new File(bootDir, Integer.toString(requestId));
        this.sshKeyFile = new File(this.requestIdDir, "sshKey");
        this.agentSetupScript = agentSetupScript;
        this.agentSetupPassword = agentSetupPassword;
        this.ambariHostname = hostName;
        this.verbose = isVerbose;
        this.clusterOsFamily = clusterOsFamily;
        this.projectVersion = projectVersion;
        this.bsImpl = impl;
        this.serverPort = serverPort;
        BootStrapStatus status = new BootStrapStatus();
        status.setLog("RUNNING");
        status.setStatus(BSStat.RUNNING);
        bsImpl.updateStatus(requestId, status);
    }

    /**
     * Update the gathered data from reading output
     *
     */
    private class BSStatusCollector implements Runnable {
        @Override
        public void run() {
            BSHostStatusCollector collector = new BSHostStatusCollector(requestIdDir, sshHostInfo.getHosts());
            collector.run();
            List<BSHostStatus> hostStatus = collector.getHostStatus();
            BootStrapStatus status = new BootStrapStatus();
            status.setHostsStatus(hostStatus);
            status.setLog("");
            status.setStatus(BSStat.RUNNING);
            bsImpl.updateStatus(requestId, status);
        }
    }

    private String createHostString(List<String> list) {
        StringBuilder ret = new StringBuilder();
        if (list == null) {
            return "";
        }

        int i = 0;
        for (String host : list) {
            ret.append(host);
            if (i++ != list.size() - 1)
                ret.append(",");
        }
        return ret.toString();
    }

    /** Create request id dir for each bootstrap call **/
    private void createRunDir() throws IOException {
        if (!bootDir.exists()) {
            // create the bootdir directory.
            if (!bootDir.mkdirs()) {
                throw new IOException("Cannot create " + bootDir);
            }
        }
        /* create the request id directory */
        if (requestIdDir.exists()) {
            /* delete the directory and make sure we start back */
            FileUtils.deleteDirectory(requestIdDir);
        }
        /* create the directory for the run dir */
        if (!requestIdDir.mkdirs()) {
            throw new IOException("Cannot create " + requestIdDir);
        }
    }

    private void writeSshKeyFile(String data) throws IOException {
        FileUtils.writeStringToFile(sshKeyFile, data);
    }

    private void writePasswordFile(String data) throws IOException {
        FileUtils.writeStringToFile(passwordFile, data);
    }

    public synchronized void finished() {
        this.finished = true;
    }

    public void beforeBootStrap(SshHostInfo sshHostInfo) {
        //check the ssh_keygen
        String sshKeygenShellPath = SCRIPTS_DIR + "/bootstrap_agent_ssh_keygen.sh";
        try {
            ShellCommandUtil.runCommand(sshKeygenShellPath, this.bsImpl.getBootstrapSSHUser());
            //set the private key 
            String privateKeyContentPath = SCRIPTS_DIR + "/bootstrap_get_private_key_content.sh";
            Result runCommand = ShellCommandUtil.runCommand(privateKeyContentPath,
                    this.bsImpl.getBootstrapSSHUser());
            sshHostInfo.setSshKey(runCommand.getStdout());
            sshHostInfo.setUser(this.bsImpl.getBootstrapSSHUser());
            //set the agent environment: create ambari user and copy the public key to agent
            List<String> hosts = sshHostInfo.getHosts();
            String agentEnvSetupShellPath = SCRIPTS_DIR + "/bootstrap_agent_env_setup.sh";
            Map<String, BSHostPasser> hostPassers = sshHostInfo.getHostPassers();

            if (hosts == null || hosts.size() == 0) {
                return;
            }
            CountDownLatch latch = new CountDownLatch(hosts.size());
            for (String host : hosts) {
                String agentUser = this.bsImpl.getAgentDefaultLoginUser();
                String agentPass = this.bsImpl.getAgentDefaultLoginPassword();
                BSHostPasser bsHostPasser = hostPassers.get(host);
                if (bsHostPasser != null && (bsHostPasser.getLoginUser() != null)) {
                    agentUser = bsHostPasser.getLoginUser();
                    agentPass = bsHostPasser.getPassword();
                }
                AgentSetupThread agentSetupThread = new AgentSetupThread(agentEnvSetupShellPath, host, agentUser,
                        agentPass, latch);
                agentSetupThread.start();
            }
            latch.await();
        } catch (IOException e) {
            LOG.error(e.getMessage());
            e.printStackTrace();
        } catch (InterruptedException e) {
            LOG.error(e.getMessage());
            e.printStackTrace();
        }
    }

    @Override
    public void run() {

        if (sshHostInfo.getSshKey() == null || sshHostInfo.getSshKey().equals("")) {
            beforeBootStrap(sshHostInfo);
        }

        String hostString = createHostString(sshHostInfo.getHosts());
        String user = sshHostInfo.getUser();
        String userRunAs = sshHostInfo.getUserRunAs();
        if (user == null || user.isEmpty()) {
            user = DEFAULT_USER;
        }
        String command[] = new String[12];
        BSStat stat = BSStat.RUNNING;
        String scriptlog = "";
        try {
            createRunDir();
            if (LOG.isDebugEnabled()) {
                // FIXME needs to be removed later
                // security hole
                LOG.debug("Using ssh key=\"" + sshHostInfo.getSshKey() + "\"");
            }

            String password = sshHostInfo.getPassword();
            if (password != null && !password.isEmpty()) {
                this.passwordFile = new File(this.requestIdDir, "host_pass");
                // TODO : line separator should be changed
                // if we are going to support multi platform server-agent solution
                String lineSeparator = System.getProperty("line.separator");
                password = password + lineSeparator;
                writePasswordFile(password);
            }

            writeSshKeyFile(sshHostInfo.getSshKey());
            /* Running command:
             * script hostlist bsdir user sshkeyfile
             */
            command[0] = this.bsScript;
            command[1] = hostString;
            command[2] = this.requestIdDir.toString();
            command[3] = user;
            command[4] = this.sshKeyFile.toString();
            command[5] = this.agentSetupScript.toString();
            command[6] = this.ambariHostname;
            command[7] = this.clusterOsFamily;
            command[8] = this.projectVersion;
            command[9] = this.serverPort + "";
            command[10] = userRunAs;
            command[11] = (this.passwordFile == null) ? "null" : this.passwordFile.toString();
            LOG.info("Host= " + hostString + " bs=" + this.bsScript + " requestDir=" + requestIdDir + " user="
                    + user + " keyfile=" + this.sshKeyFile + " passwordFile " + this.passwordFile + " server="
                    + this.ambariHostname + " version=" + projectVersion + " serverPort=" + this.serverPort
                    + " userRunAs=" + userRunAs);

            String[] env = new String[] { "AMBARI_PASSPHRASE=" + agentSetupPassword };
            if (this.verbose)
                env = new String[] { env[0], " BS_VERBOSE=\"-vvv\" " };

            if (LOG.isDebugEnabled()) {
                LOG.debug(Arrays.toString(command));
            }

            String bootStrapOutputFilePath = requestIdDir + File.separator + "bootstrap.out";
            String bootStrapErrorFilePath = requestIdDir + File.separator + "bootstrap.err";

            Process process = Runtime.getRuntime().exec(command, env);

            PrintWriter stdOutWriter = null;
            PrintWriter stdErrWriter = null;

            try {
                stdOutWriter = new PrintWriter(bootStrapOutputFilePath);
                stdErrWriter = new PrintWriter(bootStrapErrorFilePath);
                IOUtils.copy(process.getInputStream(), stdOutWriter);
                IOUtils.copy(process.getErrorStream(), stdErrWriter);
            } finally {
                if (stdOutWriter != null)
                    stdOutWriter.close();

                if (stdErrWriter != null)
                    stdErrWriter.close();
            }

            // Startup a scheduled executor service to look through the logs
            ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
            BSStatusCollector statusCollector = new BSStatusCollector();
            ScheduledFuture<?> handle = scheduler.scheduleWithFixedDelay(statusCollector, 0, 10, TimeUnit.SECONDS);
            LOG.info("Kicking off the scheduler for polling on logs in " + this.requestIdDir);
            try {

                LOG.info("Bootstrap output, log=" + bootStrapErrorFilePath + " " + bootStrapOutputFilePath);
                int exitCode = process.waitFor();
                String outMesg = "";
                String errMesg = "";
                try {
                    outMesg = FileUtils.readFileToString(new File(bootStrapOutputFilePath));
                    errMesg = FileUtils.readFileToString(new File(bootStrapErrorFilePath));
                } catch (IOException io) {
                    LOG.info("Error in reading files ", io);
                }
                scriptlog = outMesg + "\n\n" + errMesg;
                LOG.info("Script log Mesg " + scriptlog);
                if (exitCode != 0) {
                    stat = BSStat.ERROR;
                } else {
                    stat = BSStat.SUCCESS;
                }

                scheduler.schedule(new BSStatusCollector(), 0, TimeUnit.SECONDS);
                long startTime = System.currentTimeMillis();
                while (true) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Waiting for hosts status to be updated");
                    }
                    boolean pendingHosts = false;
                    BootStrapStatus tmpStatus = bsImpl.getStatus(requestId);
                    List<BSHostStatus> hostStatusList = tmpStatus.getHostsStatus();
                    if (hostStatusList != null) {
                        for (BSHostStatus status : hostStatusList) {
                            if (status.getStatus().equals("RUNNING")) {
                                pendingHosts = true;
                            }
                        }
                    } else {
                        //Failed to get host status, waiting for hosts status to be updated
                        pendingHosts = true;
                    }
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Whether hosts status yet to be updated, pending=" + pendingHosts);
                    }
                    if (!pendingHosts) {
                        break;
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // continue
                    }
                    long now = System.currentTimeMillis();
                    if (now >= (startTime + 15000)) {
                        LOG.warn("Gave up waiting for hosts status to be updated");
                        break;
                    }
                }
            } catch (InterruptedException e) {
                throw new IOException(e);
            } finally {
                handle.cancel(true);
                /* schedule a last update */
                scheduler.schedule(new BSStatusCollector(), 0, TimeUnit.SECONDS);
                scheduler.shutdownNow();
                try {
                    scheduler.awaitTermination(10, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    LOG.info("Interruped while waiting for scheduler");
                }
                process.destroy();
            }
        } catch (IOException io) {
            LOG.info("Error executing bootstrap " + io.getMessage());
            stat = BSStat.ERROR;
        } finally {
            /* get the bstatus */
            BootStrapStatus tmpStatus = bsImpl.getStatus(requestId);
            List<BSHostStatus> hostStatusList = tmpStatus.getHostsStatus();
            if (hostStatusList != null) {
                for (BSHostStatus hostStatus : hostStatusList) {
                    if ("FAILED".equals(hostStatus.getStatus())) {
                        stat = BSStat.ERROR;
                        break;
                    }
                }
            } else {
                stat = BSStat.ERROR;
            }
            tmpStatus.setLog(scriptlog);
            tmpStatus.setStatus(stat);
            bsImpl.updateStatus(requestId, tmpStatus);
            bsImpl.reset();
            // Remove private ssh key after bootstrap is complete
            try {
                FileUtils.forceDelete(sshKeyFile);
            } catch (IOException io) {
                LOG.warn(io.getMessage());
            }
            if (passwordFile != null) {
                // Remove password file after bootstrap is complete
                try {
                    FileUtils.forceDelete(passwordFile);
                } catch (IOException io) {
                    LOG.warn(io.getMessage());
                }
            }
            finished();
        }
    }

    public synchronized boolean isRunning() {
        return !this.finished;
    }

    static class AgentSetupThread extends Thread {
        private CountDownLatch latch;
        private String hostName;
        private String agentUser;
        private String agentPass;
        private String agentEnvSetupShellPath;

        public AgentSetupThread(String agentEnvSetupShellPath, String hostName, String agentUser, String agentPass,
                CountDownLatch latch) {
            this.latch = latch;
            this.agentEnvSetupShellPath = agentEnvSetupShellPath;
            this.hostName = hostName;
            this.agentUser = agentUser;
            this.agentPass = agentPass;
        }

        public void run() {
            try {
                ShellCommandUtil.runCommand(this.agentEnvSetupShellPath, this.hostName, this.agentUser,
                        this.agentPass);
            } catch (IOException e) {
                LOG.error(e.getMessage());
                e.printStackTrace();
            } catch (InterruptedException e) {
                LOG.error(e.getMessage());
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }
    }
}