lxc-extra 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/lib/lxc/extra.rb +20 -5
- data/lib/lxc/extra/channel.rb +169 -0
- data/lib/lxc/extra/proxy_client_side.rb +127 -0
- data/lib/lxc/extra/proxy_server_side.rb +122 -0
- data/lib/lxc/extra/selector.rb +103 -0
- data/lib/lxc/extra/version.rb +1 -1
- data/spec/unit/lxc_channel_spec.rb +64 -0
- data/spec/unit/lxc_extra_spec.rb +9 -0
- data/spec/unit/lxc_proxy_spec.rb +114 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ODA1YTcyNmFhODEwYmEyZTFkZDBiMjk1Y2ZkZTFiNWEwODg2ZmY0Yg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OTlkNTdiNTNlNTQ3YTAwZDExZTY0NWZmYjAxMDMyZjJiMWQ4OWE3Zg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YjBmYTdkNDM2OTMyZThiNTczOTE4ZDBkMDFmNmNlYjNiMmNlYTdmZTFlYjZl
|
10
|
+
MDIzOGMzYTNiZTAzZDFlNzlhYmRhZDc1NDc4YjY1ZjI4Y2VkOGJhMDhmMDE2
|
11
|
+
OGEzNjZjN2MxZGQzN2E4YjI5MGQ1MTVjZDM5Y2M1MGYzNWRiYmE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YjhiZjE1MTAyZmZiMWJhNjVlMGQ4ZTBmZWE5ZDJlNWYyNmNkNGRjYzJmODMz
|
14
|
+
ZGFkYjQwMTUxZGNhMmVmY2IwMzc5Y2RjNjc5OTgxMjI3Zjg4YzczNmE2Njlh
|
15
|
+
NTYxMjNjZWQwZTk1NDI3N2NmY2FiYzVmYjA5ODIxNWUwYjc4NjU=
|
data/lib/lxc/extra.rb
CHANGED
@@ -1,24 +1,31 @@
|
|
1
1
|
require 'lxc'
|
2
2
|
require 'lxc/extra/version'
|
3
|
+
require 'lxc/extra/channel'
|
3
4
|
require 'io/wait'
|
5
|
+
require 'timeout'
|
4
6
|
|
5
7
|
module LXC
|
6
8
|
module Extra
|
7
|
-
def execute(&block)
|
9
|
+
def execute(opts= {}, &block)
|
10
|
+
attach_opts = opts[:attach_options] || {}
|
11
|
+
attach_opts[:wait] ||= true
|
12
|
+
timeout = opts[:timeout] || 3600
|
8
13
|
r,w = IO.pipe
|
9
|
-
ret = attach(
|
14
|
+
ret = attach(attach_opts) do
|
10
15
|
ENV.clear
|
11
16
|
ENV['PATH'] = '/usr/bin:/bin:/usr/sbin:/sbin'
|
12
17
|
ENV['TERM'] = 'xterm-256color'
|
13
18
|
ENV['SHELL'] = '/bin/bash'
|
14
19
|
r.close
|
15
20
|
begin
|
16
|
-
|
17
|
-
|
21
|
+
Timeout::timeout(timeout) do
|
22
|
+
out = block.call
|
23
|
+
w.write(Marshal.dump(out))
|
24
|
+
end
|
18
25
|
rescue Exception => e
|
19
26
|
w.write(Marshal.dump(e))
|
20
27
|
end
|
21
|
-
end
|
28
|
+
end
|
22
29
|
w.close
|
23
30
|
o = nil
|
24
31
|
if r.ready?
|
@@ -28,6 +35,14 @@ module LXC
|
|
28
35
|
raise o if o.is_a?(Exception)
|
29
36
|
o
|
30
37
|
end
|
38
|
+
|
39
|
+
def open_channel(&block)
|
40
|
+
channel = ::LXC::Extra::Channel.new
|
41
|
+
pid = attach do
|
42
|
+
channel.listen(&block)
|
43
|
+
end
|
44
|
+
[channel, pid]
|
45
|
+
end
|
31
46
|
end
|
32
47
|
class Container
|
33
48
|
include Extra
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module LXC
|
2
|
+
module Extra
|
3
|
+
#
|
4
|
+
# Bidirectional Ruby communications between a host and an LXC container.
|
5
|
+
# It handles bidirectional communications: the host can send a message to
|
6
|
+
# the container using channel.send_message, and the container can send to
|
7
|
+
# the host using channel.send_message as well.
|
8
|
+
#
|
9
|
+
# This class is intended to be created before ct.attach and used by both
|
10
|
+
# the host and the container.
|
11
|
+
#
|
12
|
+
# == Shorthand Usage
|
13
|
+
#
|
14
|
+
# require 'lxc'
|
15
|
+
# require 'lxc/extra'
|
16
|
+
#
|
17
|
+
# container = LXC::Container.new('simple')
|
18
|
+
# channel, pid = container.open_channel do |host, number|
|
19
|
+
# puts "received #{number} INSIDE!"
|
20
|
+
# if number > 10
|
21
|
+
# host.stop
|
22
|
+
# else
|
23
|
+
# host.send_message(number+1)
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# channel.send_message(1)
|
27
|
+
# channel.listen do |container, number|
|
28
|
+
# puts "received #{number} OUTSIDE!"
|
29
|
+
# container.send_message(number+1) unless number > 10
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# == Extended Usage
|
33
|
+
#
|
34
|
+
# channel = Channel.new
|
35
|
+
# ct.attach do
|
36
|
+
# channel.listen do |host, number|
|
37
|
+
# "received #{number} INSIDE!"
|
38
|
+
# if number > 10
|
39
|
+
# host.stop
|
40
|
+
# else
|
41
|
+
# host.send(number+1)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# channel.listen do |container, number|
|
46
|
+
# "received #{number} OUTSIDE!"
|
47
|
+
# container.send(number+1)
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
class Channel
|
51
|
+
#
|
52
|
+
# Create a new LXC channel.
|
53
|
+
#
|
54
|
+
def initialize
|
55
|
+
@from_container, @to_host = IO.pipe
|
56
|
+
@from_host, @to_container = IO.pipe
|
57
|
+
@host_pid = Process.pid
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :from_container
|
61
|
+
attr_reader :to_host
|
62
|
+
attr_reader :from_host
|
63
|
+
attr_reader :to_container
|
64
|
+
attr_reader :host_pid
|
65
|
+
|
66
|
+
#
|
67
|
+
# Listen for data from the other side. Loops until stop is received.
|
68
|
+
#
|
69
|
+
# == Arguments
|
70
|
+
# &block:: callback
|
71
|
+
#
|
72
|
+
# == Example
|
73
|
+
#
|
74
|
+
# channel.listen do |*args|
|
75
|
+
# puts "Received #{args} from the other side."
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
def listen(&block)
|
79
|
+
while true
|
80
|
+
args = self.next
|
81
|
+
if args.is_a?(Stop)
|
82
|
+
return
|
83
|
+
end
|
84
|
+
block.call(self, *args)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Send a message to the other side.
|
90
|
+
# You may send arbitrary Ruby, which will be sent with Marshal.dump.
|
91
|
+
#
|
92
|
+
# == Arguments
|
93
|
+
# args:: a list of arguments to send. Arbitrary Ruby objects may be sent.
|
94
|
+
# The other side's listen callback will receive these arguments.
|
95
|
+
#
|
96
|
+
def send_message(*args)
|
97
|
+
Marshal.dump(args, write_fd)
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Get the next result.
|
102
|
+
#
|
103
|
+
# == Arguments
|
104
|
+
# stop_result:: result to return when the channel wants to shut down. Defaults to LXC::Extra::Channel::Stop.singleton
|
105
|
+
#
|
106
|
+
# == Returns
|
107
|
+
# Returns nextLXC::Extra::Channel::Stop instance if the channel closes.
|
108
|
+
def next(stop_result = Stop.singleton)
|
109
|
+
if read_fd.closed?
|
110
|
+
stop_result
|
111
|
+
else
|
112
|
+
Marshal.load(read_fd)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Stop the channel. Sends a message to the other side to stop it, too.
|
118
|
+
#
|
119
|
+
def stop
|
120
|
+
send_message(Stop.singleton)
|
121
|
+
@from_container.close
|
122
|
+
@to_host.close
|
123
|
+
@from_host.close
|
124
|
+
@to_container.close
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# Pretend a message was sent from the other side.
|
129
|
+
#
|
130
|
+
# For debug purposes.
|
131
|
+
#
|
132
|
+
def pretend_message_was_sent(*args)
|
133
|
+
if Process.pid == @host_pid
|
134
|
+
Marshal.dump(args, @to_host)
|
135
|
+
else
|
136
|
+
Marshal.dump(args, @to_container)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# File descriptor that sends data to the other side.
|
142
|
+
#
|
143
|
+
def write_fd
|
144
|
+
if Process.pid == @host_pid
|
145
|
+
@to_container
|
146
|
+
else
|
147
|
+
@to_host
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# File descriptor that receives data from the other side.
|
153
|
+
#
|
154
|
+
def read_fd
|
155
|
+
if Process.pid == @host_pid
|
156
|
+
@from_container
|
157
|
+
else
|
158
|
+
@from_host
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class Stop
|
163
|
+
def self.singleton
|
164
|
+
@@singleton ||= Stop.new
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'lxc/extra/selector'
|
2
|
+
|
3
|
+
module LXC
|
4
|
+
module Extra
|
5
|
+
#
|
6
|
+
# Proxy server that listens for connections on the client side and sends
|
7
|
+
# the data to the corresponding ProxyServerSide through an Channel.
|
8
|
+
#
|
9
|
+
# == Usage
|
10
|
+
#
|
11
|
+
# # Forward 127.0.0.1:80 in the container to google.com from the host
|
12
|
+
# channel = LXC::Extra::Channel.new
|
13
|
+
# pid = container.attach do
|
14
|
+
# server = TCPServer.new('127.0.0.1', 5559)
|
15
|
+
# proxy = LXC::Extra::ProxyClientSide.new(channel, server)
|
16
|
+
# proxy.start
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # Here is the proxy server
|
20
|
+
# proxy = LXC::Extra::ProxyServerSide.new(channel) do
|
21
|
+
# TCPSocket.new('127.0.0.1', 9995)
|
22
|
+
# end
|
23
|
+
# proxy.start
|
24
|
+
#
|
25
|
+
class ProxyClientSide
|
26
|
+
#
|
27
|
+
# Create a new ProxyClientSide.
|
28
|
+
#
|
29
|
+
# == Arguments
|
30
|
+
# channel:: Channel to communicate over
|
31
|
+
# listen_server:: TCPServer to listen to
|
32
|
+
#
|
33
|
+
def initialize(channel, listen_server)
|
34
|
+
@channel = channel
|
35
|
+
@listen_server = listen_server
|
36
|
+
@sockets = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :channel
|
40
|
+
attr_reader :listen_server
|
41
|
+
|
42
|
+
#
|
43
|
+
# Start forwarding connections and data. This call will not return until
|
44
|
+
# stop is called.
|
45
|
+
#
|
46
|
+
def start
|
47
|
+
begin
|
48
|
+
begin
|
49
|
+
@selector = Selector.new
|
50
|
+
@selector.on_select(@listen_server, &method(:on_server_accept))
|
51
|
+
@selector.on_select(@channel.read_fd, &method(:on_channel_response))
|
52
|
+
@selector.main_loop
|
53
|
+
ensure
|
54
|
+
@sockets.values.each { |socket| socket.close }
|
55
|
+
@sockets = {}
|
56
|
+
@selector = nil
|
57
|
+
end
|
58
|
+
rescue
|
59
|
+
STDERR.puts("Proxy server error on client side: #{$!}\n#{$!.backtrace.join("\n")}")
|
60
|
+
@channel.send_message(:server_error, $!)
|
61
|
+
raise
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Stop forwarding connections and data. Existing connections will be closed
|
67
|
+
# and a stop message will be sent to the other side.
|
68
|
+
#
|
69
|
+
def stop
|
70
|
+
@sockets.values.each { |socket| socket.close }
|
71
|
+
@sockets = {}
|
72
|
+
@selector = nil
|
73
|
+
@channel.send_message(:stop)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def on_server_accept(server)
|
79
|
+
socket = server.accept
|
80
|
+
@selector.on_select(socket, &method(:on_client_data))
|
81
|
+
@sockets[socket.fileno] = socket
|
82
|
+
@channel.send_message(:open, socket.fileno)
|
83
|
+
end
|
84
|
+
|
85
|
+
def on_client_data(socket)
|
86
|
+
message, *args = socket.recvmsg
|
87
|
+
if message.length == 0
|
88
|
+
@channel.send_message(:close, socket.fileno)
|
89
|
+
@selector.delete(socket)
|
90
|
+
@sockets.delete(socket.fileno)
|
91
|
+
socket.close
|
92
|
+
else
|
93
|
+
@channel.send_message(:data, socket.fileno, message)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def on_channel_response(read_fd)
|
98
|
+
message_type, fileno, *args = @channel.next(:stop)
|
99
|
+
begin
|
100
|
+
case message_type
|
101
|
+
when :data
|
102
|
+
@sockets[fileno].sendmsg(args[0])
|
103
|
+
when :close, :connection_error
|
104
|
+
@sockets[fileno].close
|
105
|
+
@selector.delete(@sockets[fileno])
|
106
|
+
@sockets.delete(fileno)
|
107
|
+
when :stop, :server_error
|
108
|
+
return :stop
|
109
|
+
else
|
110
|
+
raise "Unknown message type #{message_type} passed from server to client side proxy"
|
111
|
+
end
|
112
|
+
rescue
|
113
|
+
STDERR.puts("Error in on_channel_response: #{$!}\n#{$!.backtrace.join("\n")}")
|
114
|
+
if fileno
|
115
|
+
@server.send_message(:connection_error, fileno, $!) unless message_type == :close || message_type == :connection_error
|
116
|
+
socket = @sockets[fileno]
|
117
|
+
if socket
|
118
|
+
socket.close
|
119
|
+
@selector.delete(socket)
|
120
|
+
end
|
121
|
+
@sockets.delete(fileno)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'lxc/extra/selector'
|
2
|
+
|
3
|
+
module LXC
|
4
|
+
module Extra
|
5
|
+
#
|
6
|
+
# Proxy server that listens for connections froom the corresponsing
|
7
|
+
# ProxyClientSide and sends them to the actual server.
|
8
|
+
#
|
9
|
+
# == Usage
|
10
|
+
#
|
11
|
+
# # Forward 127.0.0.1:80 in the container to google.com from the host
|
12
|
+
# channel = LXC::Extra::Channel.new
|
13
|
+
# pid = container.attach do
|
14
|
+
# server = TCPServer.new('127.0.0.1', 5559)
|
15
|
+
# proxy = LXC::Extra::ProxyClientSide.new(channel, server)
|
16
|
+
# proxy.start
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # Here is the proxy server
|
20
|
+
# proxy = LXC::Extra::ProxyServerSide.new(channel) do
|
21
|
+
# TCPSocket.new('127.0.0.1', 9995)
|
22
|
+
# end
|
23
|
+
# proxy.start
|
24
|
+
#
|
25
|
+
class ProxyServerSide
|
26
|
+
#
|
27
|
+
# Create a new ProxyServerSide.
|
28
|
+
#
|
29
|
+
# == Arguments
|
30
|
+
# channel: Channel to send/receive connections from the other side.
|
31
|
+
# &server_connector:: block that will be called (with no arguments) when the server
|
32
|
+
# opens a new connection, to create a new connection to the
|
33
|
+
# server. Must return a socket.
|
34
|
+
#
|
35
|
+
def initialize(channel, &server_connector)
|
36
|
+
@channel = channel
|
37
|
+
@server_connector = server_connector
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :channel
|
41
|
+
attr_reader :server_connector
|
42
|
+
|
43
|
+
#
|
44
|
+
# Start forwarding connections from the other side to the server. Blocks
|
45
|
+
# until stop is called.
|
46
|
+
#
|
47
|
+
def start
|
48
|
+
@filenos = {}
|
49
|
+
@sockets = {}
|
50
|
+
begin
|
51
|
+
@selector = Selector.new
|
52
|
+
@selector.on_select(channel.read_fd, &method(:on_channel_message))
|
53
|
+
@selector.main_loop
|
54
|
+
ensure
|
55
|
+
@sockets.values.each { |socket| socket.close }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Stop forwarding connections and data. Existing connections will be closed
|
61
|
+
# and a stop message will be sent to the other side.
|
62
|
+
#
|
63
|
+
def stop
|
64
|
+
@selector.stop
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def on_channel_message(read_fd)
|
70
|
+
message_type, fileno, *args = @channel.next(:stop)
|
71
|
+
socket = @sockets[fileno]
|
72
|
+
begin
|
73
|
+
case message_type
|
74
|
+
when :open
|
75
|
+
socket = @server_connector.call
|
76
|
+
@selector.on_select(socket, &method(:on_server_response))
|
77
|
+
@sockets[fileno] = socket
|
78
|
+
@filenos[socket] = fileno
|
79
|
+
when :data
|
80
|
+
socket.sendmsg(args[0])
|
81
|
+
when :close, :connection_error
|
82
|
+
socket.close
|
83
|
+
@selector.delete(socket)
|
84
|
+
@filenos.delete(socket)
|
85
|
+
@sockets.delete(fileno)
|
86
|
+
when :stop, :server_error
|
87
|
+
return :stop
|
88
|
+
else
|
89
|
+
raise "Unknown message type #{message_type} passed from channel to host"
|
90
|
+
end
|
91
|
+
rescue
|
92
|
+
STDERR.puts("Error in on_channel_message: #{$!}\n#{$!.backtrace.join("\n")}")
|
93
|
+
if fileno
|
94
|
+
@channel.send_message(:connection_error, fileno) unless message_type == :close || message_type == :connection_error
|
95
|
+
if socket
|
96
|
+
socket.close
|
97
|
+
@selector.delete(socket)
|
98
|
+
@filenos.delete(socket)
|
99
|
+
end
|
100
|
+
@sockets.delete(fileno)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def on_server_response(socket)
|
106
|
+
message, *args = socket.recvmsg
|
107
|
+
fileno = @filenos[socket]
|
108
|
+
if message.length == 0
|
109
|
+
socket.close
|
110
|
+
if fileno
|
111
|
+
@channel.send_message(:close, fileno)
|
112
|
+
@sockets.delete(fileno)
|
113
|
+
end
|
114
|
+
@filenos.delete(socket)
|
115
|
+
@selector.delete(socket)
|
116
|
+
else
|
117
|
+
@channel.send_message(:data, fileno, message)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module LXC
|
2
|
+
module Extra
|
3
|
+
#
|
4
|
+
# Provides a way to listen to multiple disparate file descriptors with
|
5
|
+
# different callbacks to handle each one.
|
6
|
+
#
|
7
|
+
# == Example
|
8
|
+
#
|
9
|
+
# require 'lxc/extra/selector'
|
10
|
+
# require 'socket'
|
11
|
+
#
|
12
|
+
# def repeat_to(socket, selector, out)
|
13
|
+
# message, *other = socket.recvmsg
|
14
|
+
# if message.size == 0
|
15
|
+
# selector.delete(socket)
|
16
|
+
# return
|
17
|
+
# end
|
18
|
+
# out.write(message)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# selector = LXC::Extra::Selector.new()
|
22
|
+
#
|
23
|
+
# # Listen on 20480 and echo output to STDOUT
|
24
|
+
# server = TCPServer.new(20480)
|
25
|
+
# selector.on_select(server) do |server|
|
26
|
+
# socket = server.accept
|
27
|
+
# selector.on_select(socket, selector, STDOUT, &method(:repeat_to))
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# # Listen on 20481 and echo output to STDERR
|
31
|
+
# server = TCPServer.new(2049)
|
32
|
+
# selector.on_select(server) do |server|
|
33
|
+
# socket = server.accept
|
34
|
+
# selector.on_select(socket, selector, STDERR, &method(:repeat_to))
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# # Do all that listening on one thread!
|
38
|
+
# selector.main_loop
|
39
|
+
#
|
40
|
+
# NOTE: This is not presently thread-safe in that adds and deletes must be
|
41
|
+
# done on the same thread as the main_loop.
|
42
|
+
#
|
43
|
+
class Selector
|
44
|
+
#
|
45
|
+
# Create a new selector.
|
46
|
+
#
|
47
|
+
def initialize
|
48
|
+
@fd_callbacks = {}
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Add a file descriptor to listen on.
|
53
|
+
#
|
54
|
+
# == Arguments
|
55
|
+
#
|
56
|
+
# fd:: file descriptor to listen on.
|
57
|
+
# args:: optional set of arguments to pass to callback
|
58
|
+
# callback:: callback to call when file descriptor has data available
|
59
|
+
#
|
60
|
+
def on_select(fd, *args, &callback)
|
61
|
+
@fd_callbacks[fd] = {
|
62
|
+
:args => args,
|
63
|
+
:callback => callback
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Stop listening to a file descriptor.
|
69
|
+
#
|
70
|
+
# == Arguments
|
71
|
+
#
|
72
|
+
# fd:: file descriptor to stop listening to.
|
73
|
+
#
|
74
|
+
def delete(fd)
|
75
|
+
@fd_callbacks.delete(fd)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Listen for any of the given file handles to be ready (with IO.select)
|
80
|
+
# and call their callbacks when they are.
|
81
|
+
#
|
82
|
+
def main_loop
|
83
|
+
begin
|
84
|
+
while true
|
85
|
+
ready = IO.select(@fd_callbacks.keys)
|
86
|
+
ready[0].each do |fd|
|
87
|
+
callback = @fd_callbacks[fd]
|
88
|
+
if callback
|
89
|
+
args = [ fd ]
|
90
|
+
args += callback[:args] if callback[:args]
|
91
|
+
if callback[:callback].call(*args) == :stop
|
92
|
+
return
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue
|
98
|
+
raise
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/lxc/extra/version.rb
CHANGED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'lxc/extra/channel'
|
3
|
+
|
4
|
+
describe LXC::Extra::Channel do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
c = LXC::Container.new('test')
|
8
|
+
c.create('ubuntu') unless c.defined?
|
9
|
+
c.start unless c.running?
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'an open channel should successfully pass multiple objects' do
|
13
|
+
c = LXC::Container.new('test')
|
14
|
+
channel = LXC::Extra::Channel.new
|
15
|
+
pid = c.attach do
|
16
|
+
begin
|
17
|
+
channel.listen do |host, number|
|
18
|
+
host.send_message(number+1)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
begin
|
23
|
+
results = []
|
24
|
+
channel.send_message(1)
|
25
|
+
channel.listen do |container, number|
|
26
|
+
results << number
|
27
|
+
if number > 5
|
28
|
+
container.stop
|
29
|
+
else
|
30
|
+
container.send_message(number)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
Process.waitpid(pid)
|
34
|
+
rescue
|
35
|
+
Process.kill('KILL', pid)
|
36
|
+
raise
|
37
|
+
end
|
38
|
+
expect(results).to eq([ 2, 3, 4, 5, 6 ])
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'container.open_channel works the same as the explicit version' do
|
42
|
+
c = LXC::Container.new('test')
|
43
|
+
channel, pid = c.open_channel do |host, number|
|
44
|
+
host.send_message(number+1)
|
45
|
+
end
|
46
|
+
begin
|
47
|
+
results = []
|
48
|
+
channel.send_message(1)
|
49
|
+
channel.listen do |container, number|
|
50
|
+
results << number
|
51
|
+
if number > 5
|
52
|
+
container.stop
|
53
|
+
else
|
54
|
+
container.send_message(number)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
Process.waitpid(pid)
|
58
|
+
rescue
|
59
|
+
Process.kill('KILL', pid)
|
60
|
+
raise
|
61
|
+
end
|
62
|
+
expect(results).to eq([ 2, 3, 4, 5, 6 ])
|
63
|
+
end
|
64
|
+
end
|
data/spec/unit/lxc_extra_spec.rb
CHANGED
@@ -29,5 +29,14 @@ describe LXC::Extra do
|
|
29
29
|
ct.execute {raise TestError}
|
30
30
|
end.to raise_error(TestError)
|
31
31
|
end
|
32
|
+
it 'should respect timeout' do
|
33
|
+
expect do
|
34
|
+
ct.execute(timeout: 10) do
|
35
|
+
11.times do |n|
|
36
|
+
sleep 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end.to raise_error Timeout::Error
|
40
|
+
end
|
32
41
|
end
|
33
42
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'lxc/extra/channel'
|
3
|
+
require 'lxc/extra/proxy_client_side'
|
4
|
+
require 'lxc/extra/proxy_server_side'
|
5
|
+
require 'lxc/extra/selector'
|
6
|
+
require 'socket'
|
7
|
+
|
8
|
+
describe 'LXC proxy' do
|
9
|
+
|
10
|
+
before(:all) do
|
11
|
+
c = LXC::Container.new('test')
|
12
|
+
c.create('ubuntu') unless c.defined?
|
13
|
+
c.start unless c.running?
|
14
|
+
end
|
15
|
+
|
16
|
+
def rescue_me(&block)
|
17
|
+
begin
|
18
|
+
block.call
|
19
|
+
rescue
|
20
|
+
STDERR.puts "ERROR in thread: #{$!}"
|
21
|
+
STDERR.puts $!.backtrace.join("\n")
|
22
|
+
raise
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def safe_thread(&block)
|
27
|
+
Thread.new do
|
28
|
+
rescue_me(&block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'proxy server receives multiple connections and passes data back and forth' do
|
33
|
+
container = LXC::Container.new('test')
|
34
|
+
|
35
|
+
threads = []
|
36
|
+
pids = []
|
37
|
+
begin
|
38
|
+
# Forward 127.0.0.1:80 in the container to google.com from the host
|
39
|
+
channel = LXC::Extra::Channel.new
|
40
|
+
pids << container.attach do
|
41
|
+
rescue_me do
|
42
|
+
server = TCPServer.new('127.0.0.1', 5559)
|
43
|
+
proxy = LXC::Extra::ProxyClientSide.new(channel, server)
|
44
|
+
proxy.start
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Here is the proxy server
|
49
|
+
threads << safe_thread do
|
50
|
+
proxy = LXC::Extra::ProxyServerSide.new(channel) do
|
51
|
+
TCPSocket.new('127.0.0.1', 9995)
|
52
|
+
end
|
53
|
+
proxy.start
|
54
|
+
end
|
55
|
+
|
56
|
+
# Here is the real TCP server (listener)
|
57
|
+
real_server = TCPServer.new('127.0.0.1', 9995)
|
58
|
+
threads << safe_thread do
|
59
|
+
selector = LXC::Extra::Selector.new
|
60
|
+
selector.on_select(real_server) do |server|
|
61
|
+
socket = server.accept
|
62
|
+
selector.on_select(socket) do |socket|
|
63
|
+
message, *other = socket.recvmsg
|
64
|
+
if message.size == 0
|
65
|
+
selector.delete(socket)
|
66
|
+
else
|
67
|
+
socket.sendmsg((message.to_i*2).to_s)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
selector.main_loop
|
72
|
+
end
|
73
|
+
|
74
|
+
channel2 = LXC::Extra::Channel.new
|
75
|
+
# Let's open up a client in the container!
|
76
|
+
container.execute do
|
77
|
+
rescue_me do
|
78
|
+
socket = TCPSocket.new('127.0.0.1', 5559)
|
79
|
+
socket.sendmsg('10')
|
80
|
+
response, *other = socket.recvmsg
|
81
|
+
channel2.send_message(response)
|
82
|
+
socket.sendmsg('30')
|
83
|
+
response, *other = socket.recvmsg
|
84
|
+
channel2.send_message(response)
|
85
|
+
socket.close
|
86
|
+
end
|
87
|
+
end
|
88
|
+
container.execute do
|
89
|
+
rescue_me do
|
90
|
+
socket = TCPSocket.new('127.0.0.1', 5559)
|
91
|
+
socket.sendmsg('50')
|
92
|
+
response, *other = socket.recvmsg
|
93
|
+
channel2.send_message(response)
|
94
|
+
socket.sendmsg('70')
|
95
|
+
response, *other = socket.recvmsg
|
96
|
+
channel2.send_message(response)
|
97
|
+
socket.close
|
98
|
+
end
|
99
|
+
end
|
100
|
+
expect(channel2.next).to eq(['20'])
|
101
|
+
expect(channel2.next).to eq(['60'])
|
102
|
+
expect(channel2.next).to eq(['100'])
|
103
|
+
expect(channel2.next).to eq(['140'])
|
104
|
+
ensure
|
105
|
+
threads.each do |t|
|
106
|
+
t.kill
|
107
|
+
end
|
108
|
+
pids.each do |pid|
|
109
|
+
|
110
|
+
Process.kill(9, pid)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lxc-extra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ranjib Dey
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-lxc
|
@@ -79,10 +79,16 @@ files:
|
|
79
79
|
- README.md
|
80
80
|
- Rakefile
|
81
81
|
- lib/lxc/extra.rb
|
82
|
+
- lib/lxc/extra/channel.rb
|
83
|
+
- lib/lxc/extra/proxy_client_side.rb
|
84
|
+
- lib/lxc/extra/proxy_server_side.rb
|
85
|
+
- lib/lxc/extra/selector.rb
|
82
86
|
- lib/lxc/extra/version.rb
|
83
87
|
- lxc-extra.gemspec
|
84
88
|
- spec/spec_helper.rb
|
89
|
+
- spec/unit/lxc_channel_spec.rb
|
85
90
|
- spec/unit/lxc_extra_spec.rb
|
91
|
+
- spec/unit/lxc_proxy_spec.rb
|
86
92
|
homepage: https://github.com/ranjib/lxc-extra
|
87
93
|
licenses:
|
88
94
|
- MIT
|
@@ -109,4 +115,6 @@ specification_version: 4
|
|
109
115
|
summary: Additional helper methods for ruby lxc binding
|
110
116
|
test_files:
|
111
117
|
- spec/spec_helper.rb
|
118
|
+
- spec/unit/lxc_channel_spec.rb
|
112
119
|
- spec/unit/lxc_extra_spec.rb
|
120
|
+
- spec/unit/lxc_proxy_spec.rb
|