remote_run 0.1.0 → 0.1.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/lib/remote_run/configuration.rb +98 -0
- data/lib/remote_run/host.rb +91 -94
- data/lib/remote_run/runner.rb +108 -186
- data/lib/remote_run/task.rb +10 -0
- data/lib/remote_run/version.rb +1 -1
- data/lib/remote_run.rb +5 -0
- data/spec/remote_run/configuration_spec.rb +92 -0
- data/spec/remote_run/host_spec.rb +9 -9
- data/spec/remote_run/runner_spec.rb +0 -0
- data/spec/spec_helper.rb +3 -2
- metadata +15 -9
@@ -0,0 +1,98 @@
|
|
1
|
+
module RemoteRun
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :remote_path, :local_path, :login_as, :exclude, :temp_path, :quiet
|
4
|
+
attr_reader :local_hostname, :identifier, :start_time
|
5
|
+
attr_reader :host_manager, :task_manager
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@task_manager = TaskManager.new
|
9
|
+
@host_manager = HostManager.new
|
10
|
+
|
11
|
+
@local_path = Dir.getwd
|
12
|
+
@login_as = `whoami`.strip
|
13
|
+
@remote_path = "/tmp/remote"
|
14
|
+
@exclude = []
|
15
|
+
@temp_path = "/tmp/remote"
|
16
|
+
@quiet = false
|
17
|
+
@start_time = Time.now
|
18
|
+
|
19
|
+
# used in the runner
|
20
|
+
@identifier = `echo $RANDOM`.strip
|
21
|
+
@local_hostname = `hostname`.strip
|
22
|
+
|
23
|
+
$runner = self
|
24
|
+
yield self
|
25
|
+
end
|
26
|
+
|
27
|
+
def hosts
|
28
|
+
@host_manager.hosts
|
29
|
+
end
|
30
|
+
|
31
|
+
def tasks
|
32
|
+
@task_manager.tasks
|
33
|
+
end
|
34
|
+
|
35
|
+
def hosts=(hostnames)
|
36
|
+
hostnames.each do |hostname|
|
37
|
+
@host_manager.add(Host.new(hostname))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def tasks=(shell_commands)
|
42
|
+
shell_commands.each do |shell_command|
|
43
|
+
@task_manager.add(Task.new(shell_command))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def run
|
48
|
+
Runner.new(self).run
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
class HostManager
|
54
|
+
def initialize(&block)
|
55
|
+
@hosts = []
|
56
|
+
end
|
57
|
+
|
58
|
+
def add(host)
|
59
|
+
Thread.new do
|
60
|
+
if host.is_up?
|
61
|
+
@hosts << host
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def hosts
|
67
|
+
while @hosts.empty?
|
68
|
+
sleep(0.5)
|
69
|
+
end
|
70
|
+
@hosts
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class TaskManager
|
75
|
+
attr_reader :tasks
|
76
|
+
|
77
|
+
def initialize
|
78
|
+
@tasks = []
|
79
|
+
end
|
80
|
+
|
81
|
+
def add(task)
|
82
|
+
@tasks.push(task)
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_task
|
86
|
+
@tasks.shift
|
87
|
+
end
|
88
|
+
|
89
|
+
def count
|
90
|
+
@tasks.length
|
91
|
+
end
|
92
|
+
|
93
|
+
def has_more_tasks?
|
94
|
+
@tasks.size > 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/remote_run/host.rb
CHANGED
@@ -1,128 +1,125 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
def lock
|
14
|
-
unless locked?
|
15
|
-
@lock_file.get && locked_by_me?
|
1
|
+
module RemoteRun
|
2
|
+
class Host
|
3
|
+
FAIL = 1
|
4
|
+
PASS = 0
|
5
|
+
SSH_CONFIG = " -o ControlMaster=auto -o ControlPath=~/.ssh/master-%l-%r@%h:%p -o NumberOfPasswordPrompts=0 -o StrictHostKeyChecking=no -4 "
|
6
|
+
attr_reader :hostname
|
7
|
+
|
8
|
+
def initialize(hostname)
|
9
|
+
@hostname = hostname
|
10
|
+
@lock_file = LockFile.new(@hostname, $runner.local_hostname, $runner.identifier)
|
16
11
|
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def unlock
|
20
|
-
@lock_file.release
|
21
|
-
end
|
22
|
-
|
23
|
-
def run(task)
|
24
|
-
Runner.log("Running '#{task}' on #{@hostname}", :white)
|
25
|
-
command = %Q{ssh #{SSH_CONFIG} #{ssh_host_and_user} 'cd #{$runner.remote_path}; #{task}' 2>&1}
|
26
|
-
system(command)
|
27
|
-
$?.exitstatus
|
28
|
-
end
|
29
12
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
if system(%{rsync --delete --delete-excluded #{excludes.join(" ")} --rsh='ssh #{SSH_CONFIG}' --timeout=60 -a #{$runner.temp_path}/ #{ssh_host_and_user}:#{$runner.remote_path}/})
|
35
|
-
Runner.log("Finished copying to #{@hostname}", :green)
|
36
|
-
return true
|
37
|
-
else
|
38
|
-
Runner.log("rsync failed on #{@hostname}.", :red)
|
39
|
-
return false
|
13
|
+
def lock
|
14
|
+
unless locked?
|
15
|
+
@lock_file.get && locked_by_me?
|
16
|
+
end
|
40
17
|
end
|
41
|
-
end
|
42
18
|
|
43
|
-
|
44
|
-
|
45
|
-
if result == "success"
|
46
|
-
Runner.log("#{@hostname} is up", :green)
|
47
|
-
return true
|
48
|
-
else
|
49
|
-
Runner.log("#{@hostname} is down: #{result}", :red)
|
50
|
-
return false
|
19
|
+
def unlock
|
20
|
+
@lock_file.release
|
51
21
|
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def start_ssh_master_connection
|
55
|
-
system("ssh #{SSH_CONFIG} #{ssh_host_and_user} -M &> /dev/null")
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
def ssh_host_and_user
|
61
|
-
"#{$runner.login_as}@#{@hostname}"
|
62
|
-
end
|
63
|
-
|
64
|
-
def locked?
|
65
|
-
@lock_file.locked?
|
66
|
-
end
|
67
22
|
|
68
|
-
|
69
|
-
|
70
|
-
|
23
|
+
def run(task)
|
24
|
+
command = %Q{ssh #{SSH_CONFIG} #{ssh_host_and_user} 'cd #{$runner.remote_path}; #{task}' 2>&1}
|
25
|
+
system(command)
|
26
|
+
$?.exitstatus
|
27
|
+
end
|
71
28
|
|
72
|
-
|
73
|
-
|
29
|
+
def copy_codebase
|
30
|
+
system("ssh #{SSH_CONFIG} #{ssh_host_and_user} 'mkdir -p #{$runner.remote_path}'")
|
31
|
+
excludes = $runner.exclude.map { |dir| "--exclude '#{dir}'"}
|
32
|
+
if system(%{rsync --delete --delete-excluded #{excludes.join(" ")} --rsh='ssh #{SSH_CONFIG}' --timeout=60 -a #{$runner.temp_path}/ #{ssh_host_and_user}:#{$runner.remote_path}/})
|
33
|
+
return true
|
34
|
+
else
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
end
|
74
38
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
39
|
+
def is_up?
|
40
|
+
result = `ssh #{SSH_CONFIG} -o ConnectTimeout=2 #{ssh_host_and_user} "echo 'success'" 2>/dev/null`.strip
|
41
|
+
if result == "success"
|
42
|
+
return true
|
43
|
+
else
|
44
|
+
return false
|
45
|
+
end
|
79
46
|
end
|
80
47
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
48
|
+
def start_ssh_master_connection
|
49
|
+
fork do
|
50
|
+
system("ssh #{SSH_CONFIG} #{ssh_host_and_user} -M &> /dev/null")
|
84
51
|
end
|
85
52
|
end
|
86
53
|
|
54
|
+
private
|
55
|
+
|
56
|
+
def ssh_host_and_user
|
57
|
+
"#{$runner.login_as}@#{@hostname}"
|
58
|
+
end
|
59
|
+
|
87
60
|
def locked?
|
88
|
-
@
|
61
|
+
@lock_file.locked?
|
89
62
|
end
|
90
63
|
|
91
64
|
def locked_by_me?
|
92
|
-
@
|
65
|
+
@lock_file.locked_by_me?
|
93
66
|
end
|
94
67
|
|
95
|
-
|
96
|
-
|
97
|
-
end
|
68
|
+
class LockFile
|
69
|
+
FILE = "/tmp/remote-run-lock"
|
98
70
|
|
99
|
-
|
100
|
-
|
101
|
-
@
|
71
|
+
def initialize(remote_hostname, local_hostname, unique_run_marker)
|
72
|
+
@filename = FILE
|
73
|
+
@locker = "#{local_hostname}-#{unique_run_marker}"
|
74
|
+
@remote_file = RemoteFile.new(remote_hostname)
|
102
75
|
end
|
103
76
|
|
104
|
-
def
|
105
|
-
|
77
|
+
def release
|
78
|
+
if locked_by_me?
|
79
|
+
@remote_file.delete(@filename)
|
80
|
+
end
|
106
81
|
end
|
107
82
|
|
108
|
-
def
|
109
|
-
|
83
|
+
def locked?
|
84
|
+
@remote_file.exist?(@filename)
|
110
85
|
end
|
111
86
|
|
112
|
-
def
|
113
|
-
|
87
|
+
def locked_by_me?
|
88
|
+
@remote_file.exist?(@filename) && @remote_file.read(@filename).strip == @locker
|
114
89
|
end
|
115
90
|
|
116
|
-
def
|
117
|
-
|
91
|
+
def get
|
92
|
+
@remote_file.write(@filename, @locker)
|
118
93
|
end
|
119
94
|
|
120
|
-
|
121
|
-
|
122
|
-
|
95
|
+
class RemoteFile
|
96
|
+
def initialize(hostname)
|
97
|
+
@hostname = hostname
|
98
|
+
end
|
99
|
+
|
100
|
+
def exist?(file_path)
|
101
|
+
run_and_test("test -f #{file_path}")
|
102
|
+
end
|
103
|
+
|
104
|
+
def read(file_path)
|
105
|
+
run("test -e #{file_path} && cat #{file_path}")
|
106
|
+
end
|
107
|
+
|
108
|
+
def write(file_path, text)
|
109
|
+
run_and_test("test -e #{file_path} || echo #{text} > #{file_path}")
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete(file_path)
|
113
|
+
run_and_test("rm -f #{file_path}")
|
114
|
+
end
|
115
|
+
|
116
|
+
def run(command)
|
117
|
+
`ssh #{Host::SSH_CONFIG} #{$runner.login_as}@#{@hostname} '#{command};'`.strip
|
118
|
+
end
|
123
119
|
|
124
|
-
|
125
|
-
|
120
|
+
def run_and_test(command)
|
121
|
+
system("ssh #{Host::SSH_CONFIG} #{$runner.login_as}@#{@hostname} '#{command}' 2>/dev/null")
|
122
|
+
end
|
126
123
|
end
|
127
124
|
end
|
128
125
|
end
|
data/lib/remote_run/runner.rb
CHANGED
@@ -1,233 +1,155 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# config options
|
12
|
-
@local_path = Dir.getwd
|
13
|
-
@login_as = `whoami`.strip
|
14
|
-
@remote_path = "/tmp/remote"
|
15
|
-
@exclude = []
|
16
|
-
@temp_path = "/tmp/remote"
|
17
|
-
|
18
|
-
# used in the runner
|
19
|
-
@identifier = `echo $RANDOM`.strip
|
20
|
-
@local_hostname = `hostname`.strip
|
21
|
-
@results = []
|
22
|
-
@children = []
|
23
|
-
@failed = []
|
24
|
-
@last_timestamp = Time.now.strftime("%S")[0]
|
25
|
-
|
26
|
-
$runner = self
|
27
|
-
yield self
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.run(&block)
|
31
|
-
@@start_time = Time.now
|
32
|
-
runner = new(&block)
|
33
|
-
runner.run
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.run_time
|
37
|
-
minutes = ((Time.now - @@start_time) / 60).to_i
|
38
|
-
seconds = ((Time.now - @@start_time) % 60).to_i
|
39
|
-
"#{minutes}:#{"%02d" % seconds}"
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.log(message, color = :yellow)
|
43
|
-
highline = HighLine.new
|
44
|
-
system("stty #{@@stty_config} 2>/dev/null")
|
45
|
-
highline.say(highline.color("[Remote :: #{$runner.identifier} :: #{run_time}] #{message}", color))
|
46
|
-
end
|
47
|
-
|
48
|
-
def hosts
|
49
|
-
@host_manager.all
|
50
|
-
end
|
1
|
+
module RemoteRun
|
2
|
+
class Runner
|
3
|
+
def initialize(configuration)
|
4
|
+
@configuration = configuration
|
5
|
+
@results = []
|
6
|
+
@children = []
|
7
|
+
@failed = []
|
8
|
+
@stty_config = `stty -g`
|
9
|
+
@last_timestamp = Time.now.strftime("%S")[0]
|
10
|
+
@hosts = []
|
51
11
|
|
52
|
-
|
53
|
-
|
54
|
-
@
|
12
|
+
@task_manager = @configuration.task_manager
|
13
|
+
@host_manager = @configuration.host_manager
|
14
|
+
@starting_number_of_tasks = @task_manager.count
|
55
15
|
end
|
56
|
-
end
|
57
16
|
|
58
|
-
|
59
|
-
|
60
|
-
|
17
|
+
def run
|
18
|
+
setup_unlock_on_exit
|
19
|
+
start_ssh_master_connections
|
20
|
+
sync_working_copy_to_temp_location
|
21
|
+
start_tasks
|
22
|
+
wait_for_tasks_to_finish
|
23
|
+
handle_results
|
61
24
|
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def run
|
65
|
-
@host_manager.unlock_on_exit
|
66
|
-
@host_manager.start_ssh_master_connections
|
67
|
-
sync_working_copy_to_temp_location
|
68
|
-
hosts = []
|
69
|
-
|
70
|
-
Runner.log("Starting tasks... #{Time.now}")
|
71
25
|
|
72
|
-
|
73
|
-
while @task_manager.has_more_tasks?
|
74
|
-
hosts = @host_manager.hosts.dup if hosts.empty?
|
26
|
+
private
|
75
27
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
task = @task_manager.find_task
|
83
|
-
@children << fork do
|
84
|
-
begin
|
85
|
-
this_host = host.dup
|
86
|
-
unless this_host.copy_codebase
|
87
|
-
@task_manager.add(task)
|
88
|
-
status = 0
|
89
|
-
end
|
90
|
-
status = this_host.run(task)
|
91
|
-
host.unlock
|
92
|
-
Runner.log("#{host.hostname} failed.", :red) if status != 0
|
93
|
-
rescue Errno::EPIPE
|
94
|
-
Runner.log("broken pipe on #{host.hostname}...")
|
95
|
-
ensure
|
96
|
-
Process.exit!(status)
|
97
|
-
end
|
28
|
+
def setup_unlock_on_exit
|
29
|
+
at_exit do
|
30
|
+
@configuration.hosts.each do |host|
|
31
|
+
begin
|
32
|
+
host.unlock
|
33
|
+
rescue Errno::EPIPE
|
98
34
|
end
|
99
35
|
end
|
100
36
|
end
|
101
37
|
end
|
102
38
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
display_log
|
107
|
-
check_for_finished
|
108
|
-
end
|
109
|
-
|
110
|
-
failed_tasks = @results.select { |result| result != 0 }
|
111
|
-
status_code = if failed_tasks.length == 0
|
112
|
-
Runner.log("Task passed.", :green)
|
113
|
-
Host::PASS
|
114
|
-
else
|
115
|
-
Runner.log("#{failed_tasks.length} task(s) failed.", :red)
|
116
|
-
Host::FAIL
|
117
|
-
end
|
118
|
-
|
119
|
-
Runner.log("Total Time: #{self.class.run_time} minutes.")
|
120
|
-
status_code
|
121
|
-
end
|
122
|
-
|
123
|
-
def check_for_finished
|
124
|
-
@children.each do |child_pid|
|
125
|
-
if Process.waitpid(child_pid, Process::WNOHANG)
|
126
|
-
if $?.exitstatus != 0
|
127
|
-
@failed << child_pid
|
128
|
-
end
|
129
|
-
|
130
|
-
@results << $?.exitstatus
|
131
|
-
@children.delete(child_pid)
|
39
|
+
def start_ssh_master_connections
|
40
|
+
@configuration.hosts.each do |host|
|
41
|
+
host.start_ssh_master_connection
|
132
42
|
end
|
133
43
|
end
|
134
|
-
sleep(0.5)
|
135
|
-
end
|
136
|
-
|
137
|
-
private
|
138
44
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end
|
145
|
-
|
146
|
-
def display_log
|
147
|
-
now = Time.now.strftime("%S")[0]
|
148
|
-
unless now == @last_timestamp
|
149
|
-
display_status("Waiting on #{@task_manager.count} of #{@starting_number_of_tasks} tasks to start.") if @task_manager.count > 0
|
150
|
-
display_status("Waiting on #{@children.length} of #{@starting_number_of_tasks - @task_manager.count} started tasks to finish. #{@failed.size} failed.") if @children.length > 0
|
151
|
-
$stdout.print("\n\n")
|
152
|
-
$stdout.flush
|
153
|
-
@last_timestamp = now
|
45
|
+
def sync_working_copy_to_temp_location
|
46
|
+
log("Creating temporary copy of #{@configuration.local_path} in #{@configuration.temp_path}...")
|
47
|
+
excludes = @configuration.exclude.map { |dir| "--exclude '#{dir}'"}
|
48
|
+
system("rsync --delete --delete-excluded #{excludes.join(" ")} -aq #{@configuration.local_path}/ #{@configuration.temp_path}/")
|
49
|
+
log("Done.")
|
154
50
|
end
|
155
|
-
end
|
156
51
|
|
157
|
-
|
158
|
-
|
159
|
-
end
|
52
|
+
def start_tasks
|
53
|
+
log("Starting tasks... #{Time.now}")
|
160
54
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
55
|
+
while @task_manager.has_more_tasks?
|
56
|
+
display_log
|
57
|
+
check_for_finished
|
58
|
+
find_lock_and_start
|
59
|
+
end
|
165
60
|
|
166
|
-
|
167
|
-
@hosts
|
61
|
+
log("All tasks started... #{Time.now}")
|
168
62
|
end
|
169
63
|
|
170
|
-
def
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
@hosts << host
|
175
|
-
end
|
64
|
+
def wait_for_tasks_to_finish
|
65
|
+
while @children.length > 0
|
66
|
+
display_log
|
67
|
+
check_for_finished
|
176
68
|
end
|
177
69
|
end
|
178
70
|
|
179
|
-
def
|
180
|
-
|
181
|
-
|
182
|
-
|
71
|
+
def handle_results
|
72
|
+
failed_tasks = @results.select { |result| result != 0 }
|
73
|
+
status_code = if failed_tasks.length == 0
|
74
|
+
log("Task passed.", :green)
|
75
|
+
Host::PASS
|
76
|
+
else
|
77
|
+
log("#{failed_tasks.length} task(s) failed.", :red)
|
78
|
+
Host::FAIL
|
183
79
|
end
|
184
80
|
|
185
|
-
|
81
|
+
log("Total Time: #{run_time} minutes.")
|
82
|
+
status_code
|
186
83
|
end
|
187
84
|
|
188
|
-
def
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
host.unlock
|
193
|
-
rescue Errno::EPIPE
|
194
|
-
end
|
195
|
-
end
|
85
|
+
def start_task(host)
|
86
|
+
task = @task_manager.find_task
|
87
|
+
@children << fork do
|
88
|
+
start_forked_task(host, task)
|
196
89
|
end
|
197
90
|
end
|
198
91
|
|
199
|
-
def
|
200
|
-
|
201
|
-
|
202
|
-
|
92
|
+
def start_forked_task(host, task)
|
93
|
+
begin
|
94
|
+
this_host = host.dup
|
95
|
+
unless this_host.copy_codebase
|
96
|
+
@task_manager.add(task)
|
97
|
+
status = 0
|
203
98
|
end
|
99
|
+
status = this_host.run(task.command)
|
100
|
+
host.unlock
|
101
|
+
rescue Errno::EPIPE
|
102
|
+
ensure
|
103
|
+
Process.exit!(status)
|
204
104
|
end
|
205
105
|
end
|
206
|
-
end
|
207
106
|
|
208
|
-
|
209
|
-
|
210
|
-
|
107
|
+
def find_lock_and_start
|
108
|
+
@hosts = @host_manager.hosts.dup if @hosts.empty?
|
109
|
+
if host = @hosts.sample
|
110
|
+
@hosts.delete(host)
|
111
|
+
if host.lock
|
112
|
+
start_task(host)
|
113
|
+
end
|
114
|
+
end
|
211
115
|
end
|
212
116
|
|
213
|
-
def
|
214
|
-
@
|
117
|
+
def run_time
|
118
|
+
minutes = ((Time.now - @configuration.start_time) / 60).to_i
|
119
|
+
seconds = ((Time.now - @configuration.start_time) % 60).to_i
|
120
|
+
"#{minutes}:#{"%02d" % seconds}"
|
215
121
|
end
|
216
122
|
|
217
|
-
def
|
218
|
-
@
|
123
|
+
def log(message, color = :yellow)
|
124
|
+
unless @configuration.quiet
|
125
|
+
highline = HighLine.new
|
126
|
+
system("stty #{@stty_config} 2>/dev/null")
|
127
|
+
highline.say(highline.color("[Remote :: #{@configuration.identifier} :: #{run_time}] #{message}", color))
|
128
|
+
end
|
219
129
|
end
|
220
130
|
|
221
|
-
def
|
222
|
-
@
|
131
|
+
def check_for_finished
|
132
|
+
@children.each do |child_pid|
|
133
|
+
if task_is_finished?(child_pid)
|
134
|
+
@failed << child_pid unless $?.success?
|
135
|
+
@results << $?.exitstatus
|
136
|
+
@children.delete(child_pid)
|
137
|
+
end
|
138
|
+
end
|
223
139
|
end
|
224
140
|
|
225
|
-
def
|
226
|
-
|
141
|
+
def task_is_finished?(pid)
|
142
|
+
Process.waitpid(pid, Process::WNOHANG)
|
227
143
|
end
|
228
144
|
|
229
|
-
def
|
230
|
-
|
145
|
+
def display_log
|
146
|
+
now = Time.now.strftime("%S")[0]
|
147
|
+
unless now == @last_timestamp
|
148
|
+
log("Waiting on #{@task_manager.count} of #{@starting_number_of_tasks} tasks to start.") if @task_manager.count > 0
|
149
|
+
log("Waiting on #{@children.length} of #{@starting_number_of_tasks - @task_manager.count} started tasks to finish. #{@failed.size} failed.") if @children.length > 0
|
150
|
+
$stdout.flush
|
151
|
+
@last_timestamp = now
|
152
|
+
end
|
231
153
|
end
|
232
154
|
end
|
233
155
|
end
|
data/lib/remote_run/version.rb
CHANGED
data/lib/remote_run.rb
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "remote_run", "task")
|
1
2
|
require File.join(File.dirname(__FILE__), "remote_run", "host")
|
2
3
|
require File.join(File.dirname(__FILE__), "remote_run", "runner")
|
4
|
+
require File.join(File.dirname(__FILE__), "remote_run", "configuration")
|
3
5
|
require 'highline'
|
6
|
+
|
7
|
+
module RemoteRun
|
8
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RemoteRun::Configuration::HostManager do
|
4
|
+
subject { RemoteRun::Configuration::HostManager.new }
|
5
|
+
|
6
|
+
describe "#add" do
|
7
|
+
it "adds the given host to a list of hosts" do
|
8
|
+
subject.add(stub(:host, :is_up? => true, :name => "foobar"))
|
9
|
+
subject.hosts.size.should == 1
|
10
|
+
subject.hosts.first.name.should == "foobar"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#hosts" do
|
15
|
+
it "returns all hosts in the list" do
|
16
|
+
host = stub(:host, :is_up? => true, :name => "foobar")
|
17
|
+
subject.add(host)
|
18
|
+
subject.hosts.should == [host]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#start_ssh_master_connections" do
|
23
|
+
before do
|
24
|
+
@host = stub(:host, :is_up? => true, :name => "foobar")
|
25
|
+
subject.add(@host)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "asks each host to start their ssh master connection" do
|
29
|
+
@host.should_receive(:start_ssh_master_connection)
|
30
|
+
subject.start_ssh_master_connections
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe RemoteRun::Configuration::TaskManager do
|
36
|
+
subject { RemoteRun::Configuration::TaskManager.new }
|
37
|
+
describe "#add" do
|
38
|
+
it "takes a string and puts it on a list of tasks" do
|
39
|
+
task = RemoteRun::Task.new("date")
|
40
|
+
subject.add(task)
|
41
|
+
subject.tasks.should include(task)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#find_task" do
|
46
|
+
before do
|
47
|
+
@task = RemoteRun::Task.new("date")
|
48
|
+
subject.add(@task)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "finds a task from the list, returns it and removes it" do
|
52
|
+
subject.tasks.should == [@task]
|
53
|
+
subject.find_task.should == @task
|
54
|
+
subject.tasks.should == []
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#tasks" do
|
59
|
+
it "returns all of the tasks stored in the manager" do
|
60
|
+
task = RemoteRun::Task.new("foo")
|
61
|
+
task2 = RemoteRun::Task.new("bar")
|
62
|
+
subject.add(task)
|
63
|
+
subject.add(task2)
|
64
|
+
subject.tasks.should == [task, task2]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#count" do
|
69
|
+
it "returns the number of tasks stored" do
|
70
|
+
task = RemoteRun::Task.new("foo")
|
71
|
+
task2 = RemoteRun::Task.new("bar")
|
72
|
+
subject.add(task)
|
73
|
+
subject.add(task2)
|
74
|
+
subject.count.should == 2
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#has_more_tasks?" do
|
79
|
+
it "returns true when there are tasks in the list" do
|
80
|
+
task = RemoteRun::Task.new("foo")
|
81
|
+
task2 = RemoteRun::Task.new("bar")
|
82
|
+
subject.add(task)
|
83
|
+
subject.add(task2)
|
84
|
+
|
85
|
+
subject.has_more_tasks?.should be_true
|
86
|
+
subject.find_task
|
87
|
+
subject.has_more_tasks?.should be_true
|
88
|
+
subject.find_task
|
89
|
+
subject.has_more_tasks?.should be_false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Host do
|
3
|
+
describe RemoteRun::Host do
|
4
4
|
context "when locking" do
|
5
|
-
let(:host) { Host.new("localhost") }
|
5
|
+
let(:host) { RemoteRun::Host.new("localhost") }
|
6
6
|
|
7
7
|
it "can be locked" do
|
8
8
|
host.lock.should be_true
|
@@ -21,9 +21,9 @@ describe Host do
|
|
21
21
|
|
22
22
|
context "when locked by someone else" do
|
23
23
|
before { lock_file.get }
|
24
|
-
let(:host) { Host.new("localhost") }
|
24
|
+
let(:host) { RemoteRun::Host.new("localhost") }
|
25
25
|
let(:lock_file) {
|
26
|
-
lock_file = Host::LockFile.new("localhost", "myfakelocalhost", "999")
|
26
|
+
lock_file = RemoteRun::Host::LockFile.new("localhost", "myfakelocalhost", "999")
|
27
27
|
}
|
28
28
|
|
29
29
|
it "cannot be unlocked by me" do
|
@@ -33,7 +33,7 @@ describe Host do
|
|
33
33
|
|
34
34
|
context "when locked by me" do
|
35
35
|
before { host.lock }
|
36
|
-
let(:host) { Host.new("localhost") }
|
36
|
+
let(:host) { RemoteRun::Host.new("localhost") }
|
37
37
|
|
38
38
|
it "cannot be locked" do
|
39
39
|
host.lock.should be_false
|
@@ -52,7 +52,7 @@ describe Host do
|
|
52
52
|
|
53
53
|
context "when checking to see if a host is up" do
|
54
54
|
context "when using an authorized host" do
|
55
|
-
let(:host) { Host.new("localhost") }
|
55
|
+
let(:host) { RemoteRun::Host.new("localhost") }
|
56
56
|
|
57
57
|
it "returns true" do
|
58
58
|
host.is_up?.should be_true
|
@@ -60,7 +60,7 @@ describe Host do
|
|
60
60
|
end
|
61
61
|
|
62
62
|
context "when using an unauthorized host" do
|
63
|
-
let(:host) { Host.new("foozmcbarry") }
|
63
|
+
let(:host) { RemoteRun::Host.new("foozmcbarry") }
|
64
64
|
|
65
65
|
it "returns false" do
|
66
66
|
host.is_up?.should be_false
|
@@ -74,7 +74,7 @@ describe Host do
|
|
74
74
|
host.lock
|
75
75
|
end
|
76
76
|
|
77
|
-
let(:host) { Host.new("localhost") }
|
77
|
+
let(:host) { RemoteRun::Host.new("localhost") }
|
78
78
|
|
79
79
|
context "when executing a shell command with a zero status code" do
|
80
80
|
it "returns zero" do
|
@@ -95,7 +95,7 @@ describe Host do
|
|
95
95
|
host.lock
|
96
96
|
end
|
97
97
|
|
98
|
-
let(:host) { Host.new("localhost") }
|
98
|
+
let(:host) { RemoteRun::Host.new("localhost") }
|
99
99
|
|
100
100
|
it "copies the codebase to a remote directory" do
|
101
101
|
$runner.remote_path = "/tmp/testing-remote-run"
|
File without changes
|
data/spec/spec_helper.rb
CHANGED
@@ -3,13 +3,14 @@ require 'bundler/setup'
|
|
3
3
|
require 'rspec'
|
4
4
|
require Dir.pwd + '/lib/remote_run'
|
5
5
|
|
6
|
-
|
6
|
+
RemoteRun::Configuration.new do |config|
|
7
7
|
config.tasks = []
|
8
8
|
config.hosts = []
|
9
|
+
config.quiet = true
|
9
10
|
end
|
10
11
|
|
11
12
|
RSpec.configure do |config|
|
12
13
|
config.after(:each) do
|
13
|
-
system("rm -f #{Host::LockFile::FILE}")
|
14
|
+
system("rm -f #{RemoteRun::Host::LockFile::FILE}")
|
14
15
|
end
|
15
16
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: remote_run
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-09-13 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: highline
|
16
|
-
requirement: &
|
16
|
+
requirement: &2151951040 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2151951040
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &2151943360 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2151943360
|
36
36
|
description: Can be used as a parallel unit test runner
|
37
37
|
email:
|
38
38
|
- casecommons-dev@googlegroups.com
|
@@ -48,11 +48,15 @@ files:
|
|
48
48
|
- Readme.md
|
49
49
|
- examples/demo-remote-run
|
50
50
|
- lib/remote_run.rb
|
51
|
+
- lib/remote_run/configuration.rb
|
51
52
|
- lib/remote_run/host.rb
|
52
53
|
- lib/remote_run/runner.rb
|
54
|
+
- lib/remote_run/task.rb
|
53
55
|
- lib/remote_run/version.rb
|
54
56
|
- remote_run.gemspec
|
57
|
+
- spec/remote_run/configuration_spec.rb
|
55
58
|
- spec/remote_run/host_spec.rb
|
59
|
+
- spec/remote_run/runner_spec.rb
|
56
60
|
- spec/spec_helper.rb
|
57
61
|
homepage: https://github.com/Casecommons/remote_run
|
58
62
|
licenses: []
|
@@ -68,7 +72,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
72
|
version: '0'
|
69
73
|
segments:
|
70
74
|
- 0
|
71
|
-
hash:
|
75
|
+
hash: 3128109979642869878
|
72
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
77
|
none: false
|
74
78
|
requirements:
|
@@ -77,13 +81,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
81
|
version: '0'
|
78
82
|
segments:
|
79
83
|
- 0
|
80
|
-
hash:
|
84
|
+
hash: 3128109979642869878
|
81
85
|
requirements: []
|
82
86
|
rubyforge_project: remote_run
|
83
|
-
rubygems_version: 1.8.
|
87
|
+
rubygems_version: 1.8.10
|
84
88
|
signing_key:
|
85
89
|
specification_version: 3
|
86
90
|
summary: Run N shell scripts on a pool of remote hosts
|
87
91
|
test_files:
|
92
|
+
- spec/remote_run/configuration_spec.rb
|
88
93
|
- spec/remote_run/host_spec.rb
|
94
|
+
- spec/remote_run/runner_spec.rb
|
89
95
|
- spec/spec_helper.rb
|