cuboid 0.0.0 → 0.0.1alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +0 -0
- data/Gemfile +20 -5
- data/LICENSE.md +22 -0
- data/README.md +158 -19
- data/Rakefile +56 -3
- data/config/paths.yml +15 -0
- data/cuboid.gemspec +61 -23
- data/lib/cuboid.rb +96 -4
- data/lib/cuboid/application.rb +326 -0
- data/lib/cuboid/application/parts/data.rb +18 -0
- data/lib/cuboid/application/parts/report.rb +29 -0
- data/lib/cuboid/application/parts/state.rb +274 -0
- data/lib/cuboid/application/runtime.rb +25 -0
- data/lib/cuboid/banner.rb +13 -0
- data/lib/cuboid/data.rb +86 -0
- data/lib/cuboid/data/application.rb +52 -0
- data/lib/cuboid/error.rb +9 -0
- data/lib/cuboid/option_group.rb +129 -0
- data/lib/cuboid/option_groups.rb +8 -0
- data/lib/cuboid/option_groups/datastore.rb +23 -0
- data/lib/cuboid/option_groups/dispatcher.rb +38 -0
- data/lib/cuboid/option_groups/output.rb +14 -0
- data/lib/cuboid/option_groups/paths.rb +184 -0
- data/lib/cuboid/option_groups/report.rb +39 -0
- data/lib/cuboid/option_groups/rpc.rb +105 -0
- data/lib/cuboid/option_groups/scheduler.rb +27 -0
- data/lib/cuboid/option_groups/snapshot.rb +13 -0
- data/lib/cuboid/option_groups/system.rb +10 -0
- data/lib/cuboid/options.rb +254 -0
- data/lib/cuboid/processes.rb +13 -0
- data/lib/cuboid/processes/dispatchers.rb +140 -0
- data/lib/cuboid/processes/executables/base.rb +54 -0
- data/lib/cuboid/processes/executables/dispatcher.rb +5 -0
- data/lib/cuboid/processes/executables/instance.rb +12 -0
- data/lib/cuboid/processes/executables/rest_service.rb +13 -0
- data/lib/cuboid/processes/executables/scheduler.rb +5 -0
- data/lib/cuboid/processes/helpers.rb +4 -0
- data/lib/cuboid/processes/helpers/dispatchers.rb +23 -0
- data/lib/cuboid/processes/helpers/instances.rb +39 -0
- data/lib/cuboid/processes/helpers/processes.rb +23 -0
- data/lib/cuboid/processes/helpers/schedulers.rb +23 -0
- data/lib/cuboid/processes/instances.rb +203 -0
- data/lib/cuboid/processes/manager.rb +262 -0
- data/lib/cuboid/processes/schedulers.rb +128 -0
- data/lib/cuboid/report.rb +220 -0
- data/lib/cuboid/rest/server.rb +165 -0
- data/lib/cuboid/rest/server/instance_helpers.rb +99 -0
- data/lib/cuboid/rest/server/routes/dispatcher.rb +41 -0
- data/lib/cuboid/rest/server/routes/grid.rb +41 -0
- data/lib/cuboid/rest/server/routes/instances.rb +131 -0
- data/lib/cuboid/rest/server/routes/scheduler.rb +140 -0
- data/lib/cuboid/rpc/client.rb +3 -0
- data/lib/cuboid/rpc/client/base.rb +58 -0
- data/lib/cuboid/rpc/client/dispatcher.rb +58 -0
- data/lib/cuboid/rpc/client/instance.rb +100 -0
- data/lib/cuboid/rpc/client/instance/service.rb +37 -0
- data/lib/cuboid/rpc/client/scheduler.rb +46 -0
- data/lib/cuboid/rpc/serializer.rb +92 -0
- data/lib/cuboid/rpc/server/active_options.rb +38 -0
- data/lib/cuboid/rpc/server/application_wrapper.rb +138 -0
- data/lib/cuboid/rpc/server/base.rb +63 -0
- data/lib/cuboid/rpc/server/dispatcher.rb +317 -0
- data/lib/cuboid/rpc/server/dispatcher/node.rb +247 -0
- data/lib/cuboid/rpc/server/dispatcher/service.rb +145 -0
- data/lib/cuboid/rpc/server/instance.rb +338 -0
- data/lib/cuboid/rpc/server/output.rb +92 -0
- data/lib/cuboid/rpc/server/scheduler.rb +482 -0
- data/lib/cuboid/ruby.rb +4 -0
- data/lib/cuboid/ruby/array.rb +17 -0
- data/lib/cuboid/ruby/hash.rb +41 -0
- data/lib/cuboid/ruby/object.rb +32 -0
- data/lib/cuboid/snapshot.rb +186 -0
- data/lib/cuboid/state.rb +94 -0
- data/lib/cuboid/state/application.rb +309 -0
- data/lib/cuboid/state/options.rb +27 -0
- data/lib/cuboid/support.rb +11 -0
- data/lib/cuboid/support/buffer.rb +3 -0
- data/lib/cuboid/support/buffer/autoflush.rb +61 -0
- data/lib/cuboid/support/buffer/base.rb +91 -0
- data/lib/cuboid/support/cache.rb +7 -0
- data/lib/cuboid/support/cache/base.rb +226 -0
- data/lib/cuboid/support/cache/least_cost_replacement.rb +77 -0
- data/lib/cuboid/support/cache/least_recently_pushed.rb +21 -0
- data/lib/cuboid/support/cache/least_recently_used.rb +31 -0
- data/lib/cuboid/support/cache/preference.rb +31 -0
- data/lib/cuboid/support/cache/random_replacement.rb +20 -0
- data/lib/cuboid/support/crypto.rb +2 -0
- data/lib/cuboid/support/crypto/rsa_aes_cbc.rb +86 -0
- data/lib/cuboid/support/database.rb +5 -0
- data/lib/cuboid/support/database/base.rb +177 -0
- data/lib/cuboid/support/database/categorized_queue.rb +195 -0
- data/lib/cuboid/support/database/hash.rb +300 -0
- data/lib/cuboid/support/database/queue.rb +149 -0
- data/lib/cuboid/support/filter.rb +3 -0
- data/lib/cuboid/support/filter/base.rb +110 -0
- data/lib/cuboid/support/filter/set.rb +29 -0
- data/lib/cuboid/support/glob.rb +27 -0
- data/lib/cuboid/support/mixins.rb +8 -0
- data/lib/cuboid/support/mixins/observable.rb +99 -0
- data/lib/cuboid/support/mixins/parts.rb +20 -0
- data/lib/cuboid/support/mixins/profiler.rb +93 -0
- data/lib/cuboid/support/mixins/spec_instances.rb +65 -0
- data/lib/cuboid/support/mixins/terminal.rb +57 -0
- data/lib/cuboid/system.rb +119 -0
- data/lib/cuboid/system/platforms.rb +84 -0
- data/lib/cuboid/system/platforms/linux.rb +26 -0
- data/lib/cuboid/system/platforms/mixins/unix.rb +46 -0
- data/lib/cuboid/system/platforms/osx.rb +25 -0
- data/lib/cuboid/system/platforms/windows.rb +81 -0
- data/lib/cuboid/system/slots.rb +143 -0
- data/lib/cuboid/ui/output.rb +52 -0
- data/lib/cuboid/ui/output_interface.rb +43 -0
- data/lib/cuboid/ui/output_interface/abstract.rb +68 -0
- data/lib/cuboid/ui/output_interface/controls.rb +84 -0
- data/lib/cuboid/ui/output_interface/error_logging.rb +119 -0
- data/lib/cuboid/ui/output_interface/implemented.rb +58 -0
- data/lib/cuboid/ui/output_interface/personalization.rb +62 -0
- data/lib/cuboid/utilities.rb +155 -0
- data/lib/cuboid/version.rb +4 -3
- data/lib/version +1 -0
- data/logs/placeholder +0 -0
- data/spec/cuboid/application/parts/data_spec.rb +12 -0
- data/spec/cuboid/application/parts/report_spec.rb +6 -0
- data/spec/cuboid/application/parts/state_spec.rb +192 -0
- data/spec/cuboid/application/runtime_spec.rb +21 -0
- data/spec/cuboid/application_spec.rb +37 -0
- data/spec/cuboid/data/application_spec.rb +22 -0
- data/spec/cuboid/data_spec.rb +47 -0
- data/spec/cuboid/error_spec.rb +23 -0
- data/spec/cuboid/option_groups/datastore_spec.rb +54 -0
- data/spec/cuboid/option_groups/dispatcher_spec.rb +12 -0
- data/spec/cuboid/option_groups/output_spec.rb +11 -0
- data/spec/cuboid/option_groups/paths_spec.rb +184 -0
- data/spec/cuboid/option_groups/report_spec.rb +26 -0
- data/spec/cuboid/option_groups/rpc_spec.rb +53 -0
- data/spec/cuboid/option_groups/snapshot_spec.rb +26 -0
- data/spec/cuboid/option_groups/system.rb +12 -0
- data/spec/cuboid/options_spec.rb +218 -0
- data/spec/cuboid/report_spec.rb +221 -0
- data/spec/cuboid/rest/server_spec.rb +1205 -0
- data/spec/cuboid/rpc/client/base_spec.rb +151 -0
- data/spec/cuboid/rpc/client/dispatcher_spec.rb +13 -0
- data/spec/cuboid/rpc/client/instance_spec.rb +38 -0
- data/spec/cuboid/rpc/server/active_options_spec.rb +21 -0
- data/spec/cuboid/rpc/server/base_spec.rb +60 -0
- data/spec/cuboid/rpc/server/dispatcher/node_spec.rb +222 -0
- data/spec/cuboid/rpc/server/dispatcher/service_spec.rb +112 -0
- data/spec/cuboid/rpc/server/dispatcher_spec.rb +317 -0
- data/spec/cuboid/rpc/server/instance_spec.rb +307 -0
- data/spec/cuboid/rpc/server/output_spec.rb +32 -0
- data/spec/cuboid/rpc/server/scheduler_spec.rb +400 -0
- data/spec/cuboid/ruby/array_spec.rb +77 -0
- data/spec/cuboid/ruby/hash_spec.rb +63 -0
- data/spec/cuboid/ruby/object_spec.rb +22 -0
- data/spec/cuboid/snapshot_spec.rb +123 -0
- data/spec/cuboid/state/application_spec.rb +538 -0
- data/spec/cuboid/state/options_spec.rb +37 -0
- data/spec/cuboid/state_spec.rb +53 -0
- data/spec/cuboid/support/buffer/autoflush_spec.rb +78 -0
- data/spec/cuboid/support/buffer/base_spec.rb +193 -0
- data/spec/cuboid/support/cache/least_cost_replacement_spec.rb +61 -0
- data/spec/cuboid/support/cache/least_recently_pushed_spec.rb +90 -0
- data/spec/cuboid/support/cache/least_recently_used_spec.rb +80 -0
- data/spec/cuboid/support/cache/preference_spec.rb +37 -0
- data/spec/cuboid/support/cache/random_replacement_spec.rb +42 -0
- data/spec/cuboid/support/crypto/rsa_aes_cbc_spec.rb +28 -0
- data/spec/cuboid/support/database/categorized_queue_spec.rb +327 -0
- data/spec/cuboid/support/database/hash_spec.rb +204 -0
- data/spec/cuboid/support/database/scheduler_spec.rb +325 -0
- data/spec/cuboid/support/filter/set_spec.rb +19 -0
- data/spec/cuboid/support/glob_spec.rb +75 -0
- data/spec/cuboid/support/mixins/observable_spec.rb +95 -0
- data/spec/cuboid/system/platforms/linux_spec.rb +31 -0
- data/spec/cuboid/system/platforms/osx_spec.rb +32 -0
- data/spec/cuboid/system/platforms/windows_spec.rb +41 -0
- data/spec/cuboid/system/slots_spec.rb +202 -0
- data/spec/cuboid/system_spec.rb +105 -0
- data/spec/cuboid/utilities_spec.rb +131 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/support/factories/placeholder +0 -0
- data/spec/support/factories/scan_report.rb +18 -0
- data/spec/support/fixtures/empty/placeholder +0 -0
- data/spec/support/fixtures/executables/node.rb +50 -0
- data/spec/support/fixtures/mock_app.rb +61 -0
- data/spec/support/fixtures/mock_app/test_service.rb +64 -0
- data/spec/support/fixtures/services/echo.rb +64 -0
- data/spec/support/helpers/framework.rb +3 -0
- data/spec/support/helpers/matchers.rb +5 -0
- data/spec/support/helpers/misc.rb +3 -0
- data/spec/support/helpers/paths.rb +15 -0
- data/spec/support/helpers/request_helpers.rb +38 -0
- data/spec/support/helpers/requires.rb +8 -0
- data/spec/support/helpers/resets.rb +52 -0
- data/spec/support/helpers/web_server.rb +15 -0
- data/spec/support/lib/factory.rb +107 -0
- data/spec/support/lib/web_server_client.rb +41 -0
- data/spec/support/lib/web_server_dispatcher.rb +25 -0
- data/spec/support/lib/web_server_manager.rb +118 -0
- data/spec/support/logs/placeholder +0 -0
- data/spec/support/pems/cacert.pem +37 -0
- data/spec/support/pems/client/cert.pem +37 -0
- data/spec/support/pems/client/foo-cert.pem +39 -0
- data/spec/support/pems/client/foo-key.pem +51 -0
- data/spec/support/pems/client/key.pem +51 -0
- data/spec/support/pems/server/cert.pem +37 -0
- data/spec/support/pems/server/key.pem +51 -0
- data/spec/support/reports/placeholder +0 -0
- data/spec/support/shared/application.rb +10 -0
- data/spec/support/shared/component.rb +31 -0
- data/spec/support/shared/component/options/base.rb +187 -0
- data/spec/support/shared/option_group.rb +98 -0
- data/spec/support/shared/support/cache.rb +419 -0
- data/spec/support/shared/support/filter.rb +143 -0
- data/spec/support/shared/system/platforms/base.rb +25 -0
- data/spec/support/shared/system/platforms/mixins/unix.rb +37 -0
- data/spec/support/snapshots/placeholder +0 -0
- metadata +566 -21
- data/.gitignore +0 -8
- data/bin/console +0 -15
- data/bin/setup +0 -8
@@ -0,0 +1,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
|