serfx 0.0.1 → 0.0.2

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: f3bb17715c2ea3ee4ecb6de764132ba68b69c1c8
4
- data.tar.gz: 53c8a46a3907a5218a2a8fe9d7eb66807ae39bb2
3
+ metadata.gz: 8fc4921d5134cf839d21cf4ad57a23cb55147359
4
+ data.tar.gz: cb1047f5ff43db315934db351ee08aa7b1518820
5
5
  SHA512:
6
- metadata.gz: 5dcf2af95c0d178c401604c93954d10772ae769e068f64d1647721c6bda12714de7c39b2398a777396f1e30635563525f22ad27878349a1dcfd34fe0cc412f1b
7
- data.tar.gz: 61466ce03edfecc3697c8650841695bd58d7c32f36d48f33884d7fe98a0d2326c78d6ab6b0a78f011f32bc86fc4c61cb6501dfe53b34b0d96a94d3848a4ac39f
6
+ metadata.gz: b4146f8211e7c20e55a1f12ca2ada7a94b1a615221ae7e83a4563c8c380ef2c54002293ecb1993df2b5064512a2ba1ba3adf403f25e5594f00525e114aebe4fd
7
+ data.tar.gz: e123d303f6af8433a93f8c65ced93d5c5241c5958edd1046fec63fbda950f4c383683241ade956ad1f3fb1eae69a98866b8e720096f966f45fabc9530e19c5d7
@@ -9,7 +9,7 @@ rvm:
9
9
  branches:
10
10
  only:
11
11
  - master
12
- script: "CODE_COVERAGE=1 bundle exec rake spec"
12
+ script: "bundle exec rake spec"
13
13
  addons:
14
14
  code_climate:
15
15
  repo_token: 894a7a896e5ac4a4e7e497051fbc68152f348989a5b930cc3a037c713cf63df0
data/Gemfile CHANGED
@@ -4,5 +4,4 @@ gemspec
4
4
  group :development do
5
5
  gem 'irbtools'
6
6
  gem 'pry'
7
- gem 'codeclimate-test-reporter', group: :test, require: nil
8
7
  end
@@ -167,5 +167,36 @@ module Serfx
167
167
  def respond(id, payload)
168
168
  request(:respond, 'ID' => id, 'Payload' => payload)
169
169
  end
170
+
171
+ # install a new encryption key onto the cluster's keyring
172
+ #
173
+ # @param key [String] 16 bytes of base64-encoded data.
174
+ # @return [Response]
175
+ def install_key(key)
176
+ request(:install_key, 'Key' => key)
177
+ end
178
+
179
+ # change the primary key, which is used to encrypt messages
180
+ #
181
+ # @param key [String] 16 bytes of base64-encoded data.
182
+ # @return [Response]
183
+ def use_key(key)
184
+ request(:use_key, 'Key' => key)
185
+ end
186
+
187
+ # remove a key from the cluster's keyring
188
+ #
189
+ # @param key [String] 16 bytes of base64-encoded data.
190
+ # @return [Response]
191
+ def remove_key(key)
192
+ request(:remove_key, 'Key' => key)
193
+ end
194
+
195
+ # return a list of all encryption keys currently in use on the cluster
196
+ #
197
+ # @return [Response]
198
+ def list_keys
199
+ request(:list_keys)
200
+ end
170
201
  end
171
202
  end
@@ -27,7 +27,11 @@ module Serfx
27
27
  stop: [:header],
28
28
  leave: [:header],
29
29
  query: [:header],
30
- respond: [:header]
30
+ respond: [:header],
31
+ install_key: [:header, :body],
32
+ use_key: [:header, :body],
33
+ remove_key: [:header, :body],
34
+ list_keys: [:header, :body]
31
35
  }
32
36
 
33
37
  include Serfx::Commands
