warden-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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