cuboid 0.0.0 → 0.0.1alpha

Sign up to get free protection for your applications and to get access to all the features.
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