@@ -0,0 +1,171 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'json'
4
+ module Serfx
5
+ module Utils
6
+
7
+ # serf event handler invocations are blocking calls. i.e. serf
8
+ # will not process any other event when a handler invocation is
9
+ # in progress. due to this limitations long running tasks can not be
10
+ # orchestrated or invoked as serf handler directly.
11
+ # [AsynchJob] address this by spawning the task as a background job,
12
+ # allowing the handler code to return immediately. It does double fork
13
+ # where the first child process is detached (attached to init as parent
14
+ # process) and spawn the second child process with the target,
15
+ # long running task. This allows the parent process to wait and reap the
16
+ # output of target task and save it in disk so that it can be exposed
17
+ # via other serf events
18
+ #
19
+ # @example
20
+ # require 'serfx/utils/async_job'
21
+ # require 'serfx/utils/handler'
22
+ #
23
+ # job = Serfx::Utils::AsyncJob.new(
24
+ # name: "bash_test"
25
+ # command: "bash -c 'for i in `seq 1 3`; do echo $i; sleep 1; done'",
26
+ # state: '/opt/serf/states/long_task'
27
+ # )
28
+ #
29
+ # on :query, 'task_fire' do |event|
30
+ # puts job.run
31
+ # end
32
+ #
33
+ # on :query, 'task_check' do |event|
34
+ # puts job.state_info.inspect
35
+ # end
36
+ #
37
+ # on :query, 'task_kill' do |event|
38
+ # puts job.kill
39
+ # end
40
+ #
41
+ # on :query, 'task_reap' do |event|
42
+ # puts job.reap
43
+ # end
44
+ #
45
+ # run
46
+ class AsyncJob
47
+
48
+ attr_reader :command, :state_file, :stdout_file, :stderr_file
49
+
50
+ # @param opts [Hash] specify the job details
51
+ # @option opts [Symbol] :state file path which will be used to store
52
+ # task state locally
53
+ # @option opts [Symbol] :command actual command which will be invoked
54
+ # in the background
55
+ # @option opts [Symbol] :stdout standard output file for the task
56
+ # @option opts [Symbol] :stderr standard error file for the task
57
+ def initialize(opts = {})
58
+ @state_file = opts[:state] || fail(ArgumentError, 'Specify state file')
59
+ @command = opts[:command]
60
+ @stdout_file = opts[:stdout] || File::NULL
61
+ @stderr_file = opts[:stderr] || File::NULL
62
+ end
63
+
64
+ # kill an already running task
65
+ #
66
+ # @param sig [String] kill signal that will sent to the backgroun process
67
+ # @return [TrueClass,FalseClass] true on success, false on failure
68
+ def kill(sig = 'KILL')
69
+ if running?
70
+ begin
71
+ Process.kill(sig, state_info['pid'].to_i)
72
+ 'success'
73
+ rescue Exception => e
74
+ 'failed'
75
+ end
76
+ else
77
+ 'failed'
78
+ end
79
+ end
80
+
81
+ # obtain current state information about the task
82
+ #
83
+ # @return [Hash]
84
+ def state_info
85
+ if exists?
86
+ JSON.parse(File.read(state_file))
87
+ else
88
+ {'status' => 'absent' }
89
+ end
90
+ end
91
+
92
+ # delete the statefile of a finished task
93
+ #
94
+ # @return [String] 'success' if the task is reaped, 'failed' otherwise
95
+ def reap
96
+ if state_info['status'] == 'finished'
97
+ File.unlink(state_file)
98
+ 'success'
99
+ else
100
+ 'failed'
101
+ end
102
+ end
103
+
104
+
105
+ # start a background daemon and spawn another process to run specified
106
+ # command. writes back state information in the state file
107
+ # after spawning daemon process (state=invoking), after spawning the
108
+ # child process (state=running) and after reaping the child process
109
+ # (sate=finished).
110
+ #
111
+ # @return [String] 'success' if task is started
112
+ def start
113
+ if exists? or command.nil?
114
+ return 'failed'
115
+ end
116
+ pid = fork do
117
+ Process.daemon
118
+ state = {
119
+ ppid: Process.pid,
120
+ status: 'invoking',
121
+ pid: -1,
122
+ time: Time.now.to_i
123
+ }
124
+ write_state(state)
125
+ begin
126
+ child_pid = Process.spawn(command, out: stdout_file, err: stderr_file)
127
+ state[:pid] = child_pid
128
+ state[:status] = 'running'
129
+ write_state(state)
130
+ _ , status = Process.wait2(child_pid)
131
+ state[:exitstatus] = status.exitstatus
132
+ state[:status] = 'finished'
133
+ rescue Errno::ENOENT => e
134
+ state[:error] = e.class.name
135
+ state[:status] = 'failed'
136
+ end
137
+ write_state(state)
138
+ exit 0
139
+ end
140
+ Process.detach(pid)
141
+ 'success'
142
+ end
143
+
144
+ private
145
+
146
+ # check if a task already running
147
+ #
148
+ # @return [TrueClass, FalseClass] true if the task running, else false
149
+ def running?
150
+ if exists?
151
+ File.exist?(File.join('/proc', state_info['pid'].to_s))
152
+ else
153
+ false
154
+ end
155
+ end
156
+
157
+ # check if a task already exist
158
+ #
159
+ # @return [TrueClass, FalseClass] true if the task exists, else false
160
+ def exists?
161
+ File.exists?(state_file)
162
+ end
163
+
164
+ # writes a hash as json in the state_file
165
+ # @param [Hash] state represented as a hash, to be written
166
+ def write_state(state)
167
+ File.open(state_file, 'w'){|f| f.write(JSON.generate(state))}
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,91 @@
1
+ module Serfx
2
+ module Utils
3
+
4
+ # helper module to for serf custom handlers
5
+ #
6
+ # serf agents can be configured to invoke an executable
7
+ # script when an user event is received.
8
+ #
9
+ # [Serfx::Utils::Handler] module provides a set of helper methods to ease
10
+ # writing ruby based serf event handlers
11
+ #
12
+ # For example, following script will respond to any qury event named
13
+ # 'upcase' and return the uppercase version of the original query event's
14
+ # payload
15
+ # @example
16
+ # require 'serfx/utils/handler'
17
+ # include Serfx::Utils::Handler
18
+ # on :query, 'upcase' do |event|
19
+ # unless event.payload.nil?
20
+ # STDOUT.write(event.payload.upcase)
21
+ # end
22
+ # end
23
+ # run
24
+ module Handler
25
+
26
+ # when serf agent invokes a handler it passes the event payload
27
+ # through STDIN. while event metadata such as event type, name etc
28
+ # is passed as a set of environment variables.
29
+ # [SerfEvent] encapsulates such event.
30
+ class SerfEvent
31
+
32
+ attr_reader :environment, :payload, :type, :name
33
+
34
+ # @param env [Hash] environment
35
+ # @param stdin [IO] stadard input stream for the event
36
+ def initialize(env = ENV, stdin = STDIN)
37
+ @environment ={}
38
+ @payload = nil
39
+ @name = nil
40
+ env.keys.select{|k|k=~/^SERF/}.each do | k|
41
+ @environment[k] = env[k].strip
42
+ end
43
+ @type = @environment['SERF_EVENT']
44
+ case @type
45
+ when 'query'
46
+ @name = @environment['SERF_QUERY_NAME']
47
+ begin
48
+ @payload = stdin.read_nonblock(4096).strip
49
+ rescue Errno::EAGAIN => e
50
+ end
51
+ when 'user'
52
+ @name = @environment['SERF_USER_EVENT']
53
+ begin
54
+ @payload = stdin.read_nonblock(4096).strip
55
+ rescue Errno::EAGAIN => e
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ # register a callback against an event
62
+ #
63
+ # @param type [Symbol] event type for which this handler will be invoked
64
+ # @param name [String, Regex] match against name of the event
65
+ def on(type, name = nil, &block)
66
+ callbacks[type] << SerfCallback.new(name, block)
67
+ nil
68
+ end
69
+
70
+ # execute callbacks registerd using `on`
71
+ def run
72
+ event = SerfEvent.new
73
+ callbacks[event.type.downcase.to_sym].each do |cbk|
74
+ if cbk.name
75
+ cbk.block.call(event) if (event.name === cbk.name)
76
+ else
77
+ cbk.block.call(event)
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ SerfCallback = Struct.new(:name, :block)
85
+
86
+ def callbacks
87
+ @_callbacks ||= Hash.new{|h,k| h[k] = []}
88
+ end
89
+ end
90
+ end
91
+ end
@@ -2,5 +2,5 @@
2
2
  #
3
3
  # Provides version as a contsant for the serf gem
4
4
  module Serfx
5
- VERSION = '0.0.1'
5
+ VERSION = '0.0.2'
6
6
  end
@@ -1,3 +1,4 @@
1
1
  {
2
- "rpc_auth" : "awesomesecret"
2
+ "rpc_auth" : "awesomesecret",
3
+ "encrypt_key": "QHOYjmYlxSCBhdfiolhtDQ=="
3
4
  }
@@ -0,0 +1,11 @@
1
+ require 'serfx/utils/handler'
2
+
3
+ include Serfx::Utils::Handler
4
+
5
+ on :query, 'upcase' do |event|
6
+ unless event.payload.nil?
7
+ STDOUT.write(event.payload.upcase)
8
+ end
9
+ end
10
+
11
+ run
@@ -0,0 +1,24 @@
1
+ require 'serfx/utils/async_job'
2
+ require 'serfx/utils/handler'
3
+
4
+ include Serfx::Utils::Handler
5
+
6
+ command = "bash -c 'for i in `seq 1 300`; do echo $i; sleep 1; done'"
7
+ job = Serfx::Utils::AsyncJob.new(command: command, state: '/tmp/long_task')
8
+
9
+ on :query, 'task' do |event|
10
+ case event.payload
11
+ when 'start'
12
+ puts job.start
13
+ when 'check'
14
+ puts job.state_info.inspect
15
+ when 'reap'
16
+ puts job.reap
17
+ when 'kill'
18
+ puts job.kill
19
+ else
20
+ puts "unknown: #{event.payload}"
21
+ end
22
+ end
23
+
24
+ run
@@ -2,7 +2,6 @@
2
2
  #
