diff src/main/java/nl/cwi/monetdb/merovingian/Control.java @ 0:a5a898f6886c

Copy of MonetDB java directory changeset e6e32756ad31.
author Sjoerd Mullender <sjoerd@acm.org>
date Wed, 21 Sep 2016 09:34:48 +0200 (2016-09-21)
parents
children 57978db4ee57 b9b35ca2eec2
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/merovingian/Control.java
@@ -0,0 +1,401 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.  If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+ */
+
+package nl.cwi.monetdb.merovingian;
+
+import nl.cwi.monetdb.mcl.net.MapiSocket;
+import nl.cwi.monetdb.mcl.io.*;
+import nl.cwi.monetdb.mcl.MCLException;
+import nl.cwi.monetdb.mcl.parser.MCLParseException;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * A Control class to perform operations on a remote merovingian
+ * instance, using the TCP control protocol.
+ * 
+ * This class implements the protocol specific bits to perform all
+ * possible actions against a merovingian server that has remote control
+ * facilities enabled.
+ * <br />
+ * In the merovingian world, other merovingians in the vicinity are
+ * known to each merovingian, allowing to perform cluster wide actions.
+ * The implementation taken in this class is to require one known
+ * merovingian to get insight in the entire network.  Note that
+ * connecting to a merovingian requires a passphrase that is likely to
+ * be different for each merovingian.
+ *
+ * @author Fabian Groffen
+ * @version 1.0
+ */
+public class Control {
+	/** The host to connect to */
+	private final String host;
+	/** The port to connect to */
+	private final int port;
+	/** The passphrase to use when connecting */
+	private final String passphrase;
+	/** The file we should write MapiSocket debuglog to */
+	private String debug;
+
+
+	/**
+	 * Constructs a new Control object.
+	 *
+	 * @throws IllegalArgumentException if host, port or passphrase are
+	 * null or &lt;= 0
+	 */
+	public Control(String host, int port, String passphrase)
+		throws IllegalArgumentException
+	{
+		this.host = host;
+		this.port = port;
+		this.passphrase = passphrase;
+	}
+
+	/**
+	 * Instructs to write a MCL protocol debug log to the given file.
+	 * This affects any newly performed command, and can be changed
+	 * inbetween commands.  Passing null to this method disables the
+	 * debug log.
+	 *
+	 * @param filename the filename to write debug information to, or null
+	 */
+	public void setDebug(String filename) {
+		this.debug = filename;
+	}
+
+	private String controlHash(String pass, String salt) {
+		long ho;
+		long h = 0;
+
+		/* use a very simple hash function designed for a single int val
+		 * (hash buckets), we can make this more interesting if necessary in
+		 * the future.
+		 * http://www.cs.hmc.edu/~geoff/classes/hmc.cs070.200101/homework10/hashfuncs.html */
+
+		for (int i = 0; i < pass.length(); i++) {
+			ho = h & 0xF8000000L;
+			h <<= 5;
+			h &= 0xFFFFFFFFL;
+			h ^= ho >>> 27;
+			h ^= (int)(pass.charAt(i));
+		}
+
+		for (int i = 0; i < salt.length(); i++) {
+			ho = h & 0xF8000000L;
+			h <<= 5;
+			h &= 0xFFFFFFFFL;
+			h ^= ho >> 27;
+			h ^= (int)(salt.charAt(i));
+		}
+
+		return(Long.toString(h));
+	}
+	
+	final static private String RESPONSE_OK = "OK";
+
+	private List<String> sendCommand(
+			String database, String command, boolean hasOutput)
+		throws MerovingianException, IOException
+	{
+		BufferedMCLReader min;
+		BufferedMCLWriter mout;
+		MapiSocket ms = new MapiSocket();
+		ms.setDatabase("merovingian");
+		ms.setLanguage("control");
+		if (debug != null)
+			ms.debug(debug);
+		try {
+			ms.connect(host, port, "monetdb", passphrase);
+			min = ms.getReader();
+			mout = ms.getWriter();
+		} catch (MCLParseException e) {
+			throw new MerovingianException(e.getMessage());
+		} catch (MCLException e) {
+			throw new MerovingianException(e.getMessage());
+		} catch (AssertionError e) { // mcl panics
+			ms.close();
+			
+			// Try old protocol instead
+			Socket s;
+			PrintStream out;
+			BufferedReader in;
+			s = new Socket(host, port);
+			out = new PrintStream(s.getOutputStream());
+			in = new BufferedReader(
+					new InputStreamReader(s.getInputStream()));
+			try {
+				/* login ritual, step 1: get challenge from server */
+				String response = in.readLine();
+				if (response == null)
+					throw new MerovingianException("server closed the connection");
+
+				if (!response.startsWith("merovingian:1:") &&
+						!response.startsWith("merovingian:2:"))
+					throw new MerovingianException("unsupported merovingian server");
+
+				String[] tokens = response.split(":");
+				if (tokens.length < 3)
+					throw new MerovingianException("did not understand merovingian server");
+				String version = tokens[1];
+				String token = tokens[2];
+
+				response = controlHash(passphrase, token);
+				if (version.equals("1")) {
+					out.print(response + "\n");
+				} else if (version.equals("2")) {
+					// we only support control mode for now
+					out.print(response + ":control\n");
+				}
+
+				response = in.readLine();
+				if (response == null) {
+					throw new MerovingianException("server closed the connection");
+				}
+
+				if (!response.equals(RESPONSE_OK)) {
+					throw new MerovingianException(response);
+				}
+
+				/* send command, form is simple: "<db> <cmd>\n" */
+				out.print(database + " " + command + "\n");
+
+				/* Response has the first line either "OK\n" or an error
+				 * message.  In case of a command with output, the data will
+				 * follow the first line */
+				response = in.readLine();
+				if (response == null) {
+					throw new MerovingianException("server closed the connection");
+				}
+				if (!response.equals(RESPONSE_OK)) {
+					throw new MerovingianException(response);
+				}
+
+				if (!hasOutput)
+					return null;
+
+				ArrayList<String> l = new ArrayList<String>();
+				while ((response = in.readLine()) != null) {
+					l.add(response);
+				}
+				return l;
+			} finally {
+				in.close();
+				out.close();
+				s.close();
+			}
+		}
+
+		mout.writeLine(database + " " + command + "\n");
+		ArrayList<String> l = new ArrayList<String>();
+		String tmpLine = min.readLine();
+		int linetype = min.getLineType();
+		if (linetype == BufferedMCLReader.ERROR)
+			throw new MerovingianException(tmpLine.substring(6));
+		if (linetype != BufferedMCLReader.RESULT)
+			throw new MerovingianException("unexpected line: " + tmpLine);
+		if (!tmpLine.substring(1).equals(RESPONSE_OK))
+			throw new MerovingianException(tmpLine.substring(1));
+		tmpLine = min.readLine();
+		linetype = min.getLineType();
+		while (linetype != BufferedMCLReader.PROMPT) {
+			if (linetype != BufferedMCLReader.RESULT)
+				throw new MerovingianException("unexpected line: " +
+						tmpLine);
+
+			l.add(tmpLine.substring(1));
+
+			tmpLine = min.readLine();
+			linetype = min.getLineType();
+		}
+
+		ms.close();
+		return l;
+	}
+
+	public void start(String database)
+		throws MerovingianException, IOException
+	{
+		sendCommand(database, "start", false);
+	}
+
+	public void stop(String database)
+		throws MerovingianException, IOException 
+	{   
+		sendCommand(database, "stop", false);
+	}
+
+	public void kill(String database)
+		throws MerovingianException, IOException
+	{
+		sendCommand(database, "kill", false);
+	}
+
+	public void create(String database)
+		throws MerovingianException, IOException
+	{
+		sendCommand(database, "create", false);
+	}
+
+	public void destroy(String database)
+		throws MerovingianException, IOException
+	{
+		sendCommand(database, "destroy", false);
+	}
+
+	public void lock(String database)
+		throws MerovingianException, IOException
+	{
+		sendCommand(database, "lock", false);
+	}
+
+	public void release(String database)
+		throws MerovingianException, IOException
+	{
+		sendCommand(database, "release", false);
+	}
+
+	public void rename(String database, String newname)
+		throws MerovingianException, IOException
+	{
+		if (newname == null)
+			newname = ""; /* force error from merovingian */
+
+		sendCommand(database, "name=" + newname, false);
+	}
+
+	/**
+	 * Sets property for database to value.  If value is null, the
+	 * property is unset, and its inherited value becomes active again.
+	 *
+	 * @param database the target database
+	 * @param property the property to set value for
+	 * @param value the value to set
+	 * @throws MerovingianException if performing the command failed at
+	 *         the merovingian side
+	 * @throws IOException if connecting to or communicating with
+	 *         merovingian failed
+	 */
+	public void setProperty(String database, String property, String value)
+		throws MerovingianException, IOException
+	{
+		/* inherit: set to empty string */
+		if (value == null)
+			value = "";
+
+		sendCommand(database, property + "=" + value, false);
+	}
+
+	public void inheritProperty(String database, String property)
+		throws MerovingianException, IOException
+	{
+		setProperty(database, property, null);
+	}
+
+	public Properties getProperties(String database)
+		throws MerovingianException, IOException
+	{
+		Properties ret = new Properties();
+		List<String> response = sendCommand(database, "get", true);
+		for (String responseLine : response) {
+			if (responseLine.startsWith("#"))
+				continue;
+			int pos = responseLine.indexOf("=");
+			if (pos > 0) {
+				ret.setProperty(
+						responseLine.substring(0, pos),
+						responseLine.substring(pos + 1, responseLine.length()));
+			}
+		}
+		return ret;
+	}
+
+	public Properties getDefaultProperties()
+		throws MerovingianException, IOException
+	{
+		return(getProperties("#defaults"));
+	}
+
+	public SabaothDB getStatus(String database)
+		throws MerovingianException, IOException
+	{
+		List<String> response = sendCommand(database, "status", true);
+		if (response.isEmpty())
+			throw new MerovingianException("communication error");
+		return new SabaothDB(response.get(0));
+	}
+	
+	/**
+	 * Test whether a specific database exists. 
+	 * 
+	 * @param database
+	 * @return true, iff database already exists.
+	 * @throws MerovingianException
+	 * @throws IOException
+	 */
+	public boolean exists(String database)
+		throws MerovingianException, IOException
+	{
+		List<SabaothDB> all = getAllStatuses();
+		for (SabaothDB db : all) {
+			if (db.getName().equals(database)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public List<SabaothDB> getAllStatuses()
+		throws MerovingianException, IOException
+	{
+		List<SabaothDB> l = new ArrayList<SabaothDB>();
+		List<String> response = sendCommand("#all", "status", true);
+		try {
+			for (String responseLine : response) {
+				l.add(new SabaothDB(responseLine));
+			}
+		} catch (IllegalArgumentException e) {
+			throw new MerovingianException(e.getMessage());
+		}
+		return Collections.unmodifiableList(l);
+	}
+
+	public List<URI> getAllNeighbours()
+		throws MerovingianException, IOException
+	{
+		List<URI> l = new ArrayList<URI>();
+		List<String> response = sendCommand("anelosimus", "eximius", true);
+		try {
+			for (String responseLine : response) {
+				// format is <db>\t<uri>
+				String[] parts = responseLine.split("\t", 2);
+				if (parts.length != 2)
+					throw new MerovingianException("invalid entry: " +
+							responseLine);
+				if (parts[0].equals("*")) {
+					l.add(new URI(parts[1]));
+				} else {
+					l.add(new URI(parts[1] + parts[0]));
+				}
+			}
+		} catch (URISyntaxException e) {
+			throw new MerovingianException(e.getMessage());
+		}
+		return Collections.unmodifiableList(l);
+	}
+}