villein 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 38c9a67c0adcd3eafe19bf994a262a9b08e979cf
4
- data.tar.gz: a73173192b4a03fd78130a0368eaf00530b64646
3
+ metadata.gz: 5d9b9e6c34204126822c64c925e9a83ef3120b29
4
+ data.tar.gz: 548c1b7932f71f81db8df8fb23f70a9a3ae10879
5
5
  SHA512:
6
- metadata.gz: b24d9254bf8db038f6434c36fd565e452b199bdd20d37968474e499c563ab5d402dc929935df97337f8571c2aa8c122968ced37ac275c139e26cb889ca7d8d70
7
- data.tar.gz: 50eb72e17349da8b03c0ab18d81e56cd981d20be91728d190e4927c53350cb5d875877b6fd7f3ef9cfd16c2b2279488c6ebcc7b68cdc12032699497855ed4348
6
+ metadata.gz: 84d91183e28e93240f478ea4ecaeebaafdbe027c3c7506fb77f8bab97ce7fc1a1c4f4b4c45f3f8298992ea6310668d525cfc4054dc2fe0bef161316a33dee9da
7
+ data.tar.gz: f3632f50eded1238d24d645228871ff6edd3b99f2f637f0534bd0633d5bc6e147d18321ffc0f8348aaae2f62af6fac596d672697f3508ff4469781fa973937b6
data/.travis.yml CHANGED
@@ -17,7 +17,7 @@ notifications:
17
17
  - travis-ci@sorah.jp
18
18
  before_script:
19
19
  - mkdir -p vendor/serf
20
- - curl -L -o vendor/serf/serf.zip https://dl.bintray.com/mitchellh/serf/0.5.0_linux_amd64.zip
20
+ - curl -L -o vendor/serf/serf.zip https://dl.bintray.com/mitchellh/serf/0.6.0_linux_amd64.zip
21
21
  - unzip -d vendor/serf vendor/serf/serf.zip
22
22
  - export PATH=$PWD/vendor/serf:$PATH
23
23
  script: bundle exec rspec -fd ./spec
data/lib/villein/agent.rb CHANGED
@@ -11,6 +11,7 @@ module Villein
11
11
  class Agent < Client
12
12
  class AlreadyStarted < Exception; end
13
13
  class NotRunning < Exception; end
14
+ class ResponderExists < Exception; end
14
15
 
15
16
  EVENT_HANDLER_SH = File.expand_path(File.join(__dir__, '..', '..', 'misc', 'villein-event-handler'))
16
17
 
@@ -33,7 +34,9 @@ module Villein
33
34
  @custom_event_handlers, @replay = event_handlers, replay
34
35
  @initial_tags, @tags_file = tags, tags_file
35
36
  @log_level, @log = log_level, log
37
+
36
38
  @hooks = {}
39
+ @responders = {}
37
40
 
38
41
  @pid, @exitstatus = nil, nil
39
42
  @pid_lock = Mutex.new
@@ -156,6 +159,18 @@ module Villein
156
159
  cmd.flatten.map(&:to_s)
157
160
  end
158
161
 
162
+ ##
163
+ # Respond to query events.
164
+ # Raises error when override is false and responder for given query name already exists.
165
+ def respond(name, override: false, &block)
166
+ name = name.to_s
167
+ if !override && @responders[name]
168
+ raise ResponderExists, "Responder for #{name} already exists. To force, pass `override: true`"
169
+ end
170
+
171
+ @responders[name] = block
172
+ end
173
+
159
174
  private
160
175
 
161
176
  def start_process
@@ -236,7 +251,7 @@ module Villein
236
251
  break if socks[0].eof?
237
252
  end
238
253
 
239
- handle_event buf
254
+ handle_event buf, sock
240
255
  ensure
241
256
  sock.close unless sock.closed?
242
257
  end
@@ -244,7 +259,7 @@ module Villein
244
259
  end
245
260
  end
246
261
 
247
- def handle_event(json)
262
+ def handle_event(json, sock)
248
263
  event_payload = JSON.parse(json)
249
264
  event = Event.new(event_payload['env'], payload: event_payload['input'])
250
265
 
@@ -252,8 +267,15 @@ module Villein
252
267
 
253
268
  call_hooks event.type.gsub(/-/, '_'), event
254
269
  call_hooks 'event', event
270
+
271
+ if event.type == 'query' && @responders[event.query_name]
272
+ sock.write(@responders[event.query_name].call(event))
273
+ end
255
274
  rescue JSON::ParserError
256
275
  # do nothing
276
+ rescue Exception => e
277
+ $stderr.puts "Exception during handling event: #{event.inspect}"
278
+ $stderr.puts e.backtrace.map { |_| _.prepend("\t") }
257
279
  end
258
280
 
259
281
  def hooks_for(name)
@@ -6,7 +6,6 @@ module Villein
6
6
  # Villein::Client allows you to order existing serf agent.
7
7
  # You will need RPC address and agent name to command.
8
8
  class Client
9
-
10
9
  ##
11
10
  # for serf command failures
12
11
  class SerfError < Exception; end
