ruby_skynet 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +13 -0
- data/Gemfile.lock +49 -0
- data/LICENSE.txt +201 -0
- data/README.md +119 -0
- data/Rakefile +40 -0
- data/lib/ruby_skynet/client.rb +293 -0
- data/lib/ruby_skynet/doozer/client.rb +202 -0
- data/lib/ruby_skynet/doozer/exceptions.rb +5 -0
- data/lib/ruby_skynet/doozer/msg.pb.rb +118 -0
- data/lib/ruby_skynet/exceptions.rb +6 -0
- data/lib/ruby_skynet/version.rb +3 -0
- data/lib/ruby_skynet.rb +11 -0
- data/nbproject/private/config.properties +0 -0
- data/nbproject/private/private.properties +1 -0
- data/nbproject/private/rake-d.txt +4 -0
- data/nbproject/project.properties +6 -0
- data/nbproject/project.xml +15 -0
- data/skynet-0.1.0.gem +0 -0
- data/test/doozer_client_test.rb +73 -0
- data/test/ruby_skynet_client_test.rb +77 -0
- data/test/simple_server.rb +134 -0
- data/test.log +5887 -0
- metadata +131 -0
@@ -0,0 +1,293 @@
|
|
1
|
+
require 'bson'
|
2
|
+
require 'sync_attr'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
#
|
6
|
+
# RubySkynet Client
|
7
|
+
#
|
8
|
+
# Supports
|
9
|
+
# RPC calls to Skynet
|
10
|
+
# Skynet Service autodiscovery
|
11
|
+
#
|
12
|
+
module RubySkynet
|
13
|
+
class Client
|
14
|
+
include SyncAttr
|
15
|
+
|
16
|
+
# Default doozer configuration
|
17
|
+
# To replace this default, set the config as follows:
|
18
|
+
# RubySkynet::Client.doozer_config = { .... }
|
19
|
+
sync_attr_accessor :doozer_config do
|
20
|
+
{
|
21
|
+
:server => '127.0.0.1:8046',
|
22
|
+
:read_timeout => 5,
|
23
|
+
:connect_timeout => 3,
|
24
|
+
:connect_retry_interval => 0.1,
|
25
|
+
:connect_retry_count => 3
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Lazy initialize Doozer Client
|
30
|
+
sync_cattr_reader :doozer do
|
31
|
+
Doozer::Client.new
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create a client connection, call the supplied block and close the connection on
|
35
|
+
# completion of the block
|
36
|
+
#
|
37
|
+
# Example
|
38
|
+
#
|
39
|
+
# require 'ruby_skynet'
|
40
|
+
# SemanticLogger.default_level = :trace
|
41
|
+
# SemanticLogger.appenders << SemanticLogger::Appender::File(STDOUT)
|
42
|
+
# RubySkynet::Client.connect('TutorialService') do |tutorial_service|
|
43
|
+
# p tutorial_service.call(:value => 5)
|
44
|
+
# end
|
45
|
+
def self.connect(service_name, params={})
|
46
|
+
begin
|
47
|
+
client = self.new(service_name, params)
|
48
|
+
yield(client)
|
49
|
+
ensure
|
50
|
+
client.close if client
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a new RubySkynet Client for the named service
|
55
|
+
#
|
56
|
+
# Parameters:
|
57
|
+
# :service_name
|
58
|
+
# Name of the service to look for and connect to on Skynet
|
59
|
+
#
|
60
|
+
# :doozer_servers [Array of String]
|
61
|
+
# Array of URL's of doozer servers to connect to with port numbers
|
62
|
+
# ['server1:2000', 'server2:2000']
|
63
|
+
#
|
64
|
+
# The second server will only be attempted once the first server
|
65
|
+
# cannot be connected to or has timed out on connect
|
66
|
+
# A read failure or timeout will not result in switching to the second
|
67
|
+
# server, only a connection failure or during an automatic reconnect
|
68
|
+
#
|
69
|
+
# :read_timeout [Float]
|
70
|
+
# Time in seconds to timeout on read
|
71
|
+
# Can be overridden by supplying a timeout in the read call
|
72
|
+
# Default: 60
|
73
|
+
#
|
74
|
+
# :connect_timeout [Float]
|
75
|
+
# Time in seconds to timeout when trying to connect to the server
|
76
|
+
# Default: Half of the :read_timeout ( 30 seconds )
|
77
|
+
#
|
78
|
+
# :connect_retry_count [Fixnum]
|
79
|
+
# Number of times to retry connecting when a connection fails
|
80
|
+
# Default: 10
|
81
|
+
#
|
82
|
+
# :connect_retry_interval [Float]
|
83
|
+
# Number of seconds between connection retry attempts after the first failed attempt
|
84
|
+
# Default: 0.5
|
85
|
+
def initialize(service_name, params = {})
|
86
|
+
@service_name = service_name
|
87
|
+
@logger = SemanticLogger::Logger.new("#{self.class.name}: #{service_name}")
|
88
|
+
|
89
|
+
# User configurable options
|
90
|
+
params[:read_timeout] ||= 60
|
91
|
+
params[:connect_timeout] ||= 30
|
92
|
+
params[:connect_retry_interval] ||= 0.1
|
93
|
+
params[:connect_retry_count] ||= 5
|
94
|
+
|
95
|
+
# If Server name and port of where Skynet Service is running
|
96
|
+
# is not supplied look for it in Doozer
|
97
|
+
unless params[:server] || params[:servers]
|
98
|
+
params[:server] = self.class.server_for(service_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Disable buffering the send since it is a RPC call
|
102
|
+
params[:buffered] = false
|
103
|
+
|
104
|
+
@logger.trace "Socket Connection parameters", params
|
105
|
+
|
106
|
+
# For each new connection perform the Skynet handshake
|
107
|
+
params[:on_connect] = Proc.new do |socket|
|
108
|
+
# Reset user_data on each connection
|
109
|
+
socket.user_data = 0
|
110
|
+
|
111
|
+
# Receive Service Handshake
|
112
|
+
# Registered bool
|
113
|
+
# Registered indicates the state of this service. If it is false, the connection will
|
114
|
+
# close immediately and the client should look elsewhere for this service.
|
115
|
+
#
|
116
|
+
# ClientID string
|
117
|
+
# ClientID is a UUID that is used by the client to identify itself in RPC requests.
|
118
|
+
@logger.debug "Waiting for Service Handshake"
|
119
|
+
service_handshake = self.class.read_bson_document(socket)
|
120
|
+
@logger.trace 'Service Handshake', service_handshake
|
121
|
+
|
122
|
+
# #TODO When a reconnect returns registered == false we need to go back to doozer
|
123
|
+
@registered = service_handshake['registered']
|
124
|
+
@client_id = service_handshake['clientid']
|
125
|
+
|
126
|
+
# Send blank ClientHandshake
|
127
|
+
client_handshake = { 'clientid' => @client_id }
|
128
|
+
@logger.debug "Sending Client Handshake"
|
129
|
+
@logger.trace 'Client Handshake', client_handshake
|
130
|
+
socket.send(BSON.serialize(client_handshake))
|
131
|
+
end
|
132
|
+
|
133
|
+
@socket = ResilientSocket::TCPClient.new(params)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Performs a synchronous call to the Skynet Service
|
137
|
+
#
|
138
|
+
# Parameters:
|
139
|
+
# method_name [String|Symbol]:
|
140
|
+
# Name of the method to call at the service
|
141
|
+
# parameters [Hash]:
|
142
|
+
# Parameters to pass into the service
|
143
|
+
#
|
144
|
+
# Returns the Hash result returned from the Skynet Service
|
145
|
+
#
|
146
|
+
# Raises RubySkynet::ProtocolError
|
147
|
+
# Raises RubySkynet::SkynetException
|
148
|
+
def call(method_name, parameters)
|
149
|
+
# Skynet requires BSON RPC Calls to have the following format:
|
150
|
+
# https://github.com/bketelsen/skynet/blob/protocol/protocol.md
|
151
|
+
request_id = BSON::ObjectId.new.to_s
|
152
|
+
@logger.tagged request_id do
|
153
|
+
@logger.benchmark_info "Called Skynet Service: #{@service_name}.#{method_name}" do
|
154
|
+
|
155
|
+
# Resilient Send
|
156
|
+
retry_count = 0
|
157
|
+
@socket.retry_on_connection_failure do |socket|
|
158
|
+
# user_data is maintained per session and a different session could
|
159
|
+
# be supplied with each retry
|
160
|
+
socket.user_data ||= 0
|
161
|
+
header = {
|
162
|
+
'servicemethod' => "#{@service_name}.Forward",
|
163
|
+
'seq' => socket.user_data,
|
164
|
+
}
|
165
|
+
@logger.debug "Sending Header"
|
166
|
+
@logger.trace 'Header', header
|
167
|
+
socket.send(BSON.serialize(header))
|
168
|
+
|
169
|
+
@logger.trace 'Parameters:', parameters
|
170
|
+
|
171
|
+
# The parameters are placed in the request object in BSON serialized
|
172
|
+
# form
|
173
|
+
request = {
|
174
|
+
'clientid' => @client_id,
|
175
|
+
'in' => BSON.serialize(parameters).to_s,
|
176
|
+
'method' => method_name.to_s,
|
177
|
+
'requestinfo' => {
|
178
|
+
'requestid' => request_id,
|
179
|
+
# Increment retry count to indicate that the request may have been tried previously
|
180
|
+
# TODO: this should be incremented if request is retried,
|
181
|
+
'retrycount' => retry_count,
|
182
|
+
# TODO: this should be forwarded along in case of services also
|
183
|
+
# being a client and calling additional services. If empty it will
|
184
|
+
# be stuffed with connecting address
|
185
|
+
'originaddress' => ''
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
@logger.debug "Sending Request"
|
190
|
+
@logger.trace 'Request', request
|
191
|
+
socket.send(BSON.serialize(request))
|
192
|
+
end
|
193
|
+
|
194
|
+
# Once send is successful it could have been processed, so we can no
|
195
|
+
# longer retry now otherwise we could create a duplicate
|
196
|
+
# retry_count += 1
|
197
|
+
|
198
|
+
# Read header first as a separate BSON document
|
199
|
+
@logger.debug "Reading header from server"
|
200
|
+
header = self.class.read_bson_document(@socket)
|
201
|
+
@logger.debug 'Header', header
|
202
|
+
|
203
|
+
# Read the BSON response document
|
204
|
+
@logger.debug "Reading response from server"
|
205
|
+
response = self.class.read_bson_document(@socket)
|
206
|
+
@logger.trace 'Response', response
|
207
|
+
|
208
|
+
# Ensure the sequence number in the response header matches the
|
209
|
+
# sequence number sent in the request
|
210
|
+
if seq_no = header['seq']
|
211
|
+
raise ProtocolError.new("Incorrect Response received, expected seq=#{@socket.user_data}, received: #{header.inspect}") if seq_no != @socket.user_data
|
212
|
+
else
|
213
|
+
raise ProtocolError.new("Invalid Response header, missing 'seq': #{header.inspect}")
|
214
|
+
end
|
215
|
+
|
216
|
+
# Increment Sequence number only on successful response
|
217
|
+
@socket.user_data += 1
|
218
|
+
|
219
|
+
# If an error is returned from Skynet raise a Skynet exception
|
220
|
+
if error = header['error']
|
221
|
+
raise SkynetException.new(error) if error.to_s.length > 0
|
222
|
+
end
|
223
|
+
|
224
|
+
# If an error is returned from the service raise a Service exception
|
225
|
+
if error = response['error']
|
226
|
+
raise ServiceException.new(error) if error.to_s.length > 0
|
227
|
+
end
|
228
|
+
|
229
|
+
# Return Value
|
230
|
+
# The return value is inside the response object, it's a byte array of it's own and needs to be deserialized
|
231
|
+
result = BSON.deserialize(response['out'])
|
232
|
+
@logger.trace 'Return Value', result
|
233
|
+
result
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns a BSON document read from the socket.
|
239
|
+
# Returns nil if the operation times out or if a network
|
240
|
+
# connection failure occurs
|
241
|
+
def self.read_bson_document(socket)
|
242
|
+
bytebuf = BSON::ByteBuffer.new
|
243
|
+
# Read 4 byte size of following BSON document
|
244
|
+
bytes = ''
|
245
|
+
socket.read(4, bytes)
|
246
|
+
|
247
|
+
# Read BSON document
|
248
|
+
sz = bytes.unpack("V")[0]
|
249
|
+
raise "Invalid Data received from server:#{bytes.inspect}" unless sz
|
250
|
+
|
251
|
+
bytebuf.append!(bytes)
|
252
|
+
bytes = ''
|
253
|
+
sz -= 4
|
254
|
+
until bytes.size >= sz
|
255
|
+
buf = ''
|
256
|
+
socket.read(sz, buf)
|
257
|
+
bytes << buf
|
258
|
+
end
|
259
|
+
bytebuf.append!(bytes)
|
260
|
+
return BSON.deserialize(bytebuf)
|
261
|
+
end
|
262
|
+
|
263
|
+
def close()
|
264
|
+
@socket.close
|
265
|
+
end
|
266
|
+
|
267
|
+
##############################
|
268
|
+
#protected
|
269
|
+
|
270
|
+
# Returns [Array] of the hostname and port pair [String] that implements a particular service
|
271
|
+
# Performs a doozer lookup to find the servers
|
272
|
+
#
|
273
|
+
# service_name:
|
274
|
+
# version: Version of service to locate
|
275
|
+
# Default: Find latest version
|
276
|
+
def self.registered_implementers(service_name, version = '*', region = 'Development')
|
277
|
+
hosts = []
|
278
|
+
doozer.walk("/services/#{service_name}/#{version}/#{region}/*/*").each do |node|
|
279
|
+
entry = MultiJson.load(node.value)
|
280
|
+
hosts << entry if entry['Registered']
|
281
|
+
end
|
282
|
+
hosts
|
283
|
+
end
|
284
|
+
|
285
|
+
# Randomly returns a server that implements the requested service
|
286
|
+
def self.server_for(service_name, version = '*', region = 'Development')
|
287
|
+
hosts = registered_implementers(service_name, version, region)
|
288
|
+
service = hosts[rand(hosts.size)]['Config']['ServiceAddr']
|
289
|
+
"#{service['IPAddress']}:#{service['Port']}"
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
293
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'ruby_skynet/doozer/msg.pb'
|
2
|
+
require 'semantic_logger'
|
3
|
+
require 'resilient_socket'
|
4
|
+
require 'ruby_skynet/doozer/exceptions'
|
5
|
+
require 'ruby_skynet/doozer/msg.pb'
|
6
|
+
|
7
|
+
module RubySkynet
|
8
|
+
module Doozer
|
9
|
+
class Client
|
10
|
+
|
11
|
+
# Create a resilient client connection to a Doozer server
|
12
|
+
def initialize(params={})
|
13
|
+
@logger = SemanticLogger::Logger.new(self.class)
|
14
|
+
|
15
|
+
# User configurable options
|
16
|
+
params[:read_timeout] ||= 5
|
17
|
+
params[:connect_timeout] ||= 3
|
18
|
+
params[:connect_retry_interval] ||= 0.1
|
19
|
+
params[:connect_retry_count] ||= 3
|
20
|
+
|
21
|
+
# Server name and port where Doozer is running
|
22
|
+
# Defaults to 127.0.0.1:8046
|
23
|
+
params[:server] ||= '127.0.0.1:8046' unless params[:servers]
|
24
|
+
|
25
|
+
# Disable buffering the send since it is a RPC call
|
26
|
+
params[:buffered] = false
|
27
|
+
|
28
|
+
@logger.trace "Socket Connection parameters", params
|
29
|
+
|
30
|
+
# For each new connection
|
31
|
+
params[:on_connect] = Proc.new do |socket|
|
32
|
+
# Reset user_data on each connection
|
33
|
+
socket.user_data = 0
|
34
|
+
end
|
35
|
+
|
36
|
+
@socket = ResilientSocket::TCPClient.new(params)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Close this client connection to doozer
|
40
|
+
def close
|
41
|
+
@socket.close if @socket
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the current Doozer revision
|
45
|
+
def current_revision
|
46
|
+
invoke(Request.new(:verb => Request::Verb::REV)).rev
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set a value in Doozer
|
50
|
+
# path: Path to the value to be set
|
51
|
+
# value: Value to set
|
52
|
+
# rev: Revision at which to set the value
|
53
|
+
# If not supplied it will replace the latest version on the server
|
54
|
+
#
|
55
|
+
# Returns the new revision of the updated value
|
56
|
+
#
|
57
|
+
# It is recommended to set the revision so that multiple clients do not
|
58
|
+
# attempt to update the value at the same time.
|
59
|
+
# Setting the revision also allows the call to be retried automatically
|
60
|
+
# in the event of a network failure
|
61
|
+
def set(path, value, rev=-1)
|
62
|
+
invoke(Request.new(:path => path, :value => value, :rev => rev, :verb => Request::Verb::SET), false).rev
|
63
|
+
end
|
64
|
+
|
65
|
+
# Sets the current value at the supplied path
|
66
|
+
def []=(path,value)
|
67
|
+
set(path, value)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return the value at the supplied path and revision
|
71
|
+
def get(path, rev = nil)
|
72
|
+
invoke(Request.new(:path => path, :rev => rev, :verb => Request::Verb::GET))
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns just the value at the supplied path, not the revision
|
76
|
+
def [](path)
|
77
|
+
get(path).value
|
78
|
+
end
|
79
|
+
|
80
|
+
# Deletes the file at path if rev is greater than or equal to the file's revision.
|
81
|
+
# Returns nil when the file was removed
|
82
|
+
# Raises an exception if an attempt to remove the file and its revision
|
83
|
+
# is greater than that supplied
|
84
|
+
def delete(path, rev=-1)
|
85
|
+
invoke(Request.new(:path => path, :rev => rev, :verb => Request::Verb::DEL))
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the directory in the supplied path
|
90
|
+
# Use offset to get the next
|
91
|
+
# returns nil if no further paths are available
|
92
|
+
def directory(path, offset = 0, rev = nil)
|
93
|
+
begin
|
94
|
+
invoke(Request.new(:path => path, :rev => rev, :offset => offset, :verb => Request::Verb::GETDIR))
|
95
|
+
rescue RubySkynet::Doozer::ResponseError => exc
|
96
|
+
raise exc unless exc.message.include?('RANGE')
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def stat(path, rev = nil)
|
102
|
+
invoke(Request.new(:path => path, :rev => rev, :verb => Request::Verb::STAT))
|
103
|
+
end
|
104
|
+
|
105
|
+
def access(secret)
|
106
|
+
invoke(Request.new(:path => secret, :verb => Request::Verb::ACCESS))
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns every entry in the supplied path
|
110
|
+
# path can also contain wildcard characters such as '*'
|
111
|
+
# Example:
|
112
|
+
# hosts = []
|
113
|
+
# walk('/ctl/node/*/addr', current_revision).each do |node|
|
114
|
+
# hosts << node.value unless hosts.include? node.value
|
115
|
+
# end
|
116
|
+
def walk(path, rev = nil, offset = 0)
|
117
|
+
paths = []
|
118
|
+
revision = rev || current_revision
|
119
|
+
# Resume walk on network connection failure
|
120
|
+
@socket.retry_on_connection_failure do
|
121
|
+
while true
|
122
|
+
send(Request.new(:path => path, :rev => revision , :offset => offset, :verb => Request::Verb::WALK))
|
123
|
+
response = read
|
124
|
+
if response.err_code
|
125
|
+
break if response.err_code == Response::Err::RANGE
|
126
|
+
else
|
127
|
+
raise ResponseError.new("#{Response::Err.name_by_value(response.err_code)}: #{response.err_detail}") if response.err_code != 0
|
128
|
+
end
|
129
|
+
paths << response
|
130
|
+
offset += 1
|
131
|
+
end
|
132
|
+
end
|
133
|
+
paths
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns [Array] of hostname [String] with each string
|
137
|
+
# representing another Doozer server that can be connected to
|
138
|
+
def doozer_hosts
|
139
|
+
hosts = []
|
140
|
+
walk('/ctl/node/*/addr', current_revision).each do |node|
|
141
|
+
hosts << node.value unless hosts.include? node.value
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# TODO Implement watching for changes in a separate thread with it's own
|
146
|
+
# client connection
|
147
|
+
#def watch(path, rev = nil)
|
148
|
+
# invoke(Request.new(:path => secret, :verb => Request::Verb::WAIT))
|
149
|
+
#end
|
150
|
+
|
151
|
+
#####################
|
152
|
+
# protected
|
153
|
+
|
154
|
+
# Call the Doozer server
|
155
|
+
#
|
156
|
+
# When readonly ==> true the request is always retried on network failure
|
157
|
+
# When readonly ==> false the request is retried on network failure
|
158
|
+
# _only_ if a rev has been supplied
|
159
|
+
#
|
160
|
+
# When modifier is true
|
161
|
+
def invoke(request, readonly=true)
|
162
|
+
retry_read = readonly || !request.rev.nil?
|
163
|
+
response = nil
|
164
|
+
@socket.retry_on_connection_failure do
|
165
|
+
send(request)
|
166
|
+
response = read if retry_read
|
167
|
+
end
|
168
|
+
# Network error on read must be sent back to caller since we do not
|
169
|
+
# know if the modification was made
|
170
|
+
response = read unless retry_read
|
171
|
+
raise ResponseError.new("#{Response::Err.name_by_value(response.err_code)}: #{response.err_detail}") if response.err_code != 0
|
172
|
+
response
|
173
|
+
end
|
174
|
+
|
175
|
+
# Send the protobuf Request to Doozer
|
176
|
+
def send(request)
|
177
|
+
request.tag = 0
|
178
|
+
data = request.serialize_to_string
|
179
|
+
# An additional header is added to the request indicating the size of the request
|
180
|
+
head = [data.length].pack("N")
|
181
|
+
@socket.send(head+data)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Read the protobuf Response from Doozer
|
185
|
+
def read
|
186
|
+
# First strip the additional header indicating the size of the subsequent response
|
187
|
+
head = @socket.read(4)
|
188
|
+
length = head.unpack("N")[0]
|
189
|
+
|
190
|
+
# Since can returns upto 'length' bytes we need to make sure it returns
|
191
|
+
# at least 'length' bytes
|
192
|
+
# TODO: Make this a binary buffer
|
193
|
+
data = ''
|
194
|
+
until data.size >= length
|
195
|
+
data << @socket.read(length)
|
196
|
+
end
|
197
|
+
Response.new.parse_from_string(data)
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
### Generated by rprotoc. DO NOT EDIT!
|
2
|
+
### <proto file: doozerd/server/msg.proto>
|
3
|
+
# package server;
|
4
|
+
#
|
5
|
+
# // see doc/proto.md
|
6
|
+
# message Request {
|
7
|
+
# optional int32 tag = 1;
|
8
|
+
#
|
9
|
+
# enum Verb {
|
10
|
+
# GET = 1;
|
11
|
+
# SET = 2;
|
12
|
+
# DEL = 3;
|
13
|
+
# REV = 5;
|
14
|
+
# WAIT = 6;
|
15
|
+
# NOP = 7;
|
16
|
+
# WALK = 9;
|
17
|
+
# GETDIR = 14;
|
18
|
+
# STAT = 16;
|
19
|
+
# ACCESS = 99;
|
20
|
+
# }
|
21
|
+
# optional Verb verb = 2;
|
22
|
+
#
|
23
|
+
# optional string path = 4;
|
24
|
+
# optional bytes value = 5;
|
25
|
+
# optional int32 other_tag = 6;
|
26
|
+
#
|
27
|
+
# optional int32 offset = 7;
|
28
|
+
#
|
29
|
+
# optional int64 rev = 9;
|
30
|
+
# }
|
31
|
+
#
|
32
|
+
# // see doc/proto.md
|
33
|
+
# message Response {
|
34
|
+
# optional int32 tag = 1;
|
35
|
+
# optional int32 flags = 2;
|
36
|
+
#
|
37
|
+
# optional int64 rev = 3;
|
38
|
+
# optional string path = 5;
|
39
|
+
# optional bytes value = 6;
|
40
|
+
# optional int32 len = 8;
|
41
|
+
#
|
42
|
+
# enum Err {
|
43
|
+
# // don't use value 0
|
44
|
+
# OTHER = 127;
|
45
|
+
# TAG_IN_USE = 1;
|
46
|
+
# UNKNOWN_VERB = 2;
|
47
|
+
# READONLY = 3;
|
48
|
+
# TOO_LATE = 4;
|
49
|
+
# REV_MISMATCH = 5;
|
50
|
+
# BAD_PATH = 6;
|
51
|
+
# MISSING_ARG = 7;
|
52
|
+
# RANGE = 8;
|
53
|
+
# NOTDIR = 20;
|
54
|
+
# ISDIR = 21;
|
55
|
+
# NOENT = 22;
|
56
|
+
# }
|
57
|
+
# optional Err err_code = 100;
|
58
|
+
# optional string err_detail = 101;
|
59
|
+
# }
|
60
|
+
|
61
|
+
require 'protobuf/message/message'
|
62
|
+
require 'protobuf/message/enum'
|
63
|
+
require 'protobuf/message/service'
|
64
|
+
require 'protobuf/message/extend'
|
65
|
+
|
66
|
+
module RubySkynet
|
67
|
+
module Doozer
|
68
|
+
class Request < ::Protobuf::Message
|
69
|
+
defined_in __FILE__
|
70
|
+
optional :int32, :tag, 1
|
71
|
+
class Verb < ::Protobuf::Enum
|
72
|
+
defined_in __FILE__
|
73
|
+
GET = value(:GET, 1)
|
74
|
+
SET = value(:SET, 2)
|
75
|
+
DEL = value(:DEL, 3)
|
76
|
+
REV = value(:REV, 5)
|
77
|
+
WAIT = value(:WAIT, 6)
|
78
|
+
NOP = value(:NOP, 7)
|
79
|
+
WALK = value(:WALK, 9)
|
80
|
+
GETDIR = value(:GETDIR, 14)
|
81
|
+
STAT = value(:STAT, 16)
|
82
|
+
ACCESS = value(:ACCESS, 99)
|
83
|
+
end
|
84
|
+
optional :Verb, :verb, 2
|
85
|
+
optional :string, :path, 4
|
86
|
+
optional :bytes, :value, 5
|
87
|
+
optional :int32, :other_tag, 6
|
88
|
+
optional :int32, :offset, 7
|
89
|
+
optional :int64, :rev, 9
|
90
|
+
end
|
91
|
+
class Response < ::Protobuf::Message
|
92
|
+
defined_in __FILE__
|
93
|
+
optional :int32, :tag, 1
|
94
|
+
optional :int32, :flags, 2
|
95
|
+
optional :int64, :rev, 3
|
96
|
+
optional :string, :path, 5
|
97
|
+
optional :bytes, :value, 6
|
98
|
+
optional :int32, :len, 8
|
99
|
+
class Err < ::Protobuf::Enum
|
100
|
+
defined_in __FILE__
|
101
|
+
OTHER = value(:OTHER, 127)
|
102
|
+
TAG_IN_USE = value(:TAG_IN_USE, 1)
|
103
|
+
UNKNOWN_VERB = value(:UNKNOWN_VERB, 2)
|
104
|
+
READONLY = value(:READONLY, 3)
|
105
|
+
TOO_LATE = value(:TOO_LATE, 4)
|
106
|
+
REV_MISMATCH = value(:REV_MISMATCH, 5)
|
107
|
+
BAD_PATH = value(:BAD_PATH, 6)
|
108
|
+
MISSING_ARG = value(:MISSING_ARG, 7)
|
109
|
+
RANGE = value(:RANGE, 8)
|
110
|
+
NOTDIR = value(:NOTDIR, 20)
|
111
|
+
ISDIR = value(:ISDIR, 21)
|
112
|
+
NOENT = value(:NOENT, 22)
|
113
|
+
end
|
114
|
+
optional :Err, :err_code, 100
|
115
|
+
optional :string, :err_detail, 101
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/ruby_skynet.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'semantic_logger'
|
2
|
+
require 'resilient_socket'
|
3
|
+
|
4
|
+
require 'ruby_skynet/exceptions'
|
5
|
+
require 'ruby_skynet/version'
|
6
|
+
module RubySkynet
|
7
|
+
module Doozer
|
8
|
+
autoload :Client, 'ruby_skynet/doozer/client'
|
9
|
+
end
|
10
|
+
autoload :Client, 'ruby_skynet/client'
|
11
|
+
end
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
platform.active=Ruby
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<project xmlns="http://www.netbeans.org/ns/project/1">
|
3
|
+
<type>org.netbeans.modules.ruby.rubyproject</type>
|
4
|
+
<configuration>
|
5
|
+
<data xmlns="http://www.netbeans.org/ns/ruby-project/1">
|
6
|
+
<name>ruby_skynet</name>
|
7
|
+
<source-roots>
|
8
|
+
<root id="src.lib.dir" name="Source Files"/>
|
9
|
+
</source-roots>
|
10
|
+
<test-roots>
|
11
|
+
<root id="test.test.dir"/>
|
12
|
+
</test-roots>
|
13
|
+
</data>
|
14
|
+
</configuration>
|
15
|
+
</project>
|
data/skynet-0.1.0.gem
ADDED
Binary file
|