comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:a5a898f6886c
1 /*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 *
6 * Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
7 */
8
9 package nl.cwi.monetdb.merovingian;
10
11 import nl.cwi.monetdb.mcl.net.MapiSocket;
12 import nl.cwi.monetdb.mcl.io.*;
13 import nl.cwi.monetdb.mcl.MCLException;
14 import nl.cwi.monetdb.mcl.parser.MCLParseException;
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.io.PrintStream;
19 import java.net.Socket;
20 import java.net.URI;
21 import java.net.URISyntaxException;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Properties;
26
27 /**
28 * A Control class to perform operations on a remote merovingian
29 * instance, using the TCP control protocol.
30 *
31 * This class implements the protocol specific bits to perform all
32 * possible actions against a merovingian server that has remote control
33 * facilities enabled.
34 * <br />
35 * In the merovingian world, other merovingians in the vicinity are
36 * known to each merovingian, allowing to perform cluster wide actions.
37 * The implementation taken in this class is to require one known
38 * merovingian to get insight in the entire network. Note that
39 * connecting to a merovingian requires a passphrase that is likely to
40 * be different for each merovingian.
41 *
42 * @author Fabian Groffen
43 * @version 1.0
44 */
45 public class Control {
46 /** The host to connect to */
47 private final String host;
48 /** The port to connect to */
49 private final int port;
50 /** The passphrase to use when connecting */
51 private final String passphrase;
52 /** The file we should write MapiSocket debuglog to */
53 private String debug;
54
55
56 /**
57 * Constructs a new Control object.
58 *
59 * @throws IllegalArgumentException if host, port or passphrase are
60 * null or &lt;= 0
61 */
62 public Control(String host, int port, String passphrase)
63 throws IllegalArgumentException
64 {
65 this.host = host;
66 this.port = port;
67 this.passphrase = passphrase;
68 }
69
70 /**
71 * Instructs to write a MCL protocol debug log to the given file.
72 * This affects any newly performed command, and can be changed
73 * inbetween commands. Passing null to this method disables the
74 * debug log.
75 *
76 * @param filename the filename to write debug information to, or null
77 */
78 public void setDebug(String filename) {
79 this.debug = filename;
80 }
81
82 private String controlHash(String pass, String salt) {
83 long ho;
84 long h = 0;
85
86 /* use a very simple hash function designed for a single int val
87 * (hash buckets), we can make this more interesting if necessary in
88 * the future.
89 * http://www.cs.hmc.edu/~geoff/classes/hmc.cs070.200101/homework10/hashfuncs.html */
90
91 for (int i = 0; i < pass.length(); i++) {
92 ho = h & 0xF8000000L;
93 h <<= 5;
94 h &= 0xFFFFFFFFL;
95 h ^= ho >>> 27;
96 h ^= (int)(pass.charAt(i));
97 }
98
99 for (int i = 0; i < salt.length(); i++) {
100 ho = h & 0xF8000000L;
101 h <<= 5;
102 h &= 0xFFFFFFFFL;
103 h ^= ho >> 27;
104 h ^= (int)(salt.charAt(i));
105 }
106
107 return(Long.toString(h));
108 }
109
110 final static private String RESPONSE_OK = "OK";
111
112 private List<String> sendCommand(
113 String database, String command, boolean hasOutput)
114 throws MerovingianException, IOException
115 {
116 BufferedMCLReader min;
117 BufferedMCLWriter mout;
118 MapiSocket ms = new MapiSocket();
119 ms.setDatabase("merovingian");
120 ms.setLanguage("control");
121 if (debug != null)
122 ms.debug(debug);
123 try {
124 ms.connect(host, port, "monetdb", passphrase);
125 min = ms.getReader();
126 mout = ms.getWriter();
127 } catch (MCLParseException e) {
128 throw new MerovingianException(e.getMessage());
129 } catch (MCLException e) {
130 throw new MerovingianException(e.getMessage());
131 } catch (AssertionError e) { // mcl panics
132 ms.close();
133
134 // Try old protocol instead
135 Socket s;
136 PrintStream out;
137 BufferedReader in;
138 s = new Socket(host, port);
139 out = new PrintStream(s.getOutputStream());
140 in = new BufferedReader(
141 new InputStreamReader(s.getInputStream()));
142 try {
143 /* login ritual, step 1: get challenge from server */
144 String response = in.readLine();
145 if (response == null)
146 throw new MerovingianException("server closed the connection");
147
148 if (!response.startsWith("merovingian:1:") &&
149 !response.startsWith("merovingian:2:"))
150 throw new MerovingianException("unsupported merovingian server");
151
152 String[] tokens = response.split(":");
153 if (tokens.length < 3)
154 throw new MerovingianException("did not understand merovingian server");
155 String version = tokens[1];
156 String token = tokens[2];
157
158 response = controlHash(passphrase, token);
159 if (version.equals("1")) {
160 out.print(response + "\n");
161 } else if (version.equals("2")) {
162 // we only support control mode for now
163 out.print(response + ":control\n");
164 }
165
166 response = in.readLine();
167 if (response == null) {
168 throw new MerovingianException("server closed the connection");
169 }
170
171 if (!response.equals(RESPONSE_OK)) {
172 throw new MerovingianException(response);
173 }
174
175 /* send command, form is simple: "<db> <cmd>\n" */
176 out.print(database + " " + command + "\n");
177
178 /* Response has the first line either "OK\n" or an error
179 * message. In case of a command with output, the data will
180 * follow the first line */
181 response = in.readLine();
182 if (response == null) {
183 throw new MerovingianException("server closed the connection");
184 }
185 if (!response.equals(RESPONSE_OK)) {
186 throw new MerovingianException(response);
187 }
188
189 if (!hasOutput)
190 return null;
191
192 ArrayList<String> l = new ArrayList<String>();
193 while ((response = in.readLine()) != null) {
194 l.add(response);
195 }
196 return l;
197 } finally {
198 in.close();
199 out.close();
200 s.close();
201 }
202 }
203
204 mout.writeLine(database + " " + command + "\n");
205 ArrayList<String> l = new ArrayList<String>();
206 String tmpLine = min.readLine();
207 int linetype = min.getLineType();
208 if (linetype == BufferedMCLReader.ERROR)
209 throw new MerovingianException(tmpLine.substring(6));
210 if (linetype != BufferedMCLReader.RESULT)
211 throw new MerovingianException("unexpected line: " + tmpLine);
212 if (!tmpLine.substring(1).equals(RESPONSE_OK))
213 throw new MerovingianException(tmpLine.substring(1));
214 tmpLine = min.readLine();
215 linetype = min.getLineType();
216 while (linetype != BufferedMCLReader.PROMPT) {
217 if (linetype != BufferedMCLReader.RESULT)
218 throw new MerovingianException("unexpected line: " +
219 tmpLine);
220
221 l.add(tmpLine.substring(1));
222
223 tmpLine = min.readLine();
224 linetype = min.getLineType();
225 }
226
227 ms.close();
228 return l;
229 }
230
231 public void start(String database)
232 throws MerovingianException, IOException
233 {
234 sendCommand(database, "start", false);
235 }
236
237 public void stop(String database)
238 throws MerovingianException, IOException
239 {
240 sendCommand(database, "stop", false);
241 }
242
243 public void kill(String database)
244 throws MerovingianException, IOException
245 {
246 sendCommand(database, "kill", false);
247 }
248
249 public void create(String database)
250 throws MerovingianException, IOException
251 {
252 sendCommand(database, "create", false);
253 }
254
255 public void destroy(String database)
256 throws MerovingianException, IOException
257 {
258 sendCommand(database, "destroy", false);
259 }
260
261 public void lock(String database)
262 throws MerovingianException, IOException
263 {
264 sendCommand(database, "lock", false);
265 }
266
267 public void release(String database)
268 throws MerovingianException, IOException
269 {
270 sendCommand(database, "release", false);
271 }
272
273 public void rename(String database, String newname)
274 throws MerovingianException, IOException
275 {
276 if (newname == null)
277 newname = ""; /* force error from merovingian */
278
279 sendCommand(database, "name=" + newname, false);
280 }
281
282 /**
283 * Sets property for database to value. If value is null, the
284 * property is unset, and its inherited value becomes active again.
285 *
286 * @param database the target database
287 * @param property the property to set value for
288 * @param value the value to set
289 * @throws MerovingianException if performing the command failed at
290 * the merovingian side
291 * @throws IOException if connecting to or communicating with
292 * merovingian failed
293 */
294 public void setProperty(String database, String property, String value)
295 throws MerovingianException, IOException
296 {
297 /* inherit: set to empty string */
298 if (value == null)
299 value = "";
300
301 sendCommand(database, property + "=" + value, false);
302 }
303
304 public void inheritProperty(String database, String property)
305 throws MerovingianException, IOException
306 {
307 setProperty(database, property, null);
308 }
309
310 public Properties getProperties(String database)
311 throws MerovingianException, IOException
312 {
313 Properties ret = new Properties();
314 List<String> response = sendCommand(database, "get", true);
315 for (String responseLine : response) {
316 if (responseLine.startsWith("#"))
317 continue;
318 int pos = responseLine.indexOf("=");
319 if (pos > 0) {
320 ret.setProperty(
321 responseLine.substring(0, pos),
322 responseLine.substring(pos + 1, responseLine.length()));
323 }
324 }
325 return ret;
326 }
327
328 public Properties getDefaultProperties()
329 throws MerovingianException, IOException
330 {
331 return(getProperties("#defaults"));
332 }
333
334 public SabaothDB getStatus(String database)
335 throws MerovingianException, IOException
336 {
337 List<String> response = sendCommand(database, "status", true);
338 if (response.isEmpty())
339 throw new MerovingianException("communication error");
340 return new SabaothDB(response.get(0));
341 }
342
343 /**
344 * Test whether a specific database exists.
345 *
346 * @param database
347 * @return true, iff database already exists.
348 * @throws MerovingianException
349 * @throws IOException
350 */
351 public boolean exists(String database)
352 throws MerovingianException, IOException
353 {
354 List<SabaothDB> all = getAllStatuses();
355 for (SabaothDB db : all) {
356 if (db.getName().equals(database)) {
357 return true;
358 }
359 }
360 return false;
361 }
362
363 public List<SabaothDB> getAllStatuses()
364 throws MerovingianException, IOException
365 {
366 List<SabaothDB> l = new ArrayList<SabaothDB>();
367 List<String> response = sendCommand("#all", "status", true);
368 try {
369 for (String responseLine : response) {
370 l.add(new SabaothDB(responseLine));
371 }
372 } catch (IllegalArgumentException e) {
373 throw new MerovingianException(e.getMessage());
374 }
375 return Collections.unmodifiableList(l);
376 }
377
378 public List<URI> getAllNeighbours()
379 throws MerovingianException, IOException
380 {
381 List<URI> l = new ArrayList<URI>();
382 List<String> response = sendCommand("anelosimus", "eximius", true);
383 try {
384 for (String responseLine : response) {
385 // format is <db>\t<uri>
386 String[] parts = responseLine.split("\t", 2);
387 if (parts.length != 2)
388 throw new MerovingianException("invalid entry: " +
389 responseLine);
390 if (parts[0].equals("*")) {
391 l.add(new URI(parts[1]));
392 } else {
393 l.add(new URI(parts[1] + parts[0]));
394 }
395 }
396 } catch (URISyntaxException e) {
397 throw new MerovingianException(e.getMessage());
398 }
399 return Collections.unmodifiableList(l);
400 }
401 }