@@ -19,11 +18,21 @@ module Villein
19
18
  # Error for connection failures
20
19
  class SerfConnectionError < SerfError; end
21
20
 
21
+ ##
22
+ # Error when an called serf command is not found.
23
+ class SerfCommandNotFound < SerfError; end
24
+
25
+ ##
26
+ # Error when an operation is not supported by the current version.
27
+ class InsufficientVersionError < SerfError; end
28
+
22
29
  def initialize(rpc_addr, name: nil, serf: 'serf', silence: true)
23
30
  @rpc_addr = rpc_addr
24
31
  @name = name
25
32
  @serf = serf
26
33
  @silence = true
34
+
35
+ retrieve_name unless @name
27
36
  end
28
37
 
29
38
  def silence?() !!@silence; end
@@ -31,6 +40,15 @@ module Villein
31
40
 
32
41
  attr_reader :name, :rpc_addr, :serf
33
42
 
43
+ ##
44
+ # Returns a result of `serf info`.
45
+ # This may raise InsufficientVersionError when `serf info` is not supported.
46
+ def info
47
+ JSON.parse call_serf('info', '-format', 'json')
48
+ rescue SerfCommandNotFound
49
+ raise InsufficientVersionError, 'serf v0.6.0 or later is required to run `serf info`.'
50
+ end
51
+
34
52
  def event(name, payload, coalesce: true)
35
53
  options = []
36
54
 
@@ -41,6 +59,36 @@ module Villein
41
59
  call_serf 'event', *options, name, payload
42
60
  end
43
61
 
62
+ def query(name, payload, node: nil, tag: nil, timeout: nil, no_ack: false)
63
+ # TODO: version check
64
+ options = ['-format', 'json']
65
+
66
+ if node
67
+ node = [node] unless node.respond_to?(:each)
68
+ node.each do |n|
69
+ options << "-node=#{n}"
70
+ end
71
+ end
72
+
73
+ if tag
74
+ tag = [tag] unless tag.respond_to?(:each)
75
+ tag.each do |t|
76
+ options << "-tag=#{t}"
77
+ end
78
+ end
79
+
80
+ if timeout
81
+ options << "-timeout=#{timeout}"
82
+ end
83
+
84
+ if no_ack
85
+ options << "-no-ack"
86
+ end
87
+
88
+ out = call_serf('query', *options, name, payload)
89
+ JSON.parse(out)
90
+ end
91
+
44
92
  def join(addr, replay: false)
45
93
  options = []
46
94
 
@@ -106,6 +154,10 @@ module Villein
106
154
 
107
155
  private
108
156
 
157
+ def retrieve_name
158
+ @name = self.info["agent"]["name"]
159
+ end
160
+
109
161
  def call_serf(cmd, *args)
110
162
  status, out = IO.popen([@serf, cmd, "-rpc-addr=#{rpc_addr}", *args, err: [:child, :out]], 'r') do |io|
111
163
  _, s = Process.waitpid2(io.pid)
@@ -118,6 +170,8 @@ module Villein
118
170
  raise SerfConnectionError, out.chomp
119
171
  when /exceeds limit of \d+ bytes$/
120
172
  raise LengthExceedsLimitError, out.chomp
173
+ when /^Available commands are:/
174
+ raise SerfCommandNotFound
121
175
  else
122
176
  raise SerfError, out.chomp
123
177
  end
@@ -1,3 +1,3 @@
1
1
  module Villein
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,2 +1,15 @@
1
- #!/bin/bash
2
- exec ruby -rsocket -rjson -e'TCPSocket.new(ARGV[0], ARGV[1].to_i).puts({env: Hash[ENV.select{|k,v|/SERF/ =~ k}], input: $stdin.read}.to_json)' -- $*
1
+ #!/usr/bin/env ruby
2
+ require 'json'
3
+ require 'socket'
4
+
5
+ sock = TCPSocket.new(ARGV[0], ARGV[1].to_i)
6
+
7
+ serf_env = Hash[ENV.select{|k,v|/SERF/ =~ k}]
8
+ input = $stdin.read
9
+
10
+ sock.puts({env: serf_env, input: input}.to_json)
11
+ sock.close_write
12
+
13
+ if ENV["SERF_EVENT"] == "query"
14
+ $stdout.write sock.read
15
+ end
data/spec/agent_spec.rb CHANGED
@@ -76,6 +76,21 @@ describe Villein::Agent do
76
76
  expect(received2.type).to eq 'member-join'
77
77
  end
78
78
 
79
+ it "can respond to queries" do
80
+ agent.respond("hey") do |e|
81
+ expect(e).to be_a(Villein::Event)
82
+ "hello"
83
+ end
84
+
85
+ agent.start!
86
+ Thread.new { agent.wait_for_ready }.join(5)
87
+ response = agent.query("hey", '')
88
+ agent.stop!
89
+
90
+ expect(response["Responses"].values.first).to eq("hello")
91
+
92
+ end
93
+
79
94
  it "can handle unexpected stop" do
80
95
  received = nil
81
96
  agent.on_stop { |status| received = status }
