serf-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7d853eb106bf38c4f7443ff2fae266eac4e0838c
4
+ data.tar.gz: adffd09da3476e7629d0ccc8db58c49e92f31530
5
+ SHA512:
6
+ metadata.gz: 42529149cc0f3b1c37466e9ee705e79e2c99948810202c8700ea38c29f3790bc9f1dcedf86bdb568fc8e8ca47dd02641ebc83b3286a4fd17be1c5663b2f69309
7
+ data.tar.gz: bba48ab34383b6f30fde60030450db64d112b9536f163b3b941aa838d3d635748a899d6e168947f580c3629f3016b681103aa18db86b3e4399e2593a7310483f
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'pry'
5
+ end
6
+
7
+ # Specify your gem's dependencies in serf-client.gemspec
8
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jacob Evans
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
1
+ # Serf::Client
2
+
3
+ [Serf](http://serfdom.io) Client RPC for Ruby.
4
+ This is raw, new and guaranteed to be full of bugs.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'serf-client'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install serf-client
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+
24
+ require 'serf/client'
25
+ client = Serf::Client.connect address: '127.0.0.1', port: 7373
26
+
27
+ # Listen for stream events
28
+ client.stream 'user:deploy' do |resp|
29
+ puts "USER:: #{resp.body}"
30
+ end
31
+
32
+ # Trigger a new user-event in Serf asynchronously
33
+ client.event 'deploy'
34
+
35
+ # Block till your async is performed
36
+ client.event('deploy').value
37
+
38
+ # This monitors absolutely everything
39
+ client.monitor do |resp|
40
+ puts "===> #{resp}"
41
+ end
42
+
43
+ # Listen to anything, respond with a message to all queries
44
+ client.stream '*' do |resp|
45
+ # Not everything returned by '*' has a body
46
+ if body = resp.body
47
+ puts "*** #{body}"
48
+ v = body['ID']
49
+ client.respond v, 'Response from serf-client' if v
50
+ end
51
+ end
52
+
53
+ ```
54
+
55
+
56
+ Implemented:
57
+
58
+ handshake
59
+ event
60
+ force-leave
61
+ members
62
+ stream
63
+ monitor
64
+ stop
65
+ leave
66
+
67
+ Not yet implemented:
68
+
69
+ auth
70
+ query
71
+ respond
72
+ join
73
+
74
+ ## Contributing
75
+
76
+ 1. Fork it ( http://github.com/dekz/serf-client/fork )
77
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
79
+ 4. Push to the branch (`git push origin my-new-feature`)
80
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,82 @@
1
+ require 'celluloid'
2
+ require 'celluloid/io'
3
+ require 'celluloid/autostart' # Autostart the default notifier
4
+ require "serf/client/version"
5
+ require "serf/client/logger"
6
+ require "serf/client/connection"
7
+ require "serf/client/callbacks"
8
+ require "serf/client/io"
9
+
10
+ module Serf
11
+ class ClientError < StandardError; end
12
+ module Client
13
+
14
+ def self.connect opts
15
+ c = ::Serf::Client::Client.new(opts)
16
+ yield c if block_given?
17
+ c
18
+ end
19
+
20
+ class Client
21
+
22
+ def initialize opts, &block
23
+ @address = opts[:address] || 'localhost'
24
+ @port = opts[:port] || 7373
25
+
26
+ @connection = Connection.supervise(@address, @port).actors.first
27
+ @connection.handshake
28
+ end
29
+
30
+ def query opts, &block
31
+ o = {}
32
+ o['Name'] = opts[:name] if opts[:name]
33
+ o['Payload'] = opts[:payload] if opts[:payload]
34
+ o['Timeout'] = opts[:timeout] if opts[:timeout]
35
+ o['FilterNodes'] = opts[:filter_nodes] if opts[:filter_nodes]
36
+ o['FilterTags'] = opts[:filter_tags] if opts[:filter_tags]
37
+ o['RequestAck'] = opts[:request_ack] if opts[:request_ack]
38
+
39
+ @connection.call(:query, o, &block)
40
+ end
41
+
42
+ def auth key, &block
43
+ @connection.call(:auth, {'AuthKey' => key }, &block)
44
+ end
45
+
46
+ def event name, payload='', coalesce=true, &block
47
+ @connection.call(:event, {'Name' => name, 'Payload' => payload, 'Coalesce' => coalesce}, &block)
48
+ end
49
+
50
+ def stream type, &block
51
+ @connection.call(:stream, {'Type' => type}, &block)
52
+ end
53
+
54
+ def respond id, payload, &block
55
+ @connection.call(:respond, {'ID' => id, "Payload" => payload}, &block)
56
+ end
57
+
58
+ def stop seqid, &block
59
+ @connection.call(:stop, &block)
60
+ end
61
+
62
+ def monitor level='DEBUG', &block
63
+ @connection.call(:monitor, {'LogLevel' => level}, &block)
64
+ end
65
+
66
+ def members &block
67
+ @connection.call(:members, &block)
68
+ end
69
+
70
+ def force_leave name, &block
71
+ @connection.call(:'force-leave', {'Node' => name}, &block)
72
+ end
73
+
74
+ def leave &block
75
+ @connection.call(:leave, &block)
76
+ end
77
+
78
+ def join; end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,39 @@
1
+ module Serf
2
+ module Client
3
+ class Callbacks
4
+ include Celluloid
5
+ include Celluloid::Logger
6
+
7
+ execute_block_on_receiver :perform_callback
8
+
9
+ def initialize
10
+ @callbacks = Hash.new { |h,k| h[k] = [] }
11
+ async.process
12
+ end
13
+
14
+ def add id, cb
15
+ debug "callbacks#add with id #{id}"
16
+ @callbacks[id] << cb
17
+ end
18
+
19
+ def process
20
+ loop do
21
+ debug 'callbacks#process!'
22
+ resp = receive
23
+ id = resp.header["Seq"]
24
+
25
+ cbs = @callbacks[id]
26
+ cbs.each { |c| async.perform_callback(resp, c) }
27
+ debug 'callbacks#process! done'
28
+ end
29
+ end
30
+
31
+ def perform_callback resp, cb
32
+ debug 'callbacks#perform_callback'
33
+ r = cb.call resp
34
+ r
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,154 @@
1
+ module Serf
2
+ module Client
3
+ class Response < Struct.new(:header, :body); end
4
+
5
+ module Commands
6
+ COMMANDS = {
7
+ handshake: [ :header ],
8
+ members: [ :header, :body ],
9
+ event: [ :header ],
10
+ stop: [ :header ],
11
+ leave: [ :header ],
12
+ respond: [ :header ],
13
+ monitor: [ :header ],
14
+ stop: [ :header ],
15
+ stream: [ :header ],
16
+ query: [ :header ],
17
+ auth: [ :header ],
18
+ }
19
+
20
+ def command meth
21
+ COMMANDS[meth.to_sym]
22
+ end
23
+ end
24
+
25
+ class Connection
26
+ include Celluloid
27
+ include Celluloid::IO
28
+ include Celluloid::Logger
29
+ include Commands
30
+ include Logger
31
+
32
+ # This is needed to pass it on
33
+ execute_block_on_receiver :call
34
+
35
+ Celluloid.logger = ::Serf::Client::Logger::log
36
+ Celluloid.logger.level = ::Logger::INFO
37
+ #finalizer :shutdown
38
+
39
+ def initialize address, port
40
+ info "connecting to socket #{address} on #{port}"
41
+ connect address, port
42
+ @io = IO.supervise(@socket, Actor.current).actors.first # avoid self
43
+ @callbacks = Callbacks.supervise.actors.first
44
+ @seqid = 0
45
+ @messages = {}
46
+ @requests = {}
47
+ async.receive_response
48
+ end
49
+
50
+ def handshake
51
+ debug 'handshake'
52
+ send_request(:handshake, Version: 1)
53
+ end
54
+
55
+ def receive_response
56
+ loop do
57
+ # header
58
+ header = receive
59
+ debug "received: #{header}"
60
+
61
+ error header unless header['Seq']
62
+ if header["Error"].empty?
63
+ # Keep the :receive contained here
64
+ process_response(header) { r = receive; debug "received more: #{r}"; r }
65
+ else
66
+ error header["Error"]
67
+ end
68
+ end
69
+ end
70
+
71
+ # Process the response, yielding retrieves next message
72
+ def process_response header, &block
73
+ msgid = header["Seq"]
74
+
75
+ h = @requests[msgid]
76
+ raise "No request for #{header}" if not h
77
+
78
+ cmd = h[:header]['Command']
79
+ parts = command cmd
80
+ debug "Processing #{cmd}"
81
+
82
+ raise "No such command #{h}" unless parts
83
+
84
+ # This is most likely the ACK
85
+ if not h[:ack?]
86
+ if parts.include? :body
87
+ # ACK comes with a response body
88
+ body = yield
89
+ # Could probably clean up old things like events here, anything not a stream
90
+ end
91
+ h[:ack?] = true
92
+ else
93
+ # Alread ACKed -> should be a stream!
94
+ raise "Cannot handle #{h}" unless ['monitor', 'stream', 'query'].include? cmd
95
+ body = yield
96
+ end
97
+
98
+ resp = Response.new(header, body)
99
+ received_response msgid, resp
100
+ resp
101
+ end
102
+
103
+ def received_response msgid, resp
104
+ debug 'connection#received_response'
105
+ # Tell the call back actor about our new response
106
+ @callbacks.mailbox << resp
107
+ # Let the future we created know about the response
108
+ @messages[msgid] = resp
109
+ end
110
+
111
+ def call(method, param=nil, &block)
112
+ msgid = send_request(method, param)
113
+ @callbacks.add msgid, block if block_given?
114
+
115
+ future.wait_for_response msgid
116
+ #::Celluloid::Future.new do
117
+ # until msg = @messages[msgid]; end
118
+ # msg
119
+ #end
120
+ end
121
+
122
+ def wait_for_response msgid
123
+ until msg = @messages[msgid]; sleep 0.1; end
124
+ msg
125
+ end
126
+
127
+ def send_request method, param
128
+ debug 'send_request'
129
+
130
+ msgid = seqid
131
+ header = { "Command" => method.to_s, "Seq" => msgid }
132
+
133
+ # Keep a reference for our response processing
134
+ @requests[msgid] = { header: header, ack?: false }
135
+ # Send to the writer
136
+ @io.mailbox << [header, param]
137
+
138
+ msgid
139
+ end
140
+
141
+ def seqid
142
+ v = @seqid
143
+ @seqid += 1
144
+ v
145
+ end
146
+
147
+ private
148
+ def connect address, port
149
+ @socket = Celluloid::IO::TCPSocket.new(address, port)
150
+ end
151
+
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,44 @@
1
+ require 'msgpack'
2
+
3
+ module Serf
4
+ module Client
5
+ class IO
6
+ include Celluloid
7
+ include Celluloid::IO
8
+ include Celluloid::Logger
9
+
10
+ def initialize socket, handler
11
+ @socket = socket
12
+ @handler = handler
13
+ @up = MessagePack::Unpacker.new(@socket)
14
+ async.write
15
+ async.read
16
+ end
17
+
18
+ def read
19
+ loop do
20
+ debug 'IO#read'
21
+ msg = @up.read
22
+ @handler.mailbox << msg
23
+ end
24
+ end
25
+
26
+ def write
27
+ loop do
28
+ debug 'IO#write'
29
+ header, param = receive
30
+ debug "writing #{header}"
31
+
32
+ buff = MessagePack::Buffer.new
33
+ buff << header.to_msgpack
34
+ if param
35
+ buff << param.to_msgpack
36
+ end
37
+
38
+ @socket.write buff.to_str
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,12 @@
1
+ require 'logger'
2
+
3
+ module Serf
4
+ module Client
5
+ module Logger
6
+ def log
7
+ @logger ||= ::Logger.new $stdout
8
+ end
9
+ module_function :log
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ module Serf
2
+ module Client
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'serf/client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "serf-client"
8
+ spec.version = Serf::Client::VERSION
9
+ spec.authors = ["Jacob Evans"]
10
+ spec.email = ["jacob@dekz.net"]
11
+ spec.summary = %q{Serf Client}
12
+ spec.description = %q{Implementation of Serf RPC}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "msgpack"
22
+ spec.add_dependency "celluloid"
23
+ spec.add_dependency "celluloid-io"
24
+ spec.add_development_dependency "bundler", "~> 1.5"
25
+ spec.add_development_dependency "rake"
26
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: serf-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jacob Evans
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: msgpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: celluloid
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: celluloid-io
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Implementation of Serf RPC
84
+ email:
85
+ - jacob@dekz.net
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - lib/serf/client.rb
96
+ - lib/serf/client/callbacks.rb
97
+ - lib/serf/client/connection.rb
98
+ - lib/serf/client/io.rb
99
+ - lib/serf/client/logger.rb
100
+ - lib/serf/client/version.rb
101
+ - serf-client.gemspec
102
+ homepage: ''
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.0.3
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Serf Client
126
+ test_files: []
127
+ has_rdoc: