serfx 0.0.1 → 0.0.2

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: 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: