cuboid 0.0.0 → 0.0.1alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +0 -0
- data/Gemfile +20 -5
- data/LICENSE.md +22 -0
- data/README.md +158 -19
- data/Rakefile +56 -3
- data/config/paths.yml +15 -0
- data/cuboid.gemspec +61 -23
- data/lib/cuboid.rb +96 -4
- data/lib/cuboid/application.rb +326 -0
- data/lib/cuboid/application/parts/data.rb +18 -0
- data/lib/cuboid/application/parts/report.rb +29 -0
- data/lib/cuboid/application/parts/state.rb +274 -0
- data/lib/cuboid/application/runtime.rb +25 -0
- data/lib/cuboid/banner.rb +13 -0
- data/lib/cuboid/data.rb +86 -0
- data/lib/cuboid/data/application.rb +52 -0
- data/lib/cuboid/error.rb +9 -0
- data/lib/cuboid/option_group.rb +129 -0
- data/lib/cuboid/option_groups.rb +8 -0
- data/lib/cuboid/option_groups/datastore.rb +23 -0
- data/lib/cuboid/option_groups/dispatcher.rb +38 -0
- data/lib/cuboid/option_groups/output.rb +14 -0
- data/lib/cuboid/option_groups/paths.rb +184 -0
- data/lib/cuboid/option_groups/report.rb +39 -0
- data/lib/cuboid/option_groups/rpc.rb +105 -0
- data/lib/cuboid/option_groups/scheduler.rb +27 -0
- data/lib/cuboid/option_groups/snapshot.rb +13 -0
- data/lib/cuboid/option_groups/system.rb +10 -0
- data/lib/cuboid/options.rb +254 -0
- data/lib/cuboid/processes.rb +13 -0
- data/lib/cuboid/processes/dispatchers.rb +140 -0
- data/lib/cuboid/processes/executables/base.rb +54 -0
- data/lib/cuboid/processes/executables/dispatcher.rb +5 -0
- data/lib/cuboid/processes/executables/instance.rb +12 -0
- data/lib/cuboid/processes/executables/rest_service.rb +13 -0
- data/lib/cuboid/processes/executables/scheduler.rb +5 -0
- data/lib/cuboid/processes/helpers.rb +4 -0
- data/lib/cuboid/processes/helpers/dispatchers.rb +23 -0
- data/lib/cuboid/processes/helpers/instances.rb +39 -0
- data/lib/cuboid/processes/helpers/processes.rb +23 -0
- data/lib/cuboid/processes/helpers/schedulers.rb +23 -0
- data/lib/cuboid/processes/instances.rb +203 -0
- data/lib/cuboid/processes/manager.rb +262 -0
- data/lib/cuboid/processes/schedulers.rb +128 -0
- data/lib/cuboid/report.rb +220 -0
- data/lib/cuboid/rest/server.rb +165 -0
- data/lib/cuboid/rest/server/instance_helpers.rb +99 -0
- data/lib/cuboid/rest/server/routes/dispatcher.rb +41 -0
- data/lib/cuboid/rest/server/routes/grid.rb +41 -0
- data/lib/cuboid/rest/server/routes/instances.rb +131 -0
- data/lib/cuboid/rest/server/routes/scheduler.rb +140 -0
- data/lib/cuboid/rpc/client.rb +3 -0
- data/lib/cuboid/rpc/client/base.rb +58 -0
- data/lib/cuboid/rpc/client/dispatcher.rb +58 -0
- data/lib/cuboid/rpc/client/instance.rb +100 -0
- data/lib/cuboid/rpc/client/instance/service.rb +37 -0
- data/lib/cuboid/rpc/client/scheduler.rb +46 -0
- data/lib/cuboid/rpc/serializer.rb +92 -0
- data/lib/cuboid/rpc/server/active_options.rb +38 -0
- data/lib/cuboid/rpc/server/application_wrapper.rb +138 -0
- data/lib/cuboid/rpc/server/base.rb +63 -0
- data/lib/cuboid/rpc/server/dispatcher.rb +317 -0
- data/lib/cuboid/rpc/server/dispatcher/node.rb +247 -0
- data/lib/cuboid/rpc/server/dispatcher/service.rb +145 -0
- data/lib/cuboid/rpc/server/instance.rb +338 -0
- data/lib/cuboid/rpc/server/output.rb +92 -0
- data/lib/cuboid/rpc/server/scheduler.rb +482 -0
- data/lib/cuboid/ruby.rb +4 -0
- data/lib/cuboid/ruby/array.rb +17 -0
- data/lib/cuboid/ruby/hash.rb +41 -0
- data/lib/cuboid/ruby/object.rb +32 -0
- data/lib/cuboid/snapshot.rb +186 -0
- data/lib/cuboid/state.rb +94 -0
- data/lib/cuboid/state/application.rb +309 -0
- data/lib/cuboid/state/options.rb +27 -0
- data/lib/cuboid/support.rb +11 -0
- data/lib/cuboid/support/buffer.rb +3 -0
- data/lib/cuboid/support/buffer/autoflush.rb +61 -0
- data/lib/cuboid/support/buffer/base.rb +91 -0
- data/lib/cuboid/support/cache.rb +7 -0
- data/lib/cuboid/support/cache/base.rb +226 -0
- data/lib/cuboid/support/cache/least_cost_replacement.rb +77 -0
- data/lib/cuboid/support/cache/least_recently_pushed.rb +21 -0
- data/lib/cuboid/support/cache/least_recently_used.rb +31 -0
- data/lib/cuboid/support/cache/preference.rb +31 -0
- data/lib/cuboid/support/cache/random_replacement.rb +20 -0
- data/lib/cuboid/support/crypto.rb +2 -0
- data/lib/cuboid/support/crypto/rsa_aes_cbc.rb +86 -0
- data/lib/cuboid/support/database.rb +5 -0
- data/lib/cuboid/support/database/base.rb +177 -0
- data/lib/cuboid/support/database/categorized_queue.rb +195 -0
- data/lib/cuboid/support/database/hash.rb +300 -0
- data/lib/cuboid/support/database/queue.rb +149 -0
- data/lib/cuboid/support/filter.rb +3 -0
- data/lib/cuboid/support/filter/base.rb +110 -0
- data/lib/cuboid/support/filter/set.rb +29 -0
- data/lib/cuboid/support/glob.rb +27 -0
- data/lib/cuboid/support/mixins.rb +8 -0
- data/lib/cuboid/support/mixins/observable.rb +99 -0
- data/lib/cuboid/support/mixins/parts.rb +20 -0
- data/lib/cuboid/support/mixins/profiler.rb +93 -0
- data/lib/cuboid/support/mixins/spec_instances.rb +65 -0
- data/lib/cuboid/support/mixins/terminal.rb +57 -0
- data/lib/cuboid/system.rb +119 -0
- data/lib/cuboid/system/platforms.rb +84 -0
- data/lib/cuboid/system/platforms/linux.rb +26 -0
- data/lib/cuboid/system/platforms/mixins/unix.rb +46 -0
- data/lib/cuboid/system/platforms/osx.rb +25 -0
- data/lib/cuboid/system/platforms/windows.rb +81 -0
- data/lib/cuboid/system/slots.rb +143 -0
- data/lib/cuboid/ui/output.rb +52 -0
- data/lib/cuboid/ui/output_interface.rb +43 -0
- data/lib/cuboid/ui/output_interface/abstract.rb +68 -0
- data/lib/cuboid/ui/output_interface/controls.rb +84 -0
- data/lib/cuboid/ui/output_interface/error_logging.rb +119 -0
- data/lib/cuboid/ui/output_interface/implemented.rb +58 -0
- data/lib/cuboid/ui/output_interface/personalization.rb +62 -0
- data/lib/cuboid/utilities.rb +155 -0
- data/lib/cuboid/version.rb +4 -3
- data/lib/version +1 -0
- data/logs/placeholder +0 -0
- data/spec/cuboid/application/parts/data_spec.rb +12 -0
- data/spec/cuboid/application/parts/report_spec.rb +6 -0
- data/spec/cuboid/application/parts/state_spec.rb +192 -0
- data/spec/cuboid/application/runtime_spec.rb +21 -0
- data/spec/cuboid/application_spec.rb +37 -0
- data/spec/cuboid/data/application_spec.rb +22 -0
- data/spec/cuboid/data_spec.rb +47 -0
- data/spec/cuboid/error_spec.rb +23 -0
- data/spec/cuboid/option_groups/datastore_spec.rb +54 -0
- data/spec/cuboid/option_groups/dispatcher_spec.rb +12 -0
- data/spec/cuboid/option_groups/output_spec.rb +11 -0
- data/spec/cuboid/option_groups/paths_spec.rb +184 -0
- data/spec/cuboid/option_groups/report_spec.rb +26 -0
- data/spec/cuboid/option_groups/rpc_spec.rb +53 -0
- data/spec/cuboid/option_groups/snapshot_spec.rb +26 -0
- data/spec/cuboid/option_groups/system.rb +12 -0
- data/spec/cuboid/options_spec.rb +218 -0
- data/spec/cuboid/report_spec.rb +221 -0
- data/spec/cuboid/rest/server_spec.rb +1205 -0
- data/spec/cuboid/rpc/client/base_spec.rb +151 -0
- data/spec/cuboid/rpc/client/dispatcher_spec.rb +13 -0
- data/spec/cuboid/rpc/client/instance_spec.rb +38 -0
- data/spec/cuboid/rpc/server/active_options_spec.rb +21 -0
- data/spec/cuboid/rpc/server/base_spec.rb +60 -0
- data/spec/cuboid/rpc/server/dispatcher/node_spec.rb +222 -0
- data/spec/cuboid/rpc/server/dispatcher/service_spec.rb +112 -0
- data/spec/cuboid/rpc/server/dispatcher_spec.rb +317 -0
- data/spec/cuboid/rpc/server/instance_spec.rb +307 -0
- data/spec/cuboid/rpc/server/output_spec.rb +32 -0
- data/spec/cuboid/rpc/server/scheduler_spec.rb +400 -0
- data/spec/cuboid/ruby/array_spec.rb +77 -0
- data/spec/cuboid/ruby/hash_spec.rb +63 -0
- data/spec/cuboid/ruby/object_spec.rb +22 -0
- data/spec/cuboid/snapshot_spec.rb +123 -0
- data/spec/cuboid/state/application_spec.rb +538 -0
- data/spec/cuboid/state/options_spec.rb +37 -0
- data/spec/cuboid/state_spec.rb +53 -0
- data/spec/cuboid/support/buffer/autoflush_spec.rb +78 -0
- data/spec/cuboid/support/buffer/base_spec.rb +193 -0
- data/spec/cuboid/support/cache/least_cost_replacement_spec.rb +61 -0
- data/spec/cuboid/support/cache/least_recently_pushed_spec.rb +90 -0
- data/spec/cuboid/support/cache/least_recently_used_spec.rb +80 -0
- data/spec/cuboid/support/cache/preference_spec.rb +37 -0
- data/spec/cuboid/support/cache/random_replacement_spec.rb +42 -0
- data/spec/cuboid/support/crypto/rsa_aes_cbc_spec.rb +28 -0
- data/spec/cuboid/support/database/categorized_queue_spec.rb +327 -0
- data/spec/cuboid/support/database/hash_spec.rb +204 -0
- data/spec/cuboid/support/database/scheduler_spec.rb +325 -0
- data/spec/cuboid/support/filter/set_spec.rb +19 -0
- data/spec/cuboid/support/glob_spec.rb +75 -0
- data/spec/cuboid/support/mixins/observable_spec.rb +95 -0
- data/spec/cuboid/system/platforms/linux_spec.rb +31 -0
- data/spec/cuboid/system/platforms/osx_spec.rb +32 -0
- data/spec/cuboid/system/platforms/windows_spec.rb +41 -0
- data/spec/cuboid/system/slots_spec.rb +202 -0
- data/spec/cuboid/system_spec.rb +105 -0
- data/spec/cuboid/utilities_spec.rb +131 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/support/factories/placeholder +0 -0
- data/spec/support/factories/scan_report.rb +18 -0
- data/spec/support/fixtures/empty/placeholder +0 -0
- data/spec/support/fixtures/executables/node.rb +50 -0
- data/spec/support/fixtures/mock_app.rb +61 -0
- data/spec/support/fixtures/mock_app/test_service.rb +64 -0
- data/spec/support/fixtures/services/echo.rb +64 -0
- data/spec/support/helpers/framework.rb +3 -0
- data/spec/support/helpers/matchers.rb +5 -0
- data/spec/support/helpers/misc.rb +3 -0
- data/spec/support/helpers/paths.rb +15 -0
- data/spec/support/helpers/request_helpers.rb +38 -0
- data/spec/support/helpers/requires.rb +8 -0
- data/spec/support/helpers/resets.rb +52 -0
- data/spec/support/helpers/web_server.rb +15 -0
- data/spec/support/lib/factory.rb +107 -0
- data/spec/support/lib/web_server_client.rb +41 -0
- data/spec/support/lib/web_server_dispatcher.rb +25 -0
- data/spec/support/lib/web_server_manager.rb +118 -0
- data/spec/support/logs/placeholder +0 -0
- data/spec/support/pems/cacert.pem +37 -0
- data/spec/support/pems/client/cert.pem +37 -0
- data/spec/support/pems/client/foo-cert.pem +39 -0
- data/spec/support/pems/client/foo-key.pem +51 -0
- data/spec/support/pems/client/key.pem +51 -0
- data/spec/support/pems/server/cert.pem +37 -0
- data/spec/support/pems/server/key.pem +51 -0
- data/spec/support/reports/placeholder +0 -0
- data/spec/support/shared/application.rb +10 -0
- data/spec/support/shared/component.rb +31 -0
- data/spec/support/shared/component/options/base.rb +187 -0
- data/spec/support/shared/option_group.rb +98 -0
- data/spec/support/shared/support/cache.rb +419 -0
- data/spec/support/shared/support/filter.rb +143 -0
- data/spec/support/shared/system/platforms/base.rb +25 -0
- data/spec/support/shared/system/platforms/mixins/unix.rb +37 -0
- data/spec/support/snapshots/placeholder +0 -0
- metadata +566 -21
- data/.gitignore +0 -8
- data/bin/console +0 -15
- data/bin/setup +0 -8
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Cuboid
|
4
|
+
module RPC
|
5
|
+
|
6
|
+
# Used for serialization of {RPC} messages.
|
7
|
+
#
|
8
|
+
# It's simply a delegator for `MessagePack` with `Zlib` compression for messages
|
9
|
+
# that are larger than {COMPRESS_LARGER_THAN}.
|
10
|
+
#
|
11
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
12
|
+
module Serializer
|
13
|
+
|
14
|
+
# Compress object dumps larger than 1KB.
|
15
|
+
COMPRESS_LARGER_THAN = 1_000
|
16
|
+
|
17
|
+
# @param [#to_rpc_data] object
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
# {#compress Compressed} `object` dump.
|
21
|
+
def dump( object )
|
22
|
+
# ap object
|
23
|
+
compress( serializer.dump( object.to_rpc_data_or_self ) )
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [String] dump
|
27
|
+
# {#dump Dumped} object.
|
28
|
+
#
|
29
|
+
# @return [Object]
|
30
|
+
def load( dump )
|
31
|
+
serializer.load( decompress( dump ) )
|
32
|
+
end
|
33
|
+
|
34
|
+
# Simulates an object's over-the-wire transmission by {#dump dumping}
|
35
|
+
# and then {#load loading}.
|
36
|
+
#
|
37
|
+
# @param [#to_rpc_data,.from_rpc_data] object
|
38
|
+
#
|
39
|
+
# @return [Object]
|
40
|
+
# Data that the peer would receive.
|
41
|
+
def rpc_data( object )
|
42
|
+
load( dump( object ) )
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [#to_rpc_data,.from_rpc_data] object
|
46
|
+
#
|
47
|
+
# @return [Object]
|
48
|
+
def deep_clone( object )
|
49
|
+
object.class.from_rpc_data rpc_data( object )
|
50
|
+
end
|
51
|
+
|
52
|
+
# @note Ignores strings smaller than #{COMPRESS_LARGER_THAN}.
|
53
|
+
#
|
54
|
+
# @param [String] string
|
55
|
+
# String to compress.
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
# Compressed (or not) `string`.
|
59
|
+
def compress( string )
|
60
|
+
return string if string.size < COMPRESS_LARGER_THAN
|
61
|
+
Zlib::Deflate.deflate string
|
62
|
+
end
|
63
|
+
|
64
|
+
# @note Will return the `string` as is if it was not compressed.
|
65
|
+
#
|
66
|
+
# @param [String] string
|
67
|
+
# String to decompress.
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
# Decompressed string.
|
71
|
+
def decompress( string )
|
72
|
+
return '' if string.to_s.empty?
|
73
|
+
|
74
|
+
# Just an ID representing a serialized, empty data structure.
|
75
|
+
return string if string.size == 1
|
76
|
+
|
77
|
+
begin
|
78
|
+
Zlib::Inflate.inflate string
|
79
|
+
rescue Zlib::DataError
|
80
|
+
string
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def serializer
|
85
|
+
MessagePack
|
86
|
+
end
|
87
|
+
|
88
|
+
extend self
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Cuboid
|
2
|
+
module RPC
|
3
|
+
class Server
|
4
|
+
|
5
|
+
# It, for the most part, forwards calls to {Cuboid::Options} and intercepts
|
6
|
+
# a few that need to be updated at other places throughout the framework.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
10
|
+
class ActiveOptions
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@options = Cuboid::Options.instance
|
14
|
+
|
15
|
+
(@options.public_methods( false ) - public_methods( false ) ).each do |m|
|
16
|
+
self.class.class_eval do
|
17
|
+
define_method m do |*args|
|
18
|
+
@options.send( m, *args )
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @see Cuboid::Options#set
|
25
|
+
def set( options )
|
26
|
+
@options.set( options )
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_h
|
31
|
+
@options.to_rpc_data
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Cuboid
|
4
|
+
|
5
|
+
lib = Options.paths.lib
|
6
|
+
require lib + 'application'
|
7
|
+
|
8
|
+
module RPC
|
9
|
+
class Server
|
10
|
+
|
11
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
12
|
+
class ApplicationWrapper
|
13
|
+
include Utilities
|
14
|
+
|
15
|
+
attr_reader :application
|
16
|
+
|
17
|
+
extend Forwardable
|
18
|
+
def_delegators :@application, :suspended?, :suspend!, :status,
|
19
|
+
:pause!, :running?, :status_messages, :paused?,
|
20
|
+
:snapshot_path, :restore!, :resume!, :generate_report
|
21
|
+
|
22
|
+
# {RPC::Server::Application} error namespace.
|
23
|
+
#
|
24
|
+
# All {RPC::Server::Application} errors inherit from and live under it.
|
25
|
+
#
|
26
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
27
|
+
class Error < Cuboid::Application::Error
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize( application )
|
31
|
+
super()
|
32
|
+
|
33
|
+
@application = application.instance
|
34
|
+
@extended_running = false
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid_options?( options )
|
38
|
+
@application.class.valid_options?( options )
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Bool]
|
42
|
+
# `true` If the system is scanning, `false` if {#run} hasn't been called
|
43
|
+
# yet or if the scan has finished.
|
44
|
+
def busy?
|
45
|
+
![:ready, :done, :suspended].include?( @application.status ) &&
|
46
|
+
!!@extended_running
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Bool]
|
50
|
+
# `false` if already running, `true` otherwise.
|
51
|
+
def run
|
52
|
+
# Return if we're already running.
|
53
|
+
return false if busy?
|
54
|
+
@extended_running = true
|
55
|
+
|
56
|
+
# Start the scan -- we can't block the RPC server so we're using a Thread.
|
57
|
+
Thread.new do
|
58
|
+
@application.run
|
59
|
+
end
|
60
|
+
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def clean_up
|
65
|
+
return false if @rpc_cleaned_up
|
66
|
+
|
67
|
+
@rpc_cleaned_up = true
|
68
|
+
@extended_running = false
|
69
|
+
|
70
|
+
@application.clean_up
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param [Integer] starting_line
|
74
|
+
# Sets the starting line for the range of errors to return.
|
75
|
+
#
|
76
|
+
# @return [Array<String>]
|
77
|
+
def errors( starting_line = 0 )
|
78
|
+
return [] if UI::Output.error_buffer.empty?
|
79
|
+
|
80
|
+
error_strings = UI::Output.error_buffer
|
81
|
+
|
82
|
+
if starting_line != 0
|
83
|
+
error_strings = error_strings[starting_line..-1]
|
84
|
+
end
|
85
|
+
|
86
|
+
error_strings
|
87
|
+
end
|
88
|
+
|
89
|
+
# Provides aggregated progress data.
|
90
|
+
#
|
91
|
+
# @param [Hash] opts
|
92
|
+
# Options about what data to include:
|
93
|
+
# @option opts [Bool] :statistics (true)
|
94
|
+
# Master/merged statistics.
|
95
|
+
# @option opts [Bool, Integer] :errors (false)
|
96
|
+
# Logged errors. If an integer is provided it will return errors past that
|
97
|
+
# index.
|
98
|
+
#
|
99
|
+
# @return [Hash]
|
100
|
+
# Progress data.
|
101
|
+
def progress( opts = {} )
|
102
|
+
opts = opts.my_symbolize_keys
|
103
|
+
|
104
|
+
include_statistics = opts[:statistics].nil? ? true : opts[:statistics]
|
105
|
+
include_errors = opts.include?( :errors ) ?
|
106
|
+
(opts[:errors] || 0) : false
|
107
|
+
|
108
|
+
data = {
|
109
|
+
status: status,
|
110
|
+
busy: running?,
|
111
|
+
application: @application.class.to_s,
|
112
|
+
seed: Utilities.random_seed,
|
113
|
+
dispatcher_url: Cuboid::Options.dispatcher.url,
|
114
|
+
scheduler_url: Cuboid::Options.scheduler.url
|
115
|
+
}
|
116
|
+
|
117
|
+
if include_statistics
|
118
|
+
data[:statistics] = @application.statistics
|
119
|
+
end
|
120
|
+
|
121
|
+
if include_errors
|
122
|
+
data[:errors] =
|
123
|
+
errors( include_errors.is_a?( Integer ) ? include_errors : 0 )
|
124
|
+
end
|
125
|
+
|
126
|
+
data.merge( messages: status_messages )
|
127
|
+
end
|
128
|
+
|
129
|
+
# @private
|
130
|
+
def error_test( str )
|
131
|
+
@application.print_error str.to_s
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'arachni/rpc'
|
3
|
+
require_relative '../serializer'
|
4
|
+
|
5
|
+
module Cuboid
|
6
|
+
module RPC
|
7
|
+
class Server
|
8
|
+
|
9
|
+
# RPC server class
|
10
|
+
#
|
11
|
+
# @private
|
12
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
13
|
+
class Base < Arachni::RPC::Server
|
14
|
+
|
15
|
+
# @param [Hash] options
|
16
|
+
# @option options [Integer] :host
|
17
|
+
# @option options [Integer] :port
|
18
|
+
# @option options [Integer] :socket
|
19
|
+
# @option options [Integer] :ssl_ca
|
20
|
+
# @option options [Integer] :ssl_pkey
|
21
|
+
# @option options [Integer] :ssl_cert
|
22
|
+
# @param [String] token
|
23
|
+
# Optional authentication token.
|
24
|
+
def initialize( options = nil, token = nil )
|
25
|
+
|
26
|
+
# If given nil use the global defaults.
|
27
|
+
options ||= Options.rpc.to_server_options
|
28
|
+
@options = options
|
29
|
+
|
30
|
+
super(options.merge(
|
31
|
+
serializer: Serializer,
|
32
|
+
token: token
|
33
|
+
))
|
34
|
+
end
|
35
|
+
|
36
|
+
def address
|
37
|
+
@options[:external_address] || @options[:host]
|
38
|
+
end
|
39
|
+
|
40
|
+
def port
|
41
|
+
@options[:port]
|
42
|
+
end
|
43
|
+
|
44
|
+
def url
|
45
|
+
return @options[:socket] if @options[:socket]
|
46
|
+
|
47
|
+
"#{address}:#{port}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def start
|
51
|
+
super
|
52
|
+
@ready = true
|
53
|
+
end
|
54
|
+
|
55
|
+
def ready?
|
56
|
+
@ready ||= false
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
module Cuboid
|
2
|
+
|
3
|
+
lib = Options.paths.lib
|
4
|
+
require lib + 'processes/instances'
|
5
|
+
require lib + 'rpc/client'
|
6
|
+
require lib + 'rpc/server/base'
|
7
|
+
require lib + 'rpc/server/instance'
|
8
|
+
require lib + 'rpc/server/output'
|
9
|
+
|
10
|
+
module RPC
|
11
|
+
class Server
|
12
|
+
|
13
|
+
# Dispatches RPC Instances on demand and allows for extensive process monitoring.
|
14
|
+
#
|
15
|
+
# The process goes something like this:
|
16
|
+
#
|
17
|
+
# * A client issues a {#dispatch} call.
|
18
|
+
# * The Dispatcher spawns and returns Instance info to the client (url, auth token, etc.).
|
19
|
+
# * The client connects to the Instance using that info.
|
20
|
+
#
|
21
|
+
# Once the client finishes using the RPC Instance it *must* shut it down
|
22
|
+
# otherwise the system will be eaten away by zombie processes.
|
23
|
+
#
|
24
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
25
|
+
class Dispatcher
|
26
|
+
require Options.paths.lib + 'rpc/server/dispatcher/node'
|
27
|
+
require Options.paths.lib + 'rpc/server/dispatcher/service'
|
28
|
+
|
29
|
+
include Utilities
|
30
|
+
include UI::Output
|
31
|
+
|
32
|
+
SERVICE_NAMESPACE = Service
|
33
|
+
|
34
|
+
def initialize( options = Options.instance )
|
35
|
+
@options = options
|
36
|
+
|
37
|
+
@options.snapshot.path ||= @options.paths.snapshots
|
38
|
+
|
39
|
+
@server = Base.new( @options.rpc.to_server_options )
|
40
|
+
@server.logger.level = @options.datastore.log_level if @options.datastore.log_level
|
41
|
+
|
42
|
+
@server.add_async_check do |method|
|
43
|
+
# methods that expect a block are async
|
44
|
+
method.parameters.flatten.include? :block
|
45
|
+
end
|
46
|
+
|
47
|
+
Options.dispatcher.url = @url = @server.url
|
48
|
+
|
49
|
+
prep_logging
|
50
|
+
|
51
|
+
print_status 'Starting the RPC Server...'
|
52
|
+
|
53
|
+
@server.add_handler( 'dispatcher', self )
|
54
|
+
|
55
|
+
# trap interrupts and exit cleanly when required
|
56
|
+
trap_interrupts { shutdown }
|
57
|
+
|
58
|
+
@instances = []
|
59
|
+
|
60
|
+
Cuboid::Application.application.dispatcher_services.each do |name, service|
|
61
|
+
@server.add_handler( name.to_s, service.new( @options, self ) )
|
62
|
+
end
|
63
|
+
|
64
|
+
@node = Node.new( @options, @server, @logfile )
|
65
|
+
@server.add_handler( 'node', @node )
|
66
|
+
|
67
|
+
run
|
68
|
+
end
|
69
|
+
|
70
|
+
def services
|
71
|
+
Cuboid::Application.application.dispatcher_services.keys
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [TrueClass]
|
75
|
+
# true
|
76
|
+
def alive?
|
77
|
+
@server.alive?
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [String, nil]
|
81
|
+
# Depending on availability:
|
82
|
+
#
|
83
|
+
# * URL of the least burdened Dispatcher. If not a grid member it will
|
84
|
+
# return this Dispatcher's URL.
|
85
|
+
# * `nil` if all nodes are at max utilization.
|
86
|
+
def preferred( &block )
|
87
|
+
if !@node.grid_member?
|
88
|
+
block.call( self.utilization == 1 ? nil : @url )
|
89
|
+
return
|
90
|
+
end
|
91
|
+
|
92
|
+
pick_utilization = proc do |url, utilization|
|
93
|
+
(utilization == 1 || utilization.rpc_exception?) ?
|
94
|
+
nil : [url, utilization]
|
95
|
+
end
|
96
|
+
|
97
|
+
each = proc do |neighbour, iter|
|
98
|
+
connect_to_peer( neighbour ).utilization do |utilization|
|
99
|
+
iter.return pick_utilization.call( neighbour, utilization )
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
after = proc do |nodes|
|
104
|
+
nodes << pick_utilization.call( @url, self.utilization )
|
105
|
+
nodes.compact!
|
106
|
+
|
107
|
+
# All nodes are at max utilization, pass.
|
108
|
+
if nodes.empty?
|
109
|
+
block.call
|
110
|
+
next
|
111
|
+
end
|
112
|
+
|
113
|
+
block.call nodes.sort_by { |_, score| score }[0][0]
|
114
|
+
end
|
115
|
+
|
116
|
+
Arachni::Reactor.global.create_iterator( @node.neighbours ).map( each, after )
|
117
|
+
end
|
118
|
+
|
119
|
+
# Dispatches an {Instance}.
|
120
|
+
#
|
121
|
+
# @param [String] owner
|
122
|
+
# An owner to assign to the {Instance}.
|
123
|
+
# @param [Hash] helpers
|
124
|
+
# Hash of helper data to be added to the instance info.
|
125
|
+
# @param [Boolean] load_balance
|
126
|
+
# Return an {Instance} from the least burdened {Dispatcher} (when in Grid mode)
|
127
|
+
# or from this one directly?
|
128
|
+
#
|
129
|
+
# @return [Hash, nil]
|
130
|
+
# Depending on availability:
|
131
|
+
#
|
132
|
+
# * `Hash`: Connection and proc info.
|
133
|
+
# * `nil`: Max utilization, wait for one of the instances to finish and retry.
|
134
|
+
def dispatch( options = {}, &block )
|
135
|
+
options = options.my_symbolize_keys
|
136
|
+
owner = options[:owner]
|
137
|
+
helpers = options[:helpers] || {}
|
138
|
+
load_balance = options[:load_balance].nil? ? true : options[:load_balance]
|
139
|
+
|
140
|
+
if load_balance && @node.grid_member?
|
141
|
+
preferred do |url|
|
142
|
+
if !url
|
143
|
+
block.call
|
144
|
+
next
|
145
|
+
end
|
146
|
+
|
147
|
+
connect_to_peer( url ).dispatch( options.merge(
|
148
|
+
helpers: helpers.merge( via: @url ),
|
149
|
+
load_balance: false
|
150
|
+
),
|
151
|
+
&block
|
152
|
+
)
|
153
|
+
end
|
154
|
+
return
|
155
|
+
end
|
156
|
+
|
157
|
+
if System.max_utilization?
|
158
|
+
block.call
|
159
|
+
return
|
160
|
+
end
|
161
|
+
|
162
|
+
spawn_instance do |info|
|
163
|
+
info['owner'] = owner
|
164
|
+
info['helpers'] = helpers
|
165
|
+
|
166
|
+
@instances << info
|
167
|
+
|
168
|
+
block.call info
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns proc info for a given pid
|
173
|
+
#
|
174
|
+
# @param [Fixnum] pid
|
175
|
+
#
|
176
|
+
# @return [Hash]
|
177
|
+
def instance( pid )
|
178
|
+
@instances.each do |i|
|
179
|
+
next if i['pid'] != pid
|
180
|
+
i = i.dup
|
181
|
+
|
182
|
+
now = Time.now
|
183
|
+
|
184
|
+
i['now'] = now.to_s
|
185
|
+
i['age'] = now - Time.parse( i['birthdate'] )
|
186
|
+
i['alive'] = Cuboid::Processes::Manager.alive?( pid )
|
187
|
+
|
188
|
+
return i
|
189
|
+
end
|
190
|
+
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
|
194
|
+
# @return [Array<Hash>]
|
195
|
+
# Returns info for all instances.
|
196
|
+
def instances
|
197
|
+
@instances.map { |i| instance( i['pid'] ) }.compact
|
198
|
+
end
|
199
|
+
|
200
|
+
# @return [Array<Hash>]
|
201
|
+
# Returns info for all running (alive) instances.
|
202
|
+
#
|
203
|
+
# @see #instances
|
204
|
+
def running_instances
|
205
|
+
instances.select { |i| i['alive'] }
|
206
|
+
end
|
207
|
+
|
208
|
+
# @return [Array<Hash>]
|
209
|
+
# Returns info for all finished (dead) instances.
|
210
|
+
#
|
211
|
+
# @see #instances
|
212
|
+
def finished_instances
|
213
|
+
instances.reject { |i| i['alive'] }
|
214
|
+
end
|
215
|
+
|
216
|
+
# @return [Float]
|
217
|
+
# Workload score for this Dispatcher, calculated using {System#utilization}.
|
218
|
+
#
|
219
|
+
# * `0.0` => No utilization.
|
220
|
+
# * `1.0` => Max utilization.
|
221
|
+
#
|
222
|
+
# Lower is better.
|
223
|
+
def utilization
|
224
|
+
System.utilization
|
225
|
+
end
|
226
|
+
|
227
|
+
# @return [Hash]
|
228
|
+
# Returns server stats regarding the instances and pool.
|
229
|
+
def statistics
|
230
|
+
{
|
231
|
+
'utilization' => utilization,
|
232
|
+
'running_instances' => running_instances,
|
233
|
+
'finished_instances' => finished_instances,
|
234
|
+
'consumed_pids' => @instances.map { |i| i['pid'] }.compact,
|
235
|
+
'snapshots' => Dir.glob( "#{@options.snapshot.path}*.#{Snapshot::EXTENSION}" ),
|
236
|
+
'node' => @node.info
|
237
|
+
}
|
238
|
+
end
|
239
|
+
|
240
|
+
# @return [String]
|
241
|
+
# Contents of the log file
|
242
|
+
def log
|
243
|
+
IO.read prep_logging
|
244
|
+
end
|
245
|
+
|
246
|
+
# @private
|
247
|
+
def pid
|
248
|
+
Process.pid
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def trap_interrupts( &block )
|
254
|
+
%w(QUIT INT).each do |signal|
|
255
|
+
trap( signal, &block || Proc.new{ } ) if Signal.list.has_key?( signal )
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Starts the dispatcher's server
|
260
|
+
def run
|
261
|
+
Arachni::Reactor.global.on_error do |_, e|
|
262
|
+
print_error "Reactor: #{e}"
|
263
|
+
|
264
|
+
e.backtrace.each do |l|
|
265
|
+
print_error "Reactor: #{l}"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
print_status 'Ready'
|
270
|
+
@server.start
|
271
|
+
rescue => e
|
272
|
+
print_exception e
|
273
|
+
|
274
|
+
$stderr.puts "Could not start server, for details see: #{@logfile}"
|
275
|
+
exit 1
|
276
|
+
end
|
277
|
+
|
278
|
+
def shutdown
|
279
|
+
Thread.new do
|
280
|
+
print_status 'Shutting down...'
|
281
|
+
Arachni::Reactor.global.stop
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def spawn_instance( options = {}, &block )
|
286
|
+
Processes::Instances.spawn( options.merge(
|
287
|
+
address: @server.address,
|
288
|
+
port_range: Options.dispatcher.instance_port_range,
|
289
|
+
token: Utilities.generate_token,
|
290
|
+
application: Options.paths.application
|
291
|
+
)) do |client|
|
292
|
+
block.call(
|
293
|
+
'token' => client.token,
|
294
|
+
'url' => client.url,
|
295
|
+
'pid' => client.pid,
|
296
|
+
'birthdate' => Time.now.to_s,
|
297
|
+
'application' => Options.paths.application
|
298
|
+
)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def prep_logging
|
303
|
+
# reroute all output to a logfile
|
304
|
+
@logfile ||= reroute_to_file( @options.paths.logs +
|
305
|
+
"/Dispatcher - #{Process.pid}-#{@options.rpc.server_port}.log" )
|
306
|
+
end
|
307
|
+
|
308
|
+
def connect_to_peer( url )
|
309
|
+
@rpc_clients ||= {}
|
310
|
+
@rpc_clients[url] ||= Client::Dispatcher.new( url )
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|