jerbil 1.2.2
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.
- data/Bugs.rdoc +66 -0
- data/Gemfile +12 -0
- data/History.txt +359 -0
- data/Intro.txt +5 -0
- data/LICENCE.rdoc +159 -0
- data/README.md +335 -0
- data/README_SERVICES.md +410 -0
- data/README_TESTING.md +47 -0
- data/bin/jerbil +62 -0
- data/bin/jerbil-install +56 -0
- data/etc/conf.d/jerbild +15 -0
- data/etc/conf.d/jserviced +39 -0
- data/etc/init.d/jerbild +55 -0
- data/etc/init.d/jserviced +59 -0
- data/etc/jerbil/jerbil-client.rb +2 -0
- data/etc/jerbil/jerbil.rb +83 -0
- data/lib/jerbil.rb +636 -0
- data/lib/jerbil/config.md +49 -0
- data/lib/jerbil/config.rb +67 -0
- data/lib/jerbil/errors.rb +74 -0
- data/lib/jerbil/jerbil_service/base.rb +191 -0
- data/lib/jerbil/jerbil_service/client.rb +325 -0
- data/lib/jerbil/jerbil_service/config.md +119 -0
- data/lib/jerbil/jerbil_service/config.rb +72 -0
- data/lib/jerbil/jerbil_service/sclient.rb +343 -0
- data/lib/jerbil/jerbil_service/support.rb +58 -0
- data/lib/jerbil/jerbil_service/utils.rb +35 -0
- data/lib/jerbil/servers.rb +230 -0
- data/lib/jerbil/service.rb +216 -0
- data/lib/jerbil/support.rb +160 -0
- data/lib/jerbil/thor/server.rb +76 -0
- data/lib/jerbil/thor/service.rb +74 -0
- data/lib/jerbil/version.rb +13 -0
- data/sbin/jerbil-status +120 -0
- data/sbin/jerbil-stop +139 -0
- data/sbin/jerbild +186 -0
- data/sbin/jservice-status +107 -0
- data/sbin/jservice-stop +94 -0
- data/sbin/jserviced +111 -0
- data/spec/jerbil_2_jerbil_spec.rb +87 -0
- data/spec/jerbil_client1_spec.rb +80 -0
- data/spec/jerbil_client_spec.rb +114 -0
- data/spec/jerbil_client_stop_spec.rb +24 -0
- data/spec/jerbil_daemonised/jerbil_local_spec.rb +81 -0
- data/spec/jerbil_daemonised/jerbil_remote_spec.rb +116 -0
- data/spec/jerbil_load.rb +48 -0
- data/spec/jerbil_local_spec.rb +91 -0
- data/spec/jerbil_missing_spec.rb +98 -0
- data/spec/jerbil_remote_spec.rb +117 -0
- data/spec/jerbil_remote_spec_bup.rb +168 -0
- data/spec/jerbil_service_error_spec.rb +56 -0
- data/spec/jerbil_service_spec.rb +41 -0
- data/spec/jerbil_support_spec.rb +69 -0
- data/spec/jservice_utils_spec.rb +38 -0
- data/spec/server_spec.rb +69 -0
- data/spec/server_update_spec.rb +28 -0
- data/spec/service_spec.rb +72 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/test_env_spec.rb +53 -0
- data/test/bad_test_service.rb +31 -0
- data/test/conf.d/jerbil +36 -0
- data/test/conf.d/jerbil.conf +39 -0
- data/test/conf.d/jerbil.rb +55 -0
- data/test/conf.d/jerbil_local.rb +33 -0
- data/test/conf.d/jerbil_no_local.conf +39 -0
- data/test/conf.d/jerbil_old.rb +47 -0
- data/test/conf.d/jerbil_test.rb +35 -0
- data/test/conf.d/malformed +1 -0
- data/test/conf.d/missing_services +39 -0
- data/test/conf.d/ruby_test.rb +8 -0
- data/test/init.d/jerbild +14 -0
- data/test/jerbil.rb +51 -0
- data/test/jerbil_config.rb +8 -0
- data/test/jstop.rb +36 -0
- data/test/key.asc +1 -0
- data/test/lib/ruby_test.rb +37 -0
- data/test/lib/ruby_test/config.rb +56 -0
- data/test/lib/ruby_test/version.rb +13 -0
- data/test/pids/jerbil-prod.asc +1 -0
- data/test/pids/jerbil-prod.pid +1 -0
- data/test/pids/jerbil.pid +1 -0
- data/test/private_key_file.asc +3 -0
- data/test/service-stop.rb +86 -0
- data/test/service_mock.rb +94 -0
- data/test/test_service_client.rb +25 -0
- metadata +265 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# Jerbil::Config Parameters
|
2
|
+
|
3
|
+
The following parameters are defined in {Jerbil::Config} and should be used
|
4
|
+
in a configuration file. A default config file can be generated using:
|
5
|
+
|
6
|
+
jeckyl config lib/jerbil/config.rb
|
7
|
+
|
8
|
+
## Parameters
|
9
|
+
|
10
|
+
* **net_mask**
|
11
|
+
|
12
|
+
A valid netmask for the hosts to search using the above net address. This should be
|
13
|
+
between 24 (a class C network) and 30, beyound which its not much of a network. If you only have a few
|
14
|
+
hosts it will be easier to restrict them to a small subnet.
|
15
|
+
|
16
|
+
To find out more about netmasks, go to [UnixWiz](http://www.unixwiz.net/techtips/netmask-ref.html).
|
17
|
+
|
18
|
+
Default: 26
|
19
|
+
|
20
|
+
* **scan_timeout**
|
21
|
+
|
22
|
+
Provide a timeout when searching for jerbil servers on the net during startup.
|
23
|
+
Depending on the size of the net mask this timeout may make the search long.
|
24
|
+
The default should work in most cases
|
25
|
+
|
26
|
+
Default: 0.1
|
27
|
+
|
28
|
+
* **net_address**
|
29
|
+
|
30
|
+
A valid IPv4 address for the LAN on which the servers will operate.
|
31
|
+
Note that the broker uses this address to search for all servers.
|
32
|
+
Therefore a large range will take a long time to search. Set the net_mask to limit this.
|
33
|
+
|
34
|
+
Default: "192.168.0.1"
|
35
|
+
|
36
|
+
* **secret**
|
37
|
+
|
38
|
+
A secret key available to all Jerbil Servers and used to authenticate the initial registration.
|
39
|
+
If security is an issue, ensure that this config file is readable only be trusted users
|
40
|
+
|
41
|
+
No default set
|
42
|
+
|
43
|
+
|
44
|
+
## See Also
|
45
|
+
|
46
|
+
There are also parameters in:
|
47
|
+
|
48
|
+
* {JerbilService::Config}
|
49
|
+
* Jelly::Options
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
# = Jerbil Config
|
4
|
+
#
|
5
|
+
# == Update to use Jeckyl::Service
|
6
|
+
#
|
7
|
+
# Author:: Robert Sharp
|
8
|
+
# Copyright:: Copyright (c) 2012 Robert Sharp
|
9
|
+
# License:: Open Software Licence v3.0
|
10
|
+
#
|
11
|
+
# This software is licensed for use under the Open Software Licence v. 3.0
|
12
|
+
# The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
13
|
+
# and in the file copyright.txt. Under the terms of this licence, all derivative works
|
14
|
+
# must themselves be licensed under the Open Software Licence v. 3.0
|
15
|
+
#
|
16
|
+
#
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'jerbil/jerbil_service/config'
|
20
|
+
|
21
|
+
|
22
|
+
module Jerbil
|
23
|
+
|
24
|
+
# Jeckyl config parameters for the Jerbil Server
|
25
|
+
#
|
26
|
+
# The config file will include all of the parameters defined in {JerbilService::Config}
|
27
|
+
# and its parents, such as key directories, logging parameters etc.
|
28
|
+
#
|
29
|
+
# @see file:lib/jerbil/config.md Jerbil Parameter Descriptions
|
30
|
+
# @see file:lib/jerbil/jerbil_service/config.md Jerbil Service Parameter Descriptions
|
31
|
+
class Config < JerbilService::Config
|
32
|
+
|
33
|
+
def configure_net_address(naddr)
|
34
|
+
default '192.168.0.1'
|
35
|
+
comment "A valid IPv4 address for the LAN on which the servers will operate.",
|
36
|
+
"Note that the broker uses this address to search for all servers.",
|
37
|
+
"Therefore a large range will take a long time to search. Set the net_mask to limit this."
|
38
|
+
a_matching_string(naddr, /\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/)
|
39
|
+
end
|
40
|
+
|
41
|
+
def configure_net_mask(nmask)
|
42
|
+
default 26
|
43
|
+
comment "A valid netmask for the hosts to search using the above net address. This should be",
|
44
|
+
"between 24 (a class C network) and 30, beyound which its not much of a network. If you only have a few",
|
45
|
+
"hosts it will be easier to restrict them to a small subnet.",
|
46
|
+
"",
|
47
|
+
"To find out more about netmasks, go to [UnixWiz](http://www.unixwiz.net/techtips/netmask-ref.html)."
|
48
|
+
in_range(nmask, 24, 30)
|
49
|
+
end
|
50
|
+
|
51
|
+
def configure_scan_timeout(tim)
|
52
|
+
default 0.1
|
53
|
+
comment "Provide a timeout when searching for jerbil servers on the net during startup.",
|
54
|
+
"Depending on the size of the net mask this timeout may make the search long.",
|
55
|
+
"The default should work in most cases"
|
56
|
+
|
57
|
+
a_type_of(tim, Numeric)
|
58
|
+
end
|
59
|
+
|
60
|
+
def configure_secret(scrt)
|
61
|
+
comment "A secret key available to all Jerbil Servers and used to authenticate the initial registration.",
|
62
|
+
"If security is an issue, ensure that this config file is readable only be trusted users"
|
63
|
+
a_type_of(scrt, String)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#
|
2
|
+
# Description
|
3
|
+
#
|
4
|
+
# Author:: Robert Sharp
|
5
|
+
# Copyright:: Copyright (c) 2010 Robert Sharp
|
6
|
+
# License:: Open Software Licence v3.0
|
7
|
+
#
|
8
|
+
# This software is licensed for use under the Open Software Licence v. 3.0
|
9
|
+
# The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
10
|
+
# and in the file copyright.txt. Under the terms of this licence, all derivative works
|
11
|
+
# must themselves be licensed under the Open Software Licence v. 3.0
|
12
|
+
#
|
13
|
+
#
|
14
|
+
|
15
|
+
module Jerbil
|
16
|
+
|
17
|
+
# general error type
|
18
|
+
class JerbilError < RuntimeError; end
|
19
|
+
|
20
|
+
# create a class for all service related errors
|
21
|
+
class JerbilServiceError < JerbilError; end
|
22
|
+
|
23
|
+
# error while trying to connect to a Jerbil-registered service
|
24
|
+
class ServiceConnectError < JerbilServiceError; end
|
25
|
+
|
26
|
+
# the service being registered is not in /etc/services
|
27
|
+
class InvalidService < JerbilServiceError; end
|
28
|
+
|
29
|
+
# the config file used by the service raised an error
|
30
|
+
class ServiceConfigError < JerbilServiceError; end
|
31
|
+
|
32
|
+
# the key used was not the key created for this service
|
33
|
+
class InvalidServiceKey < JerbilServiceError; end
|
34
|
+
|
35
|
+
# there is no callback defined where one should be
|
36
|
+
class ServiceCallbackMissing < JerbilServiceError; end
|
37
|
+
|
38
|
+
# there is already a service with these details registered with the Jerbil Server
|
39
|
+
class ServiceAlreadyRegistered < JerbilServiceError; end
|
40
|
+
|
41
|
+
# the service being registered is not local
|
42
|
+
class ServiceNotLocal < JerbilServiceError; end
|
43
|
+
|
44
|
+
# tried to do something to a service without access
|
45
|
+
class UnauthorizedMethod < JerbilServiceError; end
|
46
|
+
|
47
|
+
# there is no service
|
48
|
+
class ServiceNotFound < JerbilServiceError; end
|
49
|
+
|
50
|
+
# create a class for all server related errors
|
51
|
+
class JerbilServerError < JerbilError; end
|
52
|
+
|
53
|
+
# authentication of a server etc failed
|
54
|
+
class JerbilAuthenticationError < JerbilError; end
|
55
|
+
|
56
|
+
# error in the jerbil config file
|
57
|
+
class JerbilConfigError < JerbilServerError; end
|
58
|
+
|
59
|
+
# no jerbil service
|
60
|
+
class MissingJerbilService < JerbilServerError; end
|
61
|
+
|
62
|
+
# failed to find the server in the list of servers provided in config
|
63
|
+
class MissingServer < JerbilServerError; end
|
64
|
+
|
65
|
+
# error while trying to connect to a Jerbil Server
|
66
|
+
class ServerConnectError < JerbilServerError; end
|
67
|
+
|
68
|
+
# the server key provided for a remote operation does not match any known server
|
69
|
+
class InvalidServerKey < JerbilServerError; end
|
70
|
+
|
71
|
+
# the master key provided for a system operation does not match this master key
|
72
|
+
class InvalidPrivateKey < JerbilServerError; end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
#
|
2
|
+
# Description
|
3
|
+
#
|
4
|
+
# Author:: Robert Sharp
|
5
|
+
# Copyright:: Copyright (c) 2010 Robert Sharp
|
6
|
+
# License:: Open Software Licence v3.0
|
7
|
+
#
|
8
|
+
# This software is licensed for use under the Open Software Licence v. 3.0
|
9
|
+
# The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
10
|
+
# and in the file copyright.txt. Under the terms of this licence, all derivative works
|
11
|
+
# must themselves be licensed under the Open Software Licence v. 3.0
|
12
|
+
#
|
13
|
+
#
|
14
|
+
require 'jerbil/service'
|
15
|
+
require 'jerbil/servers'
|
16
|
+
require 'jerbil/errors'
|
17
|
+
require 'jerbil/config'
|
18
|
+
require 'jerbil/support'
|
19
|
+
require 'jellog'
|
20
|
+
require 'jellog/config'
|
21
|
+
require 'socket'
|
22
|
+
require 'drb'
|
23
|
+
|
24
|
+
# == Jerbil Service
|
25
|
+
#
|
26
|
+
# Designed to help create ruby services easily, hiding all of the interactions with
|
27
|
+
# Jerbil itself.
|
28
|
+
#
|
29
|
+
# To create a service, create a service module (e.g. RubyTest) and within that a
|
30
|
+
# service class (e.g. RubyTest::Service) that whose parent is JerbilService::Base
|
31
|
+
# You can add a config file by copying the jerbil_service/config template and changing
|
32
|
+
# the module to your module (e.g. RubyTest::Config). Finally, use the jerbil_service/support
|
33
|
+
# module to extend your base module to include the get_config method.
|
34
|
+
#
|
35
|
+
# module RubyTest
|
36
|
+
#
|
37
|
+
# extend JerbilService::Support
|
38
|
+
#
|
39
|
+
# class Service < JerbilService::Base
|
40
|
+
#
|
41
|
+
# def initialize(pkey, options)
|
42
|
+
#
|
43
|
+
# super(:rubytest, pkey, options)
|
44
|
+
#
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# See also the Client, MultiClient and SuperClient classes to assist with a complete solution.
|
50
|
+
#
|
51
|
+
#
|
52
|
+
module JerbilService
|
53
|
+
|
54
|
+
# == JerbilService::Base
|
55
|
+
#
|
56
|
+
# Parent class to be used for all services. Manages all interactions with Jerbil
|
57
|
+
# and sets up a Jellog logger.
|
58
|
+
#
|
59
|
+
# In general, services using this class are intended to be started and stopped
|
60
|
+
# using {JerbilService::Supervisor} and clients are expected to interact with the
|
61
|
+
# service through {JerbilService::Client}.
|
62
|
+
#
|
63
|
+
# Further details can be found in {file:README_SERVICES.md Jerbil Service Readme}
|
64
|
+
#
|
65
|
+
class Base
|
66
|
+
|
67
|
+
# create a Jerbil Service
|
68
|
+
#
|
69
|
+
# @param [String] name - symbol for the service needs to correspond with /etc/services
|
70
|
+
# @param [String] pkey - private key used for privileged methods
|
71
|
+
# @param [Hash] options for the service. The easiest way to generate this hash is to use
|
72
|
+
# Jeckyl and inherit the {JerbilService::Config} class. See class description for details of
|
73
|
+
# the options required by a service
|
74
|
+
def initialize(name, pkey, options)
|
75
|
+
@name = name.to_s.capitalize
|
76
|
+
@env = options[:environment]
|
77
|
+
@private_key = pkey
|
78
|
+
|
79
|
+
# change the process name
|
80
|
+
$0 = "#{@name.downcase}-#{@env}"
|
81
|
+
|
82
|
+
# start up a logger
|
83
|
+
log_opts = Jellog::Config.intersection(options)
|
84
|
+
@logger = Jellog::Logger.new(@name, log_opts)
|
85
|
+
# @logger.log_level = options[:log_level]
|
86
|
+
|
87
|
+
@exit = options[:exit_on_stop]
|
88
|
+
|
89
|
+
begin
|
90
|
+
@service = Jerbil::ServiceRecord.new(name, @env, :verify_callback, :stop_callback)
|
91
|
+
|
92
|
+
# register the service
|
93
|
+
# Note that if the options hash includes a :jerbil_env, then this
|
94
|
+
# will be passed to select a Jerbil system running at other than :prod, the default
|
95
|
+
# This is not documented above because it is for testing Jerbil only
|
96
|
+
@jerbil_server = Jerbil::Servers.get_local_server(options[:jerbil_env])
|
97
|
+
|
98
|
+
# now connect to it
|
99
|
+
jerbil = @jerbil_server.connect
|
100
|
+
|
101
|
+
# and register self
|
102
|
+
jerbil.register(@service)
|
103
|
+
|
104
|
+
# and start it - preventing anything nasty from coming over DRb
|
105
|
+
$SAFE = 1 unless options[:unsafe]
|
106
|
+
DRb.start_service(@service.drb_address, self)
|
107
|
+
|
108
|
+
rescue Jerbil::MissingServer
|
109
|
+
@logger.fatal("Cannot find a local Jerbil server")
|
110
|
+
raise
|
111
|
+
rescue Jerbil::JerbilConfigError => err
|
112
|
+
@logger.fatal("Error in Jerbil Config File: #{err.message}")
|
113
|
+
raise
|
114
|
+
rescue Jerbil::JerbilServiceError =>jerr
|
115
|
+
@logger.fatal("Error with Jerbil Service: #{jerr.message}")
|
116
|
+
raise
|
117
|
+
rescue Jerbil::ServerConnectError
|
118
|
+
@logger.fatal("Error connecting to Jerbil Server")
|
119
|
+
raise
|
120
|
+
rescue DRb::DRbConnError =>derr
|
121
|
+
@logger.fatal("Error setting up DRb Server: #{derr.message}")
|
122
|
+
raise Jerbil::ServerConnectError
|
123
|
+
end
|
124
|
+
|
125
|
+
@logger.system "Started service: #{@service.ident}"
|
126
|
+
end
|
127
|
+
|
128
|
+
# give access to {Jerbil::ServiceRecord} Record for this service
|
129
|
+
# @deprecated - unless I can find why I allowed it!
|
130
|
+
attr_reader :service
|
131
|
+
|
132
|
+
# return the DRb address for the service
|
133
|
+
# @deprecated - as above
|
134
|
+
def drb_address
|
135
|
+
@service.drb_address
|
136
|
+
end
|
137
|
+
|
138
|
+
# this is used by callers just to check that the service is running
|
139
|
+
# if caller is unaware of the key, this will fail
|
140
|
+
#
|
141
|
+
# @param [String] skey - private key given to the service when created
|
142
|
+
#
|
143
|
+
# This method is not intended to be called directly. It is usually
|
144
|
+
# called when connecting to the service (see {Jerbil::ServiceRecord#connect}).
|
145
|
+
#
|
146
|
+
def verify_callback(skey="")
|
147
|
+
check_key(skey, @service.key)
|
148
|
+
@logger.info "Verify called"
|
149
|
+
return true
|
150
|
+
end
|
151
|
+
|
152
|
+
# used to stop the service. Requires the private key
|
153
|
+
#
|
154
|
+
# @param [String] pkey - private key given when service is created
|
155
|
+
#
|
156
|
+
# This method is not intended to be called directly. To stop a service, the
|
157
|
+
# user should use the {JerbilService::Supervisor} class.
|
158
|
+
#
|
159
|
+
def stop_callback(pkey="")
|
160
|
+
check_key(pkey)
|
161
|
+
# deregister
|
162
|
+
jerbil = @jerbil_server.connect
|
163
|
+
jerbil.remove(@service)
|
164
|
+
@logger.system "Stopped service: #{@service.ident}"
|
165
|
+
@logger.close
|
166
|
+
# and stop the DRb service, to exit gracefully
|
167
|
+
DRb.stop_service
|
168
|
+
end
|
169
|
+
|
170
|
+
# wait for calls. This hides the DRb call to be made once the
|
171
|
+
# service is up and running. It is not intended to be called by
|
172
|
+
# anyone outside of {JerbilService::Supervisor}
|
173
|
+
#
|
174
|
+
def wait(pkey='')
|
175
|
+
check_key(pkey)
|
176
|
+
DRb.thread.join
|
177
|
+
end
|
178
|
+
|
179
|
+
protected
|
180
|
+
|
181
|
+
# convenience method to check that the given key is the object's private key
|
182
|
+
# and raise {Jerbil::InvalidServiceKey} if not
|
183
|
+
def check_key(key, my_key = @private_key)
|
184
|
+
if key != my_key then
|
185
|
+
@logger.debug "Key mismatch: given #{key} but need #{my_key}"
|
186
|
+
raise Jerbil::InvalidServiceKey, @logger.error("Call made with Invalid Service Key: #{key}")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
#
|
2
|
+
# == Jerbil Client Interface
|
3
|
+
#
|
4
|
+
# Author:: Robert Sharp
|
5
|
+
# Copyright:: Copyright (c) 2011 Robert Sharp
|
6
|
+
# License:: Open Software Licence v3.0
|
7
|
+
#
|
8
|
+
# This software is licensed for use under the Open Software Licence v. 3.0
|
9
|
+
# The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
10
|
+
# and in the file copyright.txt. Under the terms of this licence, all derivative works
|
11
|
+
# must themselves be licensed under the Open Software Licence v. 3.0
|
12
|
+
#
|
13
|
+
#
|
14
|
+
|
15
|
+
require 'jerbil/config'
|
16
|
+
require 'jeckyl/errors'
|
17
|
+
require 'jerbil/errors'
|
18
|
+
require 'jerbil/servers'
|
19
|
+
require 'jerbil/service'
|
20
|
+
require 'socket'
|
21
|
+
|
22
|
+
module JerbilService
|
23
|
+
|
24
|
+
|
25
|
+
#
|
26
|
+
# JerbilService::Client provides a wrapper around a {JerbilService::Base} type service that hides
|
27
|
+
# all interaction with Jerbil itself, enabling client interfaces with a service to be constructed
|
28
|
+
# quickly and painlessly
|
29
|
+
#
|
30
|
+
# To use, call the {JerbilService::Client.find_services} method, which yields a block for
|
31
|
+
# each matching service registered with the Jerbil Server. The service's methods can then
|
32
|
+
# be called on the yielded object, which are then transparently passed back to the
|
33
|
+
# service itself.
|
34
|
+
#
|
35
|
+
# It should always be remembered that these method calls will be mediated through Ruby's
|
36
|
+
# DRb mechanisms and therefore access to objects across this interface may not be
|
37
|
+
# the same as accessing them directly. See {file:README_SERVICES.md Services Readme} for
|
38
|
+
# more details
|
39
|
+
#
|
40
|
+
class Client
|
41
|
+
|
42
|
+
private_class_method :new
|
43
|
+
|
44
|
+
# return the server's config options for the given file, or the default if none given
|
45
|
+
#
|
46
|
+
# @param [Object] modl must be the constant name of the service's module.
|
47
|
+
# @param [String] config_file is an optional path to a Jeckyl config file
|
48
|
+
#
|
49
|
+
# This method uses [Jeckyl]() to obtain the config parameters. The module name
|
50
|
+
# is expected to follow the guidelines for a JerbilService: create a module
|
51
|
+
# with the service's name (echoed in the gem name etc) and then a class
|
52
|
+
# called Service. The parameter passed in is the module name as a constant
|
53
|
+
#
|
54
|
+
# config = JerbilService::Client.get_config(MyService)
|
55
|
+
#
|
56
|
+
def self.get_config(modl, config_file=nil)
|
57
|
+
modl.get_config(config_file)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Connect to a one or more instances of a service using Jerbil
|
61
|
+
#
|
62
|
+
#
|
63
|
+
# The method will search the Jerbil Server for services of the given module, as
|
64
|
+
# defined by the what parameter:
|
65
|
+
# * :first, yields the block once with the first service regardless of how many services there are
|
66
|
+
# and with no guarantee about which services this might be
|
67
|
+
# * :local, yields the service running on the same processor as the client
|
68
|
+
# * :all, yields the block for each service.
|
69
|
+
# Within the block, the user can call any of the service's methods, together with the
|
70
|
+
# instance methods of the client itself.
|
71
|
+
#
|
72
|
+
# For example, to find a service running on a particular host:
|
73
|
+
#
|
74
|
+
# JerbilService::Client.find_services(:all, MyService, opts) do |service|
|
75
|
+
# if service.host == 'a_server.network.com' then
|
76
|
+
# # found the services I am looking for, now do something
|
77
|
+
# ...
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# To find a service running in a particular environment, you need to set the
|
82
|
+
# above option:
|
83
|
+
# opts = {:environment => :dev}
|
84
|
+
# JerbilService::Client.find_services(:all, MyService, opts) do ...
|
85
|
+
#
|
86
|
+
# If you want to use a config file to access this information, you can use
|
87
|
+
# {JerbilService::Client.get_config} and extract the env information:
|
88
|
+
#
|
89
|
+
# config = JerbilService::Client.get_config(MyService)
|
90
|
+
# opts[:environment] = config[:environment]
|
91
|
+
# ...
|
92
|
+
#
|
93
|
+
# The connection to the service is closed when the block exits.
|
94
|
+
#
|
95
|
+
# @param [Symbol] what being one of :first, :all or :local
|
96
|
+
# @param [Object] modl - constant for the service module
|
97
|
+
# @param [Hash] options - hash of the following
|
98
|
+
# @option options [Boolean] :quiet suppress messages on stdout (default: false)
|
99
|
+
# @option options [Boolean] :local find only the local service (default: false)
|
100
|
+
# @option options [IO] :output a file object or similar (not filename) to be used
|
101
|
+
# for output - defaults to $stderr. Is overridden with /dev/null if quiet.
|
102
|
+
# @option options [Boolean] :welcome output additional messages to stdout suitable for standalone operation
|
103
|
+
# @option options [Symbol] :environment being that which the service is operating in (:dev, :test, :prod)
|
104
|
+
# @yield [service] a client instance for each matching service registered with Jerbil
|
105
|
+
def self.find_services(what, modl, options={}, &block)
|
106
|
+
|
107
|
+
case what
|
108
|
+
when :first
|
109
|
+
select = :first
|
110
|
+
when :all
|
111
|
+
select = :all
|
112
|
+
when :local
|
113
|
+
select = :first
|
114
|
+
options[:local] = true
|
115
|
+
else
|
116
|
+
raise ArgumentError, "Find Services invalid search key: #{what}"
|
117
|
+
end
|
118
|
+
|
119
|
+
unless options[:environment]
|
120
|
+
# set the default environment if not already set
|
121
|
+
options[:environment] = :prod
|
122
|
+
end
|
123
|
+
|
124
|
+
new(modl, options) do |client|
|
125
|
+
client.connect(select, &block)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# backwards compatible method to connect to first service using environment
|
130
|
+
# defined in config_file which is read in from options[:config_file] or the default
|
131
|
+
# location (see get_config)
|
132
|
+
#
|
133
|
+
# @deprecated Use {JerbilService::Client.find_services} instead
|
134
|
+
#
|
135
|
+
# @param (see find_services)
|
136
|
+
# @option (see find_services)
|
137
|
+
#
|
138
|
+
def self.connect(modl, options={}, &block) # :yields: client
|
139
|
+
|
140
|
+
config_file = options[:config_file]
|
141
|
+
config = modl.get_config(config_file)
|
142
|
+
|
143
|
+
options[:environment] ||= config[:environment]
|
144
|
+
options[:jerbil_env] ||= config[:jerbil_env]
|
145
|
+
|
146
|
+
self.find_services(:first, modl, options, &block)
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
# @private create a client instance and call the block. Should be used internally only
|
152
|
+
def initialize(modl, options, &block)
|
153
|
+
|
154
|
+
@klass = modl::Service
|
155
|
+
name = modl.to_s
|
156
|
+
name_symbol = name.downcase.to_sym
|
157
|
+
|
158
|
+
quiet = options[:quiet]
|
159
|
+
unless quiet
|
160
|
+
@output = options[:output] || $stderr
|
161
|
+
else
|
162
|
+
@output = File.open('/dev/null', 'w')
|
163
|
+
end
|
164
|
+
|
165
|
+
@welcome = options[:welcome]
|
166
|
+
|
167
|
+
@output.puts "Welcome to #{name} (#{modl.ident})" if @welcome
|
168
|
+
|
169
|
+
unless quiet
|
170
|
+
@output.puts "Options:"
|
171
|
+
options.each {|key, val| @output.puts(" #{key}:#{val}") }
|
172
|
+
end
|
173
|
+
|
174
|
+
env = options[:environment]
|
175
|
+
|
176
|
+
service_opts = {:name=>name_symbol, :env=>env}
|
177
|
+
service_opts[:host] = Socket.gethostname if options[:local]
|
178
|
+
|
179
|
+
begin
|
180
|
+
# find jerbil
|
181
|
+
@jerbil_server = Jerbil::Servers.get_local_server(options[:jerbil_env])
|
182
|
+
|
183
|
+
# now connect to it
|
184
|
+
jerbil = @jerbil_server.connect
|
185
|
+
|
186
|
+
# and find all of the services
|
187
|
+
@services = []
|
188
|
+
@services = jerbil.find(service_opts)
|
189
|
+
|
190
|
+
if @services.length == 0 then
|
191
|
+
@output.puts "No services for #{name}[:#{env}] were found"
|
192
|
+
raise Jerbil::ServiceNotFound, "No services for #{name}[:#{env}] were found"
|
193
|
+
end
|
194
|
+
|
195
|
+
block.call(self)
|
196
|
+
|
197
|
+
@output.puts "Stopping the service" if @welcome
|
198
|
+
#output.close unless @output == $stderr
|
199
|
+
|
200
|
+
return nil # give the caller nothing back
|
201
|
+
|
202
|
+
rescue Jerbil::MissingServer
|
203
|
+
@output.puts("Cannot find a local Jerbil server")
|
204
|
+
raise
|
205
|
+
rescue Jerbil::JerbilConfigError => err
|
206
|
+
@output.puts("Error in Jerbil Config File: #{err.message}")
|
207
|
+
raise
|
208
|
+
rescue Jerbil::JerbilServiceError =>jerr
|
209
|
+
@output.puts("Error with Jerbil Service: #{jerr.message}")
|
210
|
+
raise
|
211
|
+
rescue Jerbil::ServerConnectError
|
212
|
+
@output.puts("Error connecting to Jerbil Server")
|
213
|
+
raise
|
214
|
+
rescue DRb::DRbConnError =>derr
|
215
|
+
@output.puts("Error setting up DRb Server: #{derr.message}")
|
216
|
+
raise Jerbil::ServerConnectError
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
#private
|
222
|
+
|
223
|
+
# @private connect to the client
|
224
|
+
def connect(index=:all, &block)
|
225
|
+
|
226
|
+
if index == :first then
|
227
|
+
@session = @services[0].connect
|
228
|
+
@service = @services[0]
|
229
|
+
block.call(self)
|
230
|
+
|
231
|
+
@session = nil
|
232
|
+
else
|
233
|
+
@services.each do |service|
|
234
|
+
@service = service
|
235
|
+
@session = service.connect
|
236
|
+
block.call(self)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
# verify that the service is working
|
243
|
+
#
|
244
|
+
# This is really a no-op in that if the client has entered the block successfully, then
|
245
|
+
# the service that is passed to the block will be working and therefore this call
|
246
|
+
# will always return true. Consider it a placebo statement for a client interface
|
247
|
+
# that only wants to check the service is running!
|
248
|
+
#
|
249
|
+
# @return [Boolean] indicating if the services if running
|
250
|
+
#
|
251
|
+
def verify
|
252
|
+
@session != nil
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
# return the name of the host on which the service is running
|
257
|
+
#
|
258
|
+
# @return [String] FQDN of the host on which the service is running
|
259
|
+
def host
|
260
|
+
return @service.host
|
261
|
+
end
|
262
|
+
|
263
|
+
# return the service key for the given service, which can be used
|
264
|
+
# by the caller where a method requires. See {file:README_SERVICES.md Services Readme}
|
265
|
+
# for details about keys.
|
266
|
+
#
|
267
|
+
# @return [String] key to use to connect to service methods requiring it
|
268
|
+
def service_key
|
269
|
+
return @service.key
|
270
|
+
end
|
271
|
+
|
272
|
+
# @private allow client to pass on methods to the remote service
|
273
|
+
#
|
274
|
+
def method_missing(symb, *parameters)
|
275
|
+
|
276
|
+
# stop anyone from calling the stop method
|
277
|
+
raise Jerbil::UnauthorizedMethod if symb == :stop_callback
|
278
|
+
|
279
|
+
# make sure this is a valid method of the receiving instance
|
280
|
+
# This is needed cos sending an unknown method over DRb with $SAFE = 1
|
281
|
+
# raises a Security Error. Could catch this, but it might happen for
|
282
|
+
# different reasons so best not to.
|
283
|
+
|
284
|
+
# need to fix symbols problem
|
285
|
+
if String.instance_methods.first.instance_of?(String) then
|
286
|
+
# < 1.9 Ruby
|
287
|
+
method_id = symb.to_s
|
288
|
+
@output.puts "Setting method id to a string: #{method_id}"
|
289
|
+
else
|
290
|
+
method_id = symb
|
291
|
+
@output.puts "Ensuring method id is a symbol: #{method_id}"
|
292
|
+
end
|
293
|
+
|
294
|
+
unless @klass.instance_methods.include?(method_id)
|
295
|
+
@output.puts "Failed to find method id:"
|
296
|
+
@output.puts "#{@klass.instance_methods.inspect}"
|
297
|
+
raise NoMethodError, "Failed to find method: #{method_id}"
|
298
|
+
end
|
299
|
+
|
300
|
+
retries = 0
|
301
|
+
begin
|
302
|
+
|
303
|
+
@session.send(symb, *parameters)
|
304
|
+
|
305
|
+
rescue DRb::DRbConnError
|
306
|
+
# service did not respond nicely
|
307
|
+
@output.puts "Failed to connected to the service - checking its OK"
|
308
|
+
jerbil = @jerbil_server.connect
|
309
|
+
if jerbil.service_missing?(@service) then
|
310
|
+
# something nasty has occurred
|
311
|
+
@output.puts "Jerbil confirms the service is missing"
|
312
|
+
raise Jerbil::ServiceConnectError, "Service is missing"
|
313
|
+
else
|
314
|
+
# seems jerbil thinks its ok, so try again
|
315
|
+
retries += 1
|
316
|
+
@output.puts "Jerbil thinks the service is OK, retrying"
|
317
|
+
retry unless retries > 2
|
318
|
+
@output.puts "Retried too many times, giving up"
|
319
|
+
raise Jerbil::ServiceConnectError, "Service is not responding"
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
end
|