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,247 @@
1
+ module Cuboid
2
+
3
+ require Options.paths.lib + 'rpc/server/output'
4
+
5
+ module RPC
6
+
7
+ # Dispatcher node class, helps maintain a list of all available Dispatchers in
8
+ # the grid and announce itself to neighbouring Dispatchers.
9
+ #
10
+ # As soon as a new Node is fired up it checks-in with its neighbour and grabs
11
+ # a list of all available peers.
12
+ #
13
+ # As soon as it receives the peer list it then announces itself to them.
14
+ #
15
+ # Upon convergence there will be a grid of Dispatchers each one with its own
16
+ # copy of all available Dispatcher URLs.
17
+ #
18
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
19
+ class Server::Dispatcher::Node
20
+ include UI::Output
21
+
22
+ # Initializes the node by:
23
+ #
24
+ # * Adding the neighbour (if the user has supplied one) to the peer list.
25
+ # * Getting the neighbour's peer list and appending them to its own.
26
+ # * Announces itself to the neighbour and instructs it to propagate our URL
27
+ # to the others.
28
+ #
29
+ # @param [Cuboid::Options] options
30
+ # @param [Server::Base] server
31
+ # Dispatcher's RPC server.
32
+ # @param [String] logfile
33
+ # Where to send the output.
34
+ def initialize( options, server, logfile = nil )
35
+ @options = options
36
+ @server = server
37
+ @url = @server.url
38
+
39
+ reroute_to_file( logfile ) if logfile
40
+
41
+ print_status 'Initializing grid node...'
42
+
43
+ @dead_nodes = Set.new
44
+ @neighbours = Set.new
45
+ @nodes_info_cache = []
46
+
47
+ if (neighbour = @options.dispatcher.neighbour)
48
+ # Grab the neighbour's neighbours.
49
+ connect_to_peer( neighbour ).neighbours do |urls|
50
+ if urls.rpc_exception?
51
+ add_dead_neighbour( neighbour )
52
+ print_info "Neighbour seems dead: #{neighbour}"
53
+ add_dead_neighbour( neighbour )
54
+ next
55
+ end
56
+
57
+ # Add neighbour and announce it to everyone.
58
+ add_neighbour( neighbour, true )
59
+
60
+ urls.each { |url| @neighbours << url if url != @url }
61
+ end
62
+ end
63
+
64
+ print_status 'Node ready.'
65
+
66
+ log_updated_neighbours
67
+
68
+ Arachni::Reactor.global.at_interval( @options.dispatcher.ping_interval ) do
69
+ ping
70
+ check_for_comebacks
71
+ end
72
+ end
73
+
74
+ # @return [Boolean]
75
+ # `true` if grid member, `false` otherwise.
76
+ def grid_member?
77
+ @neighbours.any?
78
+ end
79
+
80
+ def unplug
81
+ @neighbours.each do |peer|
82
+ connect_to_peer( peer ).remove_neighbour( @url ) {}
83
+ end
84
+
85
+ @neighbours.clear
86
+ @dead_nodes.clear
87
+
88
+ nil
89
+ end
90
+
91
+ # Adds a neighbour to the peer list.
92
+ #
93
+ # @param [String] node_url
94
+ # URL of a neighbouring node.
95
+ # @param [Boolean] propagate
96
+ # Whether or not to announce the new node to the peers.
97
+ def add_neighbour( node_url, propagate = false )
98
+ # we don't want ourselves in the Set
99
+ return false if node_url == @url
100
+ return false if @neighbours.include?( node_url )
101
+
102
+ print_status "Adding neighbour: #{node_url}"
103
+
104
+ @neighbours << node_url
105
+ log_updated_neighbours
106
+ announce( node_url ) if propagate
107
+
108
+ connect_to_peer( node_url ).add_neighbour( @url, propagate ) do |res|
109
+ next if !res.rpc_exception?
110
+ add_dead_neighbour( node_url )
111
+ print_status "Neighbour seems dead: #{node_url}"
112
+ end
113
+ true
114
+ end
115
+
116
+ def remove_neighbour( url )
117
+ @neighbours.delete url
118
+ @dead_nodes.delete url
119
+ nil
120
+ end
121
+
122
+ # @return [Array]
123
+ # Neighbour/node/peer URLs.
124
+ def neighbours
125
+ @neighbours.to_a
126
+ end
127
+
128
+ def neighbours_with_info( &block )
129
+ fail 'This method requires a block!' if !block_given?
130
+
131
+ @neighbours_cmp = ''
132
+
133
+ if @nodes_info_cache.empty? || @neighbours_cmp != neighbours.to_s
134
+ @neighbours_cmp = neighbours.to_s
135
+
136
+ each = proc do |neighbour, iter|
137
+ connect_to_peer( neighbour ).info do |info|
138
+ if info.rpc_exception?
139
+ print_info "Neighbour seems dead: #{neighbour}"
140
+ add_dead_neighbour( neighbour )
141
+ log_updated_neighbours
142
+
143
+ iter.return( nil )
144
+ else
145
+ iter.return( info )
146
+ end
147
+ end
148
+ end
149
+
150
+ after = proc do |nodes|
151
+ @nodes_info_cache = nodes.compact
152
+ block.call( @nodes_info_cache )
153
+ end
154
+
155
+ Arachni::Reactor.global.create_iterator( neighbours ).map( each, after )
156
+ else
157
+ block.call( @nodes_info_cache )
158
+ end
159
+ end
160
+
161
+ # @return [Hash]
162
+ #
163
+ # * `url` -- This node's URL.
164
+ # * `name` -- Nickname
165
+ # * `neighbours` -- Array of neighbours.
166
+ def info
167
+ {
168
+ 'url' => @url,
169
+ 'name' => @options.dispatcher.name,
170
+ 'neighbours' => @neighbours.to_a,
171
+ 'unreachable_neighbours' => @dead_nodes.to_a
172
+ }
173
+ end
174
+
175
+ def alive?
176
+ true
177
+ end
178
+
179
+ private
180
+
181
+ def add_dead_neighbour( url )
182
+ remove_neighbour( url )
183
+ @dead_nodes << url
184
+ end
185
+
186
+ def log_updated_neighbours
187
+ print_info 'Updated neighbours:'
188
+
189
+ if !neighbours.empty?
190
+ neighbours.each { |node| print_info( '---- ' + node ) }
191
+ else
192
+ print_info '<empty>'
193
+ end
194
+ end
195
+
196
+ def ping
197
+ neighbours.each do |neighbour|
198
+ connect_to_peer( neighbour ).alive? do |res|
199
+ next if !res.rpc_exception?
200
+ add_dead_neighbour( neighbour )
201
+ print_status "Found dead neighbour: #{neighbour} "
202
+ end
203
+ end
204
+ end
205
+
206
+ def check_for_comebacks
207
+ @dead_nodes.dup.each do |url|
208
+ neighbour = connect_to_peer( url )
209
+ neighbour.alive? do |res|
210
+ next if res.rpc_exception?
211
+
212
+ print_status "Dispatcher came back to life: #{url}"
213
+ ([@url] | neighbours).each do |node|
214
+ neighbour.add_neighbour( node ){}
215
+ end
216
+
217
+ add_neighbour( url )
218
+ @dead_nodes.delete url
219
+ end
220
+ end
221
+ end
222
+
223
+ # Announces the node to the ones in the peer list
224
+ #
225
+ # @param [String] node
226
+ # URL
227
+ def announce( node )
228
+ print_status "Advertising: #{node}"
229
+
230
+ neighbours.each do |peer|
231
+ next if peer == node
232
+
233
+ print_info '---- to: ' + peer
234
+ connect_to_peer( peer ).add_neighbour( node ) do |res|
235
+ add_dead_neighbour( peer ) if res.rpc_exception?
236
+ end
237
+ end
238
+ end
239
+
240
+ def connect_to_peer( url )
241
+ @rpc_clients ||= {}
242
+ @rpc_clients[url] ||= Client::Dispatcher.new( url ).node
243
+ end
244
+
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,145 @@
1
+ module Cuboid
2
+ module RPC
3
+
4
+ # Base class and namespace for all Dispatcher services.
5
+ #
6
+ # # RPC accessibility
7
+ #
8
+ # Only PUBLIC methods YOU have defined will be accessible over RPC.
9
+ #
10
+ # # Blocking operations
11
+ #
12
+ # Please try to avoid blocking operations as they will block the main Reactor loop.
13
+ #
14
+ # However, if you really need to perform such operations, you can update the
15
+ # relevant methods to expect a block and then pass the desired return value to
16
+ # that block instead of returning it the usual way.
17
+ #
18
+ # This will result in the method's payload to be deferred into a Thread of its own.
19
+ #
20
+ # In addition, you can use the {#defer} and {#run_asap} methods is you need more
21
+ # control over what gets deferred and general scheduling.
22
+ #
23
+ # # Asynchronous operations
24
+ #
25
+ # Methods which perform async operations should expect a block and pass their
26
+ # results to that block instead of returning a value.
27
+ #
28
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
29
+ class Server::Dispatcher::Service
30
+
31
+ attr_reader :options
32
+ attr_reader :dispatcher
33
+
34
+ def initialize( options, dispatcher )
35
+ @options = options
36
+ @dispatcher = dispatcher
37
+ end
38
+
39
+ # @return [Server::Dispatcher::Node]
40
+ # Local node.
41
+ def node
42
+ dispatcher.instance_eval { @node }
43
+ end
44
+
45
+ # Performs an asynchronous map operation over all running instances.
46
+ #
47
+ # @param [Proc] each
48
+ # Block to be passed {Client::Instance} and `Arachni::Reactor::Iterator`.
49
+ # @param [Proc] after
50
+ # Block to be passed the Array of results.
51
+ def map_instances( each, after )
52
+ wrap_each = proc do |instance, iterator|
53
+ each.call( connect_to_instance( instance ), iterator )
54
+ end
55
+ iterator_for( instances ).map( wrap_each, after )
56
+ end
57
+
58
+ # Performs an asynchronous iteration over all running instances.
59
+ #
60
+ # @param [Proc] block
61
+ # Block to be passed {Client::Instance} and `Arachni::Reactor::Iterator`.
62
+ def each_instance( &block )
63
+ wrap = proc do |instance, iterator|
64
+ block.call( connect_to_instance( instance ), iterator )
65
+ end
66
+ iterator_for( instances ).each( &wrap )
67
+ end
68
+
69
+ # Defers a blocking operation in order to avoid blocking the main Reactor loop.
70
+ #
71
+ # The operation will be run in its own Thread - DO NOT block forever.
72
+ #
73
+ # Accepts either 2 parameters (an `operation` and a `callback` or an operation
74
+ # as a block.
75
+ #
76
+ # @param [Proc] operation
77
+ # Operation to defer.
78
+ # @param [Proc] callback
79
+ # Block to call with the results of the operation.
80
+ #
81
+ # @param [Block] block
82
+ # Operation to defer.
83
+ def defer( operation = nil, callback = nil, &block )
84
+ Thread.new( *[operation, callback].compact, &block )
85
+ end
86
+
87
+ # Runs a block as soon as possible in the Reactor loop.
88
+ #
89
+ # @param [Block] block
90
+ def run_asap( &block )
91
+ Arachni::Reactor.global.next_tick( &block )
92
+ end
93
+
94
+ # @param [Array] list
95
+ #
96
+ # @return [Arachni::Reactor::Iterator]
97
+ # Iterator for the provided array.
98
+ def iterator_for( list, max_concurrency = 10 )
99
+ Arachni::Reactor.global.create_iterator( list, max_concurrency )
100
+ end
101
+
102
+ # @return [Array<Hash>]
103
+ # Alive instances.
104
+ def instances
105
+ dispatcher.running_instances
106
+ end
107
+
108
+ # Connects to a Dispatcher by `url`
109
+ #
110
+ # @param [String] url
111
+ #
112
+ # @return [Client::Dispatcher]
113
+ def connect_to_dispatcher( url )
114
+ @dispatcher_connections ||= {}
115
+ @dispatcher_connections[url] ||= Client::Dispatcher.new( url )
116
+ end
117
+
118
+ # Connects to an Instance by `url`.
119
+ #
120
+ # @example
121
+ # connect_to_instance( url, token )
122
+ # connect_to_instance( url: url, token: token )
123
+ # connect_to_instance( 'url' => url, 'token' => token )
124
+ #
125
+ # @param [Vararg] args
126
+ #
127
+ # @return [Client::Instance]
128
+ def connect_to_instance( *args )
129
+ url = token = nil
130
+
131
+ if args.size == 2
132
+ url, token = *args
133
+ elsif args.first.is_a? Hash
134
+ connection_options = args.first
135
+ url = connection_options['url'] || connection_options[:url]
136
+ token = connection_options['token'] || connection_options[:token]
137
+ end
138
+
139
+ @instance_connections ||= {}
140
+ @instance_connections[url] ||= Client::Instance.new( url, token )
141
+ end
142
+
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,338 @@
1
+ require 'ostruct'
2
+
3
+ module Cuboid
4
+ lib = Options.paths.lib
5
+
6
+ require lib + 'processes/manager'
7
+
8
+ require lib + 'rpc/client/instance'
9
+
10
+ require lib + 'rpc/server/base'
11
+ require lib + 'rpc/server/active_options'
12
+ require lib + 'rpc/server/output'
13
+ require lib + 'rpc/server/application_wrapper'
14
+
15
+ module RPC
16
+ class Server
17
+
18
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
19
+ class Instance
20
+ include UI::Output
21
+ include Utilities
22
+
23
+ [
24
+ :suspend!, :suspended?, :snapshot_path,
25
+ :pause!, :paused?,
26
+ :abort!, :resume!,
27
+ :running?, :status
28
+ ].each do |m|
29
+ define_method m do
30
+ @application.send m
31
+ end
32
+ end
33
+
34
+ private :error_logfile
35
+ public :error_logfile
36
+
37
+ # Initializes the RPC interface and the framework.
38
+ #
39
+ # @param [Options] options
40
+ # @param [String] token
41
+ # Authentication token.
42
+ def initialize( options, token )
43
+ @options = options
44
+ @token = token
45
+
46
+ @application = Server::ApplicationWrapper.new(
47
+ Cuboid::Application.application
48
+ )
49
+ @active_options = Server::ActiveOptions.new
50
+
51
+ @server = Base.new( @options.rpc.to_server_options, token )
52
+
53
+ if @options.datastore.log_level
54
+ @server.logger.level = @options.datastore.log_level
55
+ end
56
+
57
+ @options.datastore.token = token
58
+
59
+ if @options.output.reroute_to_logfile
60
+ reroute_to_file "#{@options.paths.logs}/Instance - #{Process.pid}" <<
61
+ "-#{@options.rpc.server_port}.log"
62
+ else
63
+ reroute_to_file false
64
+ end
65
+
66
+ set_error_logfile "#{@options.paths.logs}/Instance - #{Process.pid}" <<
67
+ "-#{@options.rpc.server_port}.error.log"
68
+
69
+ set_handlers( @server )
70
+
71
+ # trap interrupts and exit cleanly when required
72
+ %w(QUIT INT).each do |signal|
73
+ next if !Signal.list.has_key?( signal )
74
+ trap( signal ){ shutdown if !@options.datastore.do_not_trap }
75
+ end
76
+
77
+ Arachni::Reactor.global.run do
78
+ _run
79
+ end
80
+ end
81
+
82
+ def application
83
+ Application.application.to_s
84
+ end
85
+
86
+ # @return [String, nil]
87
+ # Scheduler URL to which this Instance is attached, `nil` if not attached.
88
+ def scheduler_url
89
+ @options.scheduler.url
90
+ end
91
+
92
+ # @return [String, nil]
93
+ # Dispatcher URL that provided this Instance, `nil` if not provided by a
94
+ # Dispatcher.
95
+ def dispatcher_url
96
+ @options.dispatcher.url
97
+ end
98
+
99
+ # @param (see Cuboid::Application#restore)
100
+ # @return (see Cuboid::Application#restore)
101
+ #
102
+ # @see #suspend
103
+ # @see #snapshot_path
104
+ def restore!( snapshot )
105
+ @application.restore!( snapshot ).run
106
+ true
107
+ end
108
+
109
+ # @return [true]
110
+ def alive?
111
+ @server.alive?
112
+ end
113
+
114
+ # @return [Bool]
115
+ # `true` if the scan is initializing or running, `false` otherwise.
116
+ def busy?
117
+ @run_initializing || @application.busy?
118
+ end
119
+
120
+ # Cleans up and returns the report.
121
+ #
122
+ # @return [Hash]
123
+ #
124
+ # @see #report
125
+ def abort_and_generate_report
126
+ @application.clean_up
127
+ generate_report
128
+ end
129
+
130
+ # @return [Hash]
131
+ # {Report#to_rpc_data}
132
+ def generate_report
133
+ @application.generate_report.to_rpc_data
134
+ end
135
+
136
+ # # Recommended usage
137
+ #
138
+ # Please request from the method only the things you are going to actually
139
+ # use, otherwise you'll just be wasting bandwidth.
140
+ # In addition, ask to **not** be served data you already have, like
141
+ # error messages.
142
+ #
143
+ # To be kept completely up to date on the progress of a scan (i.e. receive
144
+ # new issues and error messages asap) in an efficient manner, you will need
145
+ # to keep track of the error messages you already have and explicitly tell
146
+ # the method to not send the same data back to you on subsequent calls.
147
+ #
148
+ # ## Retrieving errors (`:errors` option) without duplicate data
149
+ #
150
+ # This is done by telling the method how many error messages you already
151
+ # have and you will be served the errors from the error-log that are past
152
+ # that line.
153
+ # So, if you were to use a loop to get fresh progress data it would look
154
+ # like so:
155
+ #
156
+ # error_cnt = 0
157
+ # i = 0
158
+ # while sleep 1
159
+ # # Test method, triggers an error log...
160
+ # instance.error_test "BOOM! #{i+=1}"
161
+ #
162
+ # # Only request errors we don't already have
163
+ # errors = instance.progress( with: { errors: error_cnt } )[:errors]
164
+ # error_cnt += errors.size
165
+ #
166
+ # # You will only see new errors
167
+ # puts errors.join("\n")
168
+ # end
169
+ #
170
+ # @param [Hash] options
171
+ # Options about what progress data to retrieve and return.
172
+ # @option options [Array<Symbol, Hash>] :with
173
+ # Specify data to include:
174
+ #
175
+ # * :errors -- Errors and the line offset to use for {#errors}.
176
+ # Pass as a hash, like: `{ errors: 10 }`
177
+ # @option options [Array<Symbol, Hash>] :without
178
+ # Specify data to exclude:
179
+ #
180
+ # * :statistics -- Don't include runtime statistics.
181
+ #
182
+ # @return [Hash]
183
+ # * `statistics` -- General runtime statistics (merged when part of Grid)
184
+ # (enabled by default)
185
+ # * `status` -- {#status}
186
+ # * `busy` -- {#busy?}
187
+ # * `errors` -- {#errors} (disabled by default)
188
+ def progress( options = {} )
189
+ progress_handler( options.merge( as_hash: true ) )
190
+ end
191
+
192
+ # Configures and runs a job.
193
+ def run( options = nil )
194
+ # If the instance isn't clean bail out now.
195
+ return false if busy? || @called
196
+
197
+ if !@application.valid_options?( options )
198
+ fail ArgumentError, 'Invalid options!'
199
+ end
200
+
201
+ # There may be follow-up/retry calls by the client in cases of network
202
+ # errors (after the request has reached us) so we need to keep minimal
203
+ # track of state in order to bail out on subsequent calls.
204
+ @called = @run_initializing = true
205
+
206
+ @active_options.set( application: options )
207
+
208
+ @application.run
209
+ @run_initializing = false
210
+
211
+ true
212
+ end
213
+
214
+ # Makes the server go bye-bye...Lights out!
215
+ def shutdown( &block )
216
+ if @shutdown
217
+ block.call if block_given?
218
+ return
219
+ end
220
+ @shutdown = true
221
+
222
+ print_status 'Shutting down...'
223
+
224
+ # We're shutting down services so we need to use a concurrent way but
225
+ # without going through the Reactor.
226
+ Thread.new do
227
+ @server.shutdown
228
+ block.call true if block_given?
229
+ end
230
+
231
+ true
232
+ end
233
+
234
+ def errors( starting_line = 0 )
235
+ @application.errors( starting_line )
236
+ end
237
+
238
+ # @private
239
+ def error_test( str )
240
+ @application.error_test( str )
241
+ end
242
+
243
+ # @private
244
+ def consumed_pids
245
+ [Process.pid]
246
+ end
247
+
248
+ def self.parse_progress_opts( options, key )
249
+ parsed = {}
250
+ [options.delete( key ) || options.delete( key.to_s )].compact.each do |w|
251
+ case w
252
+ when Array
253
+ w.compact.flatten.each do |q|
254
+ case q
255
+ when String, Symbol
256
+ parsed[q.to_sym] = nil
257
+
258
+ when Hash
259
+ parsed.merge!( q.my_symbolize_keys )
260
+ end
261
+ end
262
+
263
+ when String, Symbol
264
+ parsed[w.to_sym] = nil
265
+
266
+ when Hash
267
+ parsed.merge!( w.my_symbolize_keys )
268
+ end
269
+ end
270
+
271
+ parsed
272
+ end
273
+
274
+ private
275
+
276
+ def progress_handler( options = {}, &block )
277
+ with = self.class.parse_progress_opts( options, :with )
278
+ without = self.class.parse_progress_opts( options, :without )
279
+
280
+ options = {
281
+ as_hash: options[:as_hash],
282
+ statistics: !without.include?( :statistics )
283
+ }
284
+
285
+ if with[:errors]
286
+ options[:errors] = with[:errors]
287
+ end
288
+
289
+ @application.progress( options ) do |data|
290
+ data[:busy] = busy?
291
+ block.call( data )
292
+ end
293
+ end
294
+
295
+ # Starts RPC service.
296
+ def _run
297
+ Arachni::Reactor.global.on_error do |_, e|
298
+ print_error "Reactor: #{e}"
299
+
300
+ e.backtrace.each do |l|
301
+ print_error "Reactor: #{l}"
302
+ end
303
+ end
304
+
305
+ print_status 'Starting the server...'
306
+ @server.start
307
+ end
308
+
309
+ # Outputs the Engine banner.
310
+ #
311
+ # Displays version number, author details etc.
312
+ def banner
313
+ puts BANNER
314
+ puts
315
+ puts
316
+ end
317
+
318
+ # @param [Base] server
319
+ # Prepares all the RPC handlers for the given `server`.
320
+ def set_handlers( server )
321
+ server.add_async_check do |method|
322
+ # methods that expect a block are async
323
+ method.parameters.flatten.include? :block
324
+ end
325
+
326
+ server.add_handler( 'instance', self )
327
+ server.add_handler( 'options', @active_options )
328
+
329
+ Cuboid::Application.application.instance_services.each do |name, service|
330
+ server.add_handler( name.to_s, service.new )
331
+ end
332
+ end
333
+
334
+ end
335
+
336
+ end
337
+ end
338
+ end