repctl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in repctl.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # Repctl - Manage Replication among a set of SQL Servers
2
+
3
+ `repctl` is a utility to configure, reconfigure, start, stop, crash, generate
4
+ workloads, dump, restore, benchmark, and monitor a set of SQL servers for
5
+ development environments. Replication relationships can be set up among server
6
+ instances with a single command. While running a load generator or benchmark,
7
+ the replication status, including current lag, can be seen in a continuously
8
+ updated display. A slave can be added to an existing server that already has
9
+ data.
10
+
11
+ The `repctl` gem includes a _Thor_ script that makes all the `repctl`
12
+ functionality available at the command line.
13
+
14
+ ## Limitations
15
+
16
+ Currently only MySQL is supported but PostgresSQL will soon be added. All the
17
+ server instances must run on a single host. This restriction may be soon lifted
18
+ as well.
19
+
20
+ ## Installation
21
+
22
+ You will need to have a local installation installation of MySQL. You do not
23
+ need to do anything to configure the installation. For example, if you compile
24
+ MySQL from source, then do `make install`, then you are done! No MySQL
25
+ post-installation steps are necessary. All post-install configuration is
26
+ handled by `repctl`.
27
+
28
+ The top
29
+
30
+ == Available Commands
31
+
32
+ tethys:repctl mbs$ thor list
33
+ mysql
34
+ -----
35
+ thor mysql:change_master MASTER SLAVE FILE POSITION # Execute CHANGE MASTER TO on the SLAVE.
36
+ thor mysql:cluster_user INSTANCE # Create the cluster user account on a MySQL instance.
37
+ thor mysql:config INSTANCE # Initialize the data directory for a new instance.
38
+ thor mysql:config_all # Initialize the data directories for all instances.
39
+ thor mysql:crash INSTANCE # Crash a running MySQL server.
40
+ thor mysql:dump INSTANCE [DUMPFILE] # Dump all databases after FLUSH TABLES WITH READ LOCK
41
+ thor mysql:repl_user INSTANCE # Create the replication user account on a MySQL insta...
42
+ thor mysql:reset INSTANCE # Remove database and restart MySQL server.
43
+ thor mysql:reset_all # Remove all databases and restart MySQL instances.
44
+ thor mysql:restore INSTANCE [DUMPFILE] # Restore INSTANCE from a 'mysqldump' file DUMPFILE.
45
+ thor mysql:start_slave SLAVE # Issue START SLAVE on the SLAVE MySQL instance.
46
+ thor mysql:status # Show the status of replication.
47
+ thor mysql:stop INSTANCE # Stop a running MySQL server instance.
48
+ thor mysql:stop_all # Stop all the MySQL servers.
49
+
50
+ setup
51
+ -----
52
+ thor setup:add_slave MASTER SLAVE # Master has some data that is used to initialize the slave.
53
+ thor setup:repl_pair MASTER SLAVE # Set up a single master/slave replication pair from the very beginning.
54
+
55
+ utils
56
+ -----
57
+ thor utils:bench [INSTANCE] [PROPS] # Run the Tungsten Bristlecone benchmarker. The INSTAN...
58
+ thor utils:create_db [INSTANCE] [DBNAME] # "Create a database on a MySQL instance. INSTANCE de...
59
+ thor utils:create_tbl [INSTANCE] [DBNAME] [TBLNAME] # Create a database table. INSTANCE defaults to DEFAU...
60
+ thor utils:gen_rows [INSTANCE], [DBNAME], [TBLNAME] # Add rows to a table that was created by "utils:crea...
61
+
62
+ == Configuring Simple Repctl
63
+
64
+ This tool needs some configuration before you can use it.
65
+
66
+ You should have an valid MySQL installation and the Thor gem installed. Your existing MySQL server will not be affected by the +repctl+ script. However, binaries from this installation will be reused. In the +config.rb+ file set the constants:
67
+
68
+ * MYSQL_HOME -- the location of the local MySQL installation
69
+ * DATA_HOME -- the location of the directory where per-MySQL server data directories are created.
70
+ * DUMP_DIR -- the location where you want dump files to be stored
71
+ * RELAY_LOG -- adjust this according to your hostname
72
+
73
+ Next, define the potential instances you want to create. Edit the <tt>servers.yml</tt> file as appropriate.
74
+
75
+ Finally, edit the existing <tt>my*.cnf*</tt> files to at least have the correct <tt>datadir</tt> defined.
76
+
77
+ == Using Simple Repctl
78
+
79
+ You are now ready to rock. Run <tt>thor mysql:start_all</tt> to start up all the servers listed in your +servers.yml+ file. Instead or subsequently, you can run <tt>thor setup:repl_pair 1 2</tt> to reset everything and create a master/slave replication pair. Start up some load to the MySQL master (at socket +/tmp/mysql1.sock+, by default), then watch the replication status change by running <tt>thor mysql:status -s 1 2 -c 5</tt> to see a continuous update of the status, updated every 5 seconds. Add a new slave using instance +3+, which may or may not be running and may or may not have its data directory initialized, by running <tt>thor setup:add_slave 1 3</tt>. This does a dump on the master and a restore on the slave and restarts replication using the proper replication coordinates.
80
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/mysql.thor ADDED
@@ -0,0 +1,431 @@
1
+ require 'socket'
2
+ require 'repctl'
3
+
4
+ # Thor has the 'helpful' property that any Thor task gets executed only
5
+ # once. The 'Helpers' module makes many Thor tasks available as ordinary
6
+ # Ruby functions for internal use. The corresponding Thor tasks usually
7
+ # delegate to the corresponding helper function with 'super'.
8
+ module Helpers
9
+
10
+ include Repctl::Config
11
+ include Repctl::Servers
12
+
13
+ def start(instance)
14
+ say "Starting instance #{instance}.", :green
15
+ do_start(instance)
16
+ end
17
+
18
+ def start_all
19
+ all_instances.each do |instance|
20
+ start(instance)
21
+ end
22
+ end
23
+
24
+ def stop(instance)
25
+ say "Stopping instance #{instance}.", :green
26
+ do_admin(instance, "shutdown")
27
+ end
28
+
29
+ def stop_all
30
+ all_live_instances.each do |instance|
31
+ stop(instance)
32
+ end
33
+ end
34
+
35
+ def config(instance)
36
+ say "Initializing new data directory for instance #{instance}."
37
+ do_config(instance)
38
+ end
39
+
40
+ def config_all
41
+ all_instances.each do |instance|
42
+ config(instance)
43
+ end
44
+ end
45
+
46
+ def secure_accounts(instance)
47
+ do_secure_accounts(instance)
48
+ end
49
+
50
+ def secure_accounts_all
51
+ Repctl::Servers.all_live_instances.each do |instance|
52
+ secure_accounts(instance)
53
+ end
54
+ end
55
+
56
+ def reset(instance)
57
+ stop(instance)
58
+ config(instance)
59
+ start(instance)
60
+ secure_accounts(instance)
61
+ end
62
+
63
+ def restart(instance)
64
+ stop(instance)
65
+ start(instance)
66
+ end
67
+
68
+ def reset_all
69
+ all_instances.each do |instance|
70
+ reset(instance)
71
+ end
72
+ end
73
+
74
+ def change_master(master, slave, file, position)
75
+ say "Changing master: master = #{master}, slave = #{slave}, file = #{file}, position = #{position}"
76
+ do_change_master(master, slave, :file => file, :position => position)
77
+ end
78
+
79
+ def start_slave(slave)
80
+ say "Starting slave #{slave}", :green
81
+ run_mysql_query(slave, "START SLAVE")
82
+ end
83
+
84
+ def crash(instance)
85
+ say "Crashing instance #{instance}", :red
86
+ do_crash(instance)
87
+ end
88
+
89
+ def repl_user(instance)
90
+ say "Creating replication account on instance #{instance}.", :green
91
+ do_repl_user(instance)
92
+ end
93
+
94
+ def cluster_user(instance = 1)
95
+ say "Installing cluster user for instance #{instance}.", :green
96
+ do_cluster_user(instance)
97
+ end
98
+
99
+ end # Module helpers
100
+
101
+ class Mysql < Thor
102
+
103
+ include Thor::Actions
104
+ include Repctl::Config
105
+ include Repctl::Commands
106
+ include Repctl::Servers
107
+ include Helpers
108
+
109
+ desc "start INSTANCE", "Ensure that the given MySQL server instance is running."
110
+ def start(instance)
111
+ super
112
+ end
113
+
114
+ desc "start_all", "Start all the MySQL instances."
115
+ def start_all
116
+ super
117
+ end
118
+
119
+ desc "stop INSTANCE", "Stop a running MySQL server instance."
120
+ def stop(instance)
121
+ super
122
+ end
123
+
124
+ desc "stop_all", "Stop all the MySQL servers."
125
+ def stop_all
126
+ super
127
+ end
128
+
129
+ desc "config INSTANCE", "Initialize the data directory for a new instance."
130
+ def config(instance)
131
+ super
132
+ end
133
+
134
+ desc "config_all", "Initialize the data directories for all instances."
135
+ def config_all
136
+ super
137
+ end
138
+
139
+ desc "secure_accounts INSTANCE", "Add passwords for root and anonymous accounts."
140
+ def secure_accounts(instance)
141
+ super
142
+ end
143
+
144
+ desc "secure_accounts_all", "Add passwords for root and anonymous accounts for all instances."
145
+ def secure_accounts_all
146
+ super
147
+ end
148
+
149
+ desc "reset", "Remove the database data directory, reconfigure and restart the instance."
150
+ def reset(instance)
151
+ super
152
+ end
153
+
154
+ desc "reset_all", "Remove all databases and restart MySQL instances."
155
+ def reset_all
156
+ super
157
+ end
158
+
159
+ desc "start_slave SLAVE", "Issue START SLAVE on the SLAVE MySQL instance."
160
+ def start_slave(slave)
161
+ super
162
+ end
163
+
164
+ desc "change_master MASTER SLAVE FILE POSITION", "Execute CHANGE MASTER TO on the SLAVE."
165
+ def change_master(master, slave, file, position)
166
+ super
167
+ end
168
+
169
+ desc "crash INSTANCE", "Crash a running MySQL server."
170
+ def crash(instance)
171
+ super
172
+ end
173
+
174
+ desc "repl_user INSTANCE", "Create the replication user account on a MySQL instance."
175
+ def repl_user(instance)
176
+ super
177
+ end
178
+
179
+ desc "cluster_user INSTANCE", "Create the cluster user account on a MySQL instance."
180
+ def cluster_user(instance)
181
+ super
182
+ end
183
+
184
+ desc "repl_status", "Show the status of replication."
185
+ method_option :continuous, :aliases => "-c", :type => :numeric,
186
+ :desc => "Continuous output at specified interval (in seconds)."
187
+ method_option :servers, :aliases => "-s", :type => :array,
188
+ :desc => "Only check the status of given servers."
189
+ def repl_status
190
+ todos = options[:servers] || all_live_instances
191
+ unless todos.any?
192
+ say "No Running Servers.", :blue
193
+ return
194
+ end
195
+ header = sprintf("%-5s%-25s%-25s%-25s%-8s\n",
196
+ "inst", "master", "received", "applied", "lag")
197
+
198
+ loop do
199
+ say header, :blue
200
+
201
+ todos.each do |i|
202
+ coordinates = get_coordinates(i)
203
+ slave_status = get_slave_status(i)
204
+ is_slave = !(slave_status["Error"] == "MySQL server is not a slave.")
205
+ master_file = coordinates[:file]
206
+ master_pos = coordinates[:position]
207
+ if is_slave
208
+ recv_file = slave_status["Master_Log_File"]
209
+ recv_pos = slave_status["Read_Master_Log_Pos"]
210
+ apply_file = slave_status["Relay_Master_Log_File"]
211
+ apply_pos = slave_status["Exec_Master_Log_Pos"]
212
+ lag = slave_status["Seconds_Behind_Master"]
213
+ end
214
+
215
+ format = "%-5d%16s:%-8d"
216
+ if is_slave
217
+ if lag
218
+ lag = lag.to_s
219
+ else
220
+ lag = "-"
221
+ end
222
+ format += "%16s:%-8d%16s:%-8d%-8s"
223
+ str = sprintf(format, i, master_file, master_pos, recv_file, recv_pos,
224
+ apply_file, apply_pos, lag)
225
+ else
226
+ str = sprintf(format, i, master_file, master_pos)
227
+ end
228
+
229
+ say str + "\n", :yellow
230
+ end
231
+ break unless options[:continuous]
232
+ sleep options[:continuous]
233
+ say ""
234
+ end
235
+ end
236
+
237
+ desc "dump INSTANCE [DUMPFILE]", "Dump all databases after FLUSH TABLES WITH READ LOCK"
238
+ def dump(instance, dumpfile = DEFAULT_DUMPFILE)
239
+ coordinates = do_dump(instance, dumpfile)
240
+ file = coordinates[:file]
241
+ position = coordinates[:position]
242
+ puts "(#{file}, #{position})"
243
+ [file, String(position)]
244
+ end
245
+
246
+ desc "restore INSTANCE [DUMPFILE]", "Restore INSTANCE from a \'mysqldump\' file DUMPFILE."
247
+ def restore(slave, dumpfile = DEFAULT_DUMPFILE)
248
+ do_restore(slave, dumpfile)
249
+ end
250
+
251
+ end
252
+
253
+ class Utils < Thor
254
+
255
+ include Thor::Actions
256
+ include Repctl::Config
257
+ include Repctl::Commands
258
+ include Repctl::Servers
259
+ include Helpers
260
+
261
+ DEFAULT_MASTER = 1
262
+
263
+ desc "bench [INSTANCE] [PROPS]", "Run the Tungsten Bristlecone benchmarker.
264
+ The INSTANCE specifies the instance to perform all operations to, and PROPS
265
+ is the properties file to use. The INSTANCE defaults to #{DEFAULT_MASTER} and
266
+ the properties file defaults to #{BENCHMARK_PROPERTIES}."
267
+ def bench(instance = DEFAULT_MASTER, props = nil)
268
+ props ||= BENCHMARK_PROPERTIES
269
+ invoke :create_db, [instance, "widgets"]
270
+ run("#{BENCHMARK} -props #{props}", :verbose => true, :capture => false)
271
+ end
272
+
273
+ desc "create_db [INSTANCE] [DBNAME]", <<-EOS
274
+ "Create a database on a MySQL instance. INSTANCE defaults to DEFAULT_MASTER,
275
+ and DBNAME defaults to "widgets".
276
+ EOS
277
+ method_option :replace, :type => :boolean, :aliases => "-r",
278
+ :desc => "drop and recreate the database"
279
+ def create_db(instance = DEFAULT_MASTER, dbname = "widgets")
280
+ run_mysql_query(instance, "DROP DATABASE IF EXISTS #{dbname}") if options[:replace]
281
+ run_mysql_query(instance, "CREATE DATABASE IF NOT EXISTS #{dbname}")
282
+ end
283
+
284
+ desc "create_tbl [INSTANCE] [DBNAME] [TBLNAME]", <<-EOS
285
+ Create a database table. INSTANCE defaults to DEFAULT_MASTER, DBNAME defaults
286
+ to "widgets" and TBLNAME defaults to "users". The table schema is fixed.
287
+ EOS
288
+ method_option :replace, :type => :boolean, :aliases => "-r",
289
+ :desc => "drop and recreate the table"
290
+ def create_tbl(instance = DEFAULT_MASTER, dbname = "widgets", tblname = "users")
291
+ invoke :create_db, [instance, dbname], :replace => false
292
+ run_mysql_query(instance,
293
+ "DROP TABLE IF EXISTS #{dbname}.#{tblname}") if options[:replace]
294
+ cmd = <<-EOS
295
+ CREATE TABLE #{dbname}.#{tblname} (
296
+ id INT NOT NULL,
297
+ last_name CHAR(30) NOT NULL,
298
+ first_name CHAR(30) NOT NULL,
299
+ credentials VARCHAR(32768) NOT NULL,
300
+ PRIMARY KEY (id),
301
+ INDEX name (last_name,first_name)
302
+ )
303
+ EOS
304
+ run_mysql_query(instance, cmd)
305
+ end
306
+
307
+ desc "gen_rows [INSTANCE], [DBNAME], [TBLNAME]", <<-EOS
308
+ Add rows to a table that was created by "utils:create_tbl". INSTANCE defaults
309
+ to DEFAULT_MASTER, DBNAME defaults to "widgets", and TBLNAME defaults to "users".
310
+ EOS
311
+ method_option :delay, :type => :numeric, :aliases => "-d", :default => 0,
312
+ :desc => "sleep for the specified number of milliseconds between row inserts."
313
+ method_option :count, :type => :numeric, :aliases => "-c", :default => 1000,
314
+ :desc => "number of rows to insert"
315
+ method_option :size, :type => :numeric, :aliases => "-s", :default => 100,
316
+ :desc => "the approximate size of the record to insert (in bytes)."
317
+ method_option :forever, :type => :boolean, :aliases => "-f",
318
+ :desc => "run forever, ignoring COUNT option."
319
+ method_option :verbose, :type => :boolean, :aliases => "-v",
320
+ :desc => "print a '.' for each row inserted."
321
+ def gen_rows(instance = DEFAULT_MASTER, dbname = "widgets", tblname = "users")
322
+ invoke :create_tbl, [instance, dbname], :replace => true
323
+ size = options[:size]
324
+ size ||= 100
325
+ size = [size, 32768].min
326
+ data = IO.read("#{Mysql::DATA_HOME}/words.txt", size)
327
+ id = 1
328
+ count = 0
329
+
330
+ loop do
331
+ cmd = <<-EOS
332
+ INSERT INTO #{dbname}.#{tblname} VALUES (
333
+ #{id},
334
+ 'Fillmore',
335
+ 'Millard',
336
+ '#{data}'
337
+ )
338
+ EOS
339
+ run_mysql_query(instance, cmd)
340
+ putc "." if options[:verbose]
341
+ id += 1
342
+ count += 1
343
+ break if (count >= options[:count] and (not options[:forever]))
344
+ msecs = options[:delay]
345
+ sleep(msecs / 1000.0) if msecs > 0
346
+ end
347
+
348
+ end
349
+
350
+ end
351
+
352
+ class Setup < Thor
353
+
354
+ include ::Thor::Actions
355
+ include Repctl::Config
356
+ include Repctl::Commands
357
+ include Repctl::Servers
358
+ include Helpers
359
+
360
+ #
361
+ # Setting Up Replication with New Master and Slaves.
362
+ # Here, we stop all MySQL servers, remove the data directories, reinitialize
363
+ # the data directories, restart the servers, and set up a master/slave
364
+ # relationship.
365
+ #
366
+ desc "repl_pair MASTER SLAVE",
367
+ "Set up a single master/slave replication pair from the very beginning."
368
+ def repl_pair(master, slave)
369
+ say "master is #{master}, slave is #{slave}", :green
370
+ reset(master)
371
+ reset(slave)
372
+ cluster_user(master)
373
+ repl_user(master)
374
+ coordinates = get_coordinates(master)
375
+ file = coordinates[:file]
376
+ position = coordinates[:position]
377
+ say "File is #{file}, Position is #{position}", :green
378
+ change_master(master, slave, file, position)
379
+ start_slave(slave)
380
+ end
381
+
382
+ desc "repl_trio MASTER SLAVE1 SLAVE2",
383
+ "Set up a single master and two slave replication cluster."
384
+ method_option :reset, :type => :boolean, :default => true
385
+ def repl_trio(master, slave1, slave2)
386
+ say "master is #{master}, slaves are #{slave1} and #{slave2}", :green
387
+ if options[:reset]
388
+ reset(master)
389
+ reset(slave1)
390
+ reset(slave2)
391
+ cluster_user(master)
392
+ repl_user(master)
393
+ else
394
+ restart(master)
395
+ restart(slave1)
396
+ restart(slave2)
397
+ end
398
+ coordinates = get_coordinates(master)
399
+ file = coordinates[:file]
400
+ position = coordinates[:position]
401
+ say "File is #{file}, Position is #{position}", :green
402
+ change_master(master, slave1, file, position)
403
+ start_slave(slave1)
404
+ change_master(master, slave2, file, position)
405
+ start_slave(slave2)
406
+ end
407
+
408
+ #
409
+ # Setting Up Replication with Existing Data using the 'mysqldump' utility. The
410
+ # master has existing data.
411
+ #
412
+ desc "add_slave MASTER SLAVE", "Master has some data that is used to initialize the slave."
413
+ method_option :populate, :type => :boolean, :default => false
414
+ def add_slave(master, slave)
415
+ reset(slave)
416
+
417
+ if options[:populate]
418
+ invoke "utils:bench", [master, "/opt/MySQL/b2.properties"]
419
+ end
420
+ file, position = invoke "mysql:dump", [master]
421
+
422
+ # Slave is running, but it is not yet configured as a slave.
423
+ # Load slave from the dump file...
424
+ invoke "mysql:restore", [slave]
425
+
426
+ change_master(master, slave, file, position)
427
+ start_slave(slave)
428
+ end
429
+
430
+ end
431
+
@@ -0,0 +1,29 @@
1
+ url=jdbc:mysql://localhost:3307/widgets
2
+ user=cluster
3
+ password=secret
4
+ type=MySQL 5.6.2-m5
5
+ analyzeCmd=select 1
6
+
7
+ # Benchmark test to compare clustered and non-clustered write performance.
8
+ #
9
+ # We execute INSERT statements with varying numbers of clients and tables.
10
+ #
11
+ # To invoke this test try the following command.
12
+ # $benchmark.sh -props WriteSimpleScenario.properties
13
+
14
+ # Scenario name.
15
+ scenario=com.continuent.bristlecone.benchmark.scenarios.WriteSimpleScenario
16
+
17
+ # Database connection information.
18
+ # include=connection_postgresql.properties|connection_pcluster.properties
19
+
20
+ # Test duration and number of threads.
21
+ bound=duration
22
+ duration=60
23
+ threads=1|10|20
24
+
25
+ # Database table information.
26
+ tables=1|16
27
+ datatype=varchar
28
+ datawidth=100
29
+ datarows=100
data/config/config.rb ADDED
@@ -0,0 +1,50 @@
1
+ module Repctl
2
+
3
+ module Config
4
+
5
+ # The location of the local MySQL installation.
6
+ MYSQL_HOME = "/usr/local/mysql"
7
+
8
+ ROOT_PASSWORD = "har526"
9
+
10
+ SERVER_CONFIG = File.expand_path("../servers.yml", __FILE__)
11
+
12
+ # Define the directory where subdirectores for data will be created
13
+ # for each MySQL server instance. This should agree with the
14
+ # per server'data_dir' property in the servers.yml file, and it should
15
+ # also agree with the 'datadir' property in the server's configuration
16
+ # file (my*.cnf).
17
+ DATA_HOME = "/opt/MySQL/instances"
18
+
19
+ # The home directory of Continuent's open source replicator. Eventually,
20
+ # a command will be added to this script to switch between MySQL native
21
+ # replication and Continuent's open-source Tungsten replicator.
22
+ REPLICATOR_HOME = "/opt/continuent/replicator"
23
+
24
+ # For simplicity, we're using the load-generator/benchmarker that comes
25
+ # in the Continuent source package. This may be replaced with sql-bench
26
+ # from the MySQL source distribution.
27
+ BENCHMARK = "#{REPLICATOR_HOME}/bristlecone/bin/benchmark.sh"
28
+ BENCHMARK_PROPERTIES = File.expand_path("../bristlecone.properties", __FILE__)
29
+
30
+ # Set this to the directory where you want dump files to be stored.
31
+ DUMP_DIR = "#{DATA_HOME}/dump"
32
+
33
+ # The default name of the dump file in the DUMP_DIR directory.
34
+ DEFAULT_DUMPFILE = "dbdump.db"
35
+
36
+ # User name and password for the replication account, used only internally by
37
+ # the replication processes.
38
+ REPLICATION_USER = "repl"
39
+ REPLICATION_PASSWORD = "secret"
40
+
41
+ # Replication account is set up as %.#{REPLICATION_DOMAIN}.
42
+ REPLICATION_DOMAIN = "thirdmode.com"
43
+
44
+ # A minor convenience.
45
+ DEFAULT_MASTER = 1
46
+
47
+ # Typically, this is of the form #{HOSTNAME}-relay-bin'.
48
+ RELAY_LOG = "deimos-relay-bin"
49
+ end
50
+ end