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.
@@ -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
+
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Drbman" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'drbman'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end