Class: Jerbil::Broker
- Inherits:
-
Object
- Object
- Jerbil::Broker
- Defined in:
- lib/jerbil.rb
Overview
The Broker, being a server class that runs on each machine on which Jerbil services will run or will be required. The Broker registers services and interacts with other servers to share information about services across the network.
It is not necessary to use this interface directly. By using the JerbilService::Base class all interaction with the server is done under the hood. See Services Readme for more details.
Key methods are:
-
*#register* to add a service to the broker’s database
-
**#remove** to remove a service from the broker’s database
-
**#find to obtain information about one or more services matching given criteria
Methods used between servers are:
-
#register_server to add a remote server to the broker’s database
-
#detach_server to remove a remote server from the broker’s database
-
#get_local_services called by a remote server to get all of the
local services known to this server
-
#register_remote for a remote server to register a new service
-
#remove_remote for a remote server to remove a service
Methods used to internally:
-
#stop to stop the server gracefully
-
#missing_service? to check if a service is missing and remove it from
the database if it is
Instance Attribute Summary collapse
-
#registrations ⇒ Object
readonly
the number of registrations since the server started.
-
#remote_servers ⇒ Array
readonly
the remote servers at any one time.
-
#started ⇒ Object
readonly
date/time at which the server was started.
Instance Method Summary collapse
-
#close ⇒ Object
close the logger that Jerbil is using.
-
#detach_server(my_key, server) ⇒ Object
detach a remote server from this server.
-
#find(args = {}) ⇒ Array
return the services that match the given criteria.
-
#get(args = {}) ⇒ Array
get the first service that matches the given criteria.
-
#get_all(ignore_access = false) ⇒ Array
return all services.
- #get_all_by_server ⇒ Object
-
#get_local(args = {}) ⇒ Array
get the first service that matches the given criteria and is running on the same processor.
-
#get_local_services(my_key) ⇒ Array
get all of the local services registered with this server.
-
#initialize(options, pkey) ⇒ Broker
constructor
create a new Jerbil server.
-
#local_service_count ⇒ Numeric
the number of local services registered with the server.
-
#register(service) ⇒ Object
register a service to the local server.
-
#register_remote(my_key, service) ⇒ Object
register a remote service.
-
#register_server(server, secret, env) ⇒ String
Register a remote server, providing limited authentication.
-
#remote_service_count ⇒ Numeric
the number of remote services registered with the server.
-
#remove(service) ⇒ Object
remove a service from the register.
-
#remove_remote(my_key, service) ⇒ Object
delete a remote service from this server.
-
#restart_logger ⇒ Object
restart the logger on the other side of daemonising it.
-
#ruby_version ⇒ Object
tell remote users about what version of Ruby we are running.
-
#server ⇒ Object
provide access to the local server record.
-
#service_count ⇒ Numeric
the total number of services currently registered with the server.
-
#service_missing?(service) ⇒ Boolean
Checks for a potentially missing service and removes it if it cannot be found.
-
#stop(private_key) ⇒ Object
stop the Jerbil server.
-
#verify ⇒ Object
simple method to check that the server is running from a remote client.
-
#version ⇒ String
The current version of the Jerbil Server.
Constructor Details
#initialize(options, pkey) ⇒ Broker
create a new Jerbil server
The options for the server are defined in Config and are best created using this class. This is a [Jeckyl](github.com/osburn-sharp/jeckyl) config file. Further details are provided in the Readme file.
The private key should be unique to this server and is used to authenticate system actions and to authenticate remote servers. Its not very secure so more a way of avoiding mistakes. The key is best created using Support.create_private_key.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/jerbil.rb', line 87 def initialize(, pkey) #log_dir, log_level=:system) # store details of this server and remote servers @env = [:environment] || :prod @private_key = pkey @secret = [:secret] @local = Jerbil::Servers.create_local_server(@env, @private_key) @remote_servers = Array.new # who am i #@host = Socket.gethostname #store local and remote services @store = Array.new @remote_store = Array.new # create a jellog logger that continues any previous log and keeps the last 5 log files @app_name = "Jerbil-#{[:environment].to_s}" @log_opts = Jellog::Config.intersection() #log_opts = @log_opts.dup @logger = Jellog::Logger.new(@app_name, @log_opts) @logger.mark @logger.debug "Started the Logger for Jerbil" @logger.debug "Saved logger options: #{@log_opts.inspect}" # some statistical data @started = Time.now @registrations = 0 @logger.verbose("Searching for remote servers") network_servers = [] #Jerbil::Servers.find_servers(@env, options[:net_address], options[:net_mask], options[:scan_timeout]) #@logger.verbose("Found #{@remote_servers.length} remote servers") # now loop round the remote servers to see if any are there # DO NOTHING cos its an empty array! network_servers.each do |remote_server| rjerbil = remote_server.connect unless rjerbil.nil? @logger.debug "Getting Remote Services. Connecting to : #{remote_server.inspect}" # there is a remote server, so tell it about me begin rkey = rjerbil.register_server(@local, @secret, @env) remote_server.set_key(rkey) @logger.debug "Key for #{remote_server.fqdn}: #{rkey}" rjerbil.get_local_services(rkey).each {|ls| add_service_to_store(@remote_store, ls)} # add it to the list of verified servers @remote_servers << remote_server rescue DRb::DRbConnError # assume it is not working @logger.verbose("Failed to get remote services from server: #{remote_server.fqdn}") rescue JerbilAuthenticationError => jerr @logger.warn("Remote server authentication failed, skipping") @logger.warn(" #{jerr.}") rescue ArgumentError, NoMethodError @logger.warn("Remote server incompatibility, skipping") rescue => jerr @logger.exception(jerr) end end end @logger.system("Started up the Jerbil Server") @logger.debug "My key: #{@private_key}" @logger.debug "Stored remote keys:" @remote_servers.each do |rs| @logger.debug " #{rs.fqdn}: #{rs.key}" end #@logger.verbose "Closing logger temporarily" #@logger.close rescue => jerr @logger.exception(jerr) raise end |
Instance Attribute Details
#registrations ⇒ Object (readonly)
the number of registrations since the server started
179 180 181 |
# File 'lib/jerbil.rb', line 179 def registrations @registrations end |
#remote_servers ⇒ Array (readonly)
the remote servers at any one time
184 185 186 |
# File 'lib/jerbil.rb', line 184 def remote_servers @remote_servers end |
#started ⇒ Object (readonly)
date/time at which the server was started
176 177 178 |
# File 'lib/jerbil.rb', line 176 def started @started end |
Instance Method Details
#close ⇒ Object
close the logger that Jerbil is using
probably only useful for testing?
477 478 479 |
# File 'lib/jerbil.rb', line 477 def close @logger.close end |
#detach_server(my_key, server) ⇒ Object
detach a remote server from this server
called when the remote server is closing down. Incorrect keys are silently ignored. The remote server is removed from the database.
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 |
# File 'lib/jerbil.rb', line 628 def detach_server(my_key, server) unless @private_key == my_key @logger.warn("Detaching remote server: incorrect key: #{my_key}") return false end unless @remote_servers.include?(server) @logger.warn "Detaching remote server: server not known: #{server.ident}" return false end @logger.verbose("About to detach a remote server: #{server.ident}") @remote_store.delete_if {|s| s.host == server.fqdn} @remote_servers.delete(server) @logger.info("Detached server: #{server.ident}") end |
#find(args = {}) ⇒ Array
return the services that match the given criteria
search for services based on name, environment etc:
broker.find(:name=>'MyService', :env=>:test)
If an option is not specified it will be ignored. Find uses ServiceRecord#matches? to compare services to the given criteria.
Normally this method will log the access to each service found (keeps a count) This can be disabled by setting :ignore_access to true. This is used internally to avoid counting Jerbil operations as service accesses.
There are also various short-cut methods that can be used: get, get_local and get_all
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/jerbil.rb', line 316 def find(args={}) #options = {:name=>nil, :port=>nil, :env=>nil}.merge(args) results = Array.new services = @store + @remote_store services.each do |service| if service.matches?(args) then service.log_access unless args[:ignore_access] results << service end end @logger.verbose("Searching for services. Found #{results.length} matching.") @logger.verbose(" Arguments: #{args.inspect}") return results end |
#get(args = {}) ⇒ Array
get the first service that matches the given criteria.
Uses #find to do the real work and returns the first service. There is no guarantee of the order. In addition, unless :ignore_acess is true, this call will check if the service is connected, and will return nil if it is not
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/jerbil.rb', line 343 def get(args={}) results = Array.new results = self.find(args) if results.length >= 1 then service = results[0] @logger.verbose("Get returned #{service.ident}") unless args[:ignore_access] # check if it is working begin service.connect rescue ServiceCallbackMissing @logger.warning("Verifying #{service.ident} failed due to missing callback") # missing callback but still return it... rescue ServiceConnectError @logger.verbose("Verification failed for #{service.ident}") return nil end end return service else return nil end end |
#get_all(ignore_access = false) ⇒ Array
return all services
does not require any matching criteria.
384 385 386 |
# File 'lib/jerbil.rb', line 384 def get_all(ignore_access=false) self.find(:ignore_access => ignore_access) end |
#get_all_by_server ⇒ Object
388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/jerbil.rb', line 388 def get_all_by_server services = self.find(ignore_access: true) servers = Hash.new services.each do |serv| unless servers.has_key?(serv.host) servers[serv.host] = Array.new end servers[serv.host] << serv end return servers end |
#get_local(args = {}) ⇒ Array
get the first service that matches the given criteria and is running on the same processor
373 374 375 376 |
# File 'lib/jerbil.rb', line 373 def get_local(args={}) new_args = args.merge({:host=>@host}) return get(new_args) end |
#get_local_services(my_key) ⇒ Array
get all of the local services registered with this server
572 573 574 575 |
# File 'lib/jerbil.rb', line 572 def get_local_services(my_key) raise InvalidServerKey, @logger.error("get_local_services: incorrect key: #{my_key}") unless @private_key == my_key return @store.dup end |
#local_service_count ⇒ Numeric
the number of local services registered with the server
210 211 212 |
# File 'lib/jerbil.rb', line 210 def local_service_count @store.length end |
#register(service) ⇒ Object
register a service to the local server
The caller registers the given service. The server will check that the service is not already registered before adding it. It will then inform all the other servers it is aware of about this service so that anyone on the network can reach it. See #register_remote to see what happens when this methods registers a service with a remote server.
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/jerbil.rb', line 234 def register(service) @logger.verbose("About to register a local service: #{service.ident}") if service.local? then service.register add_service_to_store(@store, service) @registrations += 1 @logger.system("Registered Local Service: #{service.ident}") @remote_servers.each do |rserver| rjerbil = rserver.connect unless rjerbil.nil? @logger.debug("Registering remote. Connected to #{rserver.fqdn}") begin rjerbil.register_remote(rserver.key, service) @logger.verbose("Registered Service: #{service.name} on server: #{rserver.fqdn}") rescue DRb::DRbConnError # assume it is not working end end end else # someone is attempting to register a service that is not local @logger.warn("Attempt to register non-local service: #{service.ident}") raise ServiceNotLocal end end |
#register_remote(my_key, service) ⇒ Object
register a remote service
This is called by a jerbil service when it wants to register a service local to it with all the other servers. This will siltenly delete any existing service record.
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 |
# File 'lib/jerbil.rb', line 587 def register_remote(my_key, service) @logger.debug "About to register a remote service:" @logger.debug " #{service.inspect}" unless @private_key == my_key @logger.warn("register remote: incorrect key: #{my_key}, ignoring") return true end # perhaps there is a stale record for this service? Stops add below from assuming it is missing etc @remote_store.delete_if {|rservice| rservice.same_service?(service)} add_service_to_store(@remote_store, service) @logger.info("Registered Remote Service: #{service.ident}") return true end |
#register_server(server, secret, env) ⇒ String
Register a remote server, providing limited authentication.
Registering a server will purge any old server record and any old services for that server
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 |
# File 'lib/jerbil.rb', line 543 def register_server(server, secret, env) @logger.debug("Attempting to register server: #{server.ident}") unless secret == @secret @logger.debug "mismatching secret: #{secret}" raise JerbilAuthenticationError, @logger.error("Secret key from #{server.fqdn} does not match") end unless env = @env raise JerbilAuthenticationError, @logger.error("Registering server with #{env}, against #{@env}") end # need to delete any stale existing record @remote_servers.delete_if {|rserver| rserver.fqdn == server.fqdn} # registering this new server, but there may be stale services as well @remote_store.delete_if {|rservice| rservice.host == server.fqdn} @remote_servers << server @logger.debug "Registered a new server" @logger.debug " #{server.ident}: #{server.key}" return @private_key end |
#remote_service_count ⇒ Numeric
the number of remote services registered with the server
216 217 218 |
# File 'lib/jerbil.rb', line 216 def remote_service_count @remote_store.length end |
#remove(service) ⇒ Object
remove a service from the register
does nothing if the service is not registered, otherwise removes it locally and then calls #remove_remote for each registered server.
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/jerbil.rb', line 268 def remove(service) if @store.include?(service) then # its a local one @store.delete(service) @logger.system("Deleted Service: #{service.ident}") else @logger.warn("Attempt was made to remove a service that is not registered: #{service.ident}") @logger.warn("Trying to remove it remotely anyway") end @remote_servers.each do |rserver| rjerbil = rserver.connect unless rjerbil.nil? @logger.debug("Connected to #{rserver.fqdn}") begin rjerbil.remove_remote(rserver.key, service) @logger.verbose("Removed Service from remote server: #{service.ident}") rescue DRb::DRbConnError # assume it is not working @logger.debug("Skipping over remove_remote for #{rserver.fqdn} while removing #{service.ident}") end end end end |
#remove_remote(my_key, service) ⇒ Object
delete a remote service from this server
608 609 610 611 612 613 614 615 616 617 618 619 |
# File 'lib/jerbil.rb', line 608 def remove_remote(my_key, service) @logger.debug "About to remove a remote service:" @logger.debug " #{service.inspect}" unless @private_key == my_key @logger.warn("remove_remote: incorrect key: #{my_key}") return true end @remote_store.delete_if {|s| s == service} @logger.info("Deleted Remote Service: #{service.ident}") return true end |
#restart_logger ⇒ Object
restart the logger on the other side of daemonising it
NOT NEEDED!
170 171 172 173 |
# File 'lib/jerbil.rb', line 170 def restart_logger @logger = Jellog::Logger.new(@app_name, @log_opts) @logger.debug "Restarted Logger" end |
#ruby_version ⇒ Object
tell remote users about what version of Ruby we are running
192 193 194 |
# File 'lib/jerbil.rb', line 192 def ruby_version RUBY_VERSION end |
#server ⇒ Object
provide access to the local server record
187 188 189 |
# File 'lib/jerbil.rb', line 187 def server @local end |
#service_count ⇒ Numeric
the total number of services currently registered with the server
204 205 206 |
# File 'lib/jerbil.rb', line 204 def service_count @store.length + @remote_store.length end |
#service_missing?(service) ⇒ Boolean
Checks for a potentially missing service and removes it if it cannot be found.
What to do if you cannot connect to a service that Jerbil thinks is there? check if its local, try to connect and if OK then return false to allow retries otherwise remove the service and return true.
If the service is not local, find its server and ask it the same question. if the server is not there, then fake being that server and remove_remote from everyone. Don’t forget to remove it from here too!
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 |
# File 'lib/jerbil.rb', line 412 def service_missing?(service) # is it one of mine? if service.local? then #yes @logger.verbose("Local service missing for #{service.ident}?") begin service.connect # seems to be fine @logger.info("Missing service was found to be OK: #{service.ident}") return false rescue # failed to connect for some reason. # trying to stop the service @logger.debug("Local service appears to be missing: #{service.ident}") # and now remove it from the record self.remove(service) @logger.system("Removed missing local service: #{service.ident}") return true end else # not one of mine, so who owns it @logger.verbose("Missing service is not local: #{service.ident}") failed_remote_server = nil @remote_servers.each do |rserver| if rserver.fqdn == service.host then # found it, so try to warn it @logger.debug("Service: #{service.ident} belongs to #{rserver.fqdn}") begin rjerbil = rserver.connect return rjerbil.service_missing?(service) rescue # whoops, failed to connect to remote server # so assume it has gone and allow method to continue # so that it removes the service as if it was the remote server failed_remote_server = rserver end end end # only got here because could not connect to the server unless failed_remote_server.nil? @logger.warn("Failed to connect to server: #{failed_remote_server.fqdn}, removing service for it") rkey = failed_remote_server.key self.remove_remote(rkey, service) @remote_servers.each do |rserver| begin rjerbil = rserver.connect rjerbil.remove_remote(rkey, service) @logger.debug("Removed service: #{service.ident} from server #{rserver.fqdn}") rescue # server not up, so ignore @logger.debug("Failed to connect to server to remove service: #{rserver.fqdn}, but who cares!") end end return true else # strange? Should not have a service for which there is no server... @logger.warn("Could not find a server for #{service.ident}. How could this happen?") return false end end end |
#stop(private_key) ⇒ Object
stop the Jerbil server
Need to make sure the caller knows what they are doing so requires the server’s private key.
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 |
# File 'lib/jerbil.rb', line 493 def stop(private_key) if @private_key == private_key then @logger.info("About to stop the Jerbil Server") @remote_servers.each do |rserver| begin rjerbil = rserver.connect @logger.verbose("Closing connection to: #{rserver.ident}") rjerbil.detach_server(rserver.key, @local) rescue ServerConnectError, DRb::DRbConnError @logger.error("Failed to connect to #{rserver.ident}") end end @logger.system("Stopping the Jerbil Server now") @logger.close DRb.stop_service #exit! else @logger.system("Stop called with incorrect private key") @logger.debug(" Private Key provided:") @logger.debug("#{private_key}") @logger.debug(" Private Key required") @logger.debug("#{@private_key}") raise InvalidPrivateKey end rescue ServerConnectError @logger.error("Connection to remote server failed") rescue InvalidPrivateKey raise rescue => err @logger.exception(err) end |
#verify ⇒ Object
simple method to check that the server is running from a remote client
482 483 484 |
# File 'lib/jerbil.rb', line 482 def verify return true end |