servicy 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +257 -0
- data/Servicy.gemspec +47 -0
- data/VERSION +1 -0
- data/bin/servicy +305 -0
- data/lib/api.rb +126 -0
- data/lib/client.rb +114 -0
- data/lib/hash.rb +14 -0
- data/lib/load_balancer/random.rb +12 -0
- data/lib/load_balancer/round_robin.rb +18 -0
- data/lib/load_balancer.rb +25 -0
- data/lib/server/server.rb +222 -0
- data/lib/server/service_searcher.rb +115 -0
- data/lib/server.rb +8 -0
- data/lib/service.rb +200 -0
- data/lib/servicy.rb +9 -0
- data/lib/transport/in_memory_transport.rb +44 -0
- data/lib/transport/messages.rb +141 -0
- data/lib/transport/tcp_transport.rb +50 -0
- data/lib/transports.rb +54 -0
- data/test.rb +74 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: caf0064f89f13aec57352cff7a92d144db60444c
|
4
|
+
data.tar.gz: 7b95d789be54127997ed6ea51d709fd18910efe8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b3af8c73e8b8a5d2a61eef42c89dbef44b528a199e38436ff4ce1a54613d776a4f95fab796c73a736d9321b20917949d891b313ab66ab2cfcf86c011e3a729f
|
7
|
+
data.tar.gz: b4d288ad7756cd24814b5d57de1a15b8f50b1497fde40dc662ec1811d42127746337875afc3a2d6bb3da5417853729c4ff933710c81a84ce0cd1cc070188b1a1
|
data/README.md
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
Servicy
|
2
|
+
=======
|
3
|
+
|
4
|
+
A service registration and discovery platform in Ruby for SOA.
|
5
|
+
|
6
|
+
The goal of Servicy is to provide a simple daemon which allows services in an
|
7
|
+
SOA application configuration to register themselves by name and/or API, and
|
8
|
+
allow service consumers to find instances of services to use, and be relatively
|
9
|
+
sure of up-time, routing, etc.
|
10
|
+
|
11
|
+
Servicy could possibly used as a router as well, although that may be out of
|
12
|
+
scope we will see.
|
13
|
+
|
14
|
+
General architecture ideas
|
15
|
+
==========================
|
16
|
+
|
17
|
+
Servicy would be broken into a few pieces:
|
18
|
+
|
19
|
+
1. A daemon which can handle registration and query requests
|
20
|
+
2. An in-memory database of services to facilitate queries
|
21
|
+
3. A persistent cache of services to allow for fast restarts
|
22
|
+
4. (possibly) A service/API router
|
23
|
+
5. Reporting service to report on service health and usage.
|
24
|
+
6. Client library for service discovery and connection
|
25
|
+
7. Service library for registration, API discovery and building
|
26
|
+
|
27
|
+
One big issue that will have to be resolved is version dependencies. If there
|
28
|
+
are two versions of a service, each with a different public API, then Servicy
|
29
|
+
should know about the differences and send back to a service requester the
|
30
|
+
correct version based on their requirements. For this, Servicy should have a
|
31
|
+
way of discovering and/or services defining their API to Servicy.
|
32
|
+
|
33
|
+
Each service should also be able to define multiple instances, and Servicy
|
34
|
+
should have multiple strategies for choosing instances of a given service. For
|
35
|
+
example, round-robin, load-based (would require machine-level meta-data to be
|
36
|
+
reported by the services), even requests, etc. To start with, a simple
|
37
|
+
round-robin scheme will likely be the only implementation. Furthermore, Servicy
|
38
|
+
should have knowledge of if a service is up or down, and automatically remove
|
39
|
+
from its list of available providers, or add it back in as needed.
|
40
|
+
|
41
|
+
Also, Servicy should maintain statistics on usage for services and be able to
|
42
|
+
report that information back to a user. Stats should include number of
|
43
|
+
discoveries, cache hits/misses, service up-times, provider failures, and, if it
|
44
|
+
works as a router, request latency. In the event that I end up _not_ making it
|
45
|
+
a router (seems likely at this point), it should still report latency for
|
46
|
+
service discovery and query operations that require back-and-forth with the
|
47
|
+
service.
|
48
|
+
|
49
|
+
Protocol ideas
|
50
|
+
==============
|
51
|
+
|
52
|
+
Servicy is protocol agnostic, and the exact transport mechanism used to
|
53
|
+
communicate between client and server doesn't matter. The data is sent as JSON
|
54
|
+
underneath it all (for now; I may even change things to be format agnostic in
|
55
|
+
the future). You can see examples of the JSON by looking in the
|
56
|
+
Servicy::Transport::Message class.
|
57
|
+
|
58
|
+
Service registration
|
59
|
+
--------------------
|
60
|
+
|
61
|
+
Service registration allows for a service to declare that they exist, how to
|
62
|
+
reach them, and what functionality they provide. Services are named using a
|
63
|
+
Java-style, inverted domain name. For example:
|
64
|
+
|
65
|
+
```
|
66
|
+
com.mycompany.users.authentication
|
67
|
+
```
|
68
|
+
|
69
|
+
might be the name of a user authentication service provided internally. This
|
70
|
+
allows for services to be broken up by what other services they interact with,
|
71
|
+
as well as be easy to read for users browsing the registry.
|
72
|
+
|
73
|
+
Beyond the name, a service must provide some other information for its
|
74
|
+
registry. This includes:
|
75
|
+
|
76
|
+
1. Host to connect to
|
77
|
+
2. Port on the host to connect to
|
78
|
+
3. Protocol used to communicate (HTTP, HTTPS, Thrift, etc.)
|
79
|
+
4. Version number
|
80
|
+
5. Heartbeat port
|
81
|
+
|
82
|
+
The host can be either an IP address or a fully-qualified hostname. In either
|
83
|
+
case it should be route-able in the context of the Servicy host. In other
|
84
|
+
words, if Servicy cannot connect with it, it won't add it to the registry. The
|
85
|
+
port should be an integer between 1 and 65535. The protocol can be anything,
|
86
|
+
however some standards are:
|
87
|
+
|
88
|
+
* HTTP - _Must_ use un-encrypted HTTP
|
89
|
+
* HTTPS - _Must_ use TLS over HTTP
|
90
|
+
* HTTP/S - Can use either HTTP or HTTPS
|
91
|
+
* Thrift
|
92
|
+
* TCP - Direct, telnet-like communication
|
93
|
+
* UDP - Direct connection, but don't expect replies
|
94
|
+
|
95
|
+
The protocol says nothing about what information needs to be sent or received
|
96
|
+
by a client consuming the service. It is assumed that if the client is looking
|
97
|
+
for the service, it knows how to use it once it is found, and will only use the
|
98
|
+
protocol information to select between possible options provided by the
|
99
|
+
consuming library. Protocols can also be provided as a list of possible
|
100
|
+
options, ordered with most preferred first. At some point in the future, there
|
101
|
+
may be a mechanism by which a service can describe itself in a more useful way,
|
102
|
+
and client libraries can use that information to dynamically build an internal
|
103
|
+
API.
|
104
|
+
|
105
|
+
The version number must follow the format:
|
106
|
+
|
107
|
+
```
|
108
|
+
major.minor.revision-patch
|
109
|
+
```
|
110
|
+
|
111
|
+
The "-patch" part can be omitted. For example:
|
112
|
+
|
113
|
+
```
|
114
|
+
1.0.2-p143
|
115
|
+
```
|
116
|
+
|
117
|
+
The heartbeat port is a port that Servicy can connect to to ensure that the
|
118
|
+
service is still functioning. In many cases this will be the same port as the
|
119
|
+
primary connection port; Servicy does not attempt to use the heartbeat
|
120
|
+
connection for anything. It simply connects, and if successful, disconnects
|
121
|
+
again. For this reason, your services should be resilliant to this type of
|
122
|
+
traffic.
|
123
|
+
|
124
|
+
Along with the required information already listed, a service can provide some
|
125
|
+
optional information about the API that they provide. This information comes
|
126
|
+
with some pieces of information for each API endpoint or action provided. The
|
127
|
+
information is:
|
128
|
+
|
129
|
+
1. Endpoint identifier
|
130
|
+
2. Parameter information
|
131
|
+
3. Return information
|
132
|
+
4. (optional) Human-readable documentation.
|
133
|
+
|
134
|
+
The endpoint identifier can be anything, and is just a string. For example, in
|
135
|
+
an HTTP/S RESTful API, it may be something like:
|
136
|
+
|
137
|
+
```
|
138
|
+
/api/v1/user/login
|
139
|
+
```
|
140
|
+
|
141
|
+
The parameter information is information about each parameter that is expected
|
142
|
+
by this endpoint. This data is structured, and requires information about name,
|
143
|
+
data type, and weather or not the parameter is required.
|
144
|
+
|
145
|
+
The return information is simply a description of the data that should be
|
146
|
+
expected to return.
|
147
|
+
|
148
|
+
The documentation is an arbitrary-length text string which can be used as
|
149
|
+
documentation by a developer developing against the API.
|
150
|
+
|
151
|
+
Multiple service providers can provide the same service. In that case, they are
|
152
|
+
simply added to a pool of possible providers that is selected from using some
|
153
|
+
load-balancing strategy such as round-robin or random selection.
|
154
|
+
|
155
|
+
Any service provider that has registered will periodically get ICMP pinged by
|
156
|
+
Servicy. If a response does not come back, it will be removed from the pool of
|
157
|
+
providers. If a response does come back, Servicy will then attempt to connect
|
158
|
+
to the heartbeat port to determine if the service is still functioning on the
|
159
|
+
box. If it is not, the provider will be removed from the pool, otherwise it
|
160
|
+
will be left in. This two-step process is to facilitate better logging and
|
161
|
+
debugging of issues.
|
162
|
+
|
163
|
+
Service de-registration
|
164
|
+
-----------------------
|
165
|
+
|
166
|
+
A service can remove itself from the pool voluntarily be simply sending the
|
167
|
+
name, version, host, and port of the service to be removed. However, this is
|
168
|
+
not strictly needed as the periodic heartbeat check will notice if a service
|
169
|
+
goes away. These checks happen every second.
|
170
|
+
|
171
|
+
A note on security
|
172
|
+
++++++++++++++++++
|
173
|
+
|
174
|
+
For the time-being, it is assumed that you will have Servicy and all the
|
175
|
+
services it interacts with installed on internal, secured infrastructure. As
|
176
|
+
such, Servicy takes a very trusting policy. However, that means that someone
|
177
|
+
with malintent and access to your network could de-register services, or
|
178
|
+
register a bunch of fake ones. Future versions of Servicy may attempt to
|
179
|
+
resolve this issue. We'll see...
|
180
|
+
|
181
|
+
Service discovery
|
182
|
+
-----------------
|
183
|
+
|
184
|
+
Once services are registered, a client needs to be able to discover them.
|
185
|
+
Generally, they will discover a service and hold on to that information for the
|
186
|
+
lifetime of their use, only requesting a new handle should the old one be lost.
|
187
|
+
However, there is no reason why they couldn't request a new one every time.
|
188
|
+
|
189
|
+
*NOTE* In the future, it may be possible for Servicy to act as a router by
|
190
|
+
simply returning connection information for itself, and doing blind
|
191
|
+
pass-through with some strategy to the other providers. In that case, the
|
192
|
+
policy that a client uses to cache or not cache their connection information
|
193
|
+
may have to change.
|
194
|
+
|
195
|
+
A service consumer can query using one or both of two different dimensions:
|
196
|
+
|
197
|
+
1. API name
|
198
|
+
2. API functionality
|
199
|
+
|
200
|
+
When using the name dimension, you can also query by version using either an
|
201
|
+
exact match, a list of preferences, a range, a minimum, or a maximum.
|
202
|
+
|
203
|
+
When querying using functionality, you query using one or more API method
|
204
|
+
fingerprints. The fingerprints are in the following format:
|
205
|
+
|
206
|
+
```
|
207
|
+
method_name#arg-type,arg-type,...#return_type
|
208
|
+
```
|
209
|
+
|
210
|
+
where method_name is the name of the method you would like to call (or service
|
211
|
+
end-point, or whatever. This is the same as the API endpoint in the service
|
212
|
+
functionality registration information.), the `arg-type` pairs are the name of
|
213
|
+
an argument (or an empty string in the case of un-named arguments), followed by
|
214
|
+
a dash, followed by the argument type, and the return_type being the data-type
|
215
|
+
for the return value.
|
216
|
+
|
217
|
+
For example, a query could look something like this:
|
218
|
+
|
219
|
+
```
|
220
|
+
user_create#username-string,password-string#User
|
221
|
+
```
|
222
|
+
|
223
|
+
Behind the scenes, Servicy will use this information to find all possible
|
224
|
+
services that can fulfill this request. This will include any service that can
|
225
|
+
perform this request but that may have more optional parameters. Type
|
226
|
+
information can also be provided as a list of types, in the case of API's that
|
227
|
+
can handle multiple types of information:
|
228
|
+
|
229
|
+
```
|
230
|
+
user_create#username-string,password-string|signed_string#User
|
231
|
+
```
|
232
|
+
|
233
|
+
The possible other types that the data can be are separated by a pipe, '|'.
|
234
|
+
|
235
|
+
Finally, the service consumer can provide a boolean flag indicating weather or
|
236
|
+
not they wish to receive API information about the returned providers, or if
|
237
|
+
they simply want connection information. If the flag is set to true, they will
|
238
|
+
get full API documentation and definitions along with connection information.
|
239
|
+
|
240
|
+
The query system will either provide a successful "error"-code, along with a
|
241
|
+
list of one or more service providers, a "not-found" error-code indicating that
|
242
|
+
the query was valid, but that there are no available services which meet the
|
243
|
+
requirements, or an "error" error-code that indicates that something was wrong
|
244
|
+
with the provided query. In the last two queries, additional error information
|
245
|
+
may be provided, but should be considered for human-use only; ie, showing an
|
246
|
+
error message to a user.
|
247
|
+
|
248
|
+
TODO
|
249
|
+
====
|
250
|
+
|
251
|
+
Things left to do:
|
252
|
+
|
253
|
+
1. In the command-line tool, there is some kind of bug where the registration
|
254
|
+
message never returns back to the client. This causes registration to hang
|
255
|
+
forever...
|
256
|
+
1. There are a number of just kind of ugly-code things around that I should sort out.
|
257
|
+
1. Probably some other things that I'm forgetting.
|
data/Servicy.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: servicy 0.0.3 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "servicy"
|
9
|
+
s.version = "0.0.3"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Thomas Luce"]
|
14
|
+
s.date = "2014-08-11"
|
15
|
+
s.description = "A service registration and discovery framework, as a server and client library."
|
16
|
+
s.email = "thomas.luce@gmail.com"
|
17
|
+
s.executables = ["servicy"]
|
18
|
+
s.extra_rdoc_files = [
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
"README.md",
|
23
|
+
"Servicy.gemspec",
|
24
|
+
"VERSION",
|
25
|
+
"bin/servicy",
|
26
|
+
"lib/api.rb",
|
27
|
+
"lib/client.rb",
|
28
|
+
"lib/hash.rb",
|
29
|
+
"lib/load_balancer.rb",
|
30
|
+
"lib/load_balancer/random.rb",
|
31
|
+
"lib/load_balancer/round_robin.rb",
|
32
|
+
"lib/server.rb",
|
33
|
+
"lib/server/server.rb",
|
34
|
+
"lib/server/service_searcher.rb",
|
35
|
+
"lib/service.rb",
|
36
|
+
"lib/servicy.rb",
|
37
|
+
"lib/transport/in_memory_transport.rb",
|
38
|
+
"lib/transport/messages.rb",
|
39
|
+
"lib/transport/tcp_transport.rb",
|
40
|
+
"lib/transports.rb",
|
41
|
+
"test.rb"
|
42
|
+
]
|
43
|
+
s.homepage = "https://github.com/thomasluce/servicy"
|
44
|
+
s.rubygems_version = "2.2.2"
|
45
|
+
s.summary = "A service registration and discovery framework"
|
46
|
+
end
|
47
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.3
|
data/bin/servicy
ADDED
@@ -0,0 +1,305 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'rubygems'
|
6
|
+
require 'servicy'
|
7
|
+
require 'transport/tcp_transport'
|
8
|
+
require 'pp'
|
9
|
+
require 'fileutils'
|
10
|
+
|
11
|
+
VERSION = File.read(File.expand_path(File.join(File.dirname(__FILE__), '..', 'VERSION')))
|
12
|
+
|
13
|
+
# The following couple of functions are taking from the daemons gem. I don't
|
14
|
+
# need all it's functionality, and it's configuration options don't give me
|
15
|
+
# what I want. Given that I only need very simple daemonizing options here,
|
16
|
+
# this will be fine.
|
17
|
+
def safefork
|
18
|
+
tryagain = true
|
19
|
+
while tryagain
|
20
|
+
tryagain = false
|
21
|
+
begin
|
22
|
+
if pid = fork
|
23
|
+
return pid
|
24
|
+
else
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
rescue Errno::EWOULDBLOCK
|
28
|
+
sleep 5
|
29
|
+
tryagain = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def redirect_io(logfile_name)
|
35
|
+
begin; STDIN.reopen "/dev/null"; rescue ::Exception; end
|
36
|
+
|
37
|
+
if logfile_name
|
38
|
+
begin
|
39
|
+
STDOUT.reopen logfile_name, "a"
|
40
|
+
File.chmod(0644, logfile_name)
|
41
|
+
STDOUT.sync = true
|
42
|
+
rescue ::Exception
|
43
|
+
begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
|
44
|
+
end
|
45
|
+
else
|
46
|
+
begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
|
47
|
+
end
|
48
|
+
|
49
|
+
begin; STDERR.reopen STDOUT; rescue ::Exception; end
|
50
|
+
STDERR.sync = true
|
51
|
+
end
|
52
|
+
|
53
|
+
def make_daemon(logfile_name=nil, pidfile_name=nil)
|
54
|
+
# Fork and exit from the parent
|
55
|
+
safefork and exit
|
56
|
+
|
57
|
+
# Detach from the controlling terminal
|
58
|
+
unless sess_id = Process.setsid
|
59
|
+
raise 'cannot detach from controlling terminal'
|
60
|
+
end
|
61
|
+
|
62
|
+
# Prevent the possibility of acquiring a controlling terminal
|
63
|
+
trap 'SIGHUP', 'IGNORE'
|
64
|
+
exit if pid = safefork
|
65
|
+
|
66
|
+
# Release old working directory
|
67
|
+
Dir.chdir "/"
|
68
|
+
|
69
|
+
# Make stdout and stderr go to our log if defined. /dev/null otherwise.
|
70
|
+
redirect_io(logfile_name)
|
71
|
+
|
72
|
+
# Split rand streams between spawning and daemonized process
|
73
|
+
srand
|
74
|
+
|
75
|
+
# Write a pidfile
|
76
|
+
File.open(pidfile_name, 'w') { |f| f.puts Process.pid }
|
77
|
+
|
78
|
+
# Set the service name so that ps makes sense.
|
79
|
+
$0 = 'servicy'
|
80
|
+
|
81
|
+
return sess_id
|
82
|
+
end
|
83
|
+
|
84
|
+
class ServicyOptionParser
|
85
|
+
def self.parse(command, command_command, args)
|
86
|
+
options = OpenStruct.new
|
87
|
+
options.port = 8000
|
88
|
+
options.daemonize = false
|
89
|
+
options.config_file = File.expand_path("~/.servicy.conf")
|
90
|
+
options.pid_file = File.expand_path("./servicy.pid")
|
91
|
+
options.log_file = File.expand_path('./servicy.log')
|
92
|
+
options.service_version = "1.0.0"
|
93
|
+
options.protocol = "HTTP/S"
|
94
|
+
options.server_host = "localhost"
|
95
|
+
options.command = %w(client server).include?(command) ? command : nil
|
96
|
+
options.command_command = %w(start stop status info register unregister search).include?(command_command) ? command_command : nil
|
97
|
+
|
98
|
+
OptionParser.new do |opts|
|
99
|
+
opts.banner = "Usage: servicy [command] [command command] [command options]"
|
100
|
+
opts.separator "Where command is one of: server, client"
|
101
|
+
opts.separator "And command-command is a command to send to either the client or the server."
|
102
|
+
opts.separator ""
|
103
|
+
|
104
|
+
opts.separator "Server commands:"
|
105
|
+
opts.separator " start - Start the server"
|
106
|
+
opts.separator " stop - Stop a running server"
|
107
|
+
opts.separator " status - Get weather or not a server is running"
|
108
|
+
opts.separator " info - Get info about a running server"
|
109
|
+
|
110
|
+
opts.separator ""
|
111
|
+
opts.separator "Server command options:"
|
112
|
+
|
113
|
+
opts.on '-p', '--port PORT', Integer, "Set the port for the server to listen on. Defaults to #{options.port}" do |port|
|
114
|
+
options.port = port
|
115
|
+
end
|
116
|
+
|
117
|
+
opts.on '-d', '--daemon', "Daemonize after starting server. Defaults to #{options.daemonize}" do
|
118
|
+
options.daemonize = true
|
119
|
+
end
|
120
|
+
|
121
|
+
opts.on '-f', '--config CONFIG_FILE', String, "Set the location for the service config file. Defaults to #{options.config_file}" do |file|
|
122
|
+
options.config_file = File.expand_path(file)
|
123
|
+
end
|
124
|
+
|
125
|
+
opts.on '-i', '--pid PID_FILE', String, "Set the PID file location. Defaults to #{options.pid_file}" do |file|
|
126
|
+
options.pid_file = File.expand_path(file)
|
127
|
+
end
|
128
|
+
|
129
|
+
opts.on '-l', '--logfile LOG_FILE', String, "Location of log file. Defaults to #{options.log_file}" do |file|
|
130
|
+
options.log_file = File.expand_path(file)
|
131
|
+
end
|
132
|
+
|
133
|
+
opts.separator ""
|
134
|
+
opts.separator "Client commands:"
|
135
|
+
opts.separator " register - Register a new service"
|
136
|
+
opts.separator " unregister - Un-register a registered service"
|
137
|
+
opts.separator " search - Search for a registered service"
|
138
|
+
|
139
|
+
opts.separator ""
|
140
|
+
opts.separator "Client command options:"
|
141
|
+
|
142
|
+
opts.on '-n', '--name NAME', String, "The name of the service" do |name|
|
143
|
+
options.service_name = name
|
144
|
+
end
|
145
|
+
|
146
|
+
opts.on '-h', '--host HOST', String, "The hostname or IP-address of the service" do |host|
|
147
|
+
options.service_host = host
|
148
|
+
end
|
149
|
+
|
150
|
+
opts.on '-H', '--server-host HOST', String, "The host where the service registrar can be found. Defaults to #{options.server_host}" do |host|
|
151
|
+
options.server_host = host
|
152
|
+
end
|
153
|
+
|
154
|
+
opts.on '-s', '--service-port PORT', Integer, "The port the service is on" do |port|
|
155
|
+
options.service_port = port
|
156
|
+
end
|
157
|
+
|
158
|
+
opts.on '-b', '--heartbeat-port PORT', Integer, "The port that the services' heartbeat service lives. Defaults to the service port" do |port|
|
159
|
+
options.heartbeat_port = port
|
160
|
+
end
|
161
|
+
|
162
|
+
opts.on '-e', '--service-version VERSION', String, "The version of the service. Defaults to #{options.service_version}" do |version|
|
163
|
+
options.version = version
|
164
|
+
end
|
165
|
+
|
166
|
+
opts.on '-r', '--protocol PROTO', String, "The protocol of the service. Defaults to #{options.protocol}" do |protocol|
|
167
|
+
options.protocol = protocol
|
168
|
+
end
|
169
|
+
|
170
|
+
opts.on '-a', '--api API', String, "API description for the service. See documentation for shorthand help." do |api|
|
171
|
+
options.api = api
|
172
|
+
end
|
173
|
+
|
174
|
+
opts.separator ""
|
175
|
+
opts.separator "Common options:"
|
176
|
+
|
177
|
+
opts.on "--help", "Show this message and exit" do
|
178
|
+
puts opts
|
179
|
+
exit
|
180
|
+
end
|
181
|
+
|
182
|
+
opts.on "-v", "--version", "Show current version and exit" do
|
183
|
+
puts VERSION
|
184
|
+
exit
|
185
|
+
end
|
186
|
+
|
187
|
+
opts.separator ""
|
188
|
+
opts.separator "Examples:"
|
189
|
+
opts.separator ""
|
190
|
+
|
191
|
+
opts.separator "Start a service discovery/registration server on port 3214"
|
192
|
+
opts.separator " servicy server start -p 3214"
|
193
|
+
opts.separator ""
|
194
|
+
|
195
|
+
opts.separator "Register a service at the server on 192.168.0.1, port 1234"
|
196
|
+
opts.separator " servicy client register -n com.foo.bar -p 1234 -H localhost -h localhost -s 1234"
|
197
|
+
opts.separator ""
|
198
|
+
|
199
|
+
opts.separator "Find a service"
|
200
|
+
opts.separator " servicy client search -H 192.168.0.1 -n 'com.bar.foo'"
|
201
|
+
opts.separator ""
|
202
|
+
|
203
|
+
if !command || !command_command
|
204
|
+
puts opts
|
205
|
+
exit
|
206
|
+
end
|
207
|
+
end.parse!
|
208
|
+
|
209
|
+
return options
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class Command
|
214
|
+
def initialize(options)
|
215
|
+
@options = options
|
216
|
+
end
|
217
|
+
|
218
|
+
def method_missing(name, *args, &block)
|
219
|
+
@options[name]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
class ServerCommand < Command
|
224
|
+
def start
|
225
|
+
transport = Servicy::TCPTransport.new(port: port)
|
226
|
+
if File.exists?(config_file)
|
227
|
+
server = Servicy::Server.load(config_file)
|
228
|
+
server.transport = transport
|
229
|
+
else
|
230
|
+
server = Servicy::Server.new(transport, nil, STDOUT, config_file)
|
231
|
+
end
|
232
|
+
|
233
|
+
make_daemon(log_file, pid_file) if daemonize
|
234
|
+
server.go!
|
235
|
+
end
|
236
|
+
|
237
|
+
def stop
|
238
|
+
# TODO: At some point in the future, this should send a shutdown command to
|
239
|
+
# give the server time to do its thing gracefully.
|
240
|
+
raise "Pid not found" if pid == 0 || !pid
|
241
|
+
Process.kill('TERM', pid)
|
242
|
+
FileUtils.rm(pid_file)
|
243
|
+
end
|
244
|
+
|
245
|
+
def info
|
246
|
+
client = Servicy::Client.new(Servicy::TCPTransport.new(port: port))
|
247
|
+
pp client.server_stats.struct
|
248
|
+
end
|
249
|
+
|
250
|
+
def status
|
251
|
+
Process.getpgid(pid)
|
252
|
+
puts "Server is running"
|
253
|
+
rescue
|
254
|
+
puts "Server is not running"
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def pid
|
260
|
+
File.read(pid_file).strip.to_i
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
class ClientCommand < Command
|
265
|
+
def register
|
266
|
+
client.register_service({name: service_name,
|
267
|
+
host: service_host,
|
268
|
+
port: service_port,
|
269
|
+
heatbeat_port: heartbeat_port,
|
270
|
+
version: version,
|
271
|
+
protocol: protocol
|
272
|
+
})
|
273
|
+
|
274
|
+
# TODO: api parsing and registration. I want to get the search done for
|
275
|
+
# that first.
|
276
|
+
end
|
277
|
+
|
278
|
+
def unregister
|
279
|
+
end
|
280
|
+
|
281
|
+
def search
|
282
|
+
puts client.find_service(name: service_name,
|
283
|
+
host: service_host,
|
284
|
+
port: service_port,
|
285
|
+
heartbeat_port: heartbeat_port,
|
286
|
+
version: version,
|
287
|
+
protocol: protocol
|
288
|
+
).as_json.to_json
|
289
|
+
end
|
290
|
+
|
291
|
+
private
|
292
|
+
|
293
|
+
def client
|
294
|
+
Servicy::Client.new(Servicy::TCPTransport.new(port: port))
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
if ARGV.first.to_s.start_with? "-"
|
299
|
+
ServicyOptionParser.parse(nil, nil, ARGV)
|
300
|
+
exit # Help or version
|
301
|
+
else
|
302
|
+
options = ServicyOptionParser.parse(ARGV.shift, ARGV.shift, ARGV)
|
303
|
+
klass = options.command == 'server' ? ServerCommand : ClientCommand
|
304
|
+
klass.new(options).send(options.command_command)
|
305
|
+
end
|