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