cuboid 0.0.0 → 0.0.1alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -0
  3. data/Gemfile +20 -5
  4. data/LICENSE.md +22 -0
  5. data/README.md +158 -19
  6. data/Rakefile +56 -3
  7. data/config/paths.yml +15 -0
  8. data/cuboid.gemspec +61 -23
  9. data/lib/cuboid.rb +96 -4
  10. data/lib/cuboid/application.rb +326 -0
  11. data/lib/cuboid/application/parts/data.rb +18 -0
  12. data/lib/cuboid/application/parts/report.rb +29 -0
  13. data/lib/cuboid/application/parts/state.rb +274 -0
  14. data/lib/cuboid/application/runtime.rb +25 -0
  15. data/lib/cuboid/banner.rb +13 -0
  16. data/lib/cuboid/data.rb +86 -0
  17. data/lib/cuboid/data/application.rb +52 -0
  18. data/lib/cuboid/error.rb +9 -0
  19. data/lib/cuboid/option_group.rb +129 -0
  20. data/lib/cuboid/option_groups.rb +8 -0
  21. data/lib/cuboid/option_groups/datastore.rb +23 -0
  22. data/lib/cuboid/option_groups/dispatcher.rb +38 -0
  23. data/lib/cuboid/option_groups/output.rb +14 -0
  24. data/lib/cuboid/option_groups/paths.rb +184 -0
  25. data/lib/cuboid/option_groups/report.rb +39 -0
  26. data/lib/cuboid/option_groups/rpc.rb +105 -0
  27. data/lib/cuboid/option_groups/scheduler.rb +27 -0
  28. data/lib/cuboid/option_groups/snapshot.rb +13 -0
  29. data/lib/cuboid/option_groups/system.rb +10 -0
  30. data/lib/cuboid/options.rb +254 -0
  31. data/lib/cuboid/processes.rb +13 -0
  32. data/lib/cuboid/processes/dispatchers.rb +140 -0
  33. data/lib/cuboid/processes/executables/base.rb +54 -0
  34. data/lib/cuboid/processes/executables/dispatcher.rb +5 -0
  35. data/lib/cuboid/processes/executables/instance.rb +12 -0
  36. data/lib/cuboid/processes/executables/rest_service.rb +13 -0
  37. data/lib/cuboid/processes/executables/scheduler.rb +5 -0
  38. data/lib/cuboid/processes/helpers.rb +4 -0
  39. data/lib/cuboid/processes/helpers/dispatchers.rb +23 -0
  40. data/lib/cuboid/processes/helpers/instances.rb +39 -0
  41. data/lib/cuboid/processes/helpers/processes.rb +23 -0
  42. data/lib/cuboid/processes/helpers/schedulers.rb +23 -0
  43. data/lib/cuboid/processes/instances.rb +203 -0
  44. data/lib/cuboid/processes/manager.rb +262 -0
  45. data/lib/cuboid/processes/schedulers.rb +128 -0
  46. data/lib/cuboid/report.rb +220 -0
  47. data/lib/cuboid/rest/server.rb +165 -0
  48. data/lib/cuboid/rest/server/instance_helpers.rb +99 -0
  49. data/lib/cuboid/rest/server/routes/dispatcher.rb +41 -0
  50. data/lib/cuboid/rest/server/routes/grid.rb +41 -0
  51. data/lib/cuboid/rest/server/routes/instances.rb +131 -0
  52. data/lib/cuboid/rest/server/routes/scheduler.rb +140 -0
  53. data/lib/cuboid/rpc/client.rb +3 -0
  54. data/lib/cuboid/rpc/client/base.rb +58 -0
  55. data/lib/cuboid/rpc/client/dispatcher.rb +58 -0
  56. data/lib/cuboid/rpc/client/instance.rb +100 -0
  57. data/lib/cuboid/rpc/client/instance/service.rb +37 -0
  58. data/lib/cuboid/rpc/client/scheduler.rb +46 -0
  59. data/lib/cuboid/rpc/serializer.rb +92 -0
  60. data/lib/cuboid/rpc/server/active_options.rb +38 -0
  61. data/lib/cuboid/rpc/server/application_wrapper.rb +138 -0
  62. data/lib/cuboid/rpc/server/base.rb +63 -0
  63. data/lib/cuboid/rpc/server/dispatcher.rb +317 -0
  64. data/lib/cuboid/rpc/server/dispatcher/node.rb +247 -0
  65. data/lib/cuboid/rpc/server/dispatcher/service.rb +145 -0
  66. data/lib/cuboid/rpc/server/instance.rb +338 -0
  67. data/lib/cuboid/rpc/server/output.rb +92 -0
  68. data/lib/cuboid/rpc/server/scheduler.rb +482 -0
  69. data/lib/cuboid/ruby.rb +4 -0
  70. data/lib/cuboid/ruby/array.rb +17 -0
  71. data/lib/cuboid/ruby/hash.rb +41 -0
  72. data/lib/cuboid/ruby/object.rb +32 -0
  73. data/lib/cuboid/snapshot.rb +186 -0
  74. data/lib/cuboid/state.rb +94 -0
  75. data/lib/cuboid/state/application.rb +309 -0
  76. data/lib/cuboid/state/options.rb +27 -0
  77. data/lib/cuboid/support.rb +11 -0
  78. data/lib/cuboid/support/buffer.rb +3 -0
  79. data/lib/cuboid/support/buffer/autoflush.rb +61 -0
  80. data/lib/cuboid/support/buffer/base.rb +91 -0
  81. data/lib/cuboid/support/cache.rb +7 -0
  82. data/lib/cuboid/support/cache/base.rb +226 -0
  83. data/lib/cuboid/support/cache/least_cost_replacement.rb +77 -0
  84. data/lib/cuboid/support/cache/least_recently_pushed.rb +21 -0
  85. data/lib/cuboid/support/cache/least_recently_used.rb +31 -0
  86. data/lib/cuboid/support/cache/preference.rb +31 -0
  87. data/lib/cuboid/support/cache/random_replacement.rb +20 -0
  88. data/lib/cuboid/support/crypto.rb +2 -0
  89. data/lib/cuboid/support/crypto/rsa_aes_cbc.rb +86 -0
  90. data/lib/cuboid/support/database.rb +5 -0
  91. data/lib/cuboid/support/database/base.rb +177 -0
  92. data/lib/cuboid/support/database/categorized_queue.rb +195 -0
  93. data/lib/cuboid/support/database/hash.rb +300 -0
  94. data/lib/cuboid/support/database/queue.rb +149 -0
  95. data/lib/cuboid/support/filter.rb +3 -0
  96. data/lib/cuboid/support/filter/base.rb +110 -0
  97. data/lib/cuboid/support/filter/set.rb +29 -0
  98. data/lib/cuboid/support/glob.rb +27 -0
  99. data/lib/cuboid/support/mixins.rb +8 -0
  100. data/lib/cuboid/support/mixins/observable.rb +99 -0
  101. data/lib/cuboid/support/mixins/parts.rb +20 -0
  102. data/lib/cuboid/support/mixins/profiler.rb +93 -0
  103. data/lib/cuboid/support/mixins/spec_instances.rb +65 -0
  104. data/lib/cuboid/support/mixins/terminal.rb +57 -0
  105. data/lib/cuboid/system.rb +119 -0
  106. data/lib/cuboid/system/platforms.rb +84 -0
  107. data/lib/cuboid/system/platforms/linux.rb +26 -0
  108. data/lib/cuboid/system/platforms/mixins/unix.rb +46 -0
  109. data/lib/cuboid/system/platforms/osx.rb +25 -0
  110. data/lib/cuboid/system/platforms/windows.rb +81 -0
  111. data/lib/cuboid/system/slots.rb +143 -0
  112. data/lib/cuboid/ui/output.rb +52 -0
  113. data/lib/cuboid/ui/output_interface.rb +43 -0
  114. data/lib/cuboid/ui/output_interface/abstract.rb +68 -0
  115. data/lib/cuboid/ui/output_interface/controls.rb +84 -0
  116. data/lib/cuboid/ui/output_interface/error_logging.rb +119 -0
  117. data/lib/cuboid/ui/output_interface/implemented.rb +58 -0
  118. data/lib/cuboid/ui/output_interface/personalization.rb +62 -0
  119. data/lib/cuboid/utilities.rb +155 -0
  120. data/lib/cuboid/version.rb +4 -3
  121. data/lib/version +1 -0
  122. data/logs/placeholder +0 -0
  123. data/spec/cuboid/application/parts/data_spec.rb +12 -0
  124. data/spec/cuboid/application/parts/report_spec.rb +6 -0
  125. data/spec/cuboid/application/parts/state_spec.rb +192 -0
  126. data/spec/cuboid/application/runtime_spec.rb +21 -0
  127. data/spec/cuboid/application_spec.rb +37 -0
  128. data/spec/cuboid/data/application_spec.rb +22 -0
  129. data/spec/cuboid/data_spec.rb +47 -0
  130. data/spec/cuboid/error_spec.rb +23 -0
  131. data/spec/cuboid/option_groups/datastore_spec.rb +54 -0
  132. data/spec/cuboid/option_groups/dispatcher_spec.rb +12 -0
  133. data/spec/cuboid/option_groups/output_spec.rb +11 -0
  134. data/spec/cuboid/option_groups/paths_spec.rb +184 -0
  135. data/spec/cuboid/option_groups/report_spec.rb +26 -0
  136. data/spec/cuboid/option_groups/rpc_spec.rb +53 -0
  137. data/spec/cuboid/option_groups/snapshot_spec.rb +26 -0
  138. data/spec/cuboid/option_groups/system.rb +12 -0
  139. data/spec/cuboid/options_spec.rb +218 -0
  140. data/spec/cuboid/report_spec.rb +221 -0
  141. data/spec/cuboid/rest/server_spec.rb +1205 -0
  142. data/spec/cuboid/rpc/client/base_spec.rb +151 -0
  143. data/spec/cuboid/rpc/client/dispatcher_spec.rb +13 -0
  144. data/spec/cuboid/rpc/client/instance_spec.rb +38 -0
  145. data/spec/cuboid/rpc/server/active_options_spec.rb +21 -0
  146. data/spec/cuboid/rpc/server/base_spec.rb +60 -0
  147. data/spec/cuboid/rpc/server/dispatcher/node_spec.rb +222 -0
  148. data/spec/cuboid/rpc/server/dispatcher/service_spec.rb +112 -0
  149. data/spec/cuboid/rpc/server/dispatcher_spec.rb +317 -0
  150. data/spec/cuboid/rpc/server/instance_spec.rb +307 -0
  151. data/spec/cuboid/rpc/server/output_spec.rb +32 -0
  152. data/spec/cuboid/rpc/server/scheduler_spec.rb +400 -0
  153. data/spec/cuboid/ruby/array_spec.rb +77 -0
  154. data/spec/cuboid/ruby/hash_spec.rb +63 -0
  155. data/spec/cuboid/ruby/object_spec.rb +22 -0
  156. data/spec/cuboid/snapshot_spec.rb +123 -0
  157. data/spec/cuboid/state/application_spec.rb +538 -0
  158. data/spec/cuboid/state/options_spec.rb +37 -0
  159. data/spec/cuboid/state_spec.rb +53 -0
  160. data/spec/cuboid/support/buffer/autoflush_spec.rb +78 -0
  161. data/spec/cuboid/support/buffer/base_spec.rb +193 -0
  162. data/spec/cuboid/support/cache/least_cost_replacement_spec.rb +61 -0
  163. data/spec/cuboid/support/cache/least_recently_pushed_spec.rb +90 -0
  164. data/spec/cuboid/support/cache/least_recently_used_spec.rb +80 -0
  165. data/spec/cuboid/support/cache/preference_spec.rb +37 -0
  166. data/spec/cuboid/support/cache/random_replacement_spec.rb +42 -0
  167. data/spec/cuboid/support/crypto/rsa_aes_cbc_spec.rb +28 -0
  168. data/spec/cuboid/support/database/categorized_queue_spec.rb +327 -0
  169. data/spec/cuboid/support/database/hash_spec.rb +204 -0
  170. data/spec/cuboid/support/database/scheduler_spec.rb +325 -0
  171. data/spec/cuboid/support/filter/set_spec.rb +19 -0
  172. data/spec/cuboid/support/glob_spec.rb +75 -0
  173. data/spec/cuboid/support/mixins/observable_spec.rb +95 -0
  174. data/spec/cuboid/system/platforms/linux_spec.rb +31 -0
  175. data/spec/cuboid/system/platforms/osx_spec.rb +32 -0
  176. data/spec/cuboid/system/platforms/windows_spec.rb +41 -0
  177. data/spec/cuboid/system/slots_spec.rb +202 -0
  178. data/spec/cuboid/system_spec.rb +105 -0
  179. data/spec/cuboid/utilities_spec.rb +131 -0
  180. data/spec/spec_helper.rb +46 -0
  181. data/spec/support/factories/placeholder +0 -0
  182. data/spec/support/factories/scan_report.rb +18 -0
  183. data/spec/support/fixtures/empty/placeholder +0 -0
  184. data/spec/support/fixtures/executables/node.rb +50 -0
  185. data/spec/support/fixtures/mock_app.rb +61 -0
  186. data/spec/support/fixtures/mock_app/test_service.rb +64 -0
  187. data/spec/support/fixtures/services/echo.rb +64 -0
  188. data/spec/support/helpers/framework.rb +3 -0
  189. data/spec/support/helpers/matchers.rb +5 -0
  190. data/spec/support/helpers/misc.rb +3 -0
  191. data/spec/support/helpers/paths.rb +15 -0
  192. data/spec/support/helpers/request_helpers.rb +38 -0
  193. data/spec/support/helpers/requires.rb +8 -0
  194. data/spec/support/helpers/resets.rb +52 -0
  195. data/spec/support/helpers/web_server.rb +15 -0
  196. data/spec/support/lib/factory.rb +107 -0
  197. data/spec/support/lib/web_server_client.rb +41 -0
  198. data/spec/support/lib/web_server_dispatcher.rb +25 -0
  199. data/spec/support/lib/web_server_manager.rb +118 -0
  200. data/spec/support/logs/placeholder +0 -0
  201. data/spec/support/pems/cacert.pem +37 -0
  202. data/spec/support/pems/client/cert.pem +37 -0
  203. data/spec/support/pems/client/foo-cert.pem +39 -0
  204. data/spec/support/pems/client/foo-key.pem +51 -0
  205. data/spec/support/pems/client/key.pem +51 -0
  206. data/spec/support/pems/server/cert.pem +37 -0
  207. data/spec/support/pems/server/key.pem +51 -0
  208. data/spec/support/reports/placeholder +0 -0
  209. data/spec/support/shared/application.rb +10 -0
  210. data/spec/support/shared/component.rb +31 -0
  211. data/spec/support/shared/component/options/base.rb +187 -0
  212. data/spec/support/shared/option_group.rb +98 -0
  213. data/spec/support/shared/support/cache.rb +419 -0
  214. data/spec/support/shared/support/filter.rb +143 -0
  215. data/spec/support/shared/system/platforms/base.rb +25 -0
  216. data/spec/support/shared/system/platforms/mixins/unix.rb +37 -0
  217. data/spec/support/snapshots/placeholder +0 -0
  218. metadata +566 -21
  219. data/.gitignore +0 -8
  220. data/bin/console +0 -15
  221. data/bin/setup +0 -8
