lxc-extra 0.0.2 → 0.0.3
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 +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
|