servicy 0.0.3
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.
- 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
|