@@ -0,0 +1,92 @@
1
+ module Cuboid
2
+ module UI
3
+
4
+ # RPC Output interface.
5
+ #
6
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
7
+ module Output
8
+ include OutputInterface
9
+
10
+ def self.initialize
11
+ @@reroute_to_file = false
12
+ @@error_buffer = []
13
+ end
14
+ initialize
15
+
16
+ # @param [String] str
17
+ def log_error( str = '' )
18
+ super( str )
19
+ @@error_buffer << str
20
+ end
21
+
22
+ def error_buffer
23
+ @@error_buffer
24
+ end
25
+
26
+ def print_error( str = '' )
27
+ log_error( str )
28
+ push_to_output_buffer( error: str )
29
+ end
30
+
31
+ def print_bad( str = '' )
32
+ push_to_output_buffer( bad: str )
33
+ end
34
+
35
+ def print_status( str = '' )
36
+ push_to_output_buffer( status: str )
37
+ end
38
+
39
+ def print_info( str = '' )
40
+ push_to_output_buffer( info: str )
41
+ end
42
+
43
+ def print_ok( str = '' )
44
+ push_to_output_buffer( ok: str )
45
+ end
46
+
47
+ def print_debug( str = '', level = 1 )
48
+ return if !debug?( level )
49
+ push_to_output_buffer( debug: str )
50
+ end
51
+
52
+ def print_verbose( str = '' )
53
+ push_to_output_buffer( verbose: str )
54
+ end
55
+
56
+ def print_line( str = '' )
57
+ push_to_output_buffer( line: str )
58
+ end
59
+
60
+ def reroute_to_file( file )
61
+ @@reroute_to_file = file
62
+ end
63
+
64
+ def reroute_to_file?
65
+ @@reroute_to_file
66
+ end
67
+
68
+ def output_provider_file
69
+ __FILE__
70
+ end
71
+
72
+ private
73
+
74
+ def push_to_output_buffer( msg )
75
+ return if !@@reroute_to_file
76
+
77
+ # This is stupid, keep a handle open and close it on exit like with the
78
+ # error log file.
79
+ File.open( @@reroute_to_file, 'a+' ) do |f|
80
+ type = msg.keys[0]
81
+ str = msg.values[0]
82
+
83
+ f.write( "[#{Time.now.asctime}] [#{type}] #{str}\n" )
84
+ end
85
+ end
86
+
87
+ extend self
88
+
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,482 @@
1
+ module Cuboid
2
+
3
+ lib = Options.paths.lib
4
+
5
+ require lib + 'processes/manager'
6
+ require lib + 'processes/instances'
7
+
8
+ require lib + 'rpc/client/instance'
9
+ require lib + 'rpc/client/dispatcher'
10
+
11
+ require lib + 'rpc/server/base'
12
+ require lib + 'rpc/server/output'
13
+
14
+ module RPC
15
+ class Server
16
+
17
+ # RPC scheduler service which:
18
+ #
19
+ # * Maintains a priority queue of Instance jobs.
20
+ # * {#push Accepts jobs.}
21
+ # * Runs them once a slot is available -- determined by
22
+ # {System#utilization system utilization}.
23
+ # * Monitors {#running} Instances, retrieves and
24
+ # {OptionGroups::Paths#reports stores} their {Report reports} and shuts down
25
+ # their {Instance} to free its slot.
26
+ # * Makes available information on {#completed} and {#failed} Instances.
27
+ #
28
+ # In addition to the purely queue functionality, it also allows for running
29
+ # Instances to be:
30
+ #
31
+ # * {#detach Detached} from the queue monitor and transfer the management
32
+ # responsibility to the client.
33
+ # * {#attach Attached} to the queue monitor and transfer the management
34
+ # responsibility to the queue.
35
+ #
36
+ # If a {Dispatcher} has been provided, {Instance instances} will be
37
+ # {Dispatcher#dispatch provided} by it.
38
+ # If no {Dispatcher} has been given, {Instance instances} will be spawned on the
39
+ # Scheduler machine.
40
+ #
41
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
42
+ class Scheduler
43
+ include UI::Output
44
+ include Utilities
45
+
46
+ TICK_CONSUME = 0.1
47
+
48
+ def initialize
49
+ @options = Options.instance
50
+
51
+ @options.snapshot.path ||= @options.paths.snapshots
52
+ @options.report.path ||= @options.paths.reports
53
+
54
+ @server = Base.new( @options.rpc.to_server_options )
55
+ @server.logger.level = @options.datastore.log_level if @options.datastore.log_level
56
+
57
+ Options.scheduler.url = @url = @server.url
58
+
59
+ prep_logging
60
+
61
+ @queue = {}
62
+ @id_to_priority = {}
63
+ @by_priority = {}
64
+
65
+ @running = {}
66
+ @completed = {}
67
+ @failed = {}
68
+
69
+ set_handlers( @server )
70
+ trap_interrupts { Thread.new { shutdown } }
71
+
72
+ monitor_instances
73
+ consume_queue
74
+
75
+ run
76
+ end
77
+
78
+ # @return [Bool]
79
+ def empty?
80
+ self.size == 0
81
+ end
82
+
83
+ # @return [Bool]
84
+ def any?
85
+ !empty?
86
+ end
87
+
88
+ # @return [Integer]
89
+ def size
90
+ @queue.size
91
+ end
92
+
93
+ # @return [Hash<Integer,Array>]
94
+ # Queued Instances grouped and sorted by priority.
95
+ def list
96
+ @by_priority
97
+ end
98
+
99
+ # @return [Hash]
100
+ # {RPC::Client::Instance RPC connection information} on running Instances.
101
+ def running
102
+ @running.inject( {} ) do |h, (id, client)|
103
+ h.merge! id => { url: client.url, token: client.token, pid: client.pid }
104
+ h
105
+ end
106
+ end
107
+
108
+ # @return [Hash]
109
+ # Completed Instances and their report location.
110
+ def completed
111
+ @completed
112
+ end
113
+
114
+ # @return [Hash]
115
+ # Failed Instances and the associated error.
116
+ def failed
117
+ @failed
118
+ end
119
+
120
+ # @note Only returns info for queued Instances, once a Instance has passed through
121
+ # the {#running} stage it's no longer part of the queue.
122
+ #
123
+ # @param [String] id
124
+ # ID for a queued Instance.
125
+ #
126
+ # @return [Hash, nil]
127
+ # * Instance options and priority.
128
+ # * `nil` if a Instance with the given ID could not be found.
129
+ def get( id )
130
+ return if !@queue.include? id
131
+
132
+ {
133
+ options: @queue[id],
134
+ priority: @id_to_priority[id]
135
+ }
136
+ end
137
+
138
+ # @param [Hash] options
139
+ # {Instance#run Instance options} with an extra `priority` option which
140
+ # defaults to `0` (higher is more urgent).
141
+ #
142
+ # @return [String]
143
+ # Instance ID used to reference the Instance from then on.
144
+ def push( options, queue_options = {} )
145
+ priority = queue_options.delete('priority') || 0
146
+
147
+ if !Cuboid::Application.application.valid_options?( options )
148
+ fail ArgumentError, 'Invalid options!'
149
+ end
150
+
151
+ id = Utilities.generate_token
152
+
153
+ @queue[id] = options
154
+ @id_to_priority[id] = priority
155
+
156
+ (@by_priority[priority] ||= []) << id
157
+ @by_priority = Hash[@by_priority.sort_by { |k, _| -k }]
158
+
159
+ id
160
+ end
161
+
162
+ # @note Only affects queued Instances, once a Instance has passed through
163
+ # the {#running} stage it's no longer part of the queue.
164
+ #
165
+ # @param [String] id
166
+ # Instance ID to remove from the queue.
167
+ def remove( id )
168
+ return false if !@queue.include? id
169
+
170
+ @queue.delete( id )
171
+ @by_priority[@id_to_priority.delete( id )].delete( id )
172
+
173
+ true
174
+ end
175
+
176
+ # @param [String] id
177
+ # Running Instance to detach from the queue monitor.
178
+ #
179
+ # Once a Instance is detached it becomes someone else's responsibility to
180
+ # monitor, manage and shutdown to free its slot.
181
+ #
182
+ # @return [Hash, nil]
183
+ # * {RPC::Client::Instance RPC connection information} for the Instance.
184
+ # * `nil` if no running Instance with that ID is found.
185
+ def detach( id, &block )
186
+ client = @running.delete( id )
187
+ return block.call if !client
188
+
189
+ client.options.set( scheduler: { url: nil } ) do
190
+ block.call( url: client.url, token: client.token, pid: client.pid )
191
+ end
192
+ end
193
+
194
+ # Attaches a running Instance to the queue monitor.
195
+ #
196
+ # @param [String] url
197
+ # Instance URL for a running Instance.
198
+ # @param [String] token
199
+ # Authentication token for the Instance.
200
+ #
201
+ # @return [String, false, nil]
202
+ # * Instance ID for further queue reference.
203
+ # * `false` if the Instance is already attached to a Scheduler.
204
+ # * `nil` if the Instance could not be reached.
205
+ def attach( url, token, &block )
206
+ client = connect_to_instance( url, token )
207
+ client.alive? do |bool|
208
+ if bool.rpc_exception?
209
+ block.call
210
+ next
211
+ end
212
+
213
+ client.scheduler_url do |scheduler_url|
214
+ if scheduler_url
215
+ block.call false
216
+ next
217
+ end
218
+
219
+ client.options.set( scheduler: { url: @options.scheduler.url } ) do
220
+ @running[token] = client
221
+ block.call token
222
+ end
223
+ end
224
+ end
225
+ end
226
+
227
+ # @note Only affects queued Instances, once a Instance has passed through the
228
+ # {#running} stage it's no longer part of the queue.
229
+ #
230
+ # Empties the queue.
231
+ def clear
232
+ @queue.clear
233
+ @by_priority.clear
234
+ @id_to_priority.clear
235
+
236
+ nil
237
+ end
238
+
239
+ # Shuts down the service.
240
+ def shutdown
241
+ print_status 'Shutting down...'
242
+ reactor.delay 2 do
243
+ reactor.stop
244
+ end
245
+ end
246
+
247
+ # @param [Integer] starting_line
248
+ # Sets the starting line for the range of errors to return.
249
+ #
250
+ # @return [Array<String>]
251
+ def errors( starting_line = 0 )
252
+ return [] if self.error_buffer.empty?
253
+
254
+ error_strings = self.error_buffer
255
+
256
+ if starting_line != 0
257
+ error_strings = error_strings[starting_line..-1]
258
+ end
259
+
260
+ error_strings
261
+ end
262
+
263
+ # @return [TrueClass]
264
+ def alive?
265
+ @server.alive?
266
+ end
267
+
268
+ protected
269
+
270
+ def pop
271
+ return if @queue.empty?
272
+
273
+ top_priority, q = @by_priority.first
274
+
275
+ id = q.pop
276
+ r = [id, @queue.delete( id )]
277
+
278
+ @by_priority.delete( top_priority ) if q.empty?
279
+
280
+ r
281
+ end
282
+
283
+ private
284
+
285
+ def consume_queue
286
+ spawning = false
287
+ reactor.at_interval( TICK_CONSUME ) do
288
+ next if self.empty? || spawning
289
+
290
+ spawning = true
291
+ spawn_instance do |client|
292
+ if client == :error
293
+ spawning = false
294
+ next
295
+ end
296
+
297
+ if !client
298
+ print_debug 'Could not get Instance, all systems are at max utilization.'
299
+ spawning = false
300
+ next
301
+ end
302
+
303
+ id, options = self.pop
304
+
305
+ print_status "[#{id}] Got Instance: #{client.url}/#{client.token}"
306
+
307
+ client.run( options ) do
308
+ spawning = false
309
+
310
+ print_status "[#{id}] Instance started."
311
+ @running[id] = client
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ def monitor_instances
318
+ checking = false
319
+ reactor.at_interval( Options.scheduler.ping_interval ) do
320
+ next if checking
321
+ print_debug 'Checking running Instances.'
322
+ checking = true
323
+
324
+ each = proc { |(id, c), i| check_instance( id, c ) { i.next } }
325
+ after = proc { checking = false }
326
+ reactor.create_iterator( @running ).each( each, after )
327
+ end
328
+ end
329
+
330
+ def check_instance( id, client, &block )
331
+ print_debug "[#{id}] Checking status."
332
+
333
+ client.busy? do |busy|
334
+ if busy.rpc_exception?
335
+ handle_rpc_error( id, busy )
336
+ block.call
337
+ elsif busy
338
+ print_debug "[#{id}] Busy."
339
+ block.call
340
+ else
341
+ get_report_and_shutdown( id, client, &block )
342
+ end
343
+ end
344
+ end
345
+
346
+ def handle_rpc_error( id, error )
347
+ print_error "[#{id}] Failed: [#{error.class}] #{error.to_s}"
348
+
349
+ @failed[id] = {
350
+ error: error.class.to_s,
351
+ description: error.to_s
352
+ }
353
+ c = @running.delete( id )
354
+ c.close if c
355
+ end
356
+
357
+ def get_report_and_shutdown( id, client, &block )
358
+ print_status "[#{id}] Grabbing report."
359
+
360
+ client.generate_report do |report|
361
+ if report.rpc_exception?
362
+ handle_rpc_error( id, report )
363
+ block.call
364
+ next
365
+ end
366
+
367
+ path = report.save( "#{Options.report.path}/#{id}.#{Report::EXTENSION}" )
368
+
369
+ print_status "[#{id}] Report saved at: #{path}"
370
+
371
+ client.shutdown do
372
+ print_status "[#{id}] Completed."
373
+
374
+ @running.delete( id ).close
375
+ @completed[id] = path
376
+
377
+ block.call
378
+ end
379
+ end
380
+ end
381
+
382
+ # Starts the dispatcher's server
383
+ def run
384
+ reactor.on_error do |_, e|
385
+ print_error "Reactor: #{e}"
386
+
387
+ e.backtrace.each do |l|
388
+ print_error "Reactor: #{l}"
389
+ end
390
+ end
391
+
392
+ print_status 'Ready'
393
+ @server.start
394
+ rescue => e
395
+ print_exception e
396
+
397
+ $stderr.puts "Could not start server, for details see: #{@logfile}"
398
+ exit 1
399
+ end
400
+
401
+ def reactor
402
+ Arachni::Reactor.global
403
+ end
404
+
405
+ def trap_interrupts( &block )
406
+ %w(QUIT INT).each do |signal|
407
+ trap( signal, &block || Proc.new{ } ) if Signal.list.has_key?( signal )
408
+ end
409
+ end
410
+
411
+ def prep_logging
412
+ # reroute all output to a logfile
413
+ @logfile ||= reroute_to_file(
414
+ @options.paths.logs + "/Scheduler - #{Process.pid}-#{@options.rpc.server_port}.log"
415
+ )
416
+ end
417
+
418
+ def dispatcher
419
+ return if !Options.dispatcher.url
420
+ @dispatcher ||= RPC::Client::Dispatcher.new( Options.dispatcher.url )
421
+ end
422
+
423
+ def spawn_instance( &block )
424
+ if dispatcher
425
+ options = {
426
+ owner: self.class.to_s,
427
+ helpers: {
428
+ owner: {
429
+ url: @url
430
+ }
431
+ }
432
+ }
433
+
434
+ dispatcher.dispatch options do |info|
435
+ if info.rpc_exception?
436
+ print_error "Failed to contact Dispatcher at: #{dispatcher.url}"
437
+ print_error "[#{info.class}] #{info.to_s}"
438
+ block.call :error
439
+ next
440
+ end
441
+
442
+ if info
443
+ client = connect_to_instance( info['url'], info['token'] )
444
+ client.options.set( scheduler: { url: @options.scheduler.url } ) do
445
+ block.call( client )
446
+ end
447
+
448
+ else
449
+ block.call
450
+ end
451
+ end
452
+ else
453
+ return block.call if System.max_utilization?
454
+
455
+ Processes::Instances.spawn(
456
+ application: Options.paths.application,
457
+ port_range: Options.scheduler.instance_port_range,
458
+ &block
459
+ )
460
+ end
461
+ end
462
+
463
+ def connect_to_instance( url, token )
464
+ RPC::Client::Instance.new( url, token )
465
+ end
466
+
467
+ # @param [Base] server
468
+ # Prepares all the RPC handlers for the given `server`.
469
+ def set_handlers( server )
470
+ server.add_async_check do |method|
471
+ # methods that expect a block are async
472
+ method.parameters.flatten.include? :block
473
+ end
474
+
475
+ server.add_handler( 'scheduler', self )
476
+ end
477
+
478
+ end
479
+
480
+ end
481
+ end
482
+ end