All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mysql.management.HackedMysqldResource Maven / Gradle / Ivy

There is a newer version: 8.1.2
Show newest version
/*
 * Copyright 2014 Groupon, Inc
 * Copyright 2014 The Billing Project, LLC
 *
 * The Billing Project 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 com.mysql.management;


/*
 Copyright (C) 2004-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc. All rights reserved.
 Use is subject to license terms.

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License version 2 as
 published by the Free Software Foundation.

 This program 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 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

import com.mysql.jdbc.Driver;
import com.mysql.jdbc.MysqlErrorNumbers;
import com.mysql.management.util.CommandLineOptionsParser;
import com.mysql.management.util.InitializeUser;
import com.mysql.management.util.ListToString;
import com.mysql.management.util.NullPrintStream;
import com.mysql.management.util.Platform;
import com.mysql.management.util.ProcessUtil;
import com.mysql.management.util.Shell;
import com.mysql.management.util.Streams;
import com.mysql.management.util.Threads;
import com.mysql.management.util.Utils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;


// Identical to its copied class MysqldResource, except for STEPH comment below
public final class HackedMysqldResource implements MysqldResourceI {

    public static final String MYSQL_C_MXJ = "mysql-c.mxj";

    public static final String DATA = "data";

    private String versionString;

    private Map options;

    private Shell shell;

    private final File baseDir;

    private final File dataDir;

    private final File pidFile;

    private final File portFile;

    private String msgPrefix;

    private String pid;

    private Properties platformProperties;

    private Properties connectorMxjProperties;

    private String osName_osArch;

    private PrintStream out;

    private PrintStream err;

    private Exception trace;

    private int killDelay;

    private List completionListensers;

    private boolean readyForConnections;

    private String windowsKillCommand;

    // collaborators
    private HelpOptionsParser optionParser;

    private Utils utils;

    public HackedMysqldResource() {
        this(null, null, null, null, null, null);
    }

    public HackedMysqldResource(File baseDir) {
        this(baseDir, null, null, null, null, null);
    }

    public HackedMysqldResource(File baseDir, File dataDir) {
        this(baseDir, dataDir, null, null, null, null);
    }

    public HackedMysqldResource(File baseDir, File dataDir, String mysqlVersionString) {
        this(baseDir, dataDir, mysqlVersionString, null, null, null);
    }

    public HackedMysqldResource(File baseDir, File dataDir,
                                String mysqlVersionString, PrintStream out, PrintStream err) {
        this(baseDir, dataDir, mysqlVersionString, out, err, null);
    }

    HackedMysqldResource(File pBaseDir, File pDataDir, String pMysqlVersionString,
                         PrintStream pOut, PrintStream pErr, Utils pUtils) {
        this.out = (pOut != null) ? pOut : System.out;
        this.err = (pErr != null) ? pErr : System.err;
        this.utils = (pUtils != null) ? pUtils : new Utils();
        this.platformProperties = utils.streams().loadProperties(
                PLATFORM_MAP_PROPERTIES, pErr);
        this.connectorMxjProperties = utils.streams().loadProperties(
                CONNECTOR_MXJ_PROPERTIES, pErr);

        this.baseDir = utils.files().validCononicalDir(pBaseDir,
                utils.files().tmp(MYSQL_C_MXJ));
        this.dataDir = utils.files().validCononicalDir(pDataDir,
                new File(baseDir, DATA));

        this.optionParser = new HelpOptionsParser(err, utils);

        this.killDelay = getKillDelyFromProperties(connectorMxjProperties);
        this.windowsKillCommand = getWindowsKillCommand(connectorMxjProperties);

        String className = utils.str().shortClassName(getClass());
        this.pidFile = utils.files().cononical(
                new File(dataDir, className + ".pid"));
        this.portFile = new File(dataDir, className + ".port");
        setVersion(false, pMysqlVersionString);
        this.msgPrefix = "[" + className + "] ";
        this.options = new HashMap();
        setShell(null);
        setOsAndArch(System.getProperty(Platform.OS_NAME), System
                .getProperty(Platform.OS_ARCH));
        this.completionListensers = new ArrayList();
        initTrace();
    }

    private void initTrace() {
        this.trace = new Exception();
    }

    /**
     * Starts mysqld passing it the parameters specified in the arguments map.
     * No effect if MySQL is already running
     */
    public synchronized void start(String threadName, Map mysqldArgs) {
        start(threadName, mysqldArgs, false);
    }

    public synchronized void start(String threadName, Map pMysqldArgs,
                                   boolean populateAllOptions) {
        if ((getShell() != null) || processRunning()) {
            printMessage("mysqld already running (process: " + pid() + ")");
            return;
        }

        final Map mysqldArgs = new HashMap(pMysqldArgs);

        int port = parseInt(mysqldArgs.get(MysqldResourceI.PORT), 3306);

        mysqldArgs.put(MysqldResourceI.PORT, "" + port);
        mysqldArgs.remove(MysqldResourceI.MYSQLD_VERSION);

        String initUserProp = (String) mysqldArgs
                .remove(MysqldResourceI.INITIALIZE_USER);
        boolean initUser = Boolean.valueOf(initUserProp).booleanValue();
        String user = (String) mysqldArgs
                .remove(MysqldResourceI.INITIALIZE_USER_NAME);
        String password = (String) mysqldArgs
                .remove(MysqldResourceI.INITIALIZE_PASSWORD);

        setKillDelay(parseInt(mysqldArgs.remove(MysqldResourceI.KILL_DELAY),
                killDelay));

        if (populateAllOptions) {
            options = optionParser.getOptionsFromHelp(getHelp(mysqldArgs));
        } else {
            options = new HashMap();
            options.putAll(mysqldArgs);
        }

        // printMessage("mysqld : " +
        // services.str().toString(mysqldArgs.entrySet()));
        out.flush();
        addCompletionListenser(new Runnable() {
            public void run() {
                setReadyForConnection(false);
                setShell(null);
                completionListensers.remove(this);
            }
        });
        setShell(exec(threadName, mysqldArgs, out, err, true));

        reportPid();
        utils.files().writeString(portFile, port + utils.str().newLine());

        boolean ready = canConnectToServer(port, killDelay);
        setReadyForConnection(ready);

        if (initUser) {
            try {
                new InitializeUser(port, user, password, err).initializeUser();
            } catch (Throwable t) {
                t.printStackTrace(err);
            }
        }
    }

    // Will wait 250 miliseconds between each try.
    boolean canConnectToServer(int port, int milisecondsBeforeGivingUp) {
        int triesBeforeGivingUp = 1 + (milisecondsBeforeGivingUp / 1000) * 4;
        utils.str().classForName(Driver.class.getName());
        Connection conn = null;
        String bogusUser = "Connector/MXJ";
        String password = "Bogus Password";
        String url = "jdbc:mysql://127.0.0.1:" + port + "/test"
                + "?connectTimeout=150";

        for (int i = 0; i < triesBeforeGivingUp; i++) {
            try {
                conn = DriverManager.getConnection(url, bogusUser, password);
                return true; /* should never happen */
            } catch (SQLException e) {
                // Standard com.mysql.jdbc.Driver error
                if (e.getErrorCode() == MysqlErrorNumbers.ER_ACCESS_DENIED_ERROR ||
                        // STEPH we add the condition to make it work with MariaDb:
                        // mariadb seems to return error -1 and the message below.
                        e.getMessage().startsWith("Could not connect: Access denied")) {
                    return true;
                }
            } finally {
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace(err);
                }
            }
            utils.threads().pause(100);
        }
        return false;
    }

    private void setReadyForConnection(boolean ready) {
        readyForConnections = ready;
    }

    public synchronized boolean isReadyForConnections() {
        return readyForConnections;
    }

    private void reportPid() {
        final int CYCLES = 100;
        final int CYCLE_DELAY = killDelay / CYCLES;
        boolean printed = false;
        for (int i = 0; !printed && i < CYCLES; i++) {
            if (pidFile.exists() && pidFile.length() > 0) {
                utils.threads().pause(100);
                printMessage("mysqld running as process: " + pid());

                out.flush();
                printed = true;
            }
            utils.threads().pause(CYCLE_DELAY);
        }

        reportIfNoPidfile(printed);
    }

    synchronized String pid() {
        if (pid == null) {
            if (!pidFile.exists()) {
                return "No PID";
            }

            pid = utils.files().asString(pidFile).trim();
        }
        return pid;
    }

    void reportIfNoPidfile(boolean pidFileFound) {
        if (!pidFileFound) {
            printWarning("mysqld pid-file not found:  " + pidFile);
        }
    }

    /**
     * Kills the MySQL process.
     */
    public synchronized void shutdown() {
        boolean haveShell = (getShell() != null);
        if (!pidFile.exists() && !haveShell) {
            printMessage("Mysqld not running. No file: " + pidFile);
            return;
        }
        printMessage("stopping mysqld (process: " + pid() + ")");

        issueNormalKill();

        if (processRunning()) {
            issueForceKill();
        }

        if (shellRunning()) {
            destroyShell();
        }
        setShell(null);

        if (processRunning()) {
            printWarning("Process " + pid + "still running; not deleting "
                    + pidFile);
        } else {
            utils.threads().pause(150);
            pidFile.delete();
            pid = null;
            portFile.delete();

            utils.threads().pause(150);
            if (pidFile.exists()) {
                printMessage(pidFile + " still exits.");
            }
            if (portFile.exists()) {
                printMessage(portFile + " still exits.");
            }
        }

        setReadyForConnection(false);

        if (!options.isEmpty()) {
            printMessage("clearing options");
            options.clear();
        }

        printMessage("shutdown complete");
        out.flush();
    }

    void destroyShell() {
        String shellName = getShell().getName();
        printWarning("attempting to destroy thread " + shellName);
        getShell().destroyProcess();
        waitForShellToDie();
        String msg = (shellRunning() ? "not " : "") + "destroyed.";
        printWarning(shellName + " " + msg);
    }

    void issueForceKill() {
        printWarning("attempting to \"force kill\" " + pid());
        new ProcessUtil(pid(), err, err, baseDir, utils, windowsKillCommand)
                .forceKill();

        waitForProcessToDie();
        if (processRunning()) {
            String msg = (processRunning() ? "not " : "") + "killed.";
            printWarning(pid() + " " + msg);
        } else {
            printMessage("force kill " + pid() + " issued.");
        }
    }

    private void issueNormalKill() {
        if (!pidFile.exists()) {
            printWarning("Not running? File not found: " + pidFile);
            return;
        }

        new ProcessUtil(pid(), err, err, baseDir, utils, windowsKillCommand)
                .killNoThrow();
        waitForProcessToDie();
    }

    private void waitForProcessToDie() {
        long giveUp = System.currentTimeMillis() + killDelay;
        while (processRunning() && System.currentTimeMillis() < giveUp) {
            utils.threads().pause(250);
        }
    }

    private void waitForShellToDie() {
        long giveUp = System.currentTimeMillis() + killDelay;
        while (shellRunning() && System.currentTimeMillis() < giveUp) {
            utils.threads().pause(250);
        }
    }

    public synchronized Map getServerOptions() {
        if (options.isEmpty()) {
            options = optionParser.getOptionsFromHelp(getHelp(new HashMap()));
            options.put(BASEDIR, baseDir.getPath());
            options.put(DATADIR, dataDir.getPath());
        }
        return new HashMap(options);
    }

    public synchronized boolean isRunning() {
        return shellRunning() || processRunning();
    }

    private boolean processRunning() {
        if (!pidFile.exists()) {
            return false;
        }
        return new ProcessUtil(pid(), out, err, baseDir, utils,
                windowsKillCommand).isRunning();
    }

    private boolean shellRunning() {
        return (getShell() != null) && (getShell().isAlive());
    }

    public synchronized String getVersion() {
        return versionString;
    }

    private String getVersionDir() {
        return getVersion().replaceAll("\\.", "-");
    }

    private synchronized void setVersion(boolean checkRunning,
                                         String mysqlVersionString) {
        if (checkRunning && isRunning()) {
            throw new IllegalStateException("Already running");
        }

        if (mysqlVersionString == null || mysqlVersionString.equals("")) {
            versionString = System.getProperty(MYSQLD_VERSION,
                    connectorMxjProperties.getProperty(MYSQLD_VERSION))
                    + "";
        } else {
            versionString = mysqlVersionString;
        }
        versionString = versionString.trim();
    }

    public synchronized void setVersion(String mysqlVersionString) {
        setVersion(true, mysqlVersionString);
    }

    private void printMessage(String msg) {
        println(out, msg);
    }

    private void printWarning(String msg) {
        println(err, "");
        println(err, msg);
    }

    private void println(PrintStream stream, String msg) {
        stream.println(msgPrefix + msg);
    }

    /* called from constructor */
    final String getWindowsKillCommand(Properties props) {
        String key = WINDOWS_KILL_COMMAND;
        String defaultVal = "kill.exe";
        String fileVal = props.getProperty(key, defaultVal);
        String val = System.getProperty(key, fileVal).trim();
        return val.length() > 0 ? val : defaultVal;
    }

    /* called from constructor, over-ride with care */
    final void setOsAndArch(String osName, String osArch) {
        String key = stripUnwantedChars(osName + "-" + osArch);
        this.osName_osArch = platformProperties.getProperty(key, key);
    }

    String stripUnwantedChars(String str) {
        return str.replace(' ', '_').replace('/', '_').replace('\\', '_');
    }

    private Shell exec(String threadName, Map mysqldArgs,
                       PrintStream outStream, PrintStream errStream, boolean withListeners) {

        deployFiles();

        adjustParameterMap(mysqldArgs);
        String[] args = constructArgs(mysqldArgs);
        outStream.println(new ListToString().toString(args));

        Shell launch = utils.shellFactory().newShell(args, threadName,
                outStream, errStream);
        if (withListeners) {
            for (int i = 0; i < completionListensers.size(); i++) {
                Runnable listener = (Runnable) completionListensers.get(i);
                launch.addCompletionListener(listener);
            }
        }
        launch.setDaemon(true);

        printMessage("launching mysqld (" + threadName + ")");

        launch.start();
        return launch;
    }

    public void deployFiles() {
        makeMysqld();
        ensureEssentialFilesExist();
        try {
            makeMysqlClient();
        } catch (MissingResourceException e) {
            printMessage(e.getMessage() + " - OK.");
        }
    }

    private void adjustParameterMap(Map mysqldArgs) {
        ensureDir(mysqldArgs, baseDir, MysqldResourceI.BASEDIR);
        ensureDir(mysqldArgs, dataDir, MysqldResourceI.DATADIR);
        mysqldArgs.put(MysqldResourceI.PID_FILE, pidFile.getPath());
        ensureSocket(mysqldArgs);
    }

    File makeMysqld() {
        return extractExecutable(executableName());
    }

    File makeMysqlClient() {
        return extractExecutable(clientExecutableName());
    }

    private File extractExecutable(String executableName) {
        final File executable = new File(binDir(), executableName);
        if (!executable.exists()) {
            executable.getParentFile().mkdirs();
            String resource = getResourceName(executableName);
            utils.streams().createFileFromResource(resource, executable);
        }
        utils.files().addExecutableRights(executable, out, err);
        return executable;
    }

    String getResourceName() {
        return getResourceName(executableName());
    }

    private String getResourceName(String name) {
        String dir = os_arch();
        return getVersionDir() + Streams.RESOURCE_SEPARATOR + dir
                + Streams.RESOURCE_SEPARATOR + name;
    }

    String os_arch() {
        return osName_osArch;
    }

    private String executableName() {
        if (!isWindows()) {
            return "mysqld";
        }
        String key = "windows-mysqld-command";
        String defaultValue = "mysqld.exe";
        return connectorMxjProperties.getProperty(key, defaultValue);
    }

    private String clientExecutableName() {
        if (isWindows()) {
            return "mysql.exe";
        }
        return "mysql";
    }

    boolean isWindows() {
        return osName_osArch.startsWith("Win");
    }

    File getMysqldFilePointer() {
        return new File(binDir(), executableName());
    }

    private File binDir() {
        return new File(baseDir, "bin");
    }

    void ensureEssentialFilesExist() {
        if (utils.files().isEmpty(dataDir)) {
            String data_jar = getVersionDir() + Streams.RESOURCE_SEPARATOR
                    + "data_dir.jar";
            utils.streams().expandResourceJar(dataDir, data_jar);
        }
        utils.streams().expandResourceJar(baseDir,
                getVersionDir() + Streams.RESOURCE_SEPARATOR + shareJar());
    }

    void ensureSocket(Map mysqldArgs) {
        String socketString = (String) mysqldArgs.get(MysqldResourceI.SOCKET);
        if (socketString != null) {
            return;
        }
        mysqldArgs.put(MysqldResourceI.SOCKET, "mysql.sock");
    }

    private void ensureDir(Map mysqldArgs, File expected, String key) {
        String dirString = (String) mysqldArgs.get(key);
        if (dirString != null) {
            File asConnonical = utils.files().validCononicalDir(
                    new File(dirString));
            if (!expected.equals(asConnonical)) {
                String msg = dirString + " not equal to " + expected;
                throw new IllegalArgumentException(msg);
            }
        }
        mysqldArgs.put(key, utils.files().getPath(expected));
    }

    String[] constructArgs(Map mysqldArgs) {
        List strs = new ArrayList();
        strs.add(utils.files().getPath(getMysqldFilePointer()));

        strs.add("--no-defaults");
        if (isWindows()) {
            strs.add("--console");
        }
        Iterator it = mysqldArgs.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            StringBuffer buf = new StringBuffer("--");
            buf.append(key);
            if (value != null) {
                buf.append("=");
                buf.append(value);
            }
            strs.add(buf.toString());
        }

        return utils.str().toStringArray(strs);
    }

    protected void finalize() throws Throwable {
        if (getShell() != null) {
            printWarning("resource released without closure.");
            trace.printStackTrace(err);
        }
        super.finalize();
    }

    String shareJar() {
        String shareJar = "share_dir.jar";
        if (isWindows()) {
            String key = "windows-share-dir-jar";
            String defaultVal = "win_" + shareJar;
            shareJar = connectorMxjProperties.getProperty(key, defaultVal);
        }
        return shareJar;
    }

    void setShell(Shell shell) {
        this.shell = shell;
    }

    Shell getShell() {
        return shell;
    }

    public File getBaseDir() {
        return baseDir;
    }

    public File getDataDir() {
        return dataDir;
    }

    /**
     * the kill-delay is read from the properties file, but a value in System
     * Properties will act as an over-ride.
     */
    int getKillDelyFromProperties(final Properties props) {
        final int defaultDelayFiveMinutes = 5 * 60 * 1000;
        final String key = KILL_DELAY;
        String propsVal = props.getProperty(key, "" + defaultDelayFiveMinutes);
        String sysProp = System.getProperty(key, propsVal);
        return parseInt(sysProp, defaultDelayFiveMinutes);
    }

    public synchronized void setKillDelay(int millis) {
        this.killDelay = millis;
    }

    public synchronized void addCompletionListenser(Runnable listener) {
        completionListensers.add(listener);
    }

    private String getHelp(Map params) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        PrintStream capturedOut = new PrintStream(bos);

        params.put("help", null);
        params.put("verbose", null);

        exec("getOptions", params, capturedOut, capturedOut, false).join();

        params.remove("help");
        params.remove("verbose");

        utils.threads().pause(500);
        capturedOut.flush();
        capturedOut.close(); // should flush();

        return new String(bos.toByteArray());
    }

    public synchronized int getPort() {
        final int defaultVal = 0;
        if (isRunning() && portFile.exists()) {
            return parseInt(utils.files().asString(portFile), defaultVal);
        }
        return defaultVal;
    }

    int parseInt(Object parseMe, int defaultVal) {
        return utils.str().parseInt(parseMe, defaultVal, err);
    }

    // ---------------------------------------------------------
    static void printUsage(PrintStream out) {
        String command = "java " + MysqldResource.class.getName();
        String basedir = " --" + MysqldResourceI.BASEDIR;
        String datadir = " --" + MysqldResourceI.DATADIR;
        out.println("Usage to start: ");
        out.println(command + " [ server options ]");
        out.println();
        out.println("Usage to shutdown: ");
        out.println(command + " --shutdown [" + basedir
                + "=/full/path/to/basedir ]");
        out.println();
        out.println("Common server options include:");
        out.println(basedir + "=/full/path/to/basedir");
        out.println(datadir + "=/full/path/to/datadir");
        out.println(" --" + MysqldResourceI.SOCKET
                + "=/full/path/to/socketfile");
        out.println();
        out.println("Example:");
        out.println(command + basedir + "=/home/duke/dukeapp/db" + datadir
                + "=/data/dukeapp/data" + " --max_allowed_packet=65000000");
        out.println(command + " --shutdown" + basedir
                + "=/home/duke/dukeapp/db");
        out.println();
    }

    public static void main(String[] args) {
        CommandLineOptionsParser clop = new CommandLineOptionsParser(args);
        if (clop.containsKey("help")) {
            printUsage(System.out);
            return;
        }

        PrintStream out = System.out;
        PrintStream err = System.err;
        if (clop.containsKey("silent")) {
            clop.remove("silent");
            PrintStream devNull = new NullPrintStream();
            out = devNull;
            err = devNull;
        }

        MysqldResource mysqld = new MysqldResource(clop.getBaseDir(), clop
                .getDataDir(), clop.getVersion(), out, err);

        Integer newKillDelay = clop.getKillDelay(err);
        if (newKillDelay != null) {
            mysqld.setKillDelay(newKillDelay.intValue());
        }

        if (clop.isShutdown()) {
            mysqld.shutdown();
            return;
        }

        mysqld.start(new Threads().newName(), clop.asMap());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy