easy-serve 0.7 → 0.8
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/examples/multi.rb +15 -9
- data/examples/passive.rb +11 -5
- data/examples/remote-drb.rb +3 -3
- data/examples/remote-eval-passive.rb +6 -6
- data/examples/remote-eval.rb +4 -4
- data/examples/remote-manual.rb +9 -9
- data/examples/remote-multi-server.rb +4 -4
- data/examples/remote-run.rb +4 -4
- data/examples/simple.rb +11 -5
- data/lib/easy-serve.rb +55 -111
- data/lib/easy-serve/accessible-services.rb +34 -0
- data/lib/easy-serve/remote-drb.rb +5 -5
- data/lib/easy-serve/remote-eval-mgr.rb +10 -8
- data/lib/easy-serve/remote-eval.rb +10 -10
- data/lib/easy-serve/remote-run-mgr.rb +10 -8
- data/lib/easy-serve/remote-run.rb +13 -13
- data/lib/easy-serve/remote.rb +4 -4
- data/lib/easy-serve/service.rb +125 -0
- metadata +18 -17
- data/lib/easy-serve/accessible-servers.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03c328ab82c8bb6aebbc3b9e6e5c9a50a3895a5c
|
4
|
+
data.tar.gz: be6c2705dc58ee78cf2ce725f0bdb885dac96378
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d91ad208ac5237e50d2e03ca0d1524a2a8005753f78fa1ccf192b7486baa8ce70abca4c2b8ca7ecfaa25c2d364f1d0f5a6dcf6759fb2916557a3b9e9ca31b64f
|
7
|
+
data.tar.gz: 22d93a3d940daf5e2ecf4810896f9f13fbef3d49e8cad7f7f2b9a1a07991f1d4bb106163e55dfaf9d70f7a8dec8ed0bbc0a6b5a76b3856c74fbafa50e7e76491
|
data/README.md
CHANGED
data/examples/multi.rb
CHANGED
@@ -1,18 +1,24 @@
|
|
1
1
|
require 'easy-serve'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
if ARGV.delete("--tcp")
|
4
|
+
proto = :tcp
|
5
|
+
else
|
6
|
+
proto = :unix
|
6
7
|
end
|
7
8
|
|
8
|
-
|
9
|
+
services_file = ARGV.shift
|
10
|
+
unless services_file
|
11
|
+
abort "Usage: #$0 services_file # Run this in two or more shells"
|
12
|
+
end
|
13
|
+
|
14
|
+
EasyServe.start services_file: services_file do |ez|
|
9
15
|
log = ez.log
|
10
16
|
log.level = Logger::DEBUG
|
11
17
|
log.formatter = nil if $VERBOSE
|
12
18
|
|
13
|
-
ez.
|
14
|
-
ez.
|
15
|
-
log.debug {"starting
|
19
|
+
ez.start_services do
|
20
|
+
ez.service "simple-service", proto do |svr|
|
21
|
+
log.debug {"starting service"}
|
16
22
|
Thread.new do
|
17
23
|
loop do
|
18
24
|
Thread.new(svr.accept) do |conn|
|
@@ -26,13 +32,13 @@ EasyServe.start servers_file: servers_file do |ez|
|
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
29
|
-
ez.child "simple-
|
35
|
+
ez.child "simple-service" do |conn|
|
30
36
|
log.progname = "client with pid=#$$"
|
31
37
|
log.info conn.read
|
32
38
|
conn.write "hello from #{log.progname}"
|
33
39
|
end
|
34
40
|
|
35
|
-
ez.local "simple-
|
41
|
+
ez.local "simple-service" do |conn|
|
36
42
|
log.progname = "parent process"
|
37
43
|
log.info "PRESS RETURN TO STOP"
|
38
44
|
gets
|
data/examples/passive.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
1
|
require 'easy-serve'
|
2
2
|
Thread.abort_on_exception = true
|
3
3
|
|
4
|
+
if ARGV.delete("--tcp")
|
5
|
+
proto = :tcp
|
6
|
+
else
|
7
|
+
proto = :unix
|
8
|
+
end
|
9
|
+
|
4
10
|
EasyServe.start do |ez|
|
5
11
|
log = ez.log
|
6
12
|
log.level = Logger::DEBUG
|
7
13
|
log.formatter = nil if $VERBOSE
|
8
|
-
log.debug {"starting
|
14
|
+
log.debug {"starting services"}
|
9
15
|
|
10
|
-
ez.
|
11
|
-
ez.
|
16
|
+
ez.start_services do
|
17
|
+
ez.service "simple-service", proto do |svr|
|
12
18
|
Thread.new do
|
13
19
|
loop do
|
14
20
|
conn = svr.accept
|
@@ -21,7 +27,7 @@ EasyServe.start do |ez|
|
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
24
|
-
ez.child "simple-
|
30
|
+
ez.child "simple-service", passive: true do |conn|
|
25
31
|
log.progname = "client 1"
|
26
32
|
log.info conn.read
|
27
33
|
conn.write "hello from #{log.progname}, pid = #$$; sleeping..."
|
@@ -31,7 +37,7 @@ EasyServe.start do |ez|
|
|
31
37
|
|
32
38
|
sleep 0.1
|
33
39
|
|
34
|
-
ez.child "simple-
|
40
|
+
ez.child "simple-service" do |conn|
|
35
41
|
log.progname = "client 2"
|
36
42
|
log.info conn.read
|
37
43
|
conn.write "hello from #{log.progname}, pid = #$$"
|
data/examples/remote-drb.rb
CHANGED
@@ -23,8 +23,8 @@ EasyServe.start do |ez|
|
|
23
23
|
log.level = Logger::INFO
|
24
24
|
log.formatter = nil if $VERBOSE
|
25
25
|
|
26
|
-
ez.
|
27
|
-
ez.
|
26
|
+
ez.start_services do
|
27
|
+
ez.service "simple-service", :tcp do |svr|
|
28
28
|
Thread.new do
|
29
29
|
loop do
|
30
30
|
Thread.new(svr.accept) do |conn|
|
@@ -41,7 +41,7 @@ EasyServe.start do |ez|
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
ez.remote "simple-
|
44
|
+
ez.remote "simple-service", host: addr_there do |conn|
|
45
45
|
# this block runs locally, but calls methods on the remote using drb
|
46
46
|
log.progname = "druby remote on #{addr_there}"
|
47
47
|
log.info "trying to read from #{conn.inspect}"
|
@@ -22,8 +22,8 @@ EasyServe.start do |ez|
|
|
22
22
|
log.level = Logger::INFO
|
23
23
|
log.formatter = nil if $VERBOSE
|
24
24
|
|
25
|
-
ez.
|
26
|
-
ez.
|
25
|
+
ez.start_services do
|
26
|
+
ez.service "simple-service", :tcp do |svr|
|
27
27
|
Thread.new do
|
28
28
|
loop do
|
29
29
|
Thread.new(svr.accept) do |conn|
|
@@ -40,7 +40,7 @@ EasyServe.start do |ez|
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
ez.remote "simple-
|
43
|
+
ez.remote "simple-service", host: addr_there, log: true, passive: true,
|
44
44
|
eval: %{
|
45
45
|
conn = conns[0]
|
46
46
|
# this code is executed on the remote host, connected by conn, not drb
|
@@ -52,17 +52,17 @@ EasyServe.start do |ez|
|
|
52
52
|
|
53
53
|
sleep
|
54
54
|
# Without passive, this sleep would prevent the distributed app from
|
55
|
-
# exiting -- the simple-
|
55
|
+
# exiting -- the simple-service above could not be safely stopped, since
|
56
56
|
# there's no guarantee that it is no longer needed. The passive
|
57
57
|
# declaration make it clear that this process can be stopped after all
|
58
|
-
# non-passive clients have finished, and then the
|
58
|
+
# non-passive clients have finished, and then the service can be stopped.
|
59
59
|
# Of course, this also means that if all other clients execute very
|
60
60
|
# quickly, this client might never get a chance to run.
|
61
61
|
}
|
62
62
|
|
63
63
|
sleep 1 # Ensure (for testing) that the above client runs.
|
64
64
|
|
65
|
-
ez.remote "simple-
|
65
|
+
ez.remote "simple-service", host: addr_there, log: true, eval: %{
|
66
66
|
conn = conns[0]
|
67
67
|
# this code is executed on the remote host, connected by conn, not drb
|
68
68
|
log.progname = "eval remote 2 on \#{host}"
|
data/examples/remote-eval.rb
CHANGED
@@ -15,7 +15,7 @@ unless addr_there
|
|
15
15
|
|
16
16
|
The 'hostname' may by any valid hostname or ssh alias.
|
17
17
|
|
18
|
-
If --tunnel is specified, use the ssh connection to tunnel the
|
18
|
+
If --tunnel is specified, use the ssh connection to tunnel the data
|
19
19
|
traffic. Otherwise, just use tcp. (Always use ssh to start the remote
|
20
20
|
process.)
|
21
21
|
|
@@ -27,9 +27,9 @@ EasyServe.start do |ez|
|
|
27
27
|
log.level = Logger::INFO
|
28
28
|
log.formatter = nil if $VERBOSE
|
29
29
|
|
30
|
-
ez.
|
30
|
+
ez.start_services do
|
31
31
|
host = tunnel ? "localhost" : nil # no need to expose port if tunnelled
|
32
|
-
ez.
|
32
|
+
ez.service "simple-service", :tcp, bind_host: host do |svr|
|
33
33
|
Thread.new do
|
34
34
|
loop do
|
35
35
|
Thread.new(svr.accept) do |conn|
|
@@ -46,7 +46,7 @@ EasyServe.start do |ez|
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
ez.remote "simple-
|
49
|
+
ez.remote "simple-service", host: addr_there, tunnel: tunnel, log: true,
|
50
50
|
eval: %{
|
51
51
|
conn = conns[0]
|
52
52
|
# this code is executed on the remote host, connected by conn, not drb
|
data/examples/remote-manual.rb
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
require 'easy-serve'
|
2
2
|
|
3
|
-
|
4
|
-
unless
|
3
|
+
services_file = ARGV.shift
|
4
|
+
unless services_file
|
5
5
|
abort <<-END
|
6
|
-
Usage: #$0
|
7
|
-
For the client, copy the generated
|
6
|
+
Usage: #$0 services_file
|
7
|
+
For the client, copy the generated services_file to the client host, and
|
8
8
|
run with the same command.
|
9
9
|
END
|
10
10
|
end
|
11
11
|
|
12
|
-
EasyServe.start
|
12
|
+
EasyServe.start services_file: services_file do |ez|
|
13
13
|
log = ez.log
|
14
14
|
log.level = Logger::DEBUG
|
15
15
|
log.formatter = nil if $VERBOSE
|
16
16
|
|
17
|
-
ez.
|
18
|
-
ez.
|
19
|
-
log.debug {"starting
|
17
|
+
ez.start_services do
|
18
|
+
ez.service "simple-service", :tcp do |svr|
|
19
|
+
log.debug {"starting service"}
|
20
20
|
Thread.new do
|
21
21
|
loop do
|
22
22
|
Thread.new(svr.accept) do |conn|
|
@@ -30,7 +30,7 @@ EasyServe.start servers_file: servers_file do |ez|
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
ez.local "simple-
|
33
|
+
ez.local "simple-service" do |conn|
|
34
34
|
log.progname = "parent process"
|
35
35
|
log.info conn.read
|
36
36
|
conn.write "hello from #{log.progname}"
|
@@ -15,7 +15,7 @@ unless address_there
|
|
15
15
|
|
16
16
|
The 'hostname' may by any valid hostname or ssh alias.
|
17
17
|
|
18
|
-
If --tunnel is specified, use the ssh connection to tunnel the
|
18
|
+
If --tunnel is specified, use the ssh connection to tunnel the data
|
19
19
|
traffic. Otherwise, just use tcp. (Always use ssh to start the remote
|
20
20
|
process.)
|
21
21
|
|
@@ -27,10 +27,10 @@ EasyServe.start do |ez|
|
|
27
27
|
log.level = Logger::INFO
|
28
28
|
log.formatter = nil if $VERBOSE
|
29
29
|
|
30
|
-
ez.
|
30
|
+
ez.start_services do
|
31
31
|
host = tunnel ? "localhost" : nil # no need to expose port if tunnelled
|
32
32
|
|
33
|
-
ez.
|
33
|
+
ez.service "adder", :tcp, bind_host: host do |svr|
|
34
34
|
Thread.new do
|
35
35
|
loop do
|
36
36
|
Thread.new(svr.accept) do |conn|
|
@@ -56,7 +56,7 @@ EasyServe.start do |ez|
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
ez.
|
59
|
+
ez.service "multiplier", :tcp, bind_host: host do |svr|
|
60
60
|
Thread.new do
|
61
61
|
loop do
|
62
62
|
Thread.new(svr.accept) do |conn|
|
data/examples/remote-run.rb
CHANGED
@@ -15,7 +15,7 @@ unless addr_there
|
|
15
15
|
|
16
16
|
The 'hostname' may by any valid hostname or ssh alias.
|
17
17
|
|
18
|
-
If --tunnel is specified, use the ssh connection to tunnel the
|
18
|
+
If --tunnel is specified, use the ssh connection to tunnel the data
|
19
19
|
traffic. Otherwise, just use tcp. (Always use ssh to start the remote
|
20
20
|
process.)
|
21
21
|
|
@@ -31,9 +31,9 @@ EasyServe.start do |ez|
|
|
31
31
|
log.level = Logger::INFO
|
32
32
|
log.formatter = nil if $VERBOSE
|
33
33
|
|
34
|
-
ez.
|
34
|
+
ez.start_services do
|
35
35
|
host = tunnel ? "localhost" : nil # no need to expose port if tunnelled
|
36
|
-
ez.
|
36
|
+
ez.service "simple-service", :tcp, bind_host: host do |svr|
|
37
37
|
Thread.new do
|
38
38
|
loop do
|
39
39
|
Thread.new(svr.accept) do |conn|
|
@@ -50,7 +50,7 @@ EasyServe.start do |ez|
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
ez.remote "simple-
|
53
|
+
ez.remote "simple-service", host: addr_there, tunnel: tunnel,
|
54
54
|
dir: "/tmp",
|
55
55
|
file: "remote-run-script.rb",
|
56
56
|
# 'file' passed to load, so can be rel to dir or ruby's $LOAD_PATH
|
data/examples/simple.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
require 'easy-serve'
|
2
2
|
|
3
|
+
if ARGV.delete("--tcp")
|
4
|
+
proto = :tcp
|
5
|
+
else
|
6
|
+
proto = :unix
|
7
|
+
end
|
8
|
+
|
3
9
|
EasyServe.start do |ez|
|
4
10
|
log = ez.log
|
5
11
|
log.level = Logger::DEBUG
|
6
12
|
log.formatter = nil if $VERBOSE
|
7
|
-
log.debug {"starting
|
13
|
+
log.debug {"starting services"}
|
8
14
|
|
9
|
-
ez.
|
10
|
-
ez.
|
15
|
+
ez.start_services do
|
16
|
+
ez.service "simple-service", proto do |svr|
|
11
17
|
Thread.new do
|
12
18
|
loop do
|
13
19
|
conn = svr.accept
|
@@ -20,13 +26,13 @@ EasyServe.start do |ez|
|
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
|
-
ez.child "simple-
|
29
|
+
ez.child "simple-service" do |conn|
|
24
30
|
log.progname = "client 1"
|
25
31
|
log.info conn.read
|
26
32
|
conn.write "hello from #{log.progname}"
|
27
33
|
end
|
28
34
|
|
29
|
-
ez.local "simple-
|
35
|
+
ez.local "simple-service" do |conn|
|
30
36
|
log.progname = "parent process"
|
31
37
|
log.info conn.read
|
32
38
|
conn.write "hello from #{log.progname}"
|
data/lib/easy-serve.rb
CHANGED
@@ -3,16 +3,10 @@ require 'socket'
|
|
3
3
|
require 'yaml'
|
4
4
|
require 'fileutils'
|
5
5
|
|
6
|
-
|
7
|
-
VERSION = "0.7"
|
6
|
+
require 'easy-serve/service'
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def initialize name, pid, addr
|
13
|
-
@name, @pid, @addr = name, pid, addr
|
14
|
-
end
|
15
|
-
end
|
8
|
+
class EasyServe
|
9
|
+
VERSION = "0.8"
|
16
10
|
|
17
11
|
class EasyFormatter < Logger::Formatter
|
18
12
|
Format = "%s: %s: %s\n"
|
@@ -35,10 +29,10 @@ class EasyServe
|
|
35
29
|
end
|
36
30
|
|
37
31
|
attr_accessor :log
|
38
|
-
attr_accessor :
|
32
|
+
attr_accessor :services
|
39
33
|
attr_reader :children
|
40
34
|
attr_reader :passive_children
|
41
|
-
attr_reader :
|
35
|
+
attr_reader :services_file
|
42
36
|
attr_reader :interactive
|
43
37
|
|
44
38
|
def self.start(log: default_logger, **opts)
|
@@ -52,37 +46,37 @@ class EasyServe
|
|
52
46
|
end
|
53
47
|
|
54
48
|
def initialize **opts
|
55
|
-
@
|
49
|
+
@services_file = opts[:services_file]
|
56
50
|
@interactive = opts[:interactive]
|
57
51
|
@log = opts[:log] || self.class.null_logger
|
58
52
|
@children = [] # pid
|
59
53
|
@passive_children = [] # pid
|
60
54
|
@owner = false
|
61
55
|
@tmpdir = nil
|
62
|
-
@
|
56
|
+
@services = opts[:services] # name => service
|
63
57
|
|
64
|
-
unless
|
65
|
-
if
|
66
|
-
@
|
58
|
+
unless services
|
59
|
+
if services_file
|
60
|
+
@services =
|
67
61
|
begin
|
68
|
-
|
62
|
+
load_service_table
|
69
63
|
rescue Errno::ENOENT
|
70
|
-
|
64
|
+
init_service_table
|
71
65
|
end
|
72
66
|
else
|
73
|
-
|
67
|
+
init_service_table
|
74
68
|
end
|
75
69
|
end
|
76
70
|
end
|
77
71
|
|
78
|
-
def
|
79
|
-
File.open(
|
72
|
+
def load_service_table
|
73
|
+
File.open(services_file) do |f|
|
80
74
|
YAML.load(f)
|
81
75
|
end
|
82
76
|
end
|
83
77
|
|
84
|
-
def
|
85
|
-
@
|
78
|
+
def init_service_table
|
79
|
+
@services ||= begin
|
86
80
|
@owner = true
|
87
81
|
{}
|
88
82
|
end
|
@@ -113,20 +107,16 @@ class EasyServe
|
|
113
107
|
end
|
114
108
|
|
115
109
|
if @owner
|
116
|
-
|
110
|
+
services.each do |name, service|
|
117
111
|
log.info "stopping #{name}"
|
118
|
-
|
119
|
-
Process.waitpid server.pid
|
120
|
-
if server.addr.kind_of? String
|
121
|
-
FileUtils.remove_entry server.addr
|
122
|
-
end
|
112
|
+
service.cleanup
|
123
113
|
end
|
124
114
|
|
125
|
-
if
|
115
|
+
if services_file
|
126
116
|
begin
|
127
|
-
FileUtils.rm
|
117
|
+
FileUtils.rm services_file
|
128
118
|
rescue Errno::ENOENT
|
129
|
-
log.warn "
|
119
|
+
log.warn "services file #{services_file.inspect} was deleted already"
|
130
120
|
end
|
131
121
|
end
|
132
122
|
end
|
@@ -134,14 +124,14 @@ class EasyServe
|
|
134
124
|
clean_tmpdir
|
135
125
|
end
|
136
126
|
|
137
|
-
def
|
127
|
+
def start_services
|
138
128
|
if @owner
|
139
|
-
log.debug {"starting
|
129
|
+
log.debug {"starting services"}
|
140
130
|
yield
|
141
131
|
|
142
|
-
if
|
143
|
-
File.open(
|
144
|
-
YAML.dump(
|
132
|
+
if services_file
|
133
|
+
File.open(services_file, "w") do |f|
|
134
|
+
YAML.dump(services, f)
|
145
135
|
end
|
146
136
|
end
|
147
137
|
end
|
@@ -166,7 +156,7 @@ class EasyServe
|
|
166
156
|
end
|
167
157
|
end
|
168
158
|
|
169
|
-
def
|
159
|
+
def self.bump_socket_filename name
|
170
160
|
name =~ /-\d+\z/ ? name.succ : name + "-0"
|
171
161
|
end
|
172
162
|
|
@@ -186,83 +176,49 @@ class EasyServe
|
|
186
176
|
end
|
187
177
|
end
|
188
178
|
|
189
|
-
|
190
|
-
server_class, *server_addr =
|
191
|
-
case proto
|
192
|
-
when /unix/i; [UNIXServer, choose_socket_filename(name, base: host)]
|
193
|
-
when /tcp/i; [TCPServer, host || host_name, port || 0]
|
194
|
-
else raise ArgumentError, "Unknown socket protocol: #{proto.inspect}"
|
195
|
-
end
|
179
|
+
MAX_TRIES = 10
|
196
180
|
|
197
|
-
|
181
|
+
def service name, proto = nil, **opts
|
182
|
+
proto ||= opts.delete(:proto) || :unix
|
183
|
+
case proto
|
184
|
+
when :unix
|
185
|
+
opts[:path] ||= choose_socket_filename(name, base: opts[:base])
|
186
|
+
|
187
|
+
when :tcp
|
188
|
+
opts[:connect_host] ||=
|
189
|
+
case opts[:bind_host]
|
190
|
+
when nil, "0.0.0.0", /\A<any>\z/i
|
191
|
+
host_name ## maybe local connectors should use "localhost" ?
|
192
|
+
when "localhost", "127.0.0.1"
|
193
|
+
"localhost"
|
194
|
+
end
|
195
|
+
end
|
198
196
|
|
197
|
+
service = Service.for(name, proto, **opts)
|
198
|
+
rd, wr = IO.pipe
|
199
199
|
pid = fork do
|
200
200
|
rd.close
|
201
201
|
log.progname = name
|
202
202
|
log.info "starting"
|
203
|
-
|
204
|
-
svr =
|
203
|
+
|
204
|
+
svr = service.serve(max_tries: MAX_TRIES, log: log)
|
205
205
|
yield svr if block_given?
|
206
206
|
no_interrupt_if_interactive
|
207
207
|
|
208
|
-
|
209
|
-
case proto
|
210
|
-
when /unix/i; svr.addr[1]
|
211
|
-
when /tcp/i; svr.addr(false).values_at(2,1)
|
212
|
-
end
|
213
|
-
Marshal.dump addr, wr
|
208
|
+
Marshal.dump service, wr
|
214
209
|
wr.close
|
215
210
|
sleep
|
216
211
|
end
|
217
212
|
|
218
213
|
wr.close
|
219
|
-
|
214
|
+
services[name] = Marshal.load rd
|
220
215
|
rd.close
|
221
|
-
servers[name] = Server.new(name, pid, addr)
|
222
|
-
end
|
223
|
-
|
224
|
-
MAX_TRIES = 10
|
225
|
-
|
226
|
-
def server_for server_class, *server_addr
|
227
|
-
tries = 0
|
228
|
-
begin
|
229
|
-
server_class.new(*server_addr)
|
230
|
-
rescue Errno::EADDRINUSE => ex
|
231
|
-
if server_class == UNIXServer
|
232
|
-
if tries < MAX_TRIES
|
233
|
-
tries += 1
|
234
|
-
server_addr[0] = inc_socket_filename(server_addr[0])
|
235
|
-
log.warn {
|
236
|
-
"#{ex} -- trying again at path #{server_addr}, #{tries}/#{MAX_TRIES} times."
|
237
|
-
}
|
238
|
-
retry
|
239
|
-
end
|
240
|
-
elsif server_class == TCPServer
|
241
|
-
port = Integer(server_addr[1])
|
242
|
-
if port and tries < MAX_TRIES
|
243
|
-
tries += 1
|
244
|
-
port += 1
|
245
|
-
server_addr[1] = port
|
246
|
-
log.warn {
|
247
|
-
"#{ex} -- trying again at port #{port}, #{tries}/#{MAX_TRIES} times."
|
248
|
-
}
|
249
|
-
retry
|
250
|
-
end
|
251
|
-
else
|
252
|
-
raise ArgumentError, "unknown server_class: #{server_class.inspect}"
|
253
|
-
end
|
254
|
-
raise
|
255
|
-
|
256
|
-
rescue => ex
|
257
|
-
ex.message << "; addr=#{server_addr.inspect}"
|
258
|
-
raise
|
259
|
-
end
|
260
216
|
end
|
261
217
|
|
262
218
|
# A passive client child may be stopped after all active clients exit.
|
263
|
-
def child *
|
219
|
+
def child *service_names, passive: false
|
264
220
|
c = fork do
|
265
|
-
conns =
|
221
|
+
conns = service_names.map {|sn| services[sn].connect}
|
266
222
|
yield(*conns) if block_given?
|
267
223
|
no_interrupt_if_interactive
|
268
224
|
end
|
@@ -275,8 +231,8 @@ class EasyServe
|
|
275
231
|
child *args, &block
|
276
232
|
end
|
277
233
|
|
278
|
-
def local *
|
279
|
-
conns =
|
234
|
+
def local *service_names
|
235
|
+
conns = service_names.map {|sn| services[sn].connect}
|
280
236
|
yield(*conns) if block_given?
|
281
237
|
ensure
|
282
238
|
conns and conns.each do |conn|
|
@@ -285,20 +241,8 @@ class EasyServe
|
|
285
241
|
log.info "stopped local client"
|
286
242
|
end
|
287
243
|
|
288
|
-
def socket_for *addr
|
289
|
-
socket_class =
|
290
|
-
case addr.size
|
291
|
-
when 1; UNIXSocket
|
292
|
-
else TCPSocket
|
293
|
-
end
|
294
|
-
socket_class.new(*addr)
|
295
|
-
rescue => ex
|
296
|
-
ex.message << "; addr=#{addr.inspect}"
|
297
|
-
raise
|
298
|
-
end
|
299
|
-
|
300
244
|
# ^C in the irb session (parent process) should not kill the
|
301
|
-
#
|
245
|
+
# service (child process)
|
302
246
|
def no_interrupt_if_interactive
|
303
247
|
trap("INT") {} if interactive
|
304
248
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class EasyServe
|
2
|
+
# Returns list of services that are accessible from +host+, setting
|
3
|
+
# up an ssh tunnel if specified. Note that OpenSSH 6.0 or later is required
|
4
|
+
# for the tunnel option.
|
5
|
+
def accessible_services host, tunnel: false
|
6
|
+
tcp_svs = services.values.grep(TCPService)
|
7
|
+
return tcp_svs unless tunnel and host != "localhost" and host != "127.0.0.1"
|
8
|
+
|
9
|
+
tcp_svs.map do |service|
|
10
|
+
service_host =
|
11
|
+
case service.bind_host
|
12
|
+
when nil, "localhost", "127.0.0.1", "0.0.0.0", /\A<any>\z/i
|
13
|
+
"localhost"
|
14
|
+
else
|
15
|
+
service.bind_host
|
16
|
+
end
|
17
|
+
|
18
|
+
fwd = "0:#{service_host}:#{service.port}"
|
19
|
+
out = `ssh -O forward -R #{fwd} #{host}`
|
20
|
+
|
21
|
+
begin
|
22
|
+
remote_port = Integer(out)
|
23
|
+
rescue
|
24
|
+
log.error "Unable to set up dynamic ssh port forwarding. " +
|
25
|
+
"Please check if ssh -v is at least 6.0."
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
|
29
|
+
at_exit {system "ssh -O cancel -R #{fwd} #{host}"}
|
30
|
+
|
31
|
+
TCPService.new service.name, connect_host: "localhost", port: remote_port
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -6,7 +6,7 @@ class EasyServe
|
|
6
6
|
# It's up to you to start another thread inside the code block if you
|
7
7
|
# want more concurrency. This is for convenience when testing (cases in which
|
8
8
|
# concurrency needs to be controlled explicitly).
|
9
|
-
def remote_drb *
|
9
|
+
def remote_drb *service_names, host: nil
|
10
10
|
## remote logfile option?
|
11
11
|
|
12
12
|
DRb.start_service("druby://#{host_name}:0", nil)
|
@@ -24,17 +24,17 @@ class EasyServe
|
|
24
24
|
require 'yaml'
|
25
25
|
require 'easy-serve'
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
service_names = #{service_names.inspect}
|
28
|
+
services = YAML.load(#{YAML.dump(services).inspect})
|
29
29
|
log_level = #{log.level}
|
30
30
|
host_uri = #{host_uri.inspect}
|
31
31
|
|
32
|
-
EasyServe.start
|
32
|
+
EasyServe.start services: services do |ez|
|
33
33
|
log = ez.log
|
34
34
|
log.level = log_level
|
35
35
|
log.formatter = nil if $VERBOSE
|
36
36
|
|
37
|
-
ez.local *
|
37
|
+
ez.local *service_names do |*conns|
|
38
38
|
begin
|
39
39
|
DRb.start_service(host_uri, {conns: conns})
|
40
40
|
puts DRb.uri
|
@@ -3,12 +3,14 @@ require 'easy-serve'
|
|
3
3
|
|
4
4
|
def EasyServe.manage_remote_eval_client msg
|
5
5
|
$VERBOSE = msg["verbose"]
|
6
|
-
|
7
|
-
msg.values_at(*%w{
|
6
|
+
service_names, services_list, log_level, eval_string, host =
|
7
|
+
msg.values_at(*%w{service_names services_list log_level eval_string host})
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
services = {}
|
10
|
+
service_names = Marshal.load(service_names)
|
11
|
+
services_list = Marshal.load(services_list)
|
12
|
+
services_list.each do |service|
|
13
|
+
services[service.name] = service
|
12
14
|
end
|
13
15
|
|
14
16
|
log_args = msg["log"]
|
@@ -22,12 +24,12 @@ def EasyServe.manage_remote_eval_client msg
|
|
22
24
|
EasyServe.null_logger
|
23
25
|
end
|
24
26
|
|
25
|
-
EasyServe.start
|
27
|
+
EasyServe.start services: services, log: log do |ez|
|
26
28
|
log = ez.log
|
27
29
|
log.level = log_level
|
28
30
|
log.formatter = nil if $VERBOSE
|
29
31
|
|
30
|
-
ez.local *
|
32
|
+
ez.local *service_names do |*conns|
|
31
33
|
begin
|
32
34
|
pr = eval "proc do |conns, host, log| #{eval_string}; end"
|
33
35
|
pr[conns, host, log]
|
@@ -48,7 +50,7 @@ def EasyServe.handle_remote_eval_messages
|
|
48
50
|
unpacker = MessagePack::Unpacker.new($stdin)
|
49
51
|
unpacker.each do |msg|
|
50
52
|
case
|
51
|
-
when msg["
|
53
|
+
when msg["service_names"]
|
52
54
|
Thread.new {manage_remote_eval_client(msg); exit}
|
53
55
|
when msg["exit"]
|
54
56
|
puts "exiting"
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'msgpack'
|
2
|
-
require 'easy-serve/accessible-
|
2
|
+
require 'easy-serve/accessible-services'
|
3
3
|
|
4
4
|
class EasyServe
|
5
5
|
# useful simple cases in testing and in production, but long eval strings
|
@@ -13,7 +13,7 @@ class EasyServe
|
|
13
13
|
#
|
14
14
|
# 2. Log back over ssh: pass log: true.
|
15
15
|
#
|
16
|
-
def remote_eval *
|
16
|
+
def remote_eval *service_names,
|
17
17
|
host: nil, passive: false, tunnel: false, **opts
|
18
18
|
child_pid = fork do
|
19
19
|
log.progname = "remote_eval #{host}"
|
@@ -26,17 +26,17 @@ class EasyServe
|
|
26
26
|
"w+" do |ssh|
|
27
27
|
|
28
28
|
ssh.sync = true
|
29
|
-
|
29
|
+
services_list = accessible_services(host, tunnel: tunnel)
|
30
30
|
|
31
31
|
MessagePack.pack(
|
32
32
|
{
|
33
|
-
verbose:
|
34
|
-
|
35
|
-
|
36
|
-
log_level:
|
37
|
-
eval_string:
|
38
|
-
host:
|
39
|
-
log:
|
33
|
+
verbose: $VERBOSE,
|
34
|
+
service_names: Marshal.dump(service_names),
|
35
|
+
services_list: Marshal.dump(services_list),
|
36
|
+
log_level: log.level,
|
37
|
+
eval_string: opts[:eval],
|
38
|
+
host: host,
|
39
|
+
log: opts[:log]
|
40
40
|
},
|
41
41
|
ssh)
|
42
42
|
|
@@ -3,15 +3,17 @@ require 'easy-serve'
|
|
3
3
|
|
4
4
|
def EasyServe.manage_remote_run_client msg
|
5
5
|
$VERBOSE = msg["verbose"]
|
6
|
-
|
6
|
+
service_names, services_list, log_level, host, dir, file, class_name, args =
|
7
7
|
msg.values_at(*%w{
|
8
|
-
|
8
|
+
service_names services_list log_level host
|
9
9
|
dir file class_name args
|
10
10
|
})
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
services = {}
|
13
|
+
service_names = Marshal.load(service_names)
|
14
|
+
services_list = Marshal.load(services_list)
|
15
|
+
services_list.each do |service|
|
16
|
+
services[service.name] = service
|
15
17
|
end
|
16
18
|
|
17
19
|
### opt for tmpdir and send files to it via ssh
|
@@ -29,12 +31,12 @@ def EasyServe.manage_remote_run_client msg
|
|
29
31
|
EasyServe.null_logger
|
30
32
|
end
|
31
33
|
|
32
|
-
EasyServe.start
|
34
|
+
EasyServe.start services: services, log: log do |ez|
|
33
35
|
log = ez.log
|
34
36
|
log.level = log_level
|
35
37
|
log.formatter = nil if $VERBOSE
|
36
38
|
|
37
|
-
ez.local *
|
39
|
+
ez.local *service_names do |*conns|
|
38
40
|
begin
|
39
41
|
cl = Object.const_get(class_name)
|
40
42
|
ro = cl.new(conns, host, log, *args)
|
@@ -56,7 +58,7 @@ def EasyServe.handle_remote_run_messages
|
|
56
58
|
unpacker = MessagePack::Unpacker.new($stdin)
|
57
59
|
unpacker.each do |msg|
|
58
60
|
case
|
59
|
-
when msg["
|
61
|
+
when msg["service_names"]
|
60
62
|
Thread.new {manage_remote_run_client(msg); exit}
|
61
63
|
when msg["exit"]
|
62
64
|
puts "exiting"
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'msgpack'
|
2
|
-
require 'easy-serve/accessible-
|
2
|
+
require 'easy-serve/accessible-services'
|
3
3
|
|
4
4
|
class EasyServe
|
5
5
|
# useful in production, though it requires remote lib files to be set up.
|
@@ -12,7 +12,7 @@ class EasyServe
|
|
12
12
|
#
|
13
13
|
# 2. Log back over ssh: pass log: true.
|
14
14
|
#
|
15
|
-
def remote_run *
|
15
|
+
def remote_run *service_names, host: nil, passive: false, tunnel: false, **opts
|
16
16
|
child_pid = fork do
|
17
17
|
log.progname = "remote_run #{host}"
|
18
18
|
|
@@ -24,20 +24,20 @@ class EasyServe
|
|
24
24
|
"w+" do |ssh|
|
25
25
|
|
26
26
|
ssh.sync = true
|
27
|
-
|
27
|
+
services_list = accessible_services(host, tunnel: tunnel)
|
28
28
|
|
29
29
|
MessagePack.pack(
|
30
30
|
{
|
31
|
-
verbose:
|
32
|
-
|
33
|
-
|
34
|
-
log_level:
|
35
|
-
host:
|
36
|
-
dir:
|
37
|
-
file:
|
38
|
-
class_name:
|
39
|
-
args:
|
40
|
-
log:
|
31
|
+
verbose: $VERBOSE,
|
32
|
+
service_names: Marshal.dump(service_names),
|
33
|
+
services_list: Marshal.dump(services_list),
|
34
|
+
log_level: log.level,
|
35
|
+
host: host,
|
36
|
+
dir: opts[:dir],
|
37
|
+
file: opts[:file],
|
38
|
+
class_name: opts[:class_name],
|
39
|
+
args: opts[:args],
|
40
|
+
log: opts[:log]
|
41
41
|
},
|
42
42
|
ssh)
|
43
43
|
|
data/lib/easy-serve/remote.rb
CHANGED
@@ -3,20 +3,20 @@ require 'easy-serve'
|
|
3
3
|
class EasyServe
|
4
4
|
class RemoteError < RuntimeError; end
|
5
5
|
|
6
|
-
def remote *
|
6
|
+
def remote *service_names, host: nil, **opts
|
7
7
|
raise ArgumentError, "no host specified" unless host
|
8
8
|
|
9
9
|
if opts[:eval]
|
10
10
|
require 'easy-serve/remote-eval'
|
11
|
-
remote_eval(*
|
11
|
+
remote_eval(*service_names, host: host, **opts)
|
12
12
|
|
13
13
|
elsif opts[:file]
|
14
14
|
require 'easy-serve/remote-run'
|
15
|
-
remote_run(*
|
15
|
+
remote_run(*service_names, host: host, **opts)
|
16
16
|
|
17
17
|
elsif block_given?
|
18
18
|
require 'easy-serve/remote-drb'
|
19
|
-
remote_drb(*
|
19
|
+
remote_drb(*service_names, host: host, **opts, &Proc.new)
|
20
20
|
|
21
21
|
else
|
22
22
|
raise ArgumentError, "cannot select remote mode based on arguments"
|
@@ -0,0 +1,125 @@
|
|
1
|
+
class EasyServe
|
2
|
+
# Refers to a named service. Use the #serve method to start the service.
|
3
|
+
# Encapsulates current location and identity, including pid and address. A
|
4
|
+
# Service object can be serialized to a remote process so it can #connect to
|
5
|
+
# the service.
|
6
|
+
class Service
|
7
|
+
attr_reader :name
|
8
|
+
attr_reader :pid
|
9
|
+
|
10
|
+
SERVICE_CLASS = {}
|
11
|
+
|
12
|
+
def self.for name, proto, **opts
|
13
|
+
sc = SERVICE_CLASS[proto] or
|
14
|
+
raise ArgumentError, "Unknown socket protocol: #{proto.inspect}"
|
15
|
+
sc.new name, **opts
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize name
|
19
|
+
@name = name
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the client socket.
|
23
|
+
def connect
|
24
|
+
try_connect
|
25
|
+
rescue => ex
|
26
|
+
ex.message << "; #{inspect}"
|
27
|
+
raise
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the server socket.
|
31
|
+
def serve max_tries: 1, log: (raise ArgumentError, "missing log")
|
32
|
+
tries = 1
|
33
|
+
begin
|
34
|
+
try_serve.tap do
|
35
|
+
@pid = Process.pid
|
36
|
+
end
|
37
|
+
|
38
|
+
rescue Errno::EADDRINUSE => ex
|
39
|
+
raise if tries >= max_tries
|
40
|
+
log.warn {"#{ex}; #{inspect} (#{tries}/#{max_tries} tries)"}
|
41
|
+
tries += 1; bump!
|
42
|
+
log.info {"Trying: #{inspect}"}
|
43
|
+
retry
|
44
|
+
end
|
45
|
+
rescue => ex
|
46
|
+
ex.message << "; #{inspect}"
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
|
50
|
+
def cleanup
|
51
|
+
Process.kill "TERM", pid
|
52
|
+
Process.waitpid pid
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class UNIXService < Service
|
57
|
+
SERVICE_CLASS[:unix] = self
|
58
|
+
|
59
|
+
attr_reader :path
|
60
|
+
|
61
|
+
def initialize name, path: nil
|
62
|
+
super name
|
63
|
+
@path = path
|
64
|
+
end
|
65
|
+
|
66
|
+
def serve max_tries: 1, log: log
|
67
|
+
super.tap do |svr|
|
68
|
+
found_path = svr.addr[1]
|
69
|
+
log.debug "#{inspect} is listening at #{found_path}"
|
70
|
+
|
71
|
+
if found_path != path
|
72
|
+
log.error "Unexpected path: #{found_path} != #{path}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def cleanup
|
78
|
+
super
|
79
|
+
FileUtils.remove_entry path if path
|
80
|
+
end
|
81
|
+
|
82
|
+
def try_connect
|
83
|
+
UNIXSocket.new(path)
|
84
|
+
end
|
85
|
+
|
86
|
+
def try_serve
|
87
|
+
UNIXServer.new(path)
|
88
|
+
end
|
89
|
+
|
90
|
+
def bump!
|
91
|
+
@path = EasyServe.bump_socket_filename(path)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class TCPService < Service
|
96
|
+
SERVICE_CLASS[:tcp] = self
|
97
|
+
|
98
|
+
attr_reader :bind_host, :connect_host, :port
|
99
|
+
|
100
|
+
def initialize name, bind_host: nil, connect_host: nil, port: 0
|
101
|
+
super name
|
102
|
+
@bind_host, @connect_host, @port = bind_host, connect_host, port
|
103
|
+
end
|
104
|
+
|
105
|
+
def serve max_tries: 1, log: log
|
106
|
+
super.tap do |svr|
|
107
|
+
found_addr = svr.addr(false).values_at(2,1)
|
108
|
+
log.debug "#{inspect} is listening at #{found_addr.join(":")}"
|
109
|
+
@port = found_addr[1]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def try_connect
|
114
|
+
TCPSocket.new(connect_host, port)
|
115
|
+
end
|
116
|
+
|
117
|
+
def try_serve
|
118
|
+
TCPServer.new(bind_host, port)
|
119
|
+
end
|
120
|
+
|
121
|
+
def bump!
|
122
|
+
@port += 1 unless port == 0 # should not happen
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: easy-serve
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.8'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel VanderWerf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -24,8 +24,8 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
description: Framework for starting tcp/unix
|
28
|
-
parent process and on remote hosts.
|
27
|
+
description: Framework for starting tcp/unix services and connected clients under
|
28
|
+
one parent process and on remote hosts.
|
29
29
|
email: vjoel@users.sourceforge.net
|
30
30
|
executables: []
|
31
31
|
extensions: []
|
@@ -35,24 +35,25 @@ extra_rdoc_files:
|
|
35
35
|
files:
|
36
36
|
- README.md
|
37
37
|
- COPYING
|
38
|
-
- lib/easy-serve
|
38
|
+
- lib/easy-serve.rb
|
39
|
+
- lib/easy-serve/remote-eval-mgr.rb
|
39
40
|
- lib/easy-serve/remote-run.rb
|
40
41
|
- lib/easy-serve/remote-drb.rb
|
41
|
-
- lib/easy-serve/
|
42
|
-
- lib/easy-serve/
|
43
|
-
- lib/easy-serve/remote-eval-mgr.rb
|
42
|
+
- lib/easy-serve/remote.rb
|
43
|
+
- lib/easy-serve/accessible-services.rb
|
44
44
|
- lib/easy-serve/remote-eval.rb
|
45
|
-
- lib/easy-serve.rb
|
46
|
-
-
|
47
|
-
- examples/
|
48
|
-
- examples/
|
45
|
+
- lib/easy-serve/remote-run-mgr.rb
|
46
|
+
- lib/easy-serve/service.rb
|
47
|
+
- examples/simple.rb
|
48
|
+
- examples/remote-manual.rb
|
49
|
+
- examples/multi.rb
|
49
50
|
- examples/remote-eval-passive.rb
|
51
|
+
- examples/remote-run.rb
|
50
52
|
- examples/remote-drb.rb
|
53
|
+
- examples/remote-multi-server.rb
|
51
54
|
- examples/remote-run-script.rb
|
52
|
-
- examples/multi.rb
|
53
|
-
- examples/remote-manual.rb
|
54
|
-
- examples/simple.rb
|
55
55
|
- examples/remote-eval.rb
|
56
|
+
- examples/passive.rb
|
56
57
|
homepage: https://github.com/vjoel/easy-serve
|
57
58
|
licenses:
|
58
59
|
- BSD
|
@@ -83,7 +84,7 @@ rubyforge_project:
|
|
83
84
|
rubygems_version: 2.1.11
|
84
85
|
signing_key:
|
85
86
|
specification_version: 4
|
86
|
-
summary: Framework for starting tcp/unix
|
87
|
-
process and on remote hosts
|
87
|
+
summary: Framework for starting tcp/unix services and connected clients under one
|
88
|
+
parent process and on remote hosts
|
88
89
|
test_files: []
|
89
90
|
has_rdoc:
|
@@ -1,29 +0,0 @@
|
|
1
|
-
class EasyServe
|
2
|
-
# Returns list of [name, pid, addr] that are accessible from host, setting
|
3
|
-
# up ssh tunnel if specified. Note that OpenSSH 6.0 or later is required
|
4
|
-
# for the tunnel option.
|
5
|
-
def accessible_servers host, tunnel: false
|
6
|
-
if tunnel and host != "localhost" and host != "127.0.0.1"
|
7
|
-
servers.map do |n, s|
|
8
|
-
_, local_port = s.addr
|
9
|
-
fwd = "0:localhost:#{local_port}"
|
10
|
-
out = `ssh -O forward -R #{fwd} #{host}`
|
11
|
-
|
12
|
-
begin
|
13
|
-
remote_port = Integer(out)
|
14
|
-
rescue
|
15
|
-
log.error "Unable to set up dynamic ssh port forwarding. " +
|
16
|
-
"Please check if ssh -v is at least 6.0."
|
17
|
-
raise
|
18
|
-
end
|
19
|
-
|
20
|
-
at_exit {system "ssh -O cancel -R #{fwd} #{host}"}
|
21
|
-
|
22
|
-
[s.name, s.pid, ["localhost", remote_port]]
|
23
|
-
end
|
24
|
-
|
25
|
-
else
|
26
|
-
servers.map {|n, s| [s.name, s.pid, s.addr]}
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|