villein 0.2.1 → 0.3.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.
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