serf-client 0.0.1

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