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,112 @@
1
+ require 'spec_helper'
2
+ require "#{Cuboid::Options.paths.lib}/rpc/server/dispatcher"
3
+
4
+ describe Cuboid::RPC::Server::Dispatcher::Service do
5
+ before( :each ) do
6
+ Cuboid::Options.paths.services = "#{fixtures_path}services/"
7
+ Cuboid::Options.system.max_slots = 10
8
+ end
9
+ let(:instance_count) { 3 }
10
+ let(:dispatcher) { dispatcher_spawn application: "#{fixtures_path}/mock_app.rb" }
11
+ let(:subject) { dispatcher.test_service }
12
+
13
+ describe '#dispatcher' do
14
+ it 'provides access to the parent Dispatcher' do
15
+ expect(subject.test_dispatcher).to be_truthy
16
+ end
17
+ end
18
+
19
+ describe '#opts' do
20
+ it 'provides access to the Dispatcher\'s options' do
21
+ expect(subject.test_opts).to be_truthy
22
+ end
23
+ end
24
+
25
+ describe '#node' do
26
+ it 'provides access to the Dispatcher\'s node' do
27
+ expect(subject.test_node).to be_truthy
28
+ end
29
+ end
30
+
31
+ describe '#instances' do
32
+ before(:each) do
33
+ instance_count.times { dispatcher.dispatch }
34
+ end
35
+
36
+ it 'provides access to the running instances' do
37
+ expect(subject.instances.map{ |i| i['pid'] }).to eq(subject.instances.map{ |j| j['pid'] })
38
+ end
39
+ end
40
+
41
+ describe '#map_instances' do
42
+ before(:each) do
43
+ instance_count.times { dispatcher.dispatch }
44
+ end
45
+
46
+ it 'asynchronously maps all running instances' do
47
+ expect(subject.test_map_instances).to eq(
48
+ Hash[subject.instances.map { |j| [j['url'], j['token']] }]
49
+ )
50
+ end
51
+ end
52
+
53
+ describe '#each_instance' do
54
+ before(:each) do
55
+ instance_count.times { dispatcher.dispatch }
56
+ end
57
+
58
+ it 'asynchronously iterates over all running instances' do
59
+ subject.test_each_instance
60
+ auths = subject.instances.map do |j|
61
+ Cuboid::RPC::Client::Instance.new(
62
+ j['url'], j['token']
63
+ ).options.authorized_by
64
+ end
65
+
66
+ expect(auths.size).to eq(instance_count)
67
+ auths.sort!
68
+
69
+ 1.upto( instance_count ).each do |i|
70
+ expect(auths[i-1]).to eq "test_#{i}@test.com"
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#defer' do
76
+ it 'defers execution of the given block' do
77
+ args = [1, 'stuff']
78
+ expect(subject.test_defer( *args )).to eq(args)
79
+ end
80
+ end
81
+
82
+ describe '#run_asap' do
83
+ it 'runs the given block as soon as possible' do
84
+ args = [1, 'stuff']
85
+ expect(subject.test_run_asap( *args )).to eq(args)
86
+ end
87
+ end
88
+
89
+ describe '#iterator_for' do
90
+ it 'provides an asynchronous iterator' do
91
+ expect(subject.test_iterator_for).to be_truthy
92
+ end
93
+ end
94
+
95
+ describe '#connect_to_dispatcher' do
96
+ it 'connects to the a dispatcher by url' do
97
+ expect(subject.test_connect_to_dispatcher( dispatcher.url )).to be_truthy
98
+ end
99
+ end
100
+
101
+ describe '#connect_to_instance' do
102
+ it 'connects to an instance' do
103
+ dispatcher.dispatch
104
+ instance = subject.instances.first
105
+
106
+ expect(subject.test_connect_to_instance( instance )).to be_falsey
107
+ expect(subject.test_connect_to_instance( instance['url'], instance['token'] )).to be_falsey
108
+ expect(subject.test_connect_to_instance( url: instance['url'], token: instance['token'] )).to be_falsey
109
+ end
110
+ end
111
+
112
+ end
@@ -0,0 +1,317 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ require "#{Cuboid::Options.paths.lib}/rpc/server/dispatcher"
5
+
6
+ describe Cuboid::RPC::Server::Dispatcher do
7
+ before( :each ) do
8
+ Cuboid::Options.system.max_slots = slots
9
+ end
10
+
11
+ let(:instance_info_keys) { %w(token application pid url owner birthdate helpers now age) }
12
+ let(:slots) { 3 }
13
+ let(:subject) { dispatcher_spawn( application: "#{fixtures_path}/mock_app.rb" ) }
14
+
15
+ describe '#alive?' do
16
+ it 'returns true' do
17
+ expect(subject.alive?).to eq(true)
18
+ end
19
+ end
20
+
21
+ describe '#preferred' do
22
+ context 'when the dispatcher is a grid member' do
23
+ it 'returns the URL of least burdened Dispatcher' do
24
+ dispatcher_spawn( neighbour: subject.url ).dispatch( load_balance: false )
25
+ dispatcher_spawn( neighbour: subject.url ).dispatch( load_balance: false )
26
+
27
+ expect(subject.preferred).to eq(subject.url)
28
+ end
29
+
30
+ context 'and all Dispatchers are at max utilization' do
31
+ before :each do
32
+ subject.dispatch( load_balance: false )
33
+ end
34
+
35
+ let(:slots) { 1 }
36
+
37
+ it 'returns nil' do
38
+ dispatcher_spawn( neighbour: subject.url ).dispatch( load_balance: false )
39
+ dispatcher_spawn( neighbour: subject.url ).dispatch( load_balance: false )
40
+
41
+ expect(subject.preferred).to be_nil
42
+ end
43
+ end
44
+ end
45
+
46
+ context 'when the dispatcher is not a grid member' do
47
+ it 'returns the URL of the Dispatcher' do
48
+ expect(subject.preferred).to eq(subject.url)
49
+ end
50
+
51
+ context 'and it is at max utilization' do
52
+ before :each do
53
+ subject.dispatch( load_balance: false )
54
+ end
55
+
56
+ let(:slots) { 1 }
57
+
58
+ it 'returns nil' do
59
+ expect(subject.preferred).to be_nil
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '#handlers' do
66
+ it 'returns an array of loaded handlers' do
67
+ expect(subject.services.include?( 'test_service' )).to be_truthy
68
+ end
69
+ end
70
+
71
+ describe '#dispatch' do
72
+ it 'does not leak Instances' do
73
+ slots.times do
74
+ subject.dispatch
75
+ end
76
+
77
+ expect(subject.instances.size).to eq(slots)
78
+ end
79
+
80
+ it 'sets OptionGroups::Dispatcher#url' do
81
+ info = subject.dispatch
82
+ instance = instance_connect( info['url'], info['token'] )
83
+
84
+ expect(instance.dispatcher_url).to eq subject.url
85
+ end
86
+
87
+ context "when #{Cuboid::OptionGroups::RPC}#server_external_address has been set" do
88
+ before :each do
89
+ Cuboid::Options.rpc.server_external_address = address
90
+ end
91
+
92
+ let(:address) { '127.0.0.2' }
93
+
94
+ it 'advertises that address' do
95
+ expect(subject.dispatch['url']).to start_with "#{address}:"
96
+ end
97
+ end
98
+
99
+ context 'when not a Grid member' do
100
+ it 'returns Instance info' do
101
+ info = subject.dispatch( owner: 'rspec' )
102
+
103
+ %w(token application pid url owner birthdate helpers).each do |k|
104
+ expect(info[k]).to be_truthy
105
+ end
106
+
107
+ instance = instance_connect( info['url'], info['token'] )
108
+ expect(instance.alive?).to be_truthy
109
+ end
110
+
111
+ it 'assigns an optional owner' do
112
+ owner = 'blah'
113
+ expect(subject.dispatch( owner: owner )['owner']).to eq(owner)
114
+ end
115
+
116
+ context 'when the there are no available slots' do
117
+ let(:slots) { 5 }
118
+ before :each do
119
+ slots.times do
120
+ subject.dispatch
121
+ end
122
+ end
123
+
124
+ it 'returns nil' do
125
+ expect(subject.dispatch).to be nil
126
+ end
127
+
128
+ context 'and slots are freed' do
129
+ let(:free) { slots - 1 }
130
+
131
+ it 'returns Instance info' do
132
+ subject.instances[0...free].each do |info|
133
+ service = instance_connect( info['url'], info['token'] )
134
+ service.shutdown
135
+
136
+ while sleep 0.1
137
+ service.alive? rescue break
138
+ end
139
+ end
140
+
141
+ instances = []
142
+ free.times do
143
+ instances << subject.dispatch
144
+ end
145
+ instances.compact!
146
+
147
+ expect(instances.size).to eq free
148
+ expect(subject.dispatch).to be nil
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ context 'when a Grid member' do
155
+ let(:slots) { 4 }
156
+
157
+ it 'returns Instance info from the least burdened Dispatcher' do
158
+ d1 = dispatcher_spawn(
159
+ address: '127.0.0.1',
160
+ application: "#{fixtures_path}/mock_app.rb"
161
+ )
162
+
163
+ 3.times do
164
+ d1.dispatch( load_balance: false )
165
+ end
166
+
167
+ d2 = dispatcher_spawn(
168
+ address: '127.0.0.2',
169
+ neighbour: d1.url,
170
+ application: "#{fixtures_path}/mock_app.rb"
171
+ )
172
+
173
+ 2.times do
174
+ d2.dispatch( load_balance: false )
175
+ end
176
+
177
+ d3 = dispatcher_spawn(
178
+ address: '127.0.0.3',
179
+ neighbour: d1.url,
180
+ application: "#{fixtures_path}/mock_app.rb"
181
+ )
182
+ d3.dispatch( load_balance: false )
183
+ preferred = d3.url.split( ':' ).first
184
+
185
+ expect(d3.dispatch['url'].split( ':' ).first).to eq(preferred)
186
+ expect(%W{127.0.0.3 127.0.0.2}).to include d1.dispatch['url'].split( ':' ).first
187
+ expect(d2.dispatch['url'].split( ':' ).first).to eq(preferred)
188
+ expect(%W{127.0.0.1 127.0.0.3}).to include d3.dispatch['url'].split( ':' ).first
189
+ expect(%W{127.0.0.2 127.0.0.3}).to include d3.dispatch['url'].split( ':' ).first
190
+ expect(%W{127.0.0.2 127.0.0.3}).to include d1.dispatch['url'].split( ':' ).first
191
+ end
192
+
193
+ context 'when the load-balance option is set to false' do
194
+ it 'returns an Instance from the requested Dispatcher' do
195
+ d1 = dispatcher_spawn(
196
+ address: '127.0.0.1',
197
+ application: "#{fixtures_path}/mock_app.rb"
198
+ )
199
+
200
+ d1.dispatch( load_balance: false )
201
+
202
+ d2 = dispatcher_spawn(
203
+ address: '127.0.0.2',
204
+ neighbour: d1.url,
205
+ application: "#{fixtures_path}/mock_app.rb"
206
+ )
207
+ d2.dispatch( load_balance: false )
208
+
209
+ d3 = dispatcher_spawn(
210
+ address: '127.0.0.3',
211
+ neighbour: d1.url,
212
+ application: "#{fixtures_path}/mock_app.rb"
213
+ )
214
+ 2.times do
215
+ d3.dispatch( load_balance: false )
216
+ end
217
+
218
+ expect(d3.dispatch( load_balance: false )['url'].
219
+ split( ':' ).first).to eq('127.0.0.3')
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ describe '#instance' do
226
+ it 'returns proc info by PID' do
227
+ instance = subject.dispatch( owner: 'rspec' )
228
+ info = subject.instance( instance['pid'] )
229
+ instance_info_keys.each do |k|
230
+ expect(info[k]).to be_truthy
231
+ end
232
+ end
233
+ end
234
+
235
+ describe '#instances' do
236
+ it 'returns proc info by PID for all instances' do
237
+ slots.times { subject.dispatch( owner: 'rspec' ) }
238
+
239
+ subject.instances.each do |instance|
240
+ instance_info_keys.each do |k|
241
+ expect(instance[k]).to be_truthy
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ describe '#running_instances' do
248
+ it 'returns proc info for running instances' do
249
+ slots.times { subject.dispatch }
250
+
251
+ expect(subject.running_instances.size).to eq(slots)
252
+ end
253
+ end
254
+
255
+ describe '#finished_instances' do
256
+ it 'returns proc info for finished instances' do
257
+ 3.times { Cuboid::Processes::Manager.kill subject.dispatch['pid'] }
258
+
259
+ expect(subject.finished_instances.size).to eq(3)
260
+ end
261
+ end
262
+
263
+ describe '#utilization' do
264
+ it 'returns a float signifying the amount of workload' do
265
+ 3.times do
266
+ subject.dispatch
267
+ end
268
+
269
+ expect(subject.utilization).to eq(3 / Float(slots))
270
+ end
271
+ end
272
+
273
+ describe '#statistics' do
274
+ it 'returns general statistics' do
275
+ subject.dispatch
276
+ instances = subject.instances
277
+ Cuboid::Processes::Manager.kill( instances.first['pid'] )
278
+
279
+ stats = subject.statistics
280
+
281
+ %w(utilization running_instances finished_instances node
282
+ consumed_pids snapshots).each do |k|
283
+ expect(stats[k]).to be_truthy
284
+ end
285
+
286
+ finished = stats['finished_instances']
287
+ expect(finished.size).to eq(1)
288
+
289
+ expect(stats['node']).to eq(subject.node.info)
290
+ end
291
+
292
+ context 'when there are snapshots' do
293
+ it 'lists them' do
294
+ info = subject.dispatch
295
+
296
+ instance = Cuboid::RPC::Client::Instance.new(
297
+ info['url'], info['token']
298
+ )
299
+
300
+ instance.run
301
+ instance.suspend!
302
+ sleep 1 while !instance.suspended?
303
+ snapshot_path = instance.snapshot_path
304
+ instance.shutdown
305
+
306
+ expect(subject.statistics['snapshots']).to include snapshot_path
307
+ end
308
+ end
309
+ end
310
+
311
+ describe '#log' do
312
+ it 'returns the contents of the log file' do
313
+ expect(subject.log).to be_truthy
314
+ end
315
+ end
316
+
317
+ end
@@ -0,0 +1,307 @@
1
+ require 'json'
2
+ require 'spec_helper'
3
+ require "#{fixtures_path}/mock_app"
4
+
5
+ describe 'Cuboid::RPC::Server::Instance' do
6
+ let(:subject) { instance_spawn( application: "#{fixtures_path}/mock_app.rb" ) }
7
+
8
+ it 'supports UNIX sockets', if: Arachni::Reactor.supports_unix_sockets? do
9
+ socket = "#{Dir.tmpdir}/cuboid-instance-#{Cuboid::Utilities.generate_token}"
10
+ subject = instance_spawn(
11
+ socket: socket,
12
+ application: "#{fixtures_path}/mock_app.rb"
13
+ )
14
+
15
+ expect(subject.url).to eq(socket)
16
+ expect(subject.alive?).to be_truthy
17
+ end
18
+
19
+ describe '#application' do
20
+ it 'returns the application name' do
21
+ expect(subject.application).to eq 'MockApp'
22
+ end
23
+ end
24
+
25
+ describe '#snapshot_path' do
26
+ it 'returns the path to the future/current snapshot' do
27
+ subject.run
28
+
29
+ Timeout.timeout 5 do
30
+ sleep 1 while !subject.running?
31
+ end
32
+
33
+ expect(subject.snapshot_path).to end_with '.csf'
34
+ end
35
+ end
36
+
37
+ describe '#suspend!' do
38
+ it 'suspends the Instance to disk' do
39
+ subject.run
40
+
41
+ Timeout.timeout 5 do
42
+ sleep 1 while !subject.running?
43
+ end
44
+
45
+ subject.suspend!
46
+
47
+ Timeout.timeout 5 do
48
+ sleep 1 while !subject.suspended?
49
+ end
50
+
51
+ expect(File.exists?( subject.snapshot_path )).to be_truthy
52
+ end
53
+ end
54
+
55
+ describe '#suspended?' do
56
+ context 'when the Instance has not been suspended' do
57
+ it 'returns false' do
58
+ expect(subject.suspended?).to be_falsey
59
+ end
60
+ end
61
+
62
+ context 'when the Instance has been suspended' do
63
+ it 'returns true' do
64
+ subject.run
65
+
66
+ Timeout.timeout 5 do
67
+ sleep 1 while !subject.running?
68
+ end
69
+
70
+ subject.suspend!
71
+
72
+ Timeout.timeout 5 do
73
+ sleep 1 while !subject.suspended?
74
+ end
75
+
76
+ expect(subject.suspended?).to be_truthy
77
+ end
78
+ end
79
+ end
80
+
81
+ describe '#busy?' do
82
+ context 'when the Instance is not running' do
83
+ it 'returns false' do
84
+ expect(subject.busy?).to be_falsey
85
+ end
86
+ end
87
+
88
+ context 'when the Instance is running' do
89
+ it 'returns true' do
90
+ subject.run
91
+ expect(subject.busy?).to be_truthy
92
+ end
93
+ end
94
+ end
95
+
96
+ describe '#restore!' do
97
+ it 'suspends the Instance to disk' do
98
+ subject.run
99
+
100
+ options = subject.generate_report.options
101
+
102
+ subject.suspend!
103
+
104
+ Timeout.timeout 5 do
105
+ sleep 1 while subject.status != :suspended
106
+ end
107
+
108
+ snapshot_path = subject.snapshot_path
109
+ subject.shutdown
110
+
111
+ subject = instance_spawn
112
+ subject.restore! snapshot_path
113
+
114
+ File.delete snapshot_path
115
+
116
+ sleep 1 while subject.status != :done
117
+
118
+ expect(subject.generate_report.options).to eq(options)
119
+ end
120
+ end
121
+
122
+ describe '#errors' do
123
+ before(:each) do
124
+ subject.error_test error
125
+ end
126
+ let(:error) { "My error #{rand(9999)}" }
127
+
128
+ context 'when no argument has been provided' do
129
+ it 'returns all logged errors' do
130
+ expect(subject.errors.last).to end_with error
131
+ end
132
+ end
133
+
134
+ context 'when a start line-range has been provided' do
135
+ it 'returns all logged errors after that line' do
136
+ initial_errors = subject.errors
137
+ errors = subject.errors( 10 )
138
+
139
+ expect(initial_errors[10..-1]).to eq(errors)
140
+ end
141
+ end
142
+ end
143
+
144
+ describe '#error_logfile' do
145
+ before(:each) do
146
+ subject.error_test error
147
+ end
148
+ let(:error) { "My error #{rand(9999)}" }
149
+
150
+ it 'returns the path to the error logfile' do
151
+ errors = IO.read( subject.error_logfile )
152
+
153
+ subject.errors.each do |error|
154
+ expect(errors).to include error
155
+ end
156
+ end
157
+ end
158
+
159
+ describe '#alive?' do
160
+ it 'returns true' do
161
+ expect(subject.alive?).to eq(true)
162
+ end
163
+ end
164
+
165
+ describe '#paused?' do
166
+ context 'when not paused' do
167
+ it 'returns false' do
168
+ expect(subject.paused?).to be_falsey
169
+ end
170
+ end
171
+ context 'when paused' do
172
+ it 'returns true' do
173
+ subject.run
174
+
175
+ subject.pause!
176
+ Timeout.timeout 5 do
177
+ sleep 1 while !subject.paused?
178
+ end
179
+
180
+ expect(subject.paused?).to be_truthy
181
+ end
182
+ end
183
+ end
184
+
185
+ describe '#resume!' do
186
+ it 'resumes the Instance' do
187
+ subject.run
188
+
189
+ subject.pause!
190
+ Timeout.timeout 5 do
191
+ sleep 1 while !subject.paused?
192
+ end
193
+
194
+ expect(subject.paused?).to be_truthy
195
+ expect(subject.resume!).to be_truthy
196
+
197
+ Timeout.timeout 5 do
198
+ sleep 1 while subject.paused?
199
+ end
200
+
201
+ expect(subject.paused?).to be_falsey
202
+ end
203
+ end
204
+
205
+ describe '#abort_and_generate_report' do
206
+ it "cleans-up and returns the report as #{Cuboid::Report}" do
207
+ expect(subject.abort_and_generate_report).to be_kind_of Cuboid::Report
208
+ end
209
+ end
210
+
211
+ describe '#status' do
212
+ context 'after initialization' do
213
+ it 'returns :ready' do
214
+ expect(subject.status).to eq(:ready)
215
+ end
216
+ end
217
+
218
+ context 'after #run has been called' do
219
+ it 'returns :running' do
220
+ subject.run
221
+
222
+ sleep 2
223
+ expect(subject.status).to eq(:running)
224
+ end
225
+ end
226
+
227
+ context 'once the Instance has completed' do
228
+ it 'returns :done' do
229
+ subject.run
230
+
231
+ sleep 1 while subject.busy?
232
+ expect(subject.status).to eq(:done)
233
+ end
234
+ end
235
+ end
236
+
237
+ describe '#run' do
238
+ context 'on invalid options' do
239
+ it 'raises ArgumentError' do
240
+ expect do
241
+ subject.run invalid: :stuff
242
+ end.to raise_error Arachni::RPC::Exceptions::RemoteException
243
+ end
244
+ end
245
+
246
+ it 'configures and starts a job' do
247
+ expect(subject.busy?).to be false
248
+ expect(subject.status).to be :ready
249
+
250
+ subject.run
251
+
252
+ # if a run in already running it should just bail out early
253
+ expect(subject.run).to be_falsey
254
+
255
+ sleep 1 while subject.busy?
256
+
257
+ expect(subject.busy?).to be false
258
+ expect(subject.status).to be :done
259
+ end
260
+ end
261
+
262
+ describe '#progress' do
263
+ before( :each ) do
264
+ subject.run
265
+ sleep 1 while subject.busy?
266
+ end
267
+
268
+ it 'returns progress information' do
269
+ p = subject.progress
270
+ expect(p[:busy]).to be false
271
+ expect(p[:status]).to be :done
272
+ expect(p[:statistics]).to be_any
273
+
274
+ expect(p[:seed]).not_to be_empty
275
+ end
276
+
277
+ describe ':without' do
278
+ describe ':statistics' do
279
+ it 'includes statistics' do
280
+ expect(subject.progress(
281
+ without: :statistics
282
+ )).not_to include :statistics
283
+ end
284
+ end
285
+
286
+ context 'with an array of things to be excluded' do
287
+ it 'excludes those things'
288
+ end
289
+ end
290
+
291
+ describe ':with' do
292
+ context 'with an array of things to be included' do
293
+ it 'includes those things'
294
+ end
295
+ end
296
+ end
297
+
298
+ describe '#shutdown' do
299
+ it 'shuts-down the instance' do
300
+ expect(subject.shutdown).to be_truthy
301
+ sleep 4
302
+
303
+ expect { subject.alive? }.to raise_error Arachni::RPC::Exceptions::ConnectionError
304
+ end
305
+ end
306
+
307
+ end