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.
Files changed (86) hide show
  1. data/Bugs.rdoc +66 -0
  2. data/Gemfile +12 -0
  3. data/History.txt +359 -0
  4. data/Intro.txt +5 -0
  5. data/LICENCE.rdoc +159 -0
  6. data/README.md +335 -0
  7. data/README_SERVICES.md +410 -0
  8. data/README_TESTING.md +47 -0
  9. data/bin/jerbil +62 -0
  10. data/bin/jerbil-install +56 -0
  11. data/etc/conf.d/jerbild +15 -0
  12. data/etc/conf.d/jserviced +39 -0
  13. data/etc/init.d/jerbild +55 -0
  14. data/etc/init.d/jserviced +59 -0
  15. data/etc/jerbil/jerbil-client.rb +2 -0
  16. data/etc/jerbil/jerbil.rb +83 -0
  17. data/lib/jerbil.rb +636 -0
  18. data/lib/jerbil/config.md +49 -0
  19. data/lib/jerbil/config.rb +67 -0
  20. data/lib/jerbil/errors.rb +74 -0
  21. data/lib/jerbil/jerbil_service/base.rb +191 -0
  22. data/lib/jerbil/jerbil_service/client.rb +325 -0
  23. data/lib/jerbil/jerbil_service/config.md +119 -0
  24. data/lib/jerbil/jerbil_service/config.rb +72 -0
  25. data/lib/jerbil/jerbil_service/sclient.rb +343 -0
  26. data/lib/jerbil/jerbil_service/support.rb +58 -0
  27. data/lib/jerbil/jerbil_service/utils.rb +35 -0
  28. data/lib/jerbil/servers.rb +230 -0
  29. data/lib/jerbil/service.rb +216 -0
  30. data/lib/jerbil/support.rb +160 -0
  31. data/lib/jerbil/thor/server.rb +76 -0
  32. data/lib/jerbil/thor/service.rb +74 -0
  33. data/lib/jerbil/version.rb +13 -0
  34. data/sbin/jerbil-status +120 -0
  35. data/sbin/jerbil-stop +139 -0
  36. data/sbin/jerbild +186 -0
  37. data/sbin/jservice-status +107 -0
  38. data/sbin/jservice-stop +94 -0
  39. data/sbin/jserviced +111 -0
  40. data/spec/jerbil_2_jerbil_spec.rb +87 -0
  41. data/spec/jerbil_client1_spec.rb +80 -0
  42. data/spec/jerbil_client_spec.rb +114 -0
  43. data/spec/jerbil_client_stop_spec.rb +24 -0
  44. data/spec/jerbil_daemonised/jerbil_local_spec.rb +81 -0
  45. data/spec/jerbil_daemonised/jerbil_remote_spec.rb +116 -0
  46. data/spec/jerbil_load.rb +48 -0
  47. data/spec/jerbil_local_spec.rb +91 -0
  48. data/spec/jerbil_missing_spec.rb +98 -0
  49. data/spec/jerbil_remote_spec.rb +117 -0
  50. data/spec/jerbil_remote_spec_bup.rb +168 -0
  51. data/spec/jerbil_service_error_spec.rb +56 -0
  52. data/spec/jerbil_service_spec.rb +41 -0
  53. data/spec/jerbil_support_spec.rb +69 -0
  54. data/spec/jservice_utils_spec.rb +38 -0
  55. data/spec/server_spec.rb +69 -0
  56. data/spec/server_update_spec.rb +28 -0
  57. data/spec/service_spec.rb +72 -0
  58. data/spec/spec_helper.rb +12 -0
  59. data/spec/test_env_spec.rb +53 -0
  60. data/test/bad_test_service.rb +31 -0
  61. data/test/conf.d/jerbil +36 -0
  62. data/test/conf.d/jerbil.conf +39 -0
  63. data/test/conf.d/jerbil.rb +55 -0
  64. data/test/conf.d/jerbil_local.rb +33 -0
  65. data/test/conf.d/jerbil_no_local.conf +39 -0
  66. data/test/conf.d/jerbil_old.rb +47 -0
  67. data/test/conf.d/jerbil_test.rb +35 -0
  68. data/test/conf.d/malformed +1 -0
  69. data/test/conf.d/missing_services +39 -0
  70. data/test/conf.d/ruby_test.rb +8 -0
  71. data/test/init.d/jerbild +14 -0
  72. data/test/jerbil.rb +51 -0
  73. data/test/jerbil_config.rb +8 -0
  74. data/test/jstop.rb +36 -0
  75. data/test/key.asc +1 -0
  76. data/test/lib/ruby_test.rb +37 -0
  77. data/test/lib/ruby_test/config.rb +56 -0
  78. data/test/lib/ruby_test/version.rb +13 -0
  79. data/test/pids/jerbil-prod.asc +1 -0
  80. data/test/pids/jerbil-prod.pid +1 -0
  81. data/test/pids/jerbil.pid +1 -0
  82. data/test/private_key_file.asc +3 -0
  83. data/test/service-stop.rb +86 -0
  84. data/test/service_mock.rb +94 -0
  85. data/test/test_service_client.rb +25 -0
  86. metadata +265 -0