3
3
  require 'spec_helper'
4
4
  require 'timeout'
5
- require 'thread'
6
5
 
7
6
  describe Serfx do
8
7
 
@@ -129,4 +128,18 @@ describe Serfx do
129
128
  t.kill
130
129
  end
131
130
  end
131
+
132
+ it '#list, install, use and remove keys' do
133
+ keys = @conn.list_keys.body['Keys'].keys
134
+ expect(keys).to include('QHOYjmYlxSCBhdfiolhtDQ==')
135
+ @conn.install_key('Ih6cZqutM33tMdoFo1iNyw==')
136
+ keys = @conn.list_keys.body['Keys'].keys
137
+ expect(keys).to include('Ih6cZqutM33tMdoFo1iNyw==')
138
+ @conn.use_key('Ih6cZqutM33tMdoFo1iNyw==')
139
+ new_keys = @conn.list_keys.body['Keys'].keys
140
+ expect(new_keys.first).to eq('Ih6cZqutM33tMdoFo1iNyw==')
141
+ @conn.remove_key('QHOYjmYlxSCBhdfiolhtDQ==')
142
+ final_keys = @conn.list_keys.body['Keys'].keys
143
+ expect(final_keys.first).to_not include('QHOYjmYlxSCBhdfiolhtDQ==')
144
+ end
132
145
  end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'serfx/utils/handler'
3
+
4
+ describe Serfx::Utils::Handler do
5
+ it 'should invoke all the call backs' do
6
+ class CustomHandler
7
+ extend Serfx::Utils::Handler
8
+ @@state = {}
9
+ def self.state
10
+ @@state
11
+ end
12
+ on :query, 'foo' do |event|
13
+ @@state[:payload] = event.payload
14
+ end
15
+ end
16
+ ENV['SERF_EVENT'] = 'query'
17
+ ENV['SERF_QUERY_NAME'] = 'foo'
18
+ STDIN.should_receive(:read_nonblock).and_return('yeah')
19
+ CustomHandler.run
20
+ expect(CustomHandler.state[:payload]).to eq('yeah')
21
+ end
22
+ end
@@ -6,8 +6,6 @@ require 'rspec'
6
6
  require 'serfx'
7
7
  require 'singleton'
8
8
  require 'tmpdir'
9
- require 'codeclimate-test-reporter'
10
- CodeClimate::TestReporter.start if ENV['CODE_COVERAGE']
11
9
 
12
10
  module Serfx
13
11
  # adds helper method for unit testing
@@ -40,7 +38,7 @@ module Serfx
40
38
 
41
39
  def daemonize(dir, join = false)
42
40
  command = serf_command(join)
43
- pid = spawn(command, out: '/dev/null', chdir: dir)
41
+ pid = spawn(command, out: "#{dir}/stdout", chdir: dir)
44
42
  Process.detach(pid)
45
43
  pid
46
44
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serfx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rnjib Dey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-26 00:00:00.000000000 Z
11
+ date: 2014-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -161,10 +161,15 @@ files:
161
161
  - lib/serfx/log.rb
162
162
  - lib/serfx/response.rb
163
163
  - lib/serfx/utils.rb
164
+ - lib/serfx/utils/async_job.rb
165
+ - lib/serfx/utils/handler.rb
164
166
  - lib/serfx/version.rb
165
167
  - serfx.gemspec
166
168
  - spec/data/config.json
169
+ - spec/data/handler.rb
170
+ - spec/data/long_handler.rb
167
171
  - spec/serfx/client_spec.rb
172
+ - spec/serfx/handler_spec.rb
168
173
  - spec/spec_helper.rb
169
174
  homepage: https://github.com/ranjib/serfx
170
175
  licenses:
@@ -192,6 +197,9 @@ specification_version: 4
192
197
  summary: A barebone ruby client for serf
193
198
  test_files:
194
199
  - spec/data/config.json
200
+ - spec/data/handler.rb
201
+ - spec/data/long_handler.rb
195
202
  - spec/serfx/client_spec.rb
203
+ - spec/serfx/handler_spec.rb
196
204
  - spec/spec_helper.rb
197
205
  has_rdoc: