continuent-tools-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ DEFAULTS = "defaults"
2
+ REPL_RMI_PORT = "repl_rmi_port"
3
+ MGR_RMI_PORT = "mgr_rmi_port"
4
+ HOST_ENABLE_REPLICATOR = "host_enable_replicator"
5
+ HOST_ENABLE_MANAGER = "host_enable_manager"
6
+ HOST_ENABLE_CONNECTOR = "host_enable_connector"
7
+ MGR_API = "mgr_api"
8
+ MGR_API_PORT = "mgr_api_port"
9
+ MGR_API_ADDRESS = "mgr_api_address"
10
+ DEPLOYMENT_HOST = "deployment_host"
11
+ DEPLOYMENT_DATASERVICE = "deployment_dataservice"
12
+ DEPLOYMENT_SERVICE = "service_name"
13
+ HOSTS = "hosts"
14
+ DATASERVICES = "dataservices"
15
+ DATASOURCES = "datasources"
16
+ MANAGERS = "managers"
17
+ REPL_SERVICES = "repl_services"
18
+ CONNECTORS = "connectors"
19
+ CURRENT_RELEASE_DIRECTORY = "tungsten"
20
+
21
+ class MessageError < StandardError
22
+ end
23
+
24
+ class CommandError < StandardError
25
+ attr_reader :command, :rc, :result, :errors
26
+
27
+ def initialize(command, rc, result, errors="")
28
+ @command = command
29
+ @rc = rc
30
+ @result = result
31
+ @errors = errors
32
+
33
+ super(build_message())
34
+ end
35
+
36
+ def build_message
37
+ if @errors == ""
38
+ errors = "No Errors"
39
+ else
40
+ errors = "Errors: #{errors}"
41
+ end
42
+
43
+ "Failed: #{command}, RC: #{rc}, Result: #{result}, #{errors}"
44
+ end
45
+ end
46
+
47
+ class RemoteCommandError < CommandError
48
+ attr_reader :user, :host
49
+
50
+ def initialize(user, host, command, rc, result, errors = "")
51
+ @user = user
52
+ @host = host
53
+ super(command, rc, result, errors)
54
+ end
55
+
56
+ def build_message
57
+ if @errors == ""
58
+ errors = "No Errors"
59
+ else
60
+ errors = "Errors: #{errors}"
61
+ end
62
+
63
+ "Failed: #{command}, RC: #{rc}, Result: #{result}, #{errors}"
64
+ end
65
+ end
66
+
67
+ class IgnoreError < StandardError
68
+ end
69
+
70
+ # Disable guessing by the OptionParser
71
+ class OptionParser
72
+ def stack
73
+ @stack
74
+ end
75
+
76
+ class OptionMap < Hash
77
+ end
78
+
79
+ module Completion
80
+ def complete(key, icase = false, pat = nil)
81
+ end
82
+
83
+ def convert(opt = nil, val = nil, *)
84
+ val
85
+ end
86
+ end
87
+ end
88
+
89
+ # Define an additional Logger level
90
+ class Logger
91
+ NOTICE = 1.5
92
+ end
93
+
94
+ class String
95
+ if RUBY_VERSION < "1.9"
96
+ def getbyte(index)
97
+ self[index]
98
+ end
99
+ end
100
+ end
101
+
102
+ # Apply sorting when building a JSON string
103
+ module JSON
104
+ module Pure
105
+ module Generator
106
+ module GeneratorMethods
107
+ module Hash
108
+ private
109
+
110
+ def json_transform(state)
111
+ valid_keys = 0
112
+
113
+ delim = ','
114
+ delim << state.object_nl
115
+ result = '{'
116
+ result << state.object_nl
117
+ depth = state.depth += 1
118
+ first = true
119
+ indent = !state.object_nl.empty?
120
+ keys().sort{ |a, b| a.to_s() <=> b.to_s() }.each{|key|
121
+ value = self[key]
122
+ json = value.to_json(state)
123
+ if json == ""
124
+ next
125
+ end
126
+ valid_keys = valid_keys+1
127
+
128
+ result << delim unless first
129
+ result << state.indent * depth if indent
130
+ result << key.to_s.to_json(state)
131
+ result << state.space_before
132
+ result << ':'
133
+ result << state.space
134
+ result << json
135
+ first = false
136
+ }
137
+ depth = state.depth -= 1
138
+ result << state.object_nl
139
+ result << state.indent * depth if indent if indent
140
+ result << '}'
141
+
142
+ if valid_keys == 0 && depth != 0
143
+ return ""
144
+ end
145
+
146
+ result
147
+ end
148
+ end
149
+
150
+ module Array
151
+ private
152
+
153
+ def json_transform(state)
154
+ valid_keys = 0
155
+
156
+ delim = ','
157
+ delim << state.array_nl
158
+ result = '['
159
+ result << state.array_nl
160
+ depth = state.depth += 1
161
+ first = true
162
+ indent = !state.array_nl.empty?
163
+ each { |value|
164
+ json = value.to_json(state)
165
+ if json == ""
166
+ next
167
+ end
168
+ valid_keys = valid_keys+1
169
+
170
+ result << delim unless first
171
+ result << state.indent * depth if indent
172
+ result << json
173
+ first = false
174
+ }
175
+ depth = state.depth -= 1
176
+ result << state.array_nl
177
+ result << state.indent * depth if indent
178
+ result << ']'
179
+
180
+ if valid_keys == 0 && depth != 0
181
+ return ""
182
+ end
183
+
184
+ result
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,570 @@
1
+ require "open4"
2
+ require "resolv"
3
+ require "ifconfig"
4
+
5
+ class TungstenUtil
6
+ def initialize()
7
+ super()
8
+
9
+ @log_results = false
10
+ @forward_results = false
11
+ @forward_results_level = Logger::INFO
12
+ end
13
+
14
+ # Disable debug logging of command output
15
+ def log_cmd_results?(v = nil)
16
+ if v != nil
17
+ @log_results = v
18
+ end
19
+
20
+ if @log_results == nil && forward_cmd_results?() == true
21
+ false
22
+ elsif @log_results == false
23
+ false
24
+ else
25
+ true
26
+ end
27
+ end
28
+
29
+ def forward_cmd_results?(v = nil, level = Logger::INFO)
30
+ if v != nil
31
+ @forward_results = v
32
+ @forward_results_level = level
33
+ end
34
+
35
+ if @forward_results == true
36
+ true
37
+ else
38
+ false
39
+ end
40
+ end
41
+
42
+ def get_forward_log_level
43
+ @forward_results_level
44
+ end
45
+
46
+ # Run the {command} and return a string of STDOUT
47
+ def cmd_result(command, ignore_fail = false)
48
+ errors = ""
49
+ result = ""
50
+ threads = []
51
+
52
+ debug("Execute `#{command}`")
53
+ status = Open4::popen4("export LANG=en_US; #{command}") do |pid, stdin, stdout, stderr|
54
+ stdin.close
55
+
56
+ threads << Thread.new{
57
+ while data = stdout.gets()
58
+ if data.to_s() != ""
59
+ result+=data
60
+
61
+ if data != "" && forward_cmd_results?()
62
+ write(data, (parse_log_level(data) || get_forward_log_level()), nil, false)
63
+ end
64
+ end
65
+ end
66
+ }
67
+ threads << Thread.new{
68
+ while edata = stderr.gets()
69
+ if edata.to_s() != ""
70
+ errors+=edata
71
+
72
+ if edata != "" && forward_cmd_results?()
73
+ write(edata, (parse_log_level(edata) || get_forward_log_level()), nil, false)
74
+ end
75
+ end
76
+ end
77
+ }
78
+
79
+ threads.each{|t| t.join() }
80
+ end
81
+
82
+ result.strip!()
83
+ errors.strip!()
84
+
85
+ original_errors = errors
86
+ rc = status.exitstatus
87
+ if errors == ""
88
+ errors = "No Errors"
89
+ else
90
+ errors = "Errors: #{errors}"
91
+ end
92
+
93
+ if log_cmd_results?()
94
+ debug("RC: #{rc}, Result: #{result}, #{errors}")
95
+ elsif forward_cmd_results?()
96
+ debug("RC: #{rc}, Result length #{result.length}, Errors length #{original_errors.length}")
97
+ else
98
+ debug("RC: #{rc}, Result length #{result.length}, #{errors}")
99
+ end
100
+ if rc != 0 && ! ignore_fail
101
+ raise CommandError.new(command, rc, result, original_errors)
102
+ end
103
+
104
+ return result
105
+ end
106
+
107
+ def cmd(cmd)
108
+ begin
109
+ cmd_result(cmd)
110
+ return true
111
+ rescue CommandError
112
+ return false
113
+ end
114
+ end
115
+
116
+ # Run the {command} and run {&block} for each line of STDOUT
117
+ def cmd_stdout(command, ignore_fail = false, &block)
118
+ errors = ""
119
+ result = ""
120
+ threads = []
121
+
122
+ debug("Execute `#{command}`")
123
+ status = Open4::popen4("export LANG=en_US; #{command}") do |pid, stdin, stdout, stderr|
124
+ stdin.close
125
+
126
+ threads << Thread.new{
127
+ while data = stdout.gets()
128
+ if data.to_s() != ""
129
+ result+=data
130
+
131
+ if data != "" && forward_cmd_results?()
132
+ write(data, (parse_log_level(data) || get_forward_log_level()), nil, false)
133
+ end
134
+
135
+ block.call(data)
136
+ end
137
+ end
138
+ }
139
+ threads << Thread.new{
140
+ while edata = stderr.gets()
141
+ if edata.to_s() != ""
142
+ errors+=edata
143
+
144
+ if edata != "" && forward_cmd_results?()
145
+ write(edata, (parse_log_level(edata) || get_forward_log_level()), nil, false)
146
+ end
147
+ end
148
+ end
149
+ }
150
+
151
+ threads.each{|t| t.join() }
152
+ end
153
+
154
+ result.strip!()
155
+ errors.strip!()
156
+
157
+ original_errors = errors
158
+ rc = status.exitstatus
159
+ if errors == ""
160
+ errors = "No Errors"
161
+ else
162
+ errors = "Errors: #{errors}"
163
+ end
164
+
165
+ if log_cmd_results?()
166
+ debug("RC: #{rc}, Result: #{result}, #{errors}")
167
+ elsif forward_cmd_results?()
168
+ debug("RC: #{rc}, Result length #{result.length}, Errors length #{original_errors.length}")
169
+ else
170
+ debug("RC: #{rc}, Result length #{result.length}, #{errors}")
171
+ end
172
+ if rc != 0 && ! ignore_fail
173
+ raise CommandError.new(command, rc, result, original_errors)
174
+ end
175
+
176
+ return
177
+ end
178
+
179
+ # Run the {command} and run {&block} for each line of STDERR
180
+ def cmd_stderr(command, ignore_fail = false, &block)
181
+ errors = ""
182
+ result = ""
183
+ threads = []
184
+
185
+ debug("Execute `#{command}`")
186
+ status = Open4::popen4("export LANG=en_US; #{command}") do |pid, stdin, stdout, stderr|
187
+ stdin.close
188
+
189
+ threads << Thread.new{
190
+ while data = stdout.gets()
191
+ if data.to_s() != ""
192
+ result+=data
193
+
194
+ if data != "" && forward_cmd_results?()
195
+ write(data, (parse_log_level(data) || get_forward_log_level()), nil, false)
196
+ end
197
+ end
198
+ end
199
+ }
200
+ threads << Thread.new{
201
+ while edata = stderr.gets()
202
+ if edata.to_s() != ""
203
+ errors+=edata
204
+
205
+ if edata != "" && forward_cmd_results?()
206
+ write(edata, (parse_log_level(edata) || get_forward_log_level()), nil, false)
207
+ end
208
+
209
+ block.call(edata)
210
+ end
211
+ end
212
+ }
213
+
214
+ threads.each{|t| t.join() }
215
+ end
216
+
217
+ result.strip!()
218
+ errors.strip!()
219
+
220
+ original_errors = errors
221
+ rc = status.exitstatus
222
+ if errors == ""
223
+ errors = "No Errors"
224
+ else
225
+ errors = "Errors: #{errors}"
226
+ end
227
+
228
+ if log_cmd_results?()
229
+ debug("RC: #{rc}, Result: #{result}, #{errors}")
230
+ elsif forward_cmd_results?()
231
+ debug("RC: #{rc}, Result length #{result.length}, Errors length #{original_errors.length}")
232
+ else
233
+ debug("RC: #{rc}, Result length #{result.length}, #{errors}")
234
+ end
235
+ if rc != 0 && ! ignore_fail
236
+ raise CommandError.new(command, rc, result, original_errors)
237
+ end
238
+
239
+ return
240
+ end
241
+
242
+ # Run a standard check to see if SSH connectivity to the host works
243
+ def test_ssh(host, user)
244
+ begin
245
+ login_result = ssh_result("whoami", host, user)
246
+
247
+ if login_result != user
248
+ if login_result=~ /#{user}/
249
+ error "SSH to #{host} as #{user} is returning more than one line."
250
+ error "Ensure that the .bashrc and .bash_profile files are not printing messages on #{host} with out using a test like this. if [ \"$PS1\" ]; then echo \"Your message here\"; fi"
251
+ error "If you are using the 'Banner' argument in /etc/ssh/sshd_config, try putting the contents into /etc/motd"
252
+ else
253
+ error "Unable to SSH to #{host} as #{user}."
254
+
255
+ if is_localhost?(host)
256
+ error "Try running the command as #{user}"
257
+ else
258
+ error "Ensure that the host is running and that you can login as #{user} via SSH using key authentication"
259
+ end
260
+ end
261
+
262
+ return false
263
+ else
264
+ return true
265
+ end
266
+ rescue => e
267
+ exception(e)
268
+ return false
269
+ end
270
+ end
271
+
272
+ # Run the {command} on {host} as {user}
273
+ # Return a string of STDOUT
274
+ def ssh_result(command, host, user)
275
+ if host == DEFAULTS
276
+ debug("Unable to run '#{command}' because '#{host}' is not valid")
277
+ raise RemoteCommandError.new(user, host, command, nil, '')
278
+ end
279
+
280
+ # Run the command outside of SSH if possible
281
+ if is_localhost?(host) &&
282
+ user == whoami()
283
+ return cmd_result(command)
284
+ end
285
+
286
+ unless defined?(Net::SSH)
287
+ begin
288
+ require "openssl"
289
+ rescue LoadError
290
+ raise("Unable to find the Ruby openssl library. Try installing the openssl package for your version of Ruby (libopenssl-ruby#{RUBY_VERSION[0,3]}).")
291
+ end
292
+ require 'net/ssh'
293
+ end
294
+
295
+ ssh_user = get_ssh_user(user)
296
+ if user != ssh_user
297
+ debug("SSH user changed to #{ssh_user}")
298
+ command = command.tr('"', '\"')
299
+ command = "echo \"#{command}\" | sudo -u #{user} -i"
300
+ end
301
+
302
+ debug("Execute `#{command}` on #{host} as #{user}")
303
+ result = ""
304
+ rc = nil
305
+ exit_signal=nil
306
+
307
+ connection_error = "Net::SSH was unable to connect to #{host} as #{ssh_user}. Check that #{host} is online, #{ssh_user} exists and your SSH private keyfile or ssh-agent settings. Try adding --net-ssh-option=port=<SSH port number> if you are using an SSH port other than 22. Review https://docs.continuent.com/wiki/display/TEDOC/Unable+to+use+the+tpm+command+over+SSH for more help on diagnosing SSH problems."
308
+ begin
309
+ Net::SSH.start(host, ssh_user, get_ssh_options()) {
310
+ |ssh|
311
+ stdout_data = ""
312
+
313
+ ssh.open_channel do |channel|
314
+ channel.exec(". /etc/profile; #{ssh_init_profile_script()} export LANG=en_US; export LC_ALL=\"en_US.UTF-8\"; #{command}") do |ch, success|
315
+ channel.on_data do |ch,data|
316
+ stdout_data+=data
317
+
318
+ if data != "" && forward_cmd_results?()
319
+ log_level,log_data = split_log_content(data)
320
+ write(log_data, (log_level || get_forward_log_level()), host)
321
+ end
322
+ end
323
+
324
+ channel.on_extended_data do |ch,type,data|
325
+ data = data.chomp
326
+
327
+ if data != "" && forward_cmd_results?()
328
+ log_level,log_data = split_log_content(data)
329
+ write(log_data, (log_level || get_forward_log_level()), host)
330
+ end
331
+ end
332
+
333
+ channel.on_request("exit-status") do |ch,data|
334
+ rc = data.read_long
335
+ end
336
+
337
+ channel.on_request("exit-signal") do |ch, data|
338
+ exit_signal = data.read_long
339
+ end
340
+ end
341
+ end
342
+ ssh.loop
343
+ result = stdout_data.to_s.chomp
344
+ }
345
+ rescue Errno::ENOENT => ee
346
+ raise MessageError.new("Net::SSH was unable to find a private key to use for SSH authenticaton. Try creating a private keyfile or setting up ssh-agent.")
347
+ rescue OpenSSL::PKey::RSAError
348
+ raise MessageError.new(connection_error)
349
+ rescue Net::SSH::AuthenticationFailed
350
+ raise MessageError.new(connection_error)
351
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EHOSTDOWN
352
+ raise MessageError.new(connection_error)
353
+ rescue Timeout::Error
354
+ raise MessageError.new(connection_error)
355
+ rescue NotImplementedError => nie
356
+ raise MessageError.new(nie.message + ". Try modifying your ~/.ssh/config file to define values for Cipher and Ciphers that do not include this algorithm. The supported encryption algorithms are #{Net::SSH::Transport::CipherFactory::SSH_TO_OSSL.keys().delete_if{|e| e == "none"}.join(", ")}.")
357
+ rescue => e
358
+ raise e
359
+ end
360
+
361
+ if rc != 0
362
+ raise RemoteCommandError.new(user, host, command, rc, result)
363
+ else
364
+ if log_cmd_results?()
365
+ debug("RC: #{rc}, Result: #{result}")
366
+ else
367
+ debug("RC: #{rc}, Result length #{result.length}")
368
+ end
369
+ end
370
+
371
+ return result
372
+ end
373
+
374
+ def get_ssh_options
375
+ @ssh_options
376
+ end
377
+
378
+ def get_ssh_command_options
379
+ opts = ["-A", "-oStrictHostKeyChecking=no", "-oUserKnownHostsFile=/dev/null"]
380
+ @ssh_options.each{
381
+ |k,v|
382
+ opts << "-o#{k.to_s()}=#{v}"
383
+ }
384
+ return opts.join(" ")
385
+ end
386
+
387
+ def get_tungsten_command_options
388
+ opts = []
389
+
390
+ case get_log_level()
391
+ when Logger::INFO then opts << "-i"
392
+ when Logger::NOTICE then opts << "-n"
393
+ when Logger::WARN then opts << "-q"
394
+ when Logger::DEBUG then opts << "-v"
395
+ end
396
+
397
+ @ssh_options.each{
398
+ |k,v|
399
+ opts << "--net-ssh-option=#{k.to_s()}=#{v}"
400
+ }
401
+ return opts.join(" ")
402
+ end
403
+
404
+ def get_ssh_user(user = nil)
405
+ ssh_options = get_ssh_options
406
+ if ssh_options.has_key?(:user) && ssh_options[:user].to_s != ""
407
+ ssh_options[:user]
408
+ else
409
+ user
410
+ end
411
+ end
412
+
413
+ def get_ssh_port(port = 22)
414
+ ssh_options = get_ssh_options
415
+ if ssh_options.has_key?(:port) && ssh_options[:port].to_s != ""
416
+ ssh_options[:port]
417
+ else
418
+ port
419
+ end
420
+ end
421
+
422
+ def ssh_init_profile_script
423
+ if @ssh_init_profile_script == nil
424
+ init_profile_script_parts = []
425
+ [
426
+ "$HOME/.bash_profile",
427
+ "$HOME/.bash_login",
428
+ "$HOME/.profile"
429
+ ].each{
430
+ |filename|
431
+
432
+ if init_profile_script_parts.size() == 0
433
+ init_profile_script_parts << "if"
434
+ else
435
+ init_profile_script_parts << "elif"
436
+ end
437
+
438
+ init_profile_script_parts << "[ -f #{filename} ]; then . #{filename};"
439
+ }
440
+ init_profile_script_parts << "fi;"
441
+ @ssh_init_profile_script = init_profile_script_parts.join(" ")
442
+ end
443
+
444
+ return @ssh_init_profile_script
445
+ end
446
+
447
+ def hostname
448
+ `hostname 2>/dev/null`.chomp
449
+ end
450
+
451
+ def is_localhost?(hostname)
452
+ if hostname == DEFAULTS
453
+ return false
454
+ end
455
+
456
+ @_is_localhost_cache ||= {}
457
+ unless @_is_localhost_cache.has_key?(hostname)
458
+ @_is_localhost_cache[hostname] = _is_localhost?(hostname)
459
+ end
460
+
461
+ return @_is_localhost_cache[hostname]
462
+ end
463
+
464
+ def _is_localhost?(hostname)
465
+ if hostname == hostname()
466
+ return true
467
+ end
468
+
469
+ ip_addresses = get_ip_addresses(hostname)
470
+ if ip_addresses == false
471
+ return false
472
+ end
473
+
474
+ debug("Search ifconfig for #{ip_addresses.join(', ')}")
475
+ ifconfig = IfconfigWrapper.new().parse()
476
+ ifconfig.each{
477
+ |iface|
478
+
479
+ begin
480
+ # Do a string comparison so that we only match the address portion
481
+ iface.addresses().each{
482
+ |a|
483
+ if ip_addresses.include?(a.to_s())
484
+ return true
485
+ end
486
+ }
487
+ rescue ArgumentError
488
+ end
489
+ }
490
+
491
+ false
492
+ end
493
+
494
+ def get_ip_addresses(hostname)
495
+ begin
496
+ if hostname == DEFAULTS
497
+ return false
498
+ end
499
+
500
+ ip_addresses = Timeout.timeout(5) {
501
+ Resolv.getaddresses(hostname)
502
+ }
503
+
504
+ if ip_addresses.length == 0
505
+ begin
506
+ ping_result = cmd_result("ping -c1 #{hostname} 2>/dev/null | grep PING")
507
+ matches = ping_result.match("[0-9]+.[0-9]+.[0-9]+.[0-9]+")
508
+ if matches && matches.size() > 0
509
+ return [matches[0]]
510
+ end
511
+ rescue CommandError
512
+ end
513
+
514
+ warning "Unable to determine the IP addresses for '#{hostname}'"
515
+ return false
516
+ end
517
+
518
+ return ip_addresses
519
+ rescue Timeout::Error
520
+ warning "Unable to lookup #{hostname} because of a DNS timeout"
521
+ return false
522
+ rescue => e
523
+ warning "Unable to determine the IP addresses for '#{hostname}'"
524
+ return false
525
+ end
526
+ end
527
+
528
+ def is_real_hostname?(hostname)
529
+ begin
530
+ ip_addresses = Timeout.timeout(5) {
531
+ Resolv.getaddresses(hostname)
532
+ }
533
+
534
+ if ip_addresses.length == 0
535
+ begin
536
+ ping_result = cmd_result("ping -c1 #{hostname} 2>/dev/null | grep PING")
537
+ matches = ping_result.match("[0-9]+.[0-9]+.[0-9]+.[0-9]+")
538
+ if matches && matches.size() > 0
539
+ ip_addresses = [matches[0]]
540
+ end
541
+ rescue CommandError
542
+ end
543
+ end
544
+ rescue Timeout::Error
545
+ rescue
546
+ end
547
+
548
+ if ip_addresses.size() == 0
549
+ return false
550
+ else
551
+ return true
552
+ end
553
+ end
554
+
555
+ # Find out the full executable path or return nil
556
+ # if this is not executable.
557
+ def which(cmd)
558
+ if ! cmd
559
+ nil
560
+ else
561
+ path = cmd_result("which #{cmd} 2>/dev/null", true)
562
+ path.chomp!
563
+ if File.executable?(path)
564
+ path
565
+ else
566
+ nil
567
+ end
568
+ end
569
+ end
570
+ end