@@ -0,0 +1,47 @@
1
+ # Jerbil Testing
2
+
3
+ Testing is generally through rspec, development/test versions of the Jerbil server, and service mocks.
4
+
5
+ ## Basic tests
6
+
7
+ The following basic rspec tests should be run (all in spec/)
8
+
9
+ * jerbil_local_spec - set up a server and register a local service
10
+ * jerbil_remote_spec - set up a server and register a remote service (as if another server)
11
+ * jerbil_missing_spec - set up a server, register a remote service (or not) and check if it is missing
12
+ * server_spec - test the Servers interface using a local server - a local record that assists in connecting
13
+ to real servers
14
+ * service_spec - test the Service interface - local records for services known to the server
15
+
16
+ The following tests require a jerbil server to be started separately:
17
+
18
+ $ export RUBYLIB="lib"
19
+ $ sbin/jerbild -c test/conf.d/jerbil_test.rb
20
+ #do the tests
21
+ $ rspec spec/jerbil_daemonised/jerbil_local_spec.rb
22
+ $ rspec spec/jerbil_daemonised/jerbil_remote_spec.rb
23
+ #stop the server
24
+ $ sbin/jerbil-stop -c test/conf.d/jerbil_test.rb
25
+
26
+ The following tests require the RubyTest service to be started as well:
27
+
28
+ $ export RUBYLIB="lib:test/lib"
29
+ $ sbin/jerbild -c test/conf.d/jerbil_test.rb
30
+ $ sbin/jserviced -c test/conf.d/ruby_test.rb -s ruby_test -V
31
+ $ rspec spec/jerbil_client_spec.rb
32
+ $ sbin/jservice-stop -c test/conf.d/ruby_test.rb -s ruby_test -V
33
+ $ sbin/jerbil-stop -c test/conf.d/jerbil_test.rb
34
+
35
+ ## Dev and Test servers
36
+
37
+ Jerbil can be run with dev and test servers in parallel with any production server. To access these servers from another
38
+ service requires the :jerbil_env parameter to be set to the required value. This is how the rubytest service operates, so check out
39
+ the config file above.
40
+
41
+ Testing Jerbil across the network is best done using git to clone the jerbil files, and then running the RubyTest tests
42
+ described above. To check the status of a server, use the jerbil command:
43
+
44
+ $ jerbil services -c test/conf.d/jerbil_test.rb
45
+ $ # and verify
46
+ $ jerbil services -c test/conf.d/jerbil_test.rb -v
47
+
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ #
4
+ # = Jerbil command line utility
5
+ #
6
+ # == control and display jerbil related info
7
+ #
8
+ # Author:: Robert Sharp
9
+ # Copyright:: Copyright (c) 2011 Robert Sharp
10
+ # License:: Open Software Licence v3.0
11
+ #
12
+ # This software is licensed for use under the Open Software Licence v. 3.0
13
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
14
+ # and in the file copyright.txt. Under the terms of this licence, all derivative works
15
+ # must themselves be licensed under the Open Software Licence v. 3.0
16
+ #
17
+ # Trial using Thor
18
+ #
19
+ require 'rubygems'
20
+ require 'thor'
21
+ require 'thor/group'
22
+ require 'jerbil'
23
+ require 'colored'
24
+
25
+
26
+ require 'jerbil/thor/server'
27
+ require 'jerbil/thor/service'
28
+
29
+
30
+
31
+ # the main thor handler
32
+ class Jerbs < Thor
33
+
34
+ #class_option :verbose, :default=>false, :aliases=>'-V', :desc=>'print more information'
35
+
36
+ desc "test", "This is just to test things"
37
+ method_option :debug, :aliases=>'-D', :desc=>'display debug information'
38
+ def test
39
+ puts "Hello World"
40
+ if options[:debug] then
41
+ puts "Debugging..."
42
+ puts " Load Path:"
43
+ $LOAD_PATH.each do |lpath|
44
+ puts " #{lpath.to_s}"
45
+ end
46
+ end
47
+ end
48
+
49
+
50
+ register Server, :server, "server", "show information about jerbil servers"
51
+
52
+
53
+ register Service, :service, "service", "show information about registered services"
54
+
55
+ map "services"=>"service"
56
+
57
+ # use separate jeni installer
58
+ #register Installer, :install, "install", "install jerbil on local machine"
59
+
60
+ end
61
+
62
+ Jerbs.start
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby18
2
+ #
3
+ # @markup ruby
4
+ # @title Installation Script
5
+ #
6
+ # = Jerbil Installation
7
+ #
8
+ # == Uses Jeni to install Jerbil Files
9
+ #
10
+ # Author:: Robert Sharp
11
+ # Copyright:: Copyright (c) 2011 Robert Sharp
12
+ # License:: Open Software Licence v3.0
13
+ #
14
+ # This software is licensed for use under the Open Software Licence v. 3.0
15
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
16
+ # and in the file copyright.txt. Under the terms of this licence, all derivative works
17
+ # must themselves be licensed under the Open Software Licence v. 3.0
18
+ #
19
+ #
20
+ #
21
+ require 'rubygems' # jeni uses it anyway to find the jerbil gem so why not use it here?
22
+ require 'jeni'
23
+
24
+
25
+ Jeni::Installer.new_from_gem('jerbil') do |jeni|
26
+ jeni.optparse(ARGV)
27
+ jeni.usr # force files to be relative to /usr
28
+
29
+ # create a user, if not already created, with its own group
30
+ jeni.user('jerbil', :skip=>true, :user_group=>true, :home=>'/var/run/jerbil')
31
+
32
+ # make the required directories, if they do not already exist
33
+ jeni.empty_directory('/etc/jerbil')
34
+ jeni.empty_directory('/var/log/jerbil', :chown=>'jerbil', :chgrp=>'jerbil', :chmod=>0775)
35
+ jeni.empty_directory('/var/run/jerbil', :chown=>'jerbil', :chgrp=>'jerbil', :chmod=>0775)
36
+
37
+
38
+ jeni.file('etc/jerbil/jerbil.rb', '/etc/jerbil/jerbil.rb', :chown=>'jerbil', :chgrp=>'jerbil', :chmod=>0640)
39
+ jeni.file('etc/jerbil/jerbil-client.rb', '/etc/jerbil/jerbil-client.rb', :chown=>'jerbil', :chgrp=>'jerbil', :chmod=>0640)
40
+
41
+ # configuration file to run the service
42
+ jeni.file('etc/conf.d/jerbild', '/etc/conf.d/jerbild')
43
+ # and the init script itself
44
+ jeni.file('etc/init.d/jerbild', '/etc/init.d/jerbild', :chmod=>0755)
45
+ # and init files for jservice
46
+ jeni.file('etc/conf.d/jserviced', '/etc/conf.d/jserviced')
47
+ jeni.file('etc/init.d/jserviced', '/etc/init.d/jserviced', :chmod=>0755)
48
+ # the daemon itself, which is executable
49
+ jeni.wrapper('sbin/jerbild', 'sbin/jerbild', :chmod=>0755)
50
+ jeni.wrapper('sbin/jerbil-stop', 'sbin/jerbil-stop', :chmod=>0755)
51
+ jeni.wrapper('sbin/jerbil-status', 'sbin/jerbil-status', :chmod=>0755)
52
+ jeni.wrapper('sbin/jserviced', 'sbin/jserviced', :chmod=>0755)
53
+ jeni.wrapper('sbin/jservice-stop', 'sbin/jservice-stop', :chmod=>0755)
54
+ jeni.wrapper('sbin/jservice-status', 'sbin/jservice-status', :chmod=>0755)
55
+
56
+ end.run!
@@ -0,0 +1,15 @@
1
+ # NO_DAEMON can be set true to prevent the service from daemonizing and run it
2
+ # in the foreground instead. Only recommended for testing and diagnostics
3
+ #NO_DAEMON=false
4
+
5
+ # NO_SYSLOG can be set true to prevent the logger from logging messages to syslog
6
+ # Only recommended for testing
7
+ #NO_SYSLOG=false
8
+
9
+ # CONF_FILE can be used to start the service with a configuration from a file
10
+ # other than the system default (/etc/jerbil/jerbil.rb).
11
+ #CONF_FILE=
12
+
13
+ # By default, the service will suppress all messages to the terminal. For testing and
14
+ # diagnostics, set VERBOSE to be true and run with NO_DAEMON
15
+ #VERBOSE=false
@@ -0,0 +1,39 @@
1
+ # This file should be used with the jerbil service init script to set
2
+ # basic options used to start the service. The service itself is configured
3
+ # by the conf_file and the default setting for this is usually OK
4
+
5
+ # NO_DAEMON can be set true to prevent the service from daemonizing and run it
6
+ # in the foreground instead. Only recommended for testing and diagnostics
7
+ #NO_DAEMON=false
8
+
9
+ # NO_SYSLOG can be set true to prevent the logger from logging messages to syslog
10
+ # Only recommended for testing
11
+ #NO_SYSLOG=false
12
+
13
+ # CONF_FILE can be used to start the service with a configuration from a file
14
+ # other than the system default (/etc/jerbil/<service>.rb).
15
+ #CONF_FILE=
16
+
17
+ # LOG_DAEMON can be used to log the output of the daemon to a jellog log file
18
+ # which is useful for debugging purposes
19
+ LOG_DAEMON=true
20
+
21
+ # By default, the service will suppress all messages to the terminal. For testing and
22
+ # diagnostics, set VERBOSE to be true and run with NO_DAEMON
23
+ #VERBOSE=false
24
+
25
+ # A service name can be defined here, but in its absence, will be taken from the file name
26
+ # Which is probably not going to be right if the file has the usual "d" on the end!
27
+ SERVICE_NAME=""
28
+
29
+ # define the user under which this service will run
30
+ SERVICE_USER=""
31
+
32
+ # add any services that this service uses
33
+ USES="logger
34
+ net"
35
+
36
+ # Add further services that this service needs, remembering the d on the end!
37
+ NEEDS="jerbild"
38
+
39
+ DESCRIPTION="This is a test service for Jerbil!"
@@ -0,0 +1,55 @@
1
+ #!/sbin/runscript
2
+ #
3
+ # INIT Script for the Jerbil - Ruby Object Broker
4
+
5
+
6
+ depend() {
7
+ use net
8
+ use logger
9
+ }
10
+
11
+ describe() {
12
+ einfo "Reliable Broker for Ruby Services"
13
+ }
14
+
15
+ export RUBYOPT="rubygems"
16
+ export RUBYLIB=""
17
+
18
+ # create the options for starting the daemon from the settings in the
19
+ # /etc/conf.d/jerbild file
20
+
21
+ myopts=""
22
+ [ -n "${NO_DAEMON}" ] && myopts="${myopts} -n"
23
+ [ -n "${CONF_FILE}" ] && myopts="${myopts} -c ${CONF_FILE}"
24
+ [ "${VERBOSE}" == "true" ] && [ "${QUIET}" != "true" ] && myopts="${myopts} -V"
25
+ [ -n "${NO_SYSLOG}" ] && myopts="${myopts} -S"
26
+
27
+
28
+ start() {
29
+ ebegin "Starting Jerbil Server"
30
+
31
+ # /var/run is now temporary and may need to be created at each boot
32
+ if [ ! -d /var/run/jerbil ] ; then
33
+ mkdir /var/run/jerbil
34
+ chown jerbil:jerbil /var/run/jerbil
35
+ einfo "Created /var/run/jerbil"
36
+ fi
37
+
38
+ /bin/su -c "/usr/sbin/jerbild ${myopts}" - jerbil
39
+
40
+ eend $?
41
+ }
42
+
43
+ status() {
44
+ ebegin "Checking Jerbil Status"
45
+ /usr/sbin/jerbil-status ${myopts}
46
+ eend $?
47
+ }
48
+
49
+
50
+ stop() {
51
+ ebegin "Stopping Jerbil Server"
52
+ /usr/sbin/jerbil-stop ${myopts}
53
+ eend $?
54
+ }
55
+
@@ -0,0 +1,59 @@
1
+ #!/sbin/runscript
2
+ #
3
+ # INIT Script for a Jerbil Service
4
+
5
+ # create dependencies from the list of services in $NEEDS and $USES
6
+ depend() {
7
+ for need_service in $NEEDS
8
+ do
9
+ need $need_service
10
+ done
11
+ for use_service in $USES
12
+ do
13
+ use $use_service
14
+ done
15
+ }
16
+
17
+ # make sure there are no unexpected rubylibs
18
+ export RUBYLIB=""
19
+ export RUBYOPT="rubygems"
20
+
21
+ myopts=""
22
+ [ -n "${NO_DAEMON}" ] && myopts="${myopts} -n"
23
+ [ -n "${CONF_FILE}" ] && myopts="${myopts} -c ${CONF_FILE}"
24
+ [ "${VERBOSE}" == "true" ] && [ "${QUIET}" != "true" ] && myopts="${myopts} -V"
25
+ [ -n "${NO_SYSLOG}" ] && myopts="${myopts} -S"
26
+ [ -n "${LOG_DAEMON}" ] && myopts="${myopts} -l"
27
+ [ "$SERVICE_NAME" == "" ] && SERVICE_NAME="${SVCNAME}"
28
+ [ "$SERVICE_USER" == "" ] && SERVICE_USER="jerbil"
29
+
30
+ start() {
31
+ ebegin "Starting Jerbil Service ${SERVICE_NAME}"
32
+
33
+
34
+ # create the options for starting the daemon from the settings in the
35
+ # /etc/conf.d/jservice file (same name as this link)
36
+
37
+ /bin/su -c "/usr/sbin/jserviced ${myopts} -s ${SERVICE_NAME}" - ${SERVICE_USER}
38
+
39
+ eend $?
40
+ }
41
+
42
+ status() {
43
+ ebegin "Checking status for Jerbil Service ${SERVICE_NAME}"
44
+ /usr/sbin/jservice-status ${myopts} -s ${SERVICE_NAME}
45
+ eend $?
46
+ }
47
+
48
+
49
+ stop() {
50
+ ebegin "Stopping Jerbil Service ${SERVICE_NAME}"
51
+ /usr/sbin/jservice-stop ${myopts} -s ${SERVICE_NAME}
52
+ eend $?
53
+ }
54
+
55
+ describe() {
56
+ einfo ${DESCRIPTION}
57
+
58
+ }
59
+
@@ -0,0 +1,2 @@
1
+ # a simple client config file for jerbil
2
+ environment :prod
@@ -0,0 +1,83 @@
1
+ #
2
+ # Configuration Options for: Jelly::Options
3
+ #
4
+
5
+ # Number of log files to retain at any time, between 0 and 20
6
+ #log_rotation 2
7
+
8
+ # Location for Jelly (logging utility) to save log files
9
+ #log_dir "/var/log/jerbil"
10
+
11
+ # Set the string to be used for marking the log with logger.mark
12
+ #log_mark " ===== Mark ====="
13
+
14
+ # Reset the logfile when starting logging by setting to true, otherwise append to
15
+ # existing log
16
+ #log_reset false
17
+
18
+ # Setting to true (the default) will flush log messages immediately, which is useful if you
19
+ # need to monitor logs dynamically
20
+ #log_sync true
21
+
22
+ # Size of a log file (in MB) before switching to the next log, upto 20 MB
23
+ #log_length 1
24
+
25
+ # Format string for time stamps. Needs to be a string that is recognised by String.strftime
26
+ # Any characters not recognised by strftime will be printed verbatim, which may not be what you want
27
+ #log_date_time_format "%Y-%m-%d %H:%M:%S"
28
+
29
+ # Controls the amount of logging done by Jelly
30
+ #
31
+ # * :system - standard message, plus log to syslog
32
+ # * :verbose - more generous logging to help resolve problems
33
+ # * :debug - usually used only for resolving problems during development
34
+ #
35
+ #log_level :system
36
+
37
+ # Set to false to suppress colourful logging. Default colours can be changed by calling
38
+ # colours= method
39
+ #log_coloured true
40
+
41
+ #
42
+ # Configuration Options for: JerbilService::Config
43
+ #
44
+
45
+ # private key dir used to authenticate privileged users
46
+ #key_dir "/var/run/jerbil"
47
+
48
+ # Set the default environment for service commands etc.
49
+ #
50
+ # Can be one of :prod, :test, :dev
51
+ #environment :prod
52
+
53
+ # Set this only to use a Jerbil Server that is not running in the production environment
54
+ #jerbil_env
55
+
56
+ # directory used to store the daemons pid to assist in stopping reluctant servers
57
+ #pid_dir "/var/run/jerbil"
58
+
59
+ #
60
+ # Configuration Options for: Jerbil::Config
61
+ #
62
+
63
+ # A secret key available to all Jerbil Servers and used to authenticate the initial registration.
64
+ # If security is an issue, ensure that this config file is readable only be trusted users
65
+ secret "hK78l/z1mIDBOs+/Qx2q7k5beExChmdc3tpw81qTBNLmcQknRrY93oHzIAd3DNo2"
66
+
67
+ # A valid netmask for the hosts to search using the above net address. This should be
68
+ # between 24 (a class C network) and 30, beyound which its not much of a network. If you only have a few
69
+ # hosts it will be easier to restrict them to a small subnet.
70
+ #
71
+ # To find out more about netmasks, go to [UnixWiz](http://www.unixwiz.net/techtips/netmask-ref.html).
72
+ #net_mask 26
73
+
74
+ # Provide a timeout when searching for jerbil servers on the net during startup.
75
+ # Depending on the size of the net mask this timeout may make the search long.
76
+ # The default should work in most cases
77
+ #scan_timeout 0.1
78
+
79
+ # A valid IPv4 address for the LAN on which the servers will operate.
80
+ # Note that the broker uses this address to search for all servers.
81
+ # Therefore a large range will take a long time to search. Set the net_mask to limit this.
82
+ #net_address "192.168.0.1"
83
+
@@ -0,0 +1,636 @@
1
+ #
2
+ # = JERBIL
3
+ #
4
+ # == Jumpin' Ermin's Ruby Broker for Integrated Linux services!
5
+ #
6
+ # Author:: Robert Sharp
7
+ # Copyright:: Copyright (c) 2010 Robert Sharp
8
+ # License:: Open Software Licence v3.0
9
+ #
10
+ # This software is licensed for use under the Open Software Licence v. 3.0
11
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
12
+ # and in the file copyright.txt. Under the terms of this licence, all derivative works
13
+ # must themselves be licensed under the Open Software Licence v. 3.0
14
+ #
15
+ # A reliable (hopefully) object request broker for ruby services.
16
+ #
17
+ # A server runs on every machine. Servers register with Jerbil and offer a polling method so that
18
+ # Jerbil can keep checking they are alive. Servers that want to take network wide clients are
19
+ # relayed to all the other Jerbil servers.
20
+ #
21
+ # Clients ask for a service and receive the name and port to connect through using DRb.
22
+ #
23
+ # Jerbil is meant to be used behind a higher level wrapper that hides DRb as well
24
+ #
25
+ #
26
+ require 'rubygems'
27
+ require 'drb'
28
+ require 'socket'
29
+ require 'jellog'
30
+ require 'jerbil/errors'
31
+ require 'jerbil/service'
32
+ require 'jerbil/servers'
33
+ require 'jerbil/version'
34
+ require 'jerbil/support'
35
+
36
+ # == JERBIL - Jumpin' Ermin's Ruby Broker for Integrated Linux services!
37
+ #
38
+ # A network server for registering services and a suite of classes and methods to
39
+ # manage these services and to connect with them.
40
+ #
41
+ module Jerbil
42
+
43
+ # The Broker, being a server class that runs on each machine on which Jerbil services will run or will be
44
+ # required. The Broker registers services and interacts with other servers to share information
45
+ # about services across the network.
46
+ #
47
+ # It is not necessary to use this interface directly. By using the {JerbilService::Base}
48
+ # class all interaction with the server is done under the hood. See {file:README_SERVICES.md Services Readme}
49
+ # for more details.
50
+ #
51
+ # Key methods are:
52
+ #
53
+ # * *{Jerbil::Broker#register}* to add a service to the broker's database
54
+ # * **{Jerbil::Broker#remove}** to remove a service from the broker's database
55
+ # * **{#find} to obtain information about one or more services matching given criteria
56
+ #
57
+ # Methods used between servers are:
58
+ #
59
+ # * {Jerbil::Broker#register_server} to add a remote server to the broker's database
60
+ # * {Jerbil::Broker#detach_server} to remove a remote server from the broker's database
61
+ # * {Jerbil::Broker#get_local_services} called by a remote server to get all of the
62
+ # local services known to this server
63
+ # * {Jerbil::Broker#register_remote} for a remote server to register a new service
64
+ # * {Jerbil::Broker#remove_remote} for a remote server to remove a service
65
+ #
66
+ # Methods used to internally:
67
+ #
68
+ # * {Jerbil::Broker#stop} to stop the server gracefully
69
+ # * {Jerbil::Broker#missing_service?} to check if a service is missing and remove it from
70
+ # the database if it is
71
+ #
72
+ class Broker
73
+
74
+ # create a new Jerbil server
75
+ #
76
+ # The options for the server are defined in {Jerbil::Config} and are best created
77
+ # using this class. This is a [Jeckyl](https://github.com/osburn-sharp/jeckyl) config file.
78
+ # Further details are provided in the {file:README.md Readme file}.
79
+ #
80
+ # The private key should be unique to this server and is used to authenticate system actions
81
+ # and to authenticate remote servers. Its not very secure so more a way of avoiding mistakes.
82
+ # The key is best created using {Jerbil::Support.create_private_key}.
83
+ #
84
+ # @param [Hash] options a hash of various options as defined in {Jerbil::Config}.
85
+ # @param [String] pkey a private key generated by the script calling the broker and used
86
+ # to authenticate system calls
87
+ def initialize(options, pkey) #log_dir, log_level=:system)
88
+
89
+ # store details of this server and remote servers
90
+ @env = options[:environment] || :prod
91
+ @private_key = pkey
92
+ @secret = options[:secret]
93
+
94
+ @local = Jerbil::Servers.create_local_server(@env, @private_key)
95
+ @remote_servers = Array.new
96
+
97
+
98
+ # who am i
99
+ #@host = Socket.gethostname
100
+
101
+ #store local and remote services
102
+ @store = Array.new
103
+ @remote_store = Array.new
104
+
105
+ # create a jellog logger that continues any previous log and keeps the last 5 log files
106
+ app_name = "Jerbil-#{options[:environment].to_s}"
107
+ log_opts = Jellog::Logger.get_options(options)
108
+ @logger = Jellog::Logger.new(app_name, log_opts)
109
+ @logger.mark
110
+ @logger.debug "Started the Logger for Jerbil"
111
+
112
+
113
+ # some statistical data
114
+ @started = Time.now
115
+ @registrations = 0
116
+ @logger.verbose("Searching for remote servers")
117
+ network_servers = Jerbil::Servers.find_servers(@env, options[:net_address], options[:net_mask], options[:scan_timeout])
118
+ @logger.verbose("Found #{@remote_servers.length} remote servers")
119
+
120
+ # now loop round the remote servers to see if any are there
121
+ network_servers.each do |remote_server|
122
+ rjerbil = remote_server.connect
123
+ unless rjerbil.nil?
124
+ @logger.debug "Getting Remote Services. Connecting to : #{remote_server.inspect}"
125
+ # there is a remote server, so tell it about me
126
+ begin
127
+ rkey = rjerbil.register_server(@local, @secret, @env)
128
+ remote_server.set_key(rkey)
129
+ @logger.debug "Key for #{remote_server.fqdn}: #{rkey}"
130
+ rjerbil.get_local_services(rkey).each {|ls| add_service_to_store(@remote_store, ls)}
131
+ # add it to the list of verified servers
132
+ @remote_servers << remote_server
133
+ rescue DRb::DRbConnError
134
+ # assume it is not working
135
+ @logger.verbose("Failed to get remote services from server: #{remote_server.fqdn}")
136
+ rescue JerbilAuthenticationError => jerr
137
+ @logger.warn("Remote server authentication failed, skipping")
138
+ @logger.warn(" #{jerr.message}")
139
+ rescue ArgumentError, NoMethodError
140
+ @logger.warn("Remote server incompatibility, skipping")
141
+ rescue => jerr
142
+ @logger.exception(jerr)
143
+ end
144
+
145
+ end
146
+ end
147
+
148
+ @logger.system("Started up the Jerbil Server")
149
+
150
+ @logger.debug "My key: #{@private_key}"
151
+ @logger.debug "Stored remote keys:"
152
+ @remote_servers.each do |rs|
153
+ @logger.debug " #{rs.fqdn}: #{rs.key}"
154
+ end
155
+
156
+ rescue => jerr
157
+ @logger.exception(jerr)
158
+ raise
159
+ end
160
+
161
+ # date/time at which the server was started
162
+ attr_reader :started
163
+
164
+ # the number of registrations since the server started
165
+ attr_reader :registrations
166
+
167
+ # the remote servers at any one time
168
+ #
169
+ # @return [Array] of {Jerbil::Servers}
170
+ attr_reader :remote_servers
171
+
172
+ # The current version of the Jerbil Server
173
+ # @return [String] version number in the form N.N.N
174
+ def version
175
+ Jerbil::Version
176
+ end
177
+
178
+ # the total number of services currently registered with the server
179
+ # @return [Numeric] count of services
180
+ def service_count
181
+ @store.length + @remote_store.length
182
+ end
183
+
184
+ # the number of local services registered with the server
185
+ # @return [Numeric] count of services
186
+ def local_service_count
187
+ @store.length
188
+ end
189
+
190
+ # the number of remote services registered with the server
191
+ # @return [Numeric] count of services
192
+ def remote_service_count
193
+ @remote_store.length
194
+ end
195
+
196
+ # register a service to the local server
197
+ #
198
+ # The caller registers the given service. The server will check that the
199
+ # service is not already registered before adding it. It will then
200
+ # inform all the other servers it is aware of about this service so that
201
+ # anyone on the network can reach it. See {Jerbil::Broker#register_remote} to
202
+ # see what happens when this methods registers a service with a remote server.
203
+ #
204
+ # @param [Jerbil::ServiceRecord] service representing the
205
+ # service being registered
206
+ # @raise [ServiceAlreadyRegistered] if the service is already registered
207
+ # @raise [ServiceNotLocal] if someone should attempt to register a service
208
+ # that is not local to this server
209
+ #
210
+ def register(service)
211
+ @logger.verbose("About to register a local service: #{service.ident}")
212
+ if service.local? then
213
+ service.register
214
+ add_service_to_store(@store, service)
215
+ @registrations += 1
216
+ @logger.system("Registered Local Service: #{service.ident}")
217
+
218
+ @remote_servers.each do |rserver|
219
+ rjerbil = rserver.connect
220
+ unless rjerbil.nil?
221
+ @logger.debug("Registering remote. Connected to #{rserver.fqdn}")
222
+ begin
223
+ rjerbil.register_remote(rserver.key, service)
224
+ @logger.verbose("Registered Service: #{service.name} on server: #{rserver.fqdn}")
225
+ rescue DRb::DRbConnError
226
+ # assume it is not working
227
+ end
228
+ end
229
+ end
230
+ else
231
+ # someone is attempting to register a service that is not local
232
+ @logger.warn("Attempt to register non-local service: #{service.ident}")
233
+ raise ServiceNotLocal
234
+ end
235
+ end
236
+
237
+ # remove a service from the register
238
+ #
239
+ # does nothing if the service is not registered, otherwise removes it
240
+ # locally and then calls {Jerbil::Broker#remove_remote} for each
241
+ # registered server.
242
+ #
243
+ # @param [Jerbil::ServiceRecord] service to remove
244
+ def remove(service)
245
+ if @store.include?(service) then
246
+ # its a local one
247
+ @store.delete(service)
248
+ @logger.system("Deleted Service: #{service.ident}")
249
+ else
250
+ @logger.warn("Attempt was made to remove a service that is not registered: #{service.ident}")
251
+ @logger.warn("Trying to remove it remotely anyway")
252
+ end
253
+ @remote_servers.each do |rserver|
254
+ rjerbil = rserver.connect
255
+ unless rjerbil.nil?
256
+ @logger.debug("Connected to #{rserver.fqdn}")
257
+ begin
258
+ rjerbil.remove_remote(rserver.key, service)
259
+ @logger.verbose("Removed Service from remote server: #{service.ident}")
260
+ rescue DRb::DRbConnError
261
+ # assume it is not working
262
+ @logger.debug("Skipping over remove_remote for #{rserver.fqdn} while removing #{service.ident}")
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ # return the services that match the given criteria
269
+ #
270
+ # search for services based on name, environment etc:
271
+ #
272
+ # broker.find(:name=>'MyService', :env=>:test)
273
+ #
274
+ # If an option is not specified it will be ignored. Find uses {Jerbil::ServiceRecord#matches?}
275
+ # to compare services to the given criteria.
276
+ #
277
+ # Normally this method will log the access to each service found (keeps a count)
278
+ # This can be disabled by setting :ignore_access to true. This is used internally
279
+ # to avoid counting Jerbil operations as service accesses.
280
+ #
281
+ # There are also various short-cut methods that can be used:
282
+ # {Jerbil::Broker#get get}, {Jerbil::Broker#get_local get_local} and {Jerbil::Broker#get_all get_all}
283
+ #
284
+ # @param [Hash] args search arguments
285
+ # @option args [String] :name to match exactly the name of the service
286
+ # @option args [Symbol] :env to match the services environment (:dev, :test, :prod)
287
+ # @option args [String] :host to match exactly the name of the host on
288
+ # which the service is running
289
+ # @option args [String] :key to match exactly the service key
290
+ # @option args [Boolean] :ignore_access do not count this call as an access
291
+ # @return [Array] {Jerbil::ServiceRecord Services} that match or nil if none
292
+ def find(args={})
293
+ #options = {:name=>nil, :port=>nil, :env=>nil}.merge(args)
294
+ results = Array.new
295
+ services = @store + @remote_store
296
+ services.each do |service|
297
+ if service.matches?(args) then
298
+ service.log_access unless args[:ignore_access]
299
+ results << service
300
+ end
301
+ end
302
+
303
+ @logger.verbose("Searching for services. Found #{results.length} matching.")
304
+ @logger.verbose(" Arguments: #{args.inspect}")
305
+
306
+ return results
307
+ end
308
+
309
+ # get the first service that matches the given criteria.
310
+ #
311
+ # Uses {Jerbil::Broker#find} to do the real work and returns the first service.
312
+ # There is no guarantee of the order. In addition,
313
+ # unless :ignore_acess is true, this call will check if the service
314
+ # is connected, and will return nil if it is not
315
+ #
316
+ # @param (see #find)
317
+ # @option (see #find)
318
+ # @return (see #find)
319
+ def get(args={})
320
+ results = Array.new
321
+ results = self.find(args)
322
+ if results.length >= 1 then
323
+ service = results[0]
324
+ @logger.verbose("Get returned #{service.ident}")
325
+ unless args[:ignore_access]
326
+ # check if it is working
327
+ begin
328
+ service.connect
329
+ rescue ServiceCallbackMissing
330
+ @logger.warning("Verifying #{service.ident} failed due to missing callback")
331
+ # missing callback but still return it...
332
+ rescue ServiceConnectError
333
+ @logger.verbose("Verification failed for #{service.ident}")
334
+ return nil
335
+ end
336
+ end
337
+ return service
338
+ else
339
+ return nil
340
+ end
341
+ end
342
+
343
+ # get the first service that matches the given criteria and is running on the same
344
+ # processor
345
+ #
346
+ # @param (see #find)
347
+ # @option (see #find)
348
+ # @return (see #find)
349
+ def get_local(args={})
350
+ new_args = args.merge({:host=>@host})
351
+ return get(new_args)
352
+ end
353
+
354
+ # return all services
355
+ #
356
+ # does not require any matching criteria.
357
+ #
358
+ # @param [Boolean] ignore_access is the same as :ignore_access for #find
359
+ # @return (see #find)
360
+ def get_all(ignore_access=false)
361
+ self.find(:ignore_access => ignore_access)
362
+ end
363
+
364
+ # Checks for a potentially missing service and removes it if it cannot be found.
365
+ #
366
+ # What to do if you cannot connect to a service that Jerbil thinks is there?
367
+ # check if its local, try to connect and if OK then return false to allow retries
368
+ # otherwise remove the service and return true.
369
+ #
370
+ # If the service is not local, find its server and ask it the same question.
371
+ # if the server is not there, then fake being that server and remove_remote from
372
+ # everyone. Don't forget to remove it from here too!
373
+ #
374
+ # @param [Jerbil::ServiceRecord] service to check for
375
+ # @return [Boolean] true if service was missing
376
+ def service_missing?(service)
377
+ # is it one of mine?
378
+ if service.local? then
379
+ #yes
380
+ @logger.verbose("Local service missing for #{service.ident}?")
381
+ begin
382
+ service.connect
383
+ # seems to be fine
384
+ @logger.info("Missing service was found to be OK: #{service.ident}")
385
+ return false
386
+ rescue
387
+ # failed to connect for some reason.
388
+ # trying to stop the service
389
+ @logger.debug("Local service appears to be missing: #{service.ident}")
390
+ # and now remove it from the record
391
+ self.remove(service)
392
+ @logger.system("Removed missing local service: #{service.ident}")
393
+ return true
394
+ end
395
+ else
396
+ # not one of mine, so who owns it
397
+ @logger.verbose("Missing service is not local: #{service.ident}")
398
+ failed_remote_server = nil
399
+ @remote_servers.each do |rserver|
400
+ if rserver.fqdn == service.host then
401
+ # found it, so try to warn it
402
+ @logger.debug("Service: #{service.ident} belongs to #{rserver.fqdn}")
403
+ begin
404
+ rjerbil = rserver.connect
405
+ return rjerbil.service_missing?(service)
406
+ rescue
407
+ # whoops, failed to connect to remote server
408
+ # so assume it has gone and allow method to continue
409
+ # so that it removes the service as if it was the remote server
410
+ failed_remote_server = rserver
411
+ end
412
+ end
413
+ end
414
+ # only got here because could not connect to the server
415
+ unless failed_remote_server.nil?
416
+ @logger.warn("Failed to connect to server: #{failed_remote_server.fqdn}, removing service for it")
417
+ rkey = failed_remote_server.key
418
+ self.remove_remote(rkey, service)
419
+ @remote_servers.each do |rserver|
420
+ begin
421
+ rjerbil = rserver.connect
422
+ rjerbil.remove_remote(rkey, service)
423
+ @logger.debug("Removed service: #{service.ident} from server #{rserver.fqdn}")
424
+ rescue
425
+ # server not up, so ignore
426
+ @logger.debug("Failed to connect to server to remove service: #{rserver.fqdn}, but who cares!")
427
+ end
428
+ end
429
+ return true
430
+ else
431
+ # strange? Should not have a service for which there is no server...
432
+ @logger.warn("Could not find a server for #{service.ident}. How could this happen?")
433
+ return false
434
+ end
435
+ end
436
+ end
437
+
438
+ # close the logger that Jerbil is using
439
+ #
440
+ # probably only useful for testing?
441
+ def close
442
+ @logger.close
443
+ end
444
+
445
+ # simple method to check that the server is running from a remote client
446
+ def verify
447
+ return true
448
+ end
449
+
450
+
451
+ # stop the Jerbil server
452
+ #
453
+ # Need to make sure the caller knows what they are doing so requires the
454
+ # server's private key.
455
+ #
456
+ # @param [String] private_key - as given to the server at start-up.
457
+ def stop(private_key)
458
+ if @private_key == private_key then
459
+ @logger.info("About to stop the Jerbil Server")
460
+ @remote_servers.each do |rserver|
461
+ begin
462
+ rjerbil = rserver.connect
463
+ @logger.verbose("Closing connection to; #{rserver.ident}")
464
+ rjerbil.detach_server(rserver.key, @local)
465
+ rescue ServerConnectError, DRb::DRbConnError
466
+ @logger.error("Failed to connect to #{rserver.ident}")
467
+ end
468
+ end
469
+ @logger.system("Stopping the Jerbil Server now")
470
+ @logger.close
471
+ DRb.stop_service
472
+ #exit!
473
+ else
474
+ @logger.system("Stop called with incorrect private key")
475
+ @logger.debug(" Private Key provided:")
476
+ @logger.debug("#{private_key}")
477
+ @logger.debug(" Private Key required")
478
+ @logger.debug("#{@private_key}")
479
+ raise InvalidPrivateKey
480
+ end
481
+ rescue ServerConnectError
482
+ @logger.error("Connection to remote server failed")
483
+ rescue InvalidPrivateKey
484
+ raise
485
+ rescue => err
486
+ @logger.exception(err)
487
+ end
488
+
489
+ #================================================
490
+ #
491
+ # SERVER related methods
492
+ #
493
+ #================================================
494
+
495
+
496
+ # Register a remote server, providing limited authentication.
497
+ #
498
+ # Registering a server
499
+ # will purge any old server record and any old services for that server
500
+ #
501
+ # @param [Servers] server - the remote server's Servers record
502
+ # @param [String] secret shared between all servers on the net
503
+ # @param [Symbol] env of the calling server, just to ensure it is the same
504
+ # @return [String] private key of the called server to be used for further interactions
505
+ # @raise [JerbilAuthenticationError] if the server fails to authenticate
506
+ #
507
+ def register_server(server, secret, env)
508
+ @logger.debug("Attempting to register server: #{server.ident}")
509
+ unless secret == @secret
510
+ @logger.debug "mismatching secret: #{secret}"
511
+ raise JerbilAuthenticationError, @logger.error("Secret key from #{server.fqdn} does not match")
512
+ end
513
+ unless env = @env
514
+ raise JerbilAuthenticationError, @logger.error("Registering server with #{env}, against #{@env}")
515
+ end
516
+ # need to delete any stale existing record
517
+ @remote_servers.delete_if {|rserver| rserver.fqdn == server.fqdn}
518
+
519
+ # registering this new server, but there may be stale services as well
520
+ @remote_store.delete_if {|rservice| rservice.host == server.fqdn}
521
+
522
+ @remote_servers << server
523
+ @logger.debug "Registered a new server"
524
+ @logger.debug " #{server.ident}: #{server.key}"
525
+
526
+ return @private_key
527
+ end
528
+
529
+ # get all of the local services registered with this server
530
+ #
531
+ # @param [String] my_key must be the called servers private key shared
532
+ # with a remote server through {Jerbil::Broker#register_server}.
533
+ # @raise [InvalidServerKey] is the given key is incorrect
534
+ # @return [Array] of {ServiceRecord}
535
+ #
536
+ def get_local_services(my_key)
537
+ raise InvalidServerKey, @logger.error("get_local_services: incorrect key: #{my_key}") unless @private_key == my_key
538
+ return @store.dup
539
+ end
540
+
541
+
542
+ # register a remote service
543
+ #
544
+ # This is called by a jerbil service when it wants to register a service local to
545
+ # it with all the other servers. This will siltenly delete any existing service record.
546
+ #
547
+ # @param [String] my_key - the caller must provide this server's private key
548
+ # @param [Service] service - the service to be registered
549
+ # @raise ServiceAlreadyRegistered if the service is a duplicate
550
+ #
551
+ def register_remote(my_key, service)
552
+ @logger.debug "About to register a remote service:"
553
+ @logger.debug " #{service.inspect}"
554
+
555
+ unless @private_key == my_key
556
+ @logger.warn("register remote: incorrect key: #{my_key}, ignoring")
557
+ return true
558
+ end
559
+
560
+ # perhaps there is a stale record for this service? Stops add below from assuming it is missing etc
561
+ @remote_store.delete_if {|rservice| rservice.same_service?(service)}
562
+
563
+ add_service_to_store(@remote_store, service)
564
+ @logger.info("Registered Remote Service: #{service.ident}")
565
+ return true
566
+
567
+ end
568
+
569
+ # delete a remote service from this server
570
+ #
571
+ # @param (see register_remote)
572
+ def remove_remote(my_key, service)
573
+ @logger.debug "About to remove a remote service:"
574
+ @logger.debug " #{service.inspect}"
575
+
576
+ unless @private_key == my_key
577
+ @logger.warn("remove_remote: incorrect key: #{my_key}")
578
+ return true
579
+ end
580
+ @remote_store.delete_if {|s| s == service}
581
+ @logger.info("Deleted Remote Service: #{service.ident}")
582
+ return true
583
+ end
584
+
585
+ # detach a remote server from this server
586
+ #
587
+ # called when the remote server is closing down. Incorrect keys are silently
588
+ # ignored. The remote server is removed from the database.
589
+ #
590
+ # @param [String] my_key being the key of the server being called
591
+ # @param [Server] server being the record for the remote server that is detaching
592
+ def detach_server(my_key, server)
593
+ @logger.verbose("About to detach a remote server: #{server.ident}")
594
+
595
+ unless @private_key == my_key
596
+ @logger.warn("close_remote_server: incorrect key: #{my_key}")
597
+ return true
598
+ end
599
+ @remote_store.delete_if {|s| s.host == server.fqdn}
600
+ @remote_servers.delete(server)
601
+ @logger.info("Detached server: #{server.ident}")
602
+ end
603
+
604
+ protected
605
+
606
+ private
607
+
608
+ # add the given service to the given store,
609
+ #
610
+ # Used to add either local or remote services and carry out common checks.
611
+ #
612
+ # @param [Array] store which is either local or remote
613
+ # @param [ServiceRecord] service to be added
614
+ # @raise [ServiceAlreadyRegistered] when ... a service is already registered!
615
+ def add_service_to_store(store, service)
616
+ store.each do |s|
617
+ if s.same_service?(service) then
618
+ # there is already a service registered, but is it active?
619
+ @logger.verbose("There is already a service registered: #{service.ident}")
620
+ if self.service_missing?(s) then
621
+ @logger.verbose "Service: #{s.ident} was registered, but did not respond"
622
+ else
623
+ raise ServiceAlreadyRegistered, @logger.warn("Service: #{service.address}-#{service.env} already registered")
624
+ end
625
+
626
+ end
627
+ end
628
+ # either service was not registered or was missing, so add it
629
+ store << service
630
+ @logger.verbose "Added #{service.ident}"
631
+ end
632
+
633
+ end
634
+ end
635
+
636
+