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.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -0
  3. data/Gemfile +20 -5
  4. data/LICENSE.md +22 -0
  5. data/README.md +158 -19
  6. data/Rakefile +56 -3
  7. data/config/paths.yml +15 -0
  8. data/cuboid.gemspec +61 -23
  9. data/lib/cuboid.rb +96 -4
  10. data/lib/cuboid/application.rb +326 -0
  11. data/lib/cuboid/application/parts/data.rb +18 -0
  12. data/lib/cuboid/application/parts/report.rb +29 -0
  13. data/lib/cuboid/application/parts/state.rb +274 -0
  14. data/lib/cuboid/application/runtime.rb +25 -0
  15. data/lib/cuboid/banner.rb +13 -0
  16. data/lib/cuboid/data.rb +86 -0
  17. data/lib/cuboid/data/application.rb +52 -0
  18. data/lib/cuboid/error.rb +9 -0
  19. data/lib/cuboid/option_group.rb +129 -0
  20. data/lib/cuboid/option_groups.rb +8 -0
  21. data/lib/cuboid/option_groups/datastore.rb +23 -0
  22. data/lib/cuboid/option_groups/dispatcher.rb +38 -0
  23. data/lib/cuboid/option_groups/output.rb +14 -0
  24. data/lib/cuboid/option_groups/paths.rb +184 -0
  25. data/lib/cuboid/option_groups/report.rb +39 -0
  26. data/lib/cuboid/option_groups/rpc.rb +105 -0
  27. data/lib/cuboid/option_groups/scheduler.rb +27 -0
  28. data/lib/cuboid/option_groups/snapshot.rb +13 -0
  29. data/lib/cuboid/option_groups/system.rb +10 -0
  30. data/lib/cuboid/options.rb +254 -0
  31. data/lib/cuboid/processes.rb +13 -0
  32. data/lib/cuboid/processes/dispatchers.rb +140 -0
  33. data/lib/cuboid/processes/executables/base.rb +54 -0
  34. data/lib/cuboid/processes/executables/dispatcher.rb +5 -0
  35. data/lib/cuboid/processes/executables/instance.rb +12 -0
  36. data/lib/cuboid/processes/executables/rest_service.rb +13 -0
  37. data/lib/cuboid/processes/executables/scheduler.rb +5 -0
  38. data/lib/cuboid/processes/helpers.rb +4 -0
  39. data/lib/cuboid/processes/helpers/dispatchers.rb +23 -0
  40. data/lib/cuboid/processes/helpers/instances.rb +39 -0
  41. data/lib/cuboid/processes/helpers/processes.rb +23 -0
  42. data/lib/cuboid/processes/helpers/schedulers.rb +23 -0
  43. data/lib/cuboid/processes/instances.rb +203 -0
  44. data/lib/cuboid/processes/manager.rb +262 -0
  45. data/lib/cuboid/processes/schedulers.rb +128 -0
  46. data/lib/cuboid/report.rb +220 -0
  47. data/lib/cuboid/rest/server.rb +165 -0
  48. data/lib/cuboid/rest/server/instance_helpers.rb +99 -0
  49. data/lib/cuboid/rest/server/routes/dispatcher.rb +41 -0
  50. data/lib/cuboid/rest/server/routes/grid.rb +41 -0
  51. data/lib/cuboid/rest/server/routes/instances.rb +131 -0
  52. data/lib/cuboid/rest/server/routes/scheduler.rb +140 -0
  53. data/lib/cuboid/rpc/client.rb +3 -0
  54. data/lib/cuboid/rpc/client/base.rb +58 -0
  55. data/lib/cuboid/rpc/client/dispatcher.rb +58 -0
  56. data/lib/cuboid/rpc/client/instance.rb +100 -0
  57. data/lib/cuboid/rpc/client/instance/service.rb +37 -0
  58. data/lib/cuboid/rpc/client/scheduler.rb +46 -0
  59. data/lib/cuboid/rpc/serializer.rb +92 -0
  60. data/lib/cuboid/rpc/server/active_options.rb +38 -0
  61. data/lib/cuboid/rpc/server/application_wrapper.rb +138 -0
  62. data/lib/cuboid/rpc/server/base.rb +63 -0
  63. data/lib/cuboid/rpc/server/dispatcher.rb +317 -0
  64. data/lib/cuboid/rpc/server/dispatcher/node.rb +247 -0
  65. data/lib/cuboid/rpc/server/dispatcher/service.rb +145 -0
  66. data/lib/cuboid/rpc/server/instance.rb +338 -0
  67. data/lib/cuboid/rpc/server/output.rb +92 -0
  68. data/lib/cuboid/rpc/server/scheduler.rb +482 -0
  69. data/lib/cuboid/ruby.rb +4 -0
  70. data/lib/cuboid/ruby/array.rb +17 -0
  71. data/lib/cuboid/ruby/hash.rb +41 -0
  72. data/lib/cuboid/ruby/object.rb +32 -0
  73. data/lib/cuboid/snapshot.rb +186 -0
  74. data/lib/cuboid/state.rb +94 -0
  75. data/lib/cuboid/state/application.rb +309 -0
  76. data/lib/cuboid/state/options.rb +27 -0
  77. data/lib/cuboid/support.rb +11 -0
  78. data/lib/cuboid/support/buffer.rb +3 -0
  79. data/lib/cuboid/support/buffer/autoflush.rb +61 -0
  80. data/lib/cuboid/support/buffer/base.rb +91 -0
  81. data/lib/cuboid/support/cache.rb +7 -0
  82. data/lib/cuboid/support/cache/base.rb +226 -0
  83. data/lib/cuboid/support/cache/least_cost_replacement.rb +77 -0
  84. data/lib/cuboid/support/cache/least_recently_pushed.rb +21 -0
  85. data/lib/cuboid/support/cache/least_recently_used.rb +31 -0
  86. data/lib/cuboid/support/cache/preference.rb +31 -0
  87. data/lib/cuboid/support/cache/random_replacement.rb +20 -0
  88. data/lib/cuboid/support/crypto.rb +2 -0
  89. data/lib/cuboid/support/crypto/rsa_aes_cbc.rb +86 -0
  90. data/lib/cuboid/support/database.rb +5 -0
  91. data/lib/cuboid/support/database/base.rb +177 -0
  92. data/lib/cuboid/support/database/categorized_queue.rb +195 -0
  93. data/lib/cuboid/support/database/hash.rb +300 -0
  94. data/lib/cuboid/support/database/queue.rb +149 -0
  95. data/lib/cuboid/support/filter.rb +3 -0
  96. data/lib/cuboid/support/filter/base.rb +110 -0
  97. data/lib/cuboid/support/filter/set.rb +29 -0
  98. data/lib/cuboid/support/glob.rb +27 -0
  99. data/lib/cuboid/support/mixins.rb +8 -0
  100. data/lib/cuboid/support/mixins/observable.rb +99 -0
  101. data/lib/cuboid/support/mixins/parts.rb +20 -0
  102. data/lib/cuboid/support/mixins/profiler.rb +93 -0
  103. data/lib/cuboid/support/mixins/spec_instances.rb +65 -0
  104. data/lib/cuboid/support/mixins/terminal.rb +57 -0
  105. data/lib/cuboid/system.rb +119 -0
  106. data/lib/cuboid/system/platforms.rb +84 -0
  107. data/lib/cuboid/system/platforms/linux.rb +26 -0
  108. data/lib/cuboid/system/platforms/mixins/unix.rb +46 -0
  109. data/lib/cuboid/system/platforms/osx.rb +25 -0
  110. data/lib/cuboid/system/platforms/windows.rb +81 -0
  111. data/lib/cuboid/system/slots.rb +143 -0
  112. data/lib/cuboid/ui/output.rb +52 -0
  113. data/lib/cuboid/ui/output_interface.rb +43 -0
  114. data/lib/cuboid/ui/output_interface/abstract.rb +68 -0
  115. data/lib/cuboid/ui/output_interface/controls.rb +84 -0
  116. data/lib/cuboid/ui/output_interface/error_logging.rb +119 -0
  117. data/lib/cuboid/ui/output_interface/implemented.rb +58 -0
  118. data/lib/cuboid/ui/output_interface/personalization.rb +62 -0
  119. data/lib/cuboid/utilities.rb +155 -0
  120. data/lib/cuboid/version.rb +4 -3
  121. data/lib/version +1 -0
  122. data/logs/placeholder +0 -0
  123. data/spec/cuboid/application/parts/data_spec.rb +12 -0
  124. data/spec/cuboid/application/parts/report_spec.rb +6 -0
  125. data/spec/cuboid/application/parts/state_spec.rb +192 -0
  126. data/spec/cuboid/application/runtime_spec.rb +21 -0
  127. data/spec/cuboid/application_spec.rb +37 -0
  128. data/spec/cuboid/data/application_spec.rb +22 -0
  129. data/spec/cuboid/data_spec.rb +47 -0
  130. data/spec/cuboid/error_spec.rb +23 -0
  131. data/spec/cuboid/option_groups/datastore_spec.rb +54 -0
  132. data/spec/cuboid/option_groups/dispatcher_spec.rb +12 -0
  133. data/spec/cuboid/option_groups/output_spec.rb +11 -0
  134. data/spec/cuboid/option_groups/paths_spec.rb +184 -0
  135. data/spec/cuboid/option_groups/report_spec.rb +26 -0
  136. data/spec/cuboid/option_groups/rpc_spec.rb +53 -0
  137. data/spec/cuboid/option_groups/snapshot_spec.rb +26 -0
  138. data/spec/cuboid/option_groups/system.rb +12 -0
  139. data/spec/cuboid/options_spec.rb +218 -0
  140. data/spec/cuboid/report_spec.rb +221 -0
  141. data/spec/cuboid/rest/server_spec.rb +1205 -0
  142. data/spec/cuboid/rpc/client/base_spec.rb +151 -0
  143. data/spec/cuboid/rpc/client/dispatcher_spec.rb +13 -0
  144. data/spec/cuboid/rpc/client/instance_spec.rb +38 -0
  145. data/spec/cuboid/rpc/server/active_options_spec.rb +21 -0
  146. data/spec/cuboid/rpc/server/base_spec.rb +60 -0
  147. data/spec/cuboid/rpc/server/dispatcher/node_spec.rb +222 -0
  148. data/spec/cuboid/rpc/server/dispatcher/service_spec.rb +112 -0
  149. data/spec/cuboid/rpc/server/dispatcher_spec.rb +317 -0
  150. data/spec/cuboid/rpc/server/instance_spec.rb +307 -0
  151. data/spec/cuboid/rpc/server/output_spec.rb +32 -0
  152. data/spec/cuboid/rpc/server/scheduler_spec.rb +400 -0
  153. data/spec/cuboid/ruby/array_spec.rb +77 -0
  154. data/spec/cuboid/ruby/hash_spec.rb +63 -0
  155. data/spec/cuboid/ruby/object_spec.rb +22 -0
  156. data/spec/cuboid/snapshot_spec.rb +123 -0
  157. data/spec/cuboid/state/application_spec.rb +538 -0
  158. data/spec/cuboid/state/options_spec.rb +37 -0
  159. data/spec/cuboid/state_spec.rb +53 -0
  160. data/spec/cuboid/support/buffer/autoflush_spec.rb +78 -0
  161. data/spec/cuboid/support/buffer/base_spec.rb +193 -0
  162. data/spec/cuboid/support/cache/least_cost_replacement_spec.rb +61 -0
  163. data/spec/cuboid/support/cache/least_recently_pushed_spec.rb +90 -0
  164. data/spec/cuboid/support/cache/least_recently_used_spec.rb +80 -0
  165. data/spec/cuboid/support/cache/preference_spec.rb +37 -0
  166. data/spec/cuboid/support/cache/random_replacement_spec.rb +42 -0
  167. data/spec/cuboid/support/crypto/rsa_aes_cbc_spec.rb +28 -0
  168. data/spec/cuboid/support/database/categorized_queue_spec.rb +327 -0
  169. data/spec/cuboid/support/database/hash_spec.rb +204 -0
  170. data/spec/cuboid/support/database/scheduler_spec.rb +325 -0
  171. data/spec/cuboid/support/filter/set_spec.rb +19 -0
  172. data/spec/cuboid/support/glob_spec.rb +75 -0
  173. data/spec/cuboid/support/mixins/observable_spec.rb +95 -0
  174. data/spec/cuboid/system/platforms/linux_spec.rb +31 -0
  175. data/spec/cuboid/system/platforms/osx_spec.rb +32 -0
  176. data/spec/cuboid/system/platforms/windows_spec.rb +41 -0
  177. data/spec/cuboid/system/slots_spec.rb +202 -0
  178. data/spec/cuboid/system_spec.rb +105 -0
  179. data/spec/cuboid/utilities_spec.rb +131 -0
  180. data/spec/spec_helper.rb +46 -0
  181. data/spec/support/factories/placeholder +0 -0
  182. data/spec/support/factories/scan_report.rb +18 -0
  183. data/spec/support/fixtures/empty/placeholder +0 -0
  184. data/spec/support/fixtures/executables/node.rb +50 -0
  185. data/spec/support/fixtures/mock_app.rb +61 -0
  186. data/spec/support/fixtures/mock_app/test_service.rb +64 -0
  187. data/spec/support/fixtures/services/echo.rb +64 -0
  188. data/spec/support/helpers/framework.rb +3 -0
  189. data/spec/support/helpers/matchers.rb +5 -0
  190. data/spec/support/helpers/misc.rb +3 -0
  191. data/spec/support/helpers/paths.rb +15 -0
  192. data/spec/support/helpers/request_helpers.rb +38 -0
  193. data/spec/support/helpers/requires.rb +8 -0
  194. data/spec/support/helpers/resets.rb +52 -0
  195. data/spec/support/helpers/web_server.rb +15 -0
  196. data/spec/support/lib/factory.rb +107 -0
  197. data/spec/support/lib/web_server_client.rb +41 -0
  198. data/spec/support/lib/web_server_dispatcher.rb +25 -0
  199. data/spec/support/lib/web_server_manager.rb +118 -0
  200. data/spec/support/logs/placeholder +0 -0
  201. data/spec/support/pems/cacert.pem +37 -0
  202. data/spec/support/pems/client/cert.pem +37 -0
  203. data/spec/support/pems/client/foo-cert.pem +39 -0
  204. data/spec/support/pems/client/foo-key.pem +51 -0
  205. data/spec/support/pems/client/key.pem +51 -0
  206. data/spec/support/pems/server/cert.pem +37 -0
  207. data/spec/support/pems/server/key.pem +51 -0
  208. data/spec/support/reports/placeholder +0 -0
  209. data/spec/support/shared/application.rb +10 -0
  210. data/spec/support/shared/component.rb +31 -0
  211. data/spec/support/shared/component/options/base.rb +187 -0
  212. data/spec/support/shared/option_group.rb +98 -0
  213. data/spec/support/shared/support/cache.rb +419 -0
  214. data/spec/support/shared/support/filter.rb +143 -0
  215. data/spec/support/shared/system/platforms/base.rb +25 -0
  216. data/spec/support/shared/system/platforms/mixins/unix.rb +37 -0
  217. data/spec/support/snapshots/placeholder +0 -0
  218. metadata +566 -21
  219. data/.gitignore +0 -8
  220. data/bin/console +0 -15
  221. 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