warden-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rake"
6
+ gem "warden-protocol", :path => "../warden-protocol"
7
+
8
+ group :spec do
9
+ gem "rspec"
10
+ end
@@ -0,0 +1,13 @@
1
+ # warden-client
2
+
3
+ > This README describes a **client** component. Please refer to the top
4
+ > level [README][tlr] for an overview of all components.
5
+
6
+ [tlr]: /README.md
7
+
8
+ ## License
9
+
10
+ The project is licensed under the Apache 2.0 license (see the
11
+ [`LICENSE`][license] file in the root directory of the repository).
12
+
13
+ [license]: /LICENSE
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rspec/core/version"
4
+
5
+ desc "Run all examples"
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.rspec_opts = %w[--color --format documentation]
8
+ end
@@ -0,0 +1,115 @@
1
+ require "socket"
2
+ require "warden/protocol"
3
+ require "warden/client/v1"
4
+
5
+ module Warden
6
+
7
+ class Client
8
+
9
+ class Error < StandardError; end
10
+ class ServerError < Error; end
11
+
12
+ attr_reader :path
13
+
14
+ def initialize(path)
15
+ @path = path
16
+ @v1mode = false
17
+ end
18
+
19
+ def connected?
20
+ !@sock.nil?
21
+ end
22
+
23
+ def connect
24
+ raise "already connected" if connected?
25
+ @sock = ::UNIXSocket.new(path)
26
+ end
27
+
28
+ def disconnect
29
+ raise "not connected" unless connected?
30
+ @sock.close rescue nil
31
+ @sock = nil
32
+ end
33
+
34
+ def reconnect
35
+ disconnect if connected?
36
+ connect
37
+ end
38
+
39
+ def io
40
+ rv = yield
41
+ if rv.nil?
42
+ disconnect
43
+ raise ::EOFError
44
+ end
45
+
46
+ rv
47
+ end
48
+
49
+ def read
50
+ length = io { @sock.gets }
51
+ data = io { @sock.read(length.to_i) }
52
+
53
+ # Discard \r\n
54
+ io { @sock.read(2) }
55
+
56
+ response = Warden::Protocol::Message.decode(data).response
57
+
58
+ # Raise error replies
59
+ if response.is_a?(Warden::Protocol::ErrorResponse)
60
+ raise Warden::Client::ServerError.new(response.message)
61
+ end
62
+
63
+ if @v1mode
64
+ response = V1.response_to_v1(response)
65
+ end
66
+
67
+ response
68
+ end
69
+
70
+ def write(request)
71
+ if request.kind_of?(Array)
72
+ @v1mode = true
73
+ request = V1.request_from_v1(request.dup)
74
+ end
75
+
76
+ unless request.kind_of?(Warden::Protocol::BaseRequest)
77
+ raise "Expected #kind_of? Warden::Protocol::BaseRequest"
78
+ end
79
+
80
+ data = request.wrap.encode.to_s
81
+ @sock.write data.length.to_s + "\r\n"
82
+ @sock.write data + "\r\n"
83
+ end
84
+
85
+ def stream(request, &blk)
86
+ unless request.is_a?(Warden::Protocol::StreamRequest)
87
+ msg = "Expected argument to be of type:"
88
+ msg << "'#{Warden::Protocol::StreamRequest}'"
89
+ msg << ", but received: '#{request.class}'."
90
+ raise ArgumentError, msg
91
+ end
92
+
93
+ response = call(request)
94
+ while response.exit_status.nil?
95
+ blk.call(response)
96
+ response = read
97
+ end
98
+
99
+ response
100
+ end
101
+
102
+ def call(request)
103
+ write(request)
104
+ read
105
+ end
106
+
107
+ def method_missing(sym, *args, &blk)
108
+ klass_name = sym.to_s.gsub(/(^|_)([a-z])/) { $2.upcase }
109
+ klass_name += "Request"
110
+ klass = Warden::Protocol.const_get(klass_name)
111
+
112
+ call(klass.new(*args))
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,286 @@
1
+ require "warden/protocol"
2
+
3
+ module Warden
4
+ class Client
5
+ class V1
6
+ def self.request_from_v1(args)
7
+ command = args.shift
8
+
9
+ m = "convert_#{command}_request".downcase
10
+ if respond_to?(m)
11
+ send(m, args)
12
+ else
13
+ raise "Unknown command: #{command.upcase}"
14
+ end
15
+ end
16
+
17
+ def self.response_to_v1(response)
18
+ klass_name = response.class.name.split("::").last
19
+ klass_name = klass_name.gsub(/Response$/, "")
20
+ klass_name = klass_name.gsub(/(.)([A-Z])/) { |m| "#{m[0]}_#{m[1]}" }
21
+ klass_name = klass_name.downcase
22
+
23
+ m = "convert_#{klass_name}_response".downcase
24
+ if respond_to?(m)
25
+ send(m, response)
26
+ else
27
+ raise "Unknown response: #{response.class.name.split("::").last}"
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def self.convert_create_request(args)
34
+ request = Protocol::CreateRequest.new
35
+ config = args.shift || {}
36
+
37
+ config.each do |key, value|
38
+ case key
39
+ when "bind_mounts"
40
+ bind_mounts = value.map do |src_path, dst_path, mode|
41
+ bind_mount = Protocol::CreateRequest::BindMount.new
42
+ bind_mount.src_path = src_path
43
+ bind_mount.dst_path = dst_path
44
+
45
+ if mode.kind_of?(Hash)
46
+ mode = mode["mode"]
47
+ end
48
+
49
+ bind_mount.mode = Protocol::CreateRequest::BindMount::Mode.const_get(mode.to_s.upcase)
50
+ bind_mount
51
+ end
52
+
53
+ request.bind_mounts = bind_mounts
54
+ when "grace_time"
55
+ request.grace_time = Integer(value)
56
+ end
57
+ end
58
+
59
+ request
60
+ end
61
+
62
+ def self.convert_create_response(response)
63
+ response.handle
64
+ end
65
+
66
+ def self.convert_stop_request(args)
67
+ request = Protocol::StopRequest.new
68
+ request.handle = args.shift
69
+ request
70
+ end
71
+
72
+ def self.convert_stop_response(response)
73
+ "ok"
74
+ end
75
+
76
+ def self.convert_destroy_request(args)
77
+ request = Protocol::DestroyRequest.new
78
+ request.handle = args.shift
79
+ request
80
+ end
81
+
82
+ def self.convert_destroy_response(response)
83
+ "ok"
84
+ end
85
+
86
+ def self.convert_info_request(args)
87
+ request = Protocol::InfoRequest.new
88
+ request.handle = args.shift
89
+ request
90
+ end
91
+
92
+ def self.convert_info_response(response)
93
+ stringify_hash response.to_hash
94
+ end
95
+
96
+ def self.convert_spawn_request(args)
97
+ request = Protocol::SpawnRequest.new
98
+ request.handle = args.shift
99
+ request.script = args.shift
100
+ request
101
+ end
102
+
103
+ def self.convert_spawn_response(response)
104
+ response.job_id
105
+ end
106
+
107
+ def self.convert_link_request(args)
108
+ request = Protocol::LinkRequest.new
109
+ request.handle = args.shift
110
+ request.job_id = Integer(args.shift)
111
+ request
112
+ end
113
+
114
+ def self.convert_link_response(response)
115
+ [response.exit_status, response.stdout, response.stderr]
116
+ end
117
+
118
+ def self.convert_stream_request(args)
119
+ request = Protocol::StreamRequest.new
120
+ request.handle = args.shift
121
+ request.job_id = Integer(args.shift)
122
+ request
123
+ end
124
+
125
+ def self.convert_stream_response(response)
126
+ [response.name, response.data, response.exit_status]
127
+ end
128
+
129
+ def self.convert_run_request(args)
130
+ request = Protocol::RunRequest.new
131
+ request.handle = args.shift
132
+ request.script = args.shift
133
+ request
134
+ end
135
+
136
+ def self.convert_run_response(response)
137
+ [response.exit_status, response.stdout, response.stderr]
138
+ end
139
+
140
+ def self.convert_net_request(args)
141
+ request = nil
142
+ handle = args.shift
143
+ direction = args.shift
144
+
145
+ case direction
146
+ when "in"
147
+ request = Protocol::NetInRequest.new
148
+ request.handle = handle
149
+ when "out"
150
+ request = Protocol::NetOutRequest.new
151
+ request.handle = handle
152
+
153
+ network, port = args.shift.split(":", 2)
154
+ request.network = network
155
+ request.port = Integer(port)
156
+ else
157
+ raise "Unknown net direction: #{direction}"
158
+ end
159
+
160
+ request
161
+ end
162
+
163
+ def self.convert_net_in_response(response)
164
+ stringify_hash response.to_hash
165
+ end
166
+
167
+ def self.convert_net_out_response(response)
168
+ "ok"
169
+ end
170
+
171
+ def self.convert_copy_request(args)
172
+ request = nil
173
+ handle = args.shift
174
+ direction = args.shift
175
+ src_path = args.shift
176
+ dst_path = args.shift
177
+ owner = args.shift
178
+
179
+ attributes = {
180
+ :handle => handle,
181
+ :src_path => src_path,
182
+ :dst_path => dst_path
183
+ }
184
+
185
+ case direction
186
+ when "in"
187
+ request = Protocol::CopyInRequest.new(attributes)
188
+ when "out"
189
+ request = Protocol::CopyOutRequest.new(attributes)
190
+ request.owner = owner if owner
191
+ else
192
+ raise "Unknown copy direction: #{direction}"
193
+ end
194
+
195
+ request
196
+ end
197
+
198
+ def self.convert_copy_in_response(response)
199
+ "ok"
200
+ end
201
+
202
+ def self.convert_copy_out_response(response)
203
+ "ok"
204
+ end
205
+
206
+ def self.convert_limit_request(args)
207
+ request = nil
208
+ handle = args.shift
209
+ limit = args.shift
210
+
211
+ attributes = {
212
+ :handle => handle,
213
+ }
214
+
215
+ case limit
216
+ when "mem"
217
+ request = Protocol::LimitMemoryRequest.new(attributes)
218
+ request.limit_in_bytes = Integer(args.shift) unless args.empty?
219
+ when "disk"
220
+ request = Protocol::LimitDiskRequest.new(attributes)
221
+ request.byte = Integer(args.shift) unless args.empty?
222
+ when "bandwidth"
223
+ request = Protocol::LimitBandwidthRequest.new(attributes)
224
+ request.rate = Integer(args.shift) unless args.empty?
225
+ request.burst = Integer(args.shift) unless args.empty?
226
+ else
227
+ raise "Unknown limit: #{limit}"
228
+ end
229
+
230
+ request
231
+ end
232
+
233
+ def self.convert_limit_memory_response(response)
234
+ response.limit_in_bytes
235
+ end
236
+
237
+ def self.convert_limit_disk_response(response)
238
+ response.byte
239
+ end
240
+
241
+ def self.convert_limit_bandwidth_response(response)
242
+ "rate: #{response.rate} burst: #{response.burst}"
243
+ end
244
+
245
+ def self.convert_ping_request(args)
246
+ request = Protocol::PingRequest.new
247
+ request
248
+ end
249
+
250
+ def self.convert_ping_response(response)
251
+ "pong"
252
+ end
253
+
254
+ def self.convert_list_request(args)
255
+ request = Protocol::ListRequest.new
256
+ request
257
+ end
258
+
259
+ def self.convert_list_response(response)
260
+ response.handles
261
+ end
262
+
263
+ def self.convert_echo_request(args)
264
+ request = Protocol::EchoRequest.new
265
+ request.message = args.shift
266
+ request
267
+ end
268
+
269
+ def self.convert_echo_response(response)
270
+ response.message
271
+ end
272
+
273
+ private
274
+
275
+ def self.stringify_hash(hash)
276
+ Hash[hash.map do |key, value|
277
+ if value.kind_of?(Hash)
278
+ value = stringify_hash(value)
279
+ end
280
+
281
+ [key.to_s, value]
282
+ end]
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,5 @@
1
+ module Warden
2
+ class Client
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,447 @@
1
+ require "spec_helper"
2
+ require "warden/client/v1"
3
+
4
+ describe Warden::Client::V1 do
5
+ def to_request(args)
6
+ described_class.request_from_v1(args)
7
+ end
8
+
9
+ def to_response(response)
10
+ described_class.response_to_v1(response)
11
+ end
12
+
13
+ describe "create" do
14
+ describe "request" do
15
+ describe "bind_mount parameter" do
16
+ it "should use src and dst parameters" do
17
+ request = to_request [
18
+ "create",
19
+ { "bind_mounts" => [["/src", "/dst", "ro"]] },
20
+ ]
21
+
22
+ request.bind_mounts.should have(1).bind_mount
23
+
24
+ bind_mount = request.bind_mounts.first
25
+ bind_mount.src_path.should == "/src"
26
+ bind_mount.dst_path.should == "/dst"
27
+ end
28
+
29
+ ["ro", { "mode" => "ro" }].each do |mode|
30
+ it "should convert ro mode when passed as #{mode.inspect}" do
31
+ request = to_request [
32
+ "create",
33
+ { "bind_mounts" => [["/src", "/dst", mode]] },
34
+ ]
35
+
36
+ request.bind_mounts.should have(1).bind_mount
37
+
38
+ bind_mount = request.bind_mounts.first
39
+ bind_mount.src_path.should == "/src"
40
+ bind_mount.dst_path.should == "/dst"
41
+ bind_mount.mode.should == Warden::Protocol::CreateRequest::BindMount::Mode::RO
42
+ end
43
+ end
44
+
45
+ ["rw", { "mode" => "rw" }].each do |mode|
46
+ it "should convert rw mode when passed as #{mode.inspect}" do
47
+ request = to_request [
48
+ "create",
49
+ { "bind_mounts" => [["/src", "/dst", "rw"]] },
50
+ ]
51
+
52
+ request.bind_mounts.first.mode.should ==
53
+ Warden::Protocol::CreateRequest::BindMount::Mode::RW
54
+ end
55
+ end
56
+
57
+ ["rx", { "mode" => "rx" }].each do |mode|
58
+ it "should raise on an invalid mode when passed as #{mode.inspect}" do
59
+ expect do
60
+ to_request [
61
+ "create",
62
+ { "bind_mounts" => [["/src", "/dst", "rx"]] },
63
+ ]
64
+ end.to raise_error
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "grace_time parameter" do
70
+ it "should be converted to an integer" do
71
+ request = to_request [
72
+ "create",
73
+ { "grace_time" => 1.1 },
74
+ ]
75
+
76
+ request.grace_time.should == 1
77
+ end
78
+
79
+ it "should raise on an invalid value" do
80
+ expect do
81
+ to_request [
82
+ "create",
83
+ { "grace_time" => "invalid" },
84
+ ]
85
+ end.to raise_error
86
+ end
87
+ end
88
+ end
89
+
90
+ describe "response" do
91
+ it "should return the handle" do
92
+ response = to_response \
93
+ Warden::Protocol::CreateResponse.new(:handle => "handle")
94
+ response.should == "handle"
95
+ end
96
+ end
97
+ end
98
+
99
+ describe "stop" do
100
+ describe "request" do
101
+ subject { to_request ["stop", "handle"] }
102
+
103
+ its(:class) { should == Warden::Protocol::StopRequest }
104
+ its(:handle) { should == "handle" }
105
+ end
106
+
107
+ describe "response" do
108
+ it "should always be ok" do
109
+ response = to_response \
110
+ Warden::Protocol::StopResponse.new
111
+ response.should == "ok"
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "destroy" do
117
+ describe "request" do
118
+ subject { to_request ["destroy", "handle"] }
119
+
120
+ its(:class) { should == Warden::Protocol::DestroyRequest }
121
+ its(:handle) { should == "handle" }
122
+ end
123
+
124
+ describe "response" do
125
+ it "should always be ok" do
126
+ response = to_response \
127
+ Warden::Protocol::DestroyResponse.new
128
+ response.should == "ok"
129
+ end
130
+ end
131
+ end
132
+
133
+ describe "info" do
134
+ describe "request" do
135
+ subject { to_request ["info", "handle"] }
136
+
137
+ its(:class) { should == Warden::Protocol::InfoRequest }
138
+ its(:handle) { should == "handle" }
139
+ end
140
+
141
+ describe "response" do
142
+ let(:response) do
143
+ to_response \
144
+ Warden::Protocol::InfoResponse.new({
145
+ :state => "state",
146
+ :memory_stat => Warden::Protocol::InfoResponse::MemoryStat.new({
147
+ :cache => 1,
148
+ :rss => 2,
149
+ })
150
+ })
151
+ end
152
+
153
+ it "should return a hash" do
154
+ response.should be_a(Hash)
155
+ end
156
+
157
+ it "should stringify keys" do
158
+ response["state"].should == "state"
159
+ end
160
+
161
+ it "should stringify keys of nested hashes" do
162
+ response["memory_stat"]["cache"].should == 1
163
+ response["memory_stat"]["rss"].should == 2
164
+ end
165
+ end
166
+ end
167
+
168
+ describe "spawn" do
169
+ describe "request" do
170
+ subject { to_request ["spawn", "handle", "echo foo"] }
171
+
172
+ its(:class) { should == Warden::Protocol::SpawnRequest }
173
+ its(:handle) { should == "handle" }
174
+ its(:script) { should == "echo foo" }
175
+ end
176
+
177
+ describe "response" do
178
+ it "should return job_id" do
179
+ response = to_response \
180
+ Warden::Protocol::SpawnResponse.new(:job_id => 3)
181
+ response.should == 3
182
+ end
183
+ end
184
+ end
185
+
186
+ describe "link" do
187
+ describe "request" do
188
+ subject { to_request ["link", "handle", "1"] }
189
+
190
+ its(:class) { should == Warden::Protocol::LinkRequest }
191
+ its(:handle) { should == "handle" }
192
+ its(:job_id) { should == 1 }
193
+ end
194
+
195
+ describe "response" do
196
+ it "should return a 3-element tuple" do
197
+ response = to_response \
198
+ Warden::Protocol::LinkResponse.new(
199
+ :exit_status => 255,
200
+ :stdout => "stdout",
201
+ :stderr => "stderr"
202
+ )
203
+
204
+ response[0].should == 255
205
+ response[1].should == "stdout"
206
+ response[2].should == "stderr"
207
+ end
208
+ end
209
+ end
210
+
211
+ describe "stream" do
212
+ describe "request" do
213
+ subject { to_request ["stream", "handle", "1"] }
214
+
215
+ its(:class) { should == Warden::Protocol::StreamRequest }
216
+ its(:handle) { should == "handle" }
217
+ its(:job_id) { should == 1 }
218
+ end
219
+
220
+ describe "response" do
221
+ it "should return a 3-element tuple" do
222
+ response = to_response \
223
+ Warden::Protocol::StreamResponse.new(
224
+ :name => "stdout",
225
+ :data => "data",
226
+ :exit_status => 25,
227
+ )
228
+
229
+ response[0].should == "stdout"
230
+ response[1].should == "data"
231
+ response[2].should == 25
232
+ end
233
+ end
234
+ end
235
+
236
+ describe "run" do
237
+ describe "request" do
238
+ subject { to_request ["run", "handle", "echo foo"] }
239
+
240
+ its(:class) { should == Warden::Protocol::RunRequest }
241
+ its(:handle) { should == "handle" }
242
+ its(:script) { should == "echo foo" }
243
+ end
244
+
245
+ describe "response" do
246
+ it "should return a 3-element tuple" do
247
+ response = to_response \
248
+ Warden::Protocol::RunResponse.new(
249
+ :exit_status => 255,
250
+ :stdout => "stdout",
251
+ :stderr => "stderr"
252
+ )
253
+
254
+ response[0].should == 255
255
+ response[1].should == "stdout"
256
+ response[2].should == "stderr"
257
+ end
258
+ end
259
+ end
260
+
261
+ describe "net" do
262
+ describe "request (in)" do
263
+ subject { to_request ["net", "handle", "in"] }
264
+
265
+ its(:class) { should == Warden::Protocol::NetInRequest }
266
+ its(:handle) { should == "handle" }
267
+ end
268
+
269
+ describe "request (out)" do
270
+ subject { to_request ["net", "handle", "out", "network:1234"] }
271
+
272
+ its(:class) { should == Warden::Protocol::NetOutRequest }
273
+ its(:handle) { should == "handle" }
274
+ its(:network) { should == "network" }
275
+ its(:port) { should == 1234 }
276
+ end
277
+
278
+ describe "response (in)" do
279
+ it "should return a hash with both properties" do
280
+ response = to_response \
281
+ Warden::Protocol::NetInResponse.new(
282
+ :host_port => 1234,
283
+ :container_port => 2345,
284
+ )
285
+
286
+ response["host_port"].should == 1234
287
+ response["container_port"].should == 2345
288
+ end
289
+ end
290
+
291
+ describe "response (out)" do
292
+ it "should always be ok" do
293
+ response = to_response \
294
+ Warden::Protocol::NetOutResponse.new
295
+ response.should == "ok"
296
+ end
297
+ end
298
+ end
299
+
300
+ describe "copy" do
301
+ describe "request (in)" do
302
+ subject { to_request ["copy", "handle", "in", "/src", "/dst"] }
303
+
304
+ its(:class) { should == Warden::Protocol::CopyInRequest }
305
+ its(:handle) { should == "handle" }
306
+ its(:src_path) { should == "/src" }
307
+ its(:dst_path) { should == "/dst" }
308
+ end
309
+
310
+ describe "request (out)" do
311
+ subject { to_request ["copy", "handle", "out", "/src", "/dst", "owner"] }
312
+
313
+ its(:class) { should == Warden::Protocol::CopyOutRequest }
314
+ its(:handle) { should == "handle" }
315
+ its(:src_path) { should == "/src" }
316
+ its(:dst_path) { should == "/dst" }
317
+ its(:owner) { should == "owner" }
318
+ end
319
+
320
+ describe "response (in)" do
321
+ it "should always be ok" do
322
+ response = to_response \
323
+ Warden::Protocol::CopyInResponse.new
324
+ response.should == "ok"
325
+ end
326
+ end
327
+
328
+ describe "response (out)" do
329
+ it "should always be ok" do
330
+ response = to_response \
331
+ Warden::Protocol::CopyOutResponse.new
332
+ response.should == "ok"
333
+ end
334
+ end
335
+ end
336
+
337
+ describe "limit" do
338
+ describe "request (mem)" do
339
+ describe "without limit" do
340
+ subject { to_request ["limit", "handle", "mem"] }
341
+
342
+ its(:class) { should == Warden::Protocol::LimitMemoryRequest }
343
+ its(:handle) { should == "handle" }
344
+ its(:limit_in_bytes) { should be_nil }
345
+ end
346
+
347
+ describe "with limit" do
348
+ subject { to_request ["limit", "handle", "mem", "1234"] }
349
+
350
+ its(:class) { should == Warden::Protocol::LimitMemoryRequest }
351
+ its(:handle) { should == "handle" }
352
+ its(:limit_in_bytes) { should == 1234 }
353
+ end
354
+ end
355
+
356
+ describe "response (mem)" do
357
+ it "should return #limit_in_bytes" do
358
+ response = to_response \
359
+ Warden::Protocol::LimitMemoryResponse.new({
360
+ :limit_in_bytes => 1234
361
+ })
362
+ response.should == 1234
363
+ end
364
+ end
365
+
366
+ describe "request (disk)" do
367
+ describe "without limit" do
368
+ subject { to_request ["limit", "handle", "disk"] }
369
+
370
+ its(:class) { should == Warden::Protocol::LimitDiskRequest }
371
+ its(:handle) { should == "handle" }
372
+ its(:byte) { should be_nil }
373
+ end
374
+
375
+ describe "with limit" do
376
+ subject { to_request ["limit", "handle", "disk", "1234"] }
377
+
378
+ its(:class) { should == Warden::Protocol::LimitDiskRequest }
379
+ its(:handle) { should == "handle" }
380
+ its(:byte) { should == 1234 }
381
+ end
382
+ end
383
+
384
+ describe "response (disk)" do
385
+ it "should return #byte" do
386
+ response = to_response \
387
+ Warden::Protocol::LimitDiskResponse.new({
388
+ :byte => 1234
389
+ })
390
+ response.should == 1234
391
+ end
392
+ end
393
+ end
394
+
395
+ describe "ping" do
396
+ describe "request" do
397
+ subject { to_request ["ping"] }
398
+
399
+ its(:class) { should == Warden::Protocol::PingRequest }
400
+ end
401
+
402
+ describe "response" do
403
+ it "should return pong" do
404
+ response = to_response \
405
+ Warden::Protocol::PingResponse.new
406
+ response.should == "pong"
407
+ end
408
+ end
409
+ end
410
+
411
+ describe "list" do
412
+ describe "request" do
413
+ subject { to_request ["list"] }
414
+
415
+ its(:class) { should == Warden::Protocol::ListRequest }
416
+ end
417
+
418
+ describe "response" do
419
+ it "should return an array with handles" do
420
+ response = to_response \
421
+ Warden::Protocol::ListResponse.new({
422
+ :handles => ["h1", "h2"]
423
+ })
424
+ response.should == ["h1", "h2"]
425
+ end
426
+ end
427
+ end
428
+
429
+ describe "echo" do
430
+ describe "request" do
431
+ subject { to_request ["echo", "hello world"] }
432
+
433
+ its(:class) { should == Warden::Protocol::EchoRequest }
434
+ its(:message) { should == "hello world" }
435
+ end
436
+
437
+ describe "response" do
438
+ it "should return #message" do
439
+ response = to_response \
440
+ Warden::Protocol::EchoResponse.new({
441
+ :message => "hello world"
442
+ })
443
+ response.should == "hello world"
444
+ end
445
+ end
446
+ end
447
+ end
@@ -0,0 +1,224 @@
1
+ require "spec_helper"
2
+ require "support/mock_warden_server"
3
+
4
+ describe Warden::Client do
5
+
6
+ include_context :mock_warden_server
7
+
8
+ let(:client) do
9
+ new_client
10
+ end
11
+
12
+ it "shouldn't be able to connect without a server" do
13
+ expect do
14
+ client.connect
15
+ end.to raise_error
16
+
17
+ client.should_not be_connected
18
+ end
19
+
20
+ it "should be able to connect with a server" do
21
+ start_server
22
+
23
+ expect do
24
+ client.connect
25
+ end.to_not raise_error
26
+
27
+ client.should be_connected
28
+ end
29
+
30
+ context "connection management" do
31
+
32
+ # This is super-racy: the ivar is updated from the server thread
33
+ def connection_count
34
+ sleep 0.001
35
+ @sessions.size
36
+ end
37
+
38
+ before(:each) do
39
+ @sessions = {}
40
+ start_server do |session, _|
41
+ @sessions[session] = 1
42
+ end
43
+ end
44
+
45
+ context "when connected" do
46
+
47
+ before(:each) do
48
+ client.connect
49
+ client.should be_connected
50
+ connection_count.should == 1
51
+ end
52
+
53
+ it "should not allow connecting" do
54
+ expect do
55
+ client.connect
56
+ end.to raise_error
57
+
58
+ # This should not affect the connection
59
+ client.should be_connected
60
+
61
+ # This should not reconnect
62
+ connection_count.should == 1
63
+ end
64
+
65
+ it "should allow disconnecting" do
66
+ client.disconnect
67
+ client.should_not be_connected
68
+
69
+ # This should not reconnect
70
+ connection_count.should == 1
71
+ end
72
+
73
+ it "should allow reconnecting" do
74
+ client.reconnect
75
+ client.should be_connected
76
+
77
+ # This should have reconnected
78
+ connection_count.should == 2
79
+ end
80
+ end
81
+
82
+ context "when disconnected" do
83
+
84
+ before(:each) do
85
+ connection_count.should == 0
86
+ end
87
+
88
+ it "should not allow disconnecting" do
89
+ expect do
90
+ client.disconnect
91
+ end.to raise_error
92
+
93
+ # This should not affect the connection
94
+ client.should_not be_connected
95
+
96
+ # This should not reconnect
97
+ connection_count.should == 0
98
+ end
99
+
100
+ it "should allow connecting" do
101
+ client.connect
102
+ client.should be_connected
103
+
104
+ # This should have connected
105
+ connection_count.should == 1
106
+ end
107
+
108
+ # While it is semantically impossible to reconnect when the client was
109
+ # never connected to begin with, it IS possible.
110
+ it "should allow reconnecting" do
111
+ client.reconnect
112
+ client.should be_connected
113
+
114
+ # This should have connected
115
+ connection_count.should == 1
116
+ end
117
+ end
118
+ end
119
+
120
+ context "when connected" do
121
+
122
+ before(:each) do
123
+ container = nil
124
+ job_id = nil
125
+
126
+ start_server do |session, request|
127
+ next if request.nil?
128
+
129
+ if request.class == Warden::Protocol::EchoRequest
130
+ case request.message
131
+ when "eof"
132
+ session.close
133
+ when "error"
134
+ args = { :message => "error" }
135
+ session.respond(Warden::Protocol::ErrorResponse.new(args))
136
+ else
137
+ args = { :message => request.message }
138
+ session.respond(request.create_response(args))
139
+ end
140
+ elsif request.class == Warden::Protocol::CreateRequest
141
+ raise 'Cannot create more than one container' unless container.nil?
142
+
143
+ container = "test"
144
+ args = { :handle => container }
145
+ session.respond(Warden::Protocol::CreateResponse.new(args))
146
+ elsif request.class == Warden::Protocol::SpawnRequest
147
+ raise 'Unknown handle' unless request.handle == container
148
+ raise 'Cannot spawn more than one job' unless job_id.nil?
149
+
150
+ job_id = 1
151
+ args = { :job_id => job_id }
152
+ session.respond(Warden::Protocol::SpawnResponse.new(args))
153
+ elsif request.class == Warden::Protocol::StreamRequest
154
+ raise 'Unknown handle' unless request.handle == container
155
+ raise 'Unknown job' unless request.job_id == job_id
156
+
157
+ args = { :name => "stream", :data => "test" }
158
+ session.respond(Warden::Protocol::StreamResponse.new(args))
159
+ args = { :exit_status => 0 }
160
+ session.respond(Warden::Protocol::StreamResponse.new(args))
161
+ else
162
+ raise "Unknown request type: #{request.class}."
163
+ end
164
+ end
165
+
166
+ client.connect
167
+ client.should be_connected
168
+ end
169
+
170
+ it "should raise EOFError on eof" do
171
+ expect do
172
+ client.echo(:message => "eof")
173
+ end.to raise_error(::EOFError)
174
+
175
+ # This should update the connection status
176
+ client.should_not be_connected
177
+ end
178
+
179
+ it "should raise Warden::Client::ServerError on error payloads" do
180
+ expect do
181
+ client.echo(:message => "error")
182
+ end.to raise_error(Warden::Client::ServerError)
183
+
184
+ # This should not affect the connection
185
+ client.should be_connected
186
+ end
187
+
188
+ it "should return decoded payload for non-error replies" do
189
+ response = client.echo(:message => "hello")
190
+ response.message.should == "hello"
191
+ end
192
+
193
+ it "should work when called with the old API" do
194
+ response = client.call(["echo", "hello"])
195
+ response.should == "hello"
196
+ end
197
+
198
+ it "should stream data" do
199
+ handle = client.create.handle
200
+ response = client.spawn(:handle => handle, :script => "echo test")
201
+
202
+ called = false
203
+ block = lambda do |response|
204
+ raise "Block should not be called more than once." if called
205
+
206
+ response.should be_an_instance_of Warden::Protocol::StreamResponse
207
+ response.data.should == "test"
208
+ response.name.should == "stream"
209
+ response.exit_status.should be_nil
210
+
211
+ called = true
212
+ end
213
+
214
+ request = Warden::Protocol::StreamRequest.new(:handle => handle,
215
+ :job_id => response.job_id)
216
+ response = client.stream(request, &block)
217
+ response.data.should be_nil
218
+ response.name.should be_nil
219
+ response.exit_status.should == 0
220
+
221
+ called.should be_true
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,6 @@
1
+ require "rspec"
2
+ require "warden/client"
3
+
4
+ RSpec.configure do |config|
5
+ # not much to do here...
6
+ end
@@ -0,0 +1,78 @@
1
+ require "socket"
2
+ require "tempfile"
3
+ require "warden/protocol"
4
+
5
+ class Session
6
+
7
+ def initialize(sock, handler = nil)
8
+ @sock = sock
9
+ @handler = handler
10
+
11
+ # Post-initialization
12
+ handle(nil)
13
+ end
14
+
15
+ def handle(request)
16
+ @handler.call(self, request) if @handler
17
+ end
18
+
19
+ def close
20
+ @sock.close
21
+ ensure
22
+ @sock = nil
23
+ end
24
+
25
+ def respond(*responses)
26
+ responses.each do |response|
27
+ data = response.wrap.encode.to_s
28
+ @sock.write data.length.to_s + "\r\n"
29
+ @sock.write data + "\r\n"
30
+ end
31
+ end
32
+
33
+ def run!
34
+ while @sock && length = @sock.gets
35
+ data = @sock.read(length.to_i)
36
+
37
+ # Discard \r\n
38
+ @sock.read(2)
39
+
40
+ handle(Warden::Protocol::Message.decode(data).request)
41
+ end
42
+ end
43
+ end
44
+
45
+ shared_context :mock_warden_server do
46
+
47
+ SERVER_PATH = File.expand_path("../../../tmp/mock_server.sock", __FILE__)
48
+
49
+ def new_client
50
+ Warden::Client.new(SERVER_PATH)
51
+ end
52
+
53
+ def start_server(&blk)
54
+ # Make sure the path to the unix socket is not used
55
+ FileUtils.rm_rf(SERVER_PATH)
56
+
57
+ # Create unix socket server
58
+ server = UNIXServer.new(SERVER_PATH)
59
+
60
+ # Accept new connections from a thread
61
+ @server = Thread.new do
62
+ begin
63
+ loop do
64
+ session = Session.new(server.accept, blk)
65
+ session.run!
66
+ end
67
+ rescue => ex
68
+ STDERR.puts ex.message
69
+ STDERR.puts ex.backtrace
70
+ raise
71
+ end
72
+ end
73
+ end
74
+
75
+ after(:each) do
76
+ @server.kill if @server
77
+ end
78
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "warden/client/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "warden-client"
7
+ s.version = Warden::Client::VERSION
8
+ s.authors = ["Pieter Noordhuis", "Matt Page"]
9
+ s.email = ["pcnoordhuis@gmail.com", "mpage@vmware.com"]
10
+ s.homepage = "http://www.cloudfoundry.org/"
11
+ s.summary = %q{Client driver for warden, the ephemeral container manager.}
12
+ s.description = %q{Provides a blocking client for interacting with the Warden.}
13
+
14
+ s.files = Dir.glob("**/*")
15
+ s.test_files = Dir.glob("spec/**/*")
16
+ s.executables = []
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_runtime_dependency "warden-protocol", "~> 0.1.0"
20
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: warden-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pieter Noordhuis
9
+ - Matt Page
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-06-12 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: warden-protocol
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 0.1.0
31
+ description: Provides a blocking client for interacting with the Warden.
32
+ email:
33
+ - pcnoordhuis@gmail.com
34
+ - mpage@vmware.com
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - Gemfile
40
+ - lib/warden/client/v1.rb
41
+ - lib/warden/client/version.rb
42
+ - lib/warden/client.rb
43
+ - Rakefile
44
+ - README.md
45
+ - spec/client/v1_spec.rb
46
+ - spec/client_spec.rb
47
+ - spec/spec_helper.rb
48
+ - spec/support/mock_warden_server.rb
49
+ - warden-client.gemspec
50
+ homepage: http://www.cloudfoundry.org/
51
+ licenses: []
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 1.8.25
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Client driver for warden, the ephemeral container manager.
74
+ test_files:
75
+ - spec/client/v1_spec.rb
76
+ - spec/client_spec.rb
77
+ - spec/spec_helper.rb
78
+ - spec/support/mock_warden_server.rb