data/spec/client_spec.rb CHANGED
@@ -50,6 +50,71 @@ describe Villein::Client do
50
50
  end
51
51
  end
52
52
 
53
+ describe "#initialize" do
54
+ context "without name" do
55
+ subject(:client) { described_class.new('x.x.x.x:nnnn') }
56
+
57
+ it "retrieves name using #info" do
58
+ # we can't use allow(client) here because it calls #initialize!
59
+ allow_any_instance_of(described_class).to receive(:info).and_return(
60
+ "agent" => {"name" => "the-name"},
61
+ )
62
+
63
+ expect(client.name).to eq('the-name')
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "#info" do
69
+ let(:json) { (<<-EOJ).gsub(/\n|\s+/,'') }
70
+ {
71
+ "agent": {
72
+ "name": "foo"
73
+ },
74
+ "runtime": {
75
+ "arch": "amd64",
76
+ "cpu_count": "8",
77
+ "goroutines": "22",
78
+ "max_procs": "1",
79
+ "os": "darwin",
80
+ "version": "go1.2"
81
+ },
82
+ "serf": {
83
+ "event_queue": "0",
84
+ "event_time": "1",
85
+ "failed": "0",
86
+ "intent_queue": "0",
87
+ "left": "0",
88
+ "member_time": "2",
89
+ "members": "2",
90
+ "query_queue": "0",
91
+ "query_time": "3"
92
+ },
93
+ "tags": {
94
+ "thisis": "tag"
95
+ }
96
+ }
97
+ EOJ
98
+
99
+ subject(:info) { client.info }
100
+
101
+ it "returns `serf info`" do
102
+ expect_serf('info', '-format', 'json', message: json)
103
+
104
+ expect(info).to eq(JSON.parse(json))
105
+ end
106
+
107
+ context "when not available" do
108
+ it "raises error" do
109
+ expect_serf('info', '-format', 'json', message: 'Available commands are:', success: false)
110
+
111
+ expect { info }.to raise_error(Villein::Client::InsufficientVersionError)
112
+ end
113
+ end
114
+
115
+ include_examples "failure cases"
116
+ end
117
+
53
118
  describe "#event" do
54
119
  subject { client.event('test', 'payload') }
55
120
 
@@ -84,7 +149,76 @@ describe Villein::Client do
84
149
  'Error sending event: user event exceeds limit of 256 bytes')
85
150
  end
86
151
  end
152
+ end
153
+
154
+ describe "#query" do
155
+ let(:json) { (<<-EOJ).gsub(/\n|\s+/,'') }
156
+ {"Acks":["foo","bar"], "Responses":{"foo":"response"}}
157
+ EOJ
87
158
 
159
+ subject(:query) { client.query('test', 'payload') }
160
+
161
+ it "sends query event" do
162
+ expect_serf('query', '-format', 'json', 'test', 'payload', message: json)
163
+
164
+ expect(query).to eq(JSON.parse(json))
165
+ end
166
+
167
+ context "with node filter" do
168
+ context "in String" do
169
+ subject(:query) { client.query('test', 'payload', node: 'foo') }
170
+
171
+ it "queries with -node" do
172
+ expect_serf('query', '-format', 'json', '-node=foo', 'test', 'payload', message: json)
173
+ query
174
+ end
175
+ end
176
+
177
+ context "in Array" do
178
+ subject(:query) { client.query('test', 'payload', node: %w(foo bar)) }
179
+
180
+ it "queries with -node" do
181
+ expect_serf('query', '-format', 'json', '-node=foo', '-node=bar', 'test', 'payload', message: json)
182
+ query
183
+ end
184
+ end
185
+ end
186
+
187
+ context "with tag filter" do
188
+ context "in String" do
189
+ subject(:query) { client.query('test', 'payload', tag: 'foo') }
190
+
191
+ it "queries with -tag" do
192
+ expect_serf('query', '-format', 'json', '-tag=foo', 'test', 'payload', message: json)
193
+ query
194
+ end
195
+ end
196
+
197
+ context "in Array" do
198
+ subject(:query) { client.query('test', 'payload', tag: %w(foo bar)) }
199
+
200
+ it "queries with -tag" do
201
+ expect_serf('query', '-format', 'json', '-tag=foo', '-tag=bar', 'test', 'payload', message: json)
202
+ query
203
+ end
204
+ end
205
+ end
206
+
207
+ include_examples "failure cases"
208
+
209
+ context "when length exceeds limit" do
210
+ it "raises error" do
211
+ expect_serf('query', '-format', 'json', 'test', 'payload',
212
+ success: false,
213
+ message: 'Error sending event: query exceeds limit of 1024 bytes')
214
+
215
+ expect {
216
+ subject
217
+ }.to raise_error(
218
+ Villein::Client::LengthExceedsLimitError,
219
+ 'Error sending event: query exceeds limit of 1024 bytes')
220
+ end
221
+ end
88
222
  end
89
223
 
90
224
  describe "#join" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: villein
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shota Fukumori (sora_h)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-30 00:00:00.000000000 Z
11
+ date: 2014-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec