comparison src/main/java/org/monetdb/merovingian/Control.java @ 391:f523727db392

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