royw-drbman 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.
- data/.document +5 -0
- data/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +14 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/bin/drbman +7 -0
- data/drbman.gemspec +91 -0
- data/examples/primes/VERSION +1 -0
- data/examples/primes/bin/primes +7 -0
- data/examples/primes/lib/drb_server/prime_helper.rb +34 -0
- data/examples/primes/lib/primes/cli.rb +81 -0
- data/examples/primes/lib/primes/kernel_extensions.rb +12 -0
- data/examples/primes/lib/primes/primes.rb +19 -0
- data/examples/primes/lib/primes/sieve_of_eratosthenes.rb +80 -0
- data/examples/primes/lib/primes.rb +29 -0
- data/examples/primes/spec/primes_spec.rb +24 -0
- data/examples/primes/spec/spec_helper.rb +8 -0
- data/lib/drbman/cli.rb +85 -0
- data/lib/drbman/drb_pool.rb +111 -0
- data/lib/drbman/drbman.rb +229 -0
- data/lib/drbman/host_machine.rb +216 -0
- data/lib/drbman.rb +28 -0
- data/spec/drbman_spec.rb +7 -0
- data/spec/host_machine_spec.rb +45 -0
- data/spec/spec_helper.rb +9 -0
- metadata +148 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
# == Synopsis
|
2
|
+
# A pool of drb objects
|
3
|
+
class DrbPool
|
4
|
+
# Create the pool of drb objects.
|
5
|
+
# @param [Array<HostMachine>] the host_machine instances to use to populate the pool of drb objects
|
6
|
+
# @param [Logger] the logger
|
7
|
+
# @yield [self]
|
8
|
+
# @example Without using a block
|
9
|
+
# pool = DrbPool.new(hosts, logger)
|
10
|
+
# pool.getObject {|obj| obj.do_something}
|
11
|
+
# pool.shutdown
|
12
|
+
# @example Using a block
|
13
|
+
# DrbPool.new(hosts, logger) do |pool|
|
14
|
+
# pool.getObject {|obj| obj.do_something}
|
15
|
+
# end
|
16
|
+
def initialize(hosts, logger, &block)
|
17
|
+
@logger = logger
|
18
|
+
@objects = []
|
19
|
+
|
20
|
+
threads = []
|
21
|
+
mutex = Mutex.new
|
22
|
+
hosts.each do |host_name, host_machine|
|
23
|
+
threads << Thread.new(host_machine) do |host|
|
24
|
+
obj = get_drb_object(host.machine, host.port)
|
25
|
+
mutex.synchronize do
|
26
|
+
@objects << obj
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
threads.each {|thrd| thrd.join}
|
31
|
+
|
32
|
+
unless block.nil?
|
33
|
+
block.call(self)
|
34
|
+
shutdown
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Use an object from the pool
|
39
|
+
# @yield [DRbObject]
|
40
|
+
# @example Usage
|
41
|
+
# pool.get_object {|obj| obj.do_something}
|
42
|
+
def get_object(&block)
|
43
|
+
mutex = Mutex.new
|
44
|
+
while((object = next_object(mutex)).nil?)
|
45
|
+
sleep 0.1
|
46
|
+
end
|
47
|
+
raise ArgumentError.new('a block is required') if block.nil?
|
48
|
+
block.call(object)
|
49
|
+
object.in_use = false
|
50
|
+
end
|
51
|
+
|
52
|
+
# Shut the pool down
|
53
|
+
# Only necessary if not using a block with DrbPool.new
|
54
|
+
# @example
|
55
|
+
# pool = DrbPool.new(hosts, logger)
|
56
|
+
# pool.getObject {|obj| obj.do_something}
|
57
|
+
# pool.shutdown
|
58
|
+
def shutdown
|
59
|
+
@objects.each do |obj|
|
60
|
+
obj.stop_service
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# find the next available object
|
67
|
+
# @return [DRbObject, nil] returns nil if no objects are available
|
68
|
+
def next_object(mutex)
|
69
|
+
object = nil
|
70
|
+
mutex.synchronize do
|
71
|
+
object = @objects.select {|obj| !obj.in_use?}.first
|
72
|
+
object.in_use = true unless object.nil?
|
73
|
+
end
|
74
|
+
object
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get a DRbObject to place into the pool
|
78
|
+
# The object is extended with an in_use attribute
|
79
|
+
# @param machine [String] the host machine name, for example: 'foo.example.com'
|
80
|
+
# @param port [Integer] the port the drb server on the host machine is using
|
81
|
+
# @return [DRbObject,nil] returns nil if unable to get the DRbObject
|
82
|
+
def get_drb_object(machine, port)
|
83
|
+
obj = nil
|
84
|
+
retry_cnt = 0
|
85
|
+
DRb.start_service
|
86
|
+
begin
|
87
|
+
obj = DRbObject.new(nil, "druby://#{machine}:#{port}")
|
88
|
+
obj.extend(InUse)
|
89
|
+
name = obj.name
|
90
|
+
obj.in_use = false
|
91
|
+
rescue Exception => e
|
92
|
+
retry_cnt += 1
|
93
|
+
raise e if retry_cnt > 10
|
94
|
+
sleep 0.2
|
95
|
+
@logger.debug {"retrying (#{retry_cnt})"}
|
96
|
+
retry
|
97
|
+
end
|
98
|
+
obj
|
99
|
+
end
|
100
|
+
|
101
|
+
# Adds an in_use attribute
|
102
|
+
module InUse
|
103
|
+
def in_use=(flag)
|
104
|
+
@in_use = (flag ? true : false)
|
105
|
+
end
|
106
|
+
def in_use?
|
107
|
+
@in_use
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# Drbman is the drb manager
|
2
|
+
#
|
3
|
+
# == Synopsis
|
4
|
+
# Drbman will create a project directory on a host machine,
|
5
|
+
# then copy a set of files to the host machine, make sure
|
6
|
+
# a given set of gems is installed on the host machine, then
|
7
|
+
# run the drb server on the host machine.
|
8
|
+
# Drbman also supports issuing a termination command to
|
9
|
+
# the drb server on the remote machine and cleaning up
|
10
|
+
# the project by removing the files installed onto the
|
11
|
+
# host machine.
|
12
|
+
#
|
13
|
+
# == Notes
|
14
|
+
# Uses the Command design pattern
|
15
|
+
class Drbman
|
16
|
+
# @param [Logger] logger the logger
|
17
|
+
# @param [UserChoices,Hash] choices
|
18
|
+
# @option choices [Array<String>] :dirs array of local directories to copy to the host machines. REQUIRED
|
19
|
+
# @option choices [String] :run the name of the file to run on the host machine. REQUIRED
|
20
|
+
# This file should start the drb server. Note, this file will be daemonized before running.
|
21
|
+
# @option choices [Array<String>] :hosts array of host machine descriptions "{user{:password}@}machine{:port}"
|
22
|
+
# This defaults to ['localhost']
|
23
|
+
# @option choices [Integer] :port default port number used to assign to hosts without a port number.
|
24
|
+
# The port number is incremented for each host. This defaults to 9000
|
25
|
+
# @option choices [Array<String>] :gems array of gem names to verify are installed on the host machine.
|
26
|
+
# Note, 'daemons' is always added to this array.
|
27
|
+
# @yield [Drbman]
|
28
|
+
# @example Usage
|
29
|
+
# Drbman.new(logger, choices) do |drbman|
|
30
|
+
# drbman.get_object do |obj|
|
31
|
+
# obj.do_something
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
def initialize(logger, choices, &block)
|
35
|
+
@logger = logger
|
36
|
+
@user_choices = choices
|
37
|
+
|
38
|
+
# @hosts[machine_description] = HostMachine instance
|
39
|
+
@hosts = {}
|
40
|
+
|
41
|
+
choices[:port] ||= 9000
|
42
|
+
choices[:hosts] ||= ['localhost']
|
43
|
+
choices[:gems] ||= []
|
44
|
+
choices[:gems] = (choices[:gems] + ['daemons']).uniq.compact
|
45
|
+
|
46
|
+
raise ArgumentError.new('Missing choices[:run]') if choices[:run].blank?
|
47
|
+
raise ArgumentError.new('Missing choices[:hosts]') if choices[:hosts].blank?
|
48
|
+
raise ArgumentError.new('Missing choices[:dirs]') if choices[:dirs].blank?
|
49
|
+
|
50
|
+
# populate the @hosts hash. key => host machine description, value => HostMachine instance
|
51
|
+
port = choices[:port]
|
52
|
+
@user_choices[:hosts].each do |host|
|
53
|
+
host = "#{host}:#{port}" unless host =~ /\:\d+\s*$/
|
54
|
+
@hosts[host] = HostMachine.new(host, @logger)
|
55
|
+
port += 1
|
56
|
+
end
|
57
|
+
|
58
|
+
unless block.nil?
|
59
|
+
begin
|
60
|
+
setup
|
61
|
+
@pool = DrbPool.new(@hosts, @logger)
|
62
|
+
block.call(self)
|
63
|
+
rescue Exception => e
|
64
|
+
@logger.error { e }
|
65
|
+
@logger.debug { e.backtrace.join("\n") }
|
66
|
+
ensure
|
67
|
+
@pool.shutdown unless @pool.nil?
|
68
|
+
shutdown
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Use an object from the pool
|
74
|
+
# @yield [DRbObject]
|
75
|
+
# @example Usage
|
76
|
+
# drbman.get_object {|obj| obj.do_something}
|
77
|
+
def get_object(&block)
|
78
|
+
@pool.get_object(&block)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# setup the host machine drb servers
|
84
|
+
# @raise [Exception] when a component is not installed on the host
|
85
|
+
def setup
|
86
|
+
threads = []
|
87
|
+
@hosts.each do |name, machine|
|
88
|
+
threads << Thread.new(machine) do |host_machine|
|
89
|
+
host_machine.session do |host|
|
90
|
+
# begin
|
91
|
+
startup(host)
|
92
|
+
# rescue Exception => e
|
93
|
+
# @logger.error { e }
|
94
|
+
# @logger.debug { e.backtrace.join("\n") }
|
95
|
+
# end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
threads.each {|thrd| thrd.join}
|
100
|
+
sleep 1 # give the drb servers a little time to get running
|
101
|
+
end
|
102
|
+
|
103
|
+
# stop and remove the host machine drb servers
|
104
|
+
def shutdown
|
105
|
+
threads = []
|
106
|
+
@hosts.each do |name, machine|
|
107
|
+
threads << Thread.new(machine) do |host_machine|
|
108
|
+
host_machine.session do |host|
|
109
|
+
begin
|
110
|
+
cleanup(host)
|
111
|
+
rescue Exception => e
|
112
|
+
@logger.error { e }
|
113
|
+
@logger.debug { e.backtrace.join("\n") }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
threads.each {|thrd| thrd.join}
|
119
|
+
end
|
120
|
+
|
121
|
+
# Setup the drb server on the given host then start it
|
122
|
+
# @param [HostMachine] host the host machine
|
123
|
+
# @raise [Exception] when a component is not installed on the host
|
124
|
+
def startup(host)
|
125
|
+
@logger.info { "Setting up: #{host.name}" }
|
126
|
+
check_gems(host)
|
127
|
+
create_directory(host)
|
128
|
+
upload_dirs(host)
|
129
|
+
create_controller(host)
|
130
|
+
run_drb_server(host)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Stop the drb server on the given host then remove the drb files from the host
|
134
|
+
# @param [HostMachine] host the host machine
|
135
|
+
def cleanup(host)
|
136
|
+
@logger.info { "Cleaning up: #{host.name}" }
|
137
|
+
stop_drb_server(host)
|
138
|
+
cleanup_files(host)
|
139
|
+
end
|
140
|
+
|
141
|
+
# remove the host directory and any files in it from
|
142
|
+
# the host machine
|
143
|
+
# @param [HostMachine] host the host machine
|
144
|
+
def cleanup_files(host)
|
145
|
+
unless host.dir.blank? || (host.dir =~ /[\*\?]/)
|
146
|
+
@logger.debug { "#{host.name}: rm -rf #{host.dir}"}
|
147
|
+
host.sh("rm -rf #{host.dir}")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# run the drb server stop command on the host machine
|
152
|
+
# @param [HostMachine] host the host machine
|
153
|
+
# @raise [Exception] when ruby is not installed on the host
|
154
|
+
def stop_drb_server(host)
|
155
|
+
case host.sh("cd #{host.dir};ruby #{host.controller} stop")
|
156
|
+
when /command not found/
|
157
|
+
raise Exception.new "Ruby is not installed on #{host.name}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# run the drb server start command on the host machine
|
162
|
+
# @param [HostMachine] host the host machine
|
163
|
+
# @raise [Exception] when ruby is not installed on the host
|
164
|
+
def run_drb_server(host)
|
165
|
+
unless host.controller.blank?
|
166
|
+
case host.sh("cd #{host.dir};ruby #{host.controller} start -- #{host.machine} #{host.port}")
|
167
|
+
when /command not found/
|
168
|
+
raise Exception.new "Ruby is not installed on #{host.name}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Create the daemon controller on the host machine
|
174
|
+
# @param [HostMachine] host the host machine
|
175
|
+
def create_controller(host)
|
176
|
+
unless @user_choices[:run].blank?
|
177
|
+
host.controller = File.basename(@user_choices[:run], '.*') + '_controller.rb'
|
178
|
+
controller = "require 'rubygems' ; require 'daemons' ; Daemons.run('#{@user_choices[:run]}')"
|
179
|
+
host.sh("cd #{host.dir};echo \"#{controller}\" > #{host.controller}")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# copy files from local directories to the created
|
184
|
+
# directory on the host machine
|
185
|
+
# @param [HostMachine] host the host machine
|
186
|
+
def upload_dirs(host)
|
187
|
+
unless @user_choices[:dirs].blank?
|
188
|
+
@user_choices[:dirs].each do |name|
|
189
|
+
if File.directory?(name)
|
190
|
+
host.upload(name, "#{host.dir}/#{File.basename(name)}")
|
191
|
+
else
|
192
|
+
@logger.error { "\"#{name}\" is not a directory" }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# check if the required gems are installed on the host
|
199
|
+
# @param [HostMachine] host the host machine
|
200
|
+
# @raise [Exception] when rubygems is not installed on the host
|
201
|
+
def check_gems(host)
|
202
|
+
missing_gems = []
|
203
|
+
@user_choices[:gems].each do |gem_name|
|
204
|
+
case host.sh("gem list -l -i #{gem_name}")
|
205
|
+
when /false/i
|
206
|
+
missing_gems << gem_name
|
207
|
+
when /command not found/
|
208
|
+
raise Exception.new "Rubygems is not installed on #{host.name}"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
unless missing_gems.empty?
|
212
|
+
raise Exception.new "The following gems are not installed on #{host.name}: #{missing_gems.join(', ')}"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Create the directory that will hold the drb files.
|
217
|
+
# The directory created is ~/.drbman/{uuid}
|
218
|
+
# Note, requires 'uuidgen' in the path on the host machine
|
219
|
+
# @todo maybe generate a random uuid locally instead
|
220
|
+
# @param [HostMachine] host the host machine
|
221
|
+
def create_directory(host)
|
222
|
+
host.uuid = UUIDTools::UUID.random_create
|
223
|
+
host.dir = "~/.drbman/#{host.uuid}".strip
|
224
|
+
host.sh("mkdir -p #{host.dir}")
|
225
|
+
@logger.debug { "host directory: #{host.dir}" }
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# HostMachine is used to interface with a host machine
|
2
|
+
#
|
3
|
+
# == Notes
|
4
|
+
# A host machine may be either another machine or the localhost.
|
5
|
+
# By supporting localhost, it is likely that the process will
|
6
|
+
# be on a different core than the current processes.
|
7
|
+
#
|
8
|
+
# Once HostMachine opens an ssh connection, it does not close
|
9
|
+
# the connection until a disconnect() is invoked.
|
10
|
+
class HostMachine
|
11
|
+
attr_accessor :uuid, :dir, :controller
|
12
|
+
attr_reader :name, :machine, :user, :port
|
13
|
+
|
14
|
+
# @param [String] host_string describes the host to connect to.
|
15
|
+
# The format is "{user{:password}@}machine{:port}"
|
16
|
+
# @param [Logger] logger the logger to use
|
17
|
+
def initialize(host_string, logger)
|
18
|
+
@logger = logger
|
19
|
+
@machine = 'localhost'
|
20
|
+
@user = ENV['USER']
|
21
|
+
@port = 9000
|
22
|
+
@password = {:keys => ['~/.ssh/id_dsa']}
|
23
|
+
case host_string
|
24
|
+
when /^(\S+)\:(\S+)\@(\S+)\:(\d+)$/
|
25
|
+
@user = $1
|
26
|
+
@password = {:password => $2}
|
27
|
+
@machine = $3
|
28
|
+
@port = $4.to_i
|
29
|
+
when /^(\S+)\:(\S+)\@(\S+)$/
|
30
|
+
@user = $1
|
31
|
+
@password = {:password => $2}
|
32
|
+
@machine = $3
|
33
|
+
when /^(\S+)\@(\S+)\:(\d+)$/
|
34
|
+
@user = $1
|
35
|
+
@machine = $2
|
36
|
+
@port = $3.to_i
|
37
|
+
when /^(\S+)\@(\S+)$/
|
38
|
+
@user = $1
|
39
|
+
@machine = $2
|
40
|
+
when /^(\S+)\:(\d+)$/
|
41
|
+
@machine = $1
|
42
|
+
@port = $2.to_i
|
43
|
+
when /^(\S+)$/
|
44
|
+
@machine = $1
|
45
|
+
end
|
46
|
+
@name = "#{user}@#{machine}:#{port}"
|
47
|
+
@ssh = nil
|
48
|
+
@logger.debug { self.pretty_inspect }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Connect to the host, execute the given block, then disconnect from the host
|
52
|
+
# @yield [HostMachine]
|
53
|
+
# @example
|
54
|
+
# host_machine = HostMachine.new('localhost', @logger)
|
55
|
+
# host_machine.session do |host|
|
56
|
+
# host.upload(local_dir, "#{host.dir}/#{File.basename(local_dir)}")
|
57
|
+
# @logger.debug { host.sh("ls -lR #{host.dir}") }
|
58
|
+
# end
|
59
|
+
def session(&block)
|
60
|
+
connect
|
61
|
+
yield self
|
62
|
+
disconnect
|
63
|
+
end
|
64
|
+
|
65
|
+
# upload a directory structure to the host machine.
|
66
|
+
# @param [String] local_src the source directory on the local machine
|
67
|
+
# @param [String] remote_dest the destination directory on the host machine
|
68
|
+
# @raise [Exception] if the files are not copied
|
69
|
+
def upload(local_src, remote_dest)
|
70
|
+
@logger.debug { "upload(\"#{local_src}\", \"#{remote_dest}\")" }
|
71
|
+
connect
|
72
|
+
result = nil
|
73
|
+
unless @ssh.nil?
|
74
|
+
begin
|
75
|
+
@ssh.scp.upload!(local_src, remote_dest, :recursive => true) do |ch, name, sent, total|
|
76
|
+
@logger.debug { "#{name}: #{sent}/#{total}" }
|
77
|
+
end
|
78
|
+
@ssh.loop
|
79
|
+
rescue Exception => e
|
80
|
+
# only raise the exception if the files differ
|
81
|
+
raise e unless same_files?(local_src, remote_dest)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# download a directory structure from the host machine.
|
87
|
+
# @param [String] remote_src the source directory on the host machine
|
88
|
+
# @param [String] local_dest the destination directory on the local machine
|
89
|
+
# @raise [Exception] if the files are not copied
|
90
|
+
def download(remote_src, local_dest)
|
91
|
+
connect
|
92
|
+
result = nil
|
93
|
+
unless @ssh.nil?
|
94
|
+
begin
|
95
|
+
@ssh.scp.download!(local_src, remote_dest, :recursive => true) do |ch, name, sent, total|
|
96
|
+
@logger.debug { "#{name}: #{sent}/#{total}" }
|
97
|
+
end
|
98
|
+
@ssh.loop
|
99
|
+
rescue Exception => e
|
100
|
+
# only raise the exception if the files differ
|
101
|
+
raise e unless same_files?(local_dest, remote_src)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# run a command on the host machine
|
107
|
+
# Note that the environment on the host machine is the default environment instead
|
108
|
+
# of the user's environment. So by default we try to source ~/.profile and ~/.bashrc
|
109
|
+
#
|
110
|
+
# @param [String] command the command to run
|
111
|
+
# @param [Hash] opts
|
112
|
+
# @options opts [Array<String>] :source array of files to source. defaults to ['~/.profile', '~/.bashrc']
|
113
|
+
def sh(command, opts={})
|
114
|
+
@logger.debug { "sh \"#{command}\""}
|
115
|
+
# if opts[:source].blank?
|
116
|
+
# opts[:source] = ['~/.profile', '~/.bashrc']
|
117
|
+
# end
|
118
|
+
connect
|
119
|
+
result = nil
|
120
|
+
unless @ssh.nil?
|
121
|
+
if @pre_commands.nil?
|
122
|
+
@pre_commands = []
|
123
|
+
opts[:source] ||= []
|
124
|
+
opts[:source].each do |name|
|
125
|
+
ls_out = @ssh.exec!("ls #{name}")
|
126
|
+
@pre_commands << "source #{name}" if ls_out =~ /^\s*\S+\/#{File.basename(name)}\s*$/
|
127
|
+
end
|
128
|
+
end
|
129
|
+
commands = @pre_commands.clone
|
130
|
+
commands << command
|
131
|
+
command_line = commands.join(' && ')
|
132
|
+
result = @ssh.exec!(command_line)
|
133
|
+
end
|
134
|
+
result
|
135
|
+
end
|
136
|
+
|
137
|
+
# run a command as the superuser on the host machine
|
138
|
+
# Note that the environment on the host machine is the default environment instead
|
139
|
+
# of the user's environment. So by default we try to source ~/.profile and ~/.bashrc
|
140
|
+
#
|
141
|
+
# @param [String] command the command to run
|
142
|
+
# @param [Hash] opts
|
143
|
+
# @options opts [Array<String>] :source array of files to source. defaults to ['~/.profile', '~/.bashrc']
|
144
|
+
def sudo(command, opts={})
|
145
|
+
@logger.debug { "sudo \"#{command}\""}
|
146
|
+
# if opts[:source].blank?
|
147
|
+
# opts[:source] = ['~/.profile', '~/.bashrc']
|
148
|
+
# end
|
149
|
+
connect
|
150
|
+
result = nil
|
151
|
+
unless @ssh.nil?
|
152
|
+
buf = []
|
153
|
+
@ssh.open_channel do |channel|
|
154
|
+
if @pre_commands.nil?
|
155
|
+
@pre_commands = []
|
156
|
+
opts[:source] ||= []
|
157
|
+
opts[:source].each do |name|
|
158
|
+
ls_out = @ssh.exec!("ls #{name}")
|
159
|
+
@pre_commands << "source #{name}" if ls_out =~ /^\s*\S+\/#{File.basename(name)}\s*$/
|
160
|
+
end
|
161
|
+
end
|
162
|
+
commands = @pre_commands.clone
|
163
|
+
commands << "sudo -p 'sudo password: ' #{command}"
|
164
|
+
command_line = commands.join(' && ')
|
165
|
+
channel.exec(command_line) do |ch, success|
|
166
|
+
ch.on_data do |ch, data|
|
167
|
+
if data =~ /sudo password: /
|
168
|
+
ch.send_data("#{@password[:password]}\n")
|
169
|
+
else
|
170
|
+
buf << data
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
@ssh.loop
|
176
|
+
result = buf.compact.join('')
|
177
|
+
end
|
178
|
+
result
|
179
|
+
end
|
180
|
+
|
181
|
+
# connect to the host machine
|
182
|
+
def connect
|
183
|
+
if @ssh.nil?
|
184
|
+
@ssh = Net::SSH.start(@machine, @user, @password)
|
185
|
+
# @ssh.forward.local(@port, @machine, @port)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# disconnect from the host machine
|
190
|
+
def disconnect
|
191
|
+
if @ssh
|
192
|
+
@ssh.close
|
193
|
+
@ssh = nil
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
# Does the local directory tree and the remote directory tree contain the same files?
|
200
|
+
# Calculates a MD5 hash for each file then compares the hashes
|
201
|
+
# @param [String] local_path local directory
|
202
|
+
# @param [String] remote_path remote directory
|
203
|
+
def same_files?(local_path, remote_path)
|
204
|
+
remote_md5 = @ssh.exec!(md5_command_line(remote_path))
|
205
|
+
local_md5 = `#{md5_command_line(local_path)}`
|
206
|
+
@logger.debug { "same_files? local_md5 => #{local_md5}, remote_md5 => #{remote_md5}"}
|
207
|
+
remote_md5 == local_md5
|
208
|
+
end
|
209
|
+
|
210
|
+
def md5_command_line(dirname)
|
211
|
+
line = "cat \`find #{dirname} -type f | sort\` | ruby -e \"require 'digest/md5';puts Digest::MD5.hexdigest(STDIN.read)\""
|
212
|
+
@logger.debug { line }
|
213
|
+
line
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
data/lib/drbman.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
# gem 'ruby-debug', '>=0.10.3'
|
7
|
+
gem 'log4r', '>=1.0.5'
|
8
|
+
gem 'user-choices', '>=1.1.6'
|
9
|
+
gem 'extlib', '>=0.9.12'
|
10
|
+
gem 'net-ssh', '>=2.0.11'
|
11
|
+
gem 'net-scp', '>=1.0.2'
|
12
|
+
gem 'daemons', '>=1.0.10'
|
13
|
+
gem 'uuidtools', '>=2.0.0'
|
14
|
+
|
15
|
+
require 'extlib'
|
16
|
+
# require 'ruby-debug'
|
17
|
+
require 'log4r'
|
18
|
+
require 'user-choices'
|
19
|
+
require 'net/ssh'
|
20
|
+
require 'net/scp'
|
21
|
+
require 'daemons'
|
22
|
+
require 'uuidtools'
|
23
|
+
|
24
|
+
# require all of the .rb files in the drbman/ subdirectory
|
25
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'drbman/**/*.rb')).each do |name|
|
26
|
+
require name
|
27
|
+
end
|
28
|
+
|
data/spec/drbman_spec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe('HostMachine') do
|
4
|
+
before(:each) do
|
5
|
+
@logger = Log4r::Logger.new('host_machine_spec')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should parse: localhost" do
|
9
|
+
host_machine = HostMachine.new('localhost', @logger)
|
10
|
+
host_machine.machine.should == 'localhost'
|
11
|
+
host_machine.name.should == 'localhost'
|
12
|
+
host_machine.port.should == 9000
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should parse: localhost:1234" do
|
16
|
+
host_machine = HostMachine.new('localhost:1234', @logger)
|
17
|
+
host_machine.machine.should == 'localhost'
|
18
|
+
host_machine.name.should == 'localhost:1234'
|
19
|
+
host_machine.port.should == 1234
|
20
|
+
end
|
21
|
+
it "should parse: me@localhost" do
|
22
|
+
host_machine = HostMachine.new('me@localhost', @logger)
|
23
|
+
host_machine.machine.should == 'localhost'
|
24
|
+
host_machine.name.should == 'me@localhost'
|
25
|
+
host_machine.port.should == 9000
|
26
|
+
end
|
27
|
+
it "should parse: me@localhost:1234" do
|
28
|
+
host_machine = HostMachine.new('me@localhost:1234', @logger)
|
29
|
+
host_machine.machine.should == 'localhost'
|
30
|
+
host_machine.name.should == 'me@localhost:1234'
|
31
|
+
host_machine.port.should == 1234
|
32
|
+
end
|
33
|
+
it "should parse: me:sekret@localhost" do
|
34
|
+
host_machine = HostMachine.new('me:sekret@localhost', @logger)
|
35
|
+
host_machine.machine.should == 'localhost'
|
36
|
+
host_machine.name.should == 'me:sekret@localhost'
|
37
|
+
host_machine.port.should == 9000
|
38
|
+
end
|
39
|
+
it "should parse: me:sekret@localhost:1234" do
|
40
|
+
host_machine = HostMachine.new('me:sekret@localhost:1234', @logger)
|
41
|
+
host_machine.machine.should == 'localhost'
|
42
|
+
host_machine.name.should == 'me:sekret@localhost:1234'
|
43
|
+
host_machine.port.should == 1234
|
44
|
+
end
|
45
|
+
end
|