automate-em 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/LGPL3-LICENSE +165 -0
  2. data/README.textile +48 -0
  3. data/Rakefile +40 -0
  4. data/app/models/control_system.rb +20 -0
  5. data/app/models/controller_device.rb +21 -0
  6. data/app/models/controller_http_service.rb +17 -0
  7. data/app/models/controller_logic.rb +5 -0
  8. data/app/models/controller_zone.rb +10 -0
  9. data/app/models/dependency.rb +20 -0
  10. data/app/models/server.rb +12 -0
  11. data/app/models/setting.rb +38 -0
  12. data/app/models/trusted_device.rb +63 -0
  13. data/app/models/user_zone.rb +10 -0
  14. data/app/models/zone.rb +16 -0
  15. data/db/migrate/20111001022500_init.rb +147 -0
  16. data/db/migrate/20111017213801_one_time_key.rb +9 -0
  17. data/db/migrate/20111021071632_encrypt_setting.rb +9 -0
  18. data/db/migrate/20111110075444_servers.rb +15 -0
  19. data/db/migrate/20111114074538_default_port.rb +9 -0
  20. data/db/migrate/20111122073055_makebreak.rb +9 -0
  21. data/db/migrate/20111211062846_create_controller_http_services.rb +18 -0
  22. data/lib/automate-em.rb +155 -0
  23. data/lib/automate-em/constants.rb +6 -0
  24. data/lib/automate-em/core/communicator.rb +318 -0
  25. data/lib/automate-em/core/modules.rb +373 -0
  26. data/lib/automate-em/core/resolver_pool.rb +76 -0
  27. data/lib/automate-em/core/system.rb +356 -0
  28. data/lib/automate-em/device/datagram_server.rb +111 -0
  29. data/lib/automate-em/device/device.rb +140 -0
  30. data/lib/automate-em/device/device_connection.rb +689 -0
  31. data/lib/automate-em/device/tcp_control.rb +210 -0
  32. data/lib/automate-em/engine.rb +36 -0
  33. data/lib/automate-em/interfaces/OLD CODE/deferred.rb +67 -0
  34. data/lib/automate-em/interfaces/OLD CODE/telnet/ansi.rb +137 -0
  35. data/lib/automate-em/interfaces/OLD CODE/telnet/telnet.rb +137 -0
  36. data/lib/automate-em/interfaces/html5.rb +302 -0
  37. data/lib/automate-em/logic/logic.rb +76 -0
  38. data/lib/automate-em/service/http_service.rb +584 -0
  39. data/lib/automate-em/service/service.rb +48 -0
  40. data/lib/automate-em/status.rb +89 -0
  41. data/lib/automate-em/utilities.rb +195 -0
  42. data/lib/automate-em/version.rb +3 -0
  43. data/lib/generators/module/USAGE +8 -0
  44. data/lib/generators/module/module_generator.rb +47 -0
  45. data/lib/tasks/automate-em_tasks.rake +5 -0
  46. data/test/automate-em_test.rb +7 -0
  47. data/test/dummy/README.rdoc +261 -0
  48. data/test/dummy/Rakefile +7 -0
  49. data/test/dummy/app/assets/javascripts/application.js +15 -0
  50. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  51. data/test/dummy/app/controllers/application_controller.rb +3 -0
  52. data/test/dummy/app/helpers/application_helper.rb +2 -0
  53. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/test/dummy/config.ru +4 -0
  55. data/test/dummy/config/application.rb +56 -0
  56. data/test/dummy/config/boot.rb +10 -0
  57. data/test/dummy/config/database.yml +25 -0
  58. data/test/dummy/config/environment.rb +5 -0
  59. data/test/dummy/config/environments/development.rb +37 -0
  60. data/test/dummy/config/environments/production.rb +67 -0
  61. data/test/dummy/config/environments/test.rb +37 -0
  62. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  63. data/test/dummy/config/initializers/inflections.rb +15 -0
  64. data/test/dummy/config/initializers/mime_types.rb +5 -0
  65. data/test/dummy/config/initializers/secret_token.rb +7 -0
  66. data/test/dummy/config/initializers/session_store.rb +8 -0
  67. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  68. data/test/dummy/config/locales/en.yml +5 -0
  69. data/test/dummy/config/routes.rb +4 -0
  70. data/test/dummy/public/404.html +26 -0
  71. data/test/dummy/public/422.html +26 -0
  72. data/test/dummy/public/500.html +25 -0
  73. data/test/dummy/public/favicon.ico +0 -0
  74. data/test/dummy/script/rails +6 -0
  75. data/test/integration/navigation_test.rb +10 -0
  76. data/test/test_helper.rb +15 -0
  77. metadata +328 -0
@@ -0,0 +1,584 @@
1
+ require 'uri'
2
+ require 'atomic'
3
+
4
+
5
+ #
6
+ # Include by default (keep it simple for developers)
7
+ #
8
+ require 'em-http/middleware/oauth'
9
+ require 'em-http/middleware/json_response'
10
+
11
+
12
+ class HttpDebugInspector
13
+
14
+ def request(client, head, body)
15
+ AutomateEm::System.logger.debug "HTTP request #{client.req.method} #{client.req.uri} #{head.inspect}:#{body.inspect}"
16
+ [head,body]
17
+ end
18
+
19
+ def response(resp)
20
+ AutomateEm::System.logger.debug "HTTP Response #{resp.response.inspect}"
21
+ end
22
+
23
+ end
24
+
25
+ #
26
+ # This contains the basic constructs required for
27
+ # serialised comms over TCP and UDP
28
+ # => TODO:: SSL
29
+ #
30
+ module AutomateEm
31
+ class HttpService
32
+ VERBS = [:get, :post, :put, :delete, :head]
33
+
34
+ def initialize(parent, settings)
35
+
36
+ @config = {
37
+ :priority_bonus => 20,
38
+
39
+
40
+ :connect_timeout => 5,
41
+ :inactivity_timeout => 10
42
+ # :ssl
43
+ # :bind
44
+ # :proxy
45
+ }
46
+ @uri = URI.parse(settings.uri)
47
+ @config[:ssl] = parent.certificates if parent.respond_to?(:certificates)
48
+ #@connection = EventMachine::HttpRequest.new(@uri, @config)
49
+
50
+ @default_request_options = {
51
+ :wait => true, # Wait for response
52
+ :delay => 0, # Delay next request by x.y seconds
53
+ :delay_on_recieve => 0, # Delay next request after a recieve by x.y seconds (only works when we are waiting for responses)
54
+ #:emit
55
+ :retries => 2,
56
+ :priority => 50,
57
+
58
+ #
59
+ # EM:http related
60
+ #
61
+ # query
62
+ # body
63
+ # custom_client => block
64
+ :path => '/',
65
+ #file => path to file for streaming
66
+ #query
67
+ #
68
+ :keepalive => true,
69
+ :redirects => 0,
70
+ :verb => :get,
71
+ :stream => false, # send chunked data
72
+ #:stream_closed => block
73
+ #:headers
74
+
75
+ #:callback => nil, # Alternative to the received function
76
+ #:errback => nil,
77
+ }
78
+
79
+
80
+ #
81
+ # Queues
82
+ #
83
+ @task_queue = EM::Queue.new # basically we add tasks here that we want to run in a strict order
84
+ @wait_queue = EM::Queue.new
85
+ @send_queue = EM::PriorityQueue.new(:fifo => true) {|x,y| x < y} # regular priority
86
+
87
+ #
88
+ # Named commands
89
+ # Allowing for state control
90
+ #
91
+ @named_commands = {}
92
+
93
+
94
+ #
95
+ # Locks
96
+ #
97
+ @received_lock = Mutex.new
98
+ @task_lock = Mutex.new
99
+ @status_lock = Mutex.new
100
+ @send_monitor = Object.new.extend(MonitorMixin)
101
+
102
+
103
+ #
104
+ # State
105
+ #
106
+ @last_sent_at = 0.0
107
+ @last_recieve_at = 0.0
108
+
109
+
110
+ #
111
+ # Configure links between objects
112
+ #
113
+ @parent = parent
114
+ @parent.setbase(self)
115
+ @shutting_down = Atomic.new(false)
116
+
117
+
118
+ #
119
+ # Task event loop
120
+ #
121
+ @task_queue_proc = Proc.new do |task|
122
+ if !@shutting_down.value
123
+ EM.defer do
124
+ begin
125
+ @task_lock.synchronize {
126
+ task.call
127
+ }
128
+ rescue => e
129
+ AutomateEm.print_error(logger, e, {
130
+ :message => "module #{@parent.class} in http_service.rb : error in task loop",
131
+ :level => Logger::ERROR
132
+ })
133
+ ensure
134
+ ActiveRecord::Base.clear_active_connections!
135
+ @task_queue.pop &@task_queue_proc # First task is ready
136
+ end
137
+ end
138
+ end
139
+ end
140
+ @task_queue.pop &@task_queue_proc # First task is ready
141
+
142
+
143
+ #
144
+ # send loop
145
+ #
146
+ @wait_queue_proc = Proc.new do |ignore|
147
+ if ignore != :shutdown
148
+
149
+ @send_queue.pop {|command|
150
+ if command != :shutdown
151
+
152
+ begin
153
+
154
+ process = true
155
+ if command[:name].present?
156
+ name = command[:name]
157
+ @named_commands[name][0].pop # Extract the command data
158
+ command = @named_commands[name][1]
159
+
160
+ if @named_commands[name][0].empty? # See if any more of these commands are queued
161
+ @named_commands.delete(name) # Delete if there are not
162
+ else
163
+ @named_commands[name][1] = nil # Reset if there are
164
+ end
165
+
166
+ if command.nil? # decide if to continue or not
167
+ process = false
168
+ end
169
+ end
170
+
171
+ if process
172
+ if command[:delay] > 0.0
173
+ delay = @last_sent_at + command[:delay] - Time.now.to_f
174
+ if delay > 0.0
175
+ EM.add_timer delay do
176
+ process_send(command)
177
+ end
178
+ else
179
+ process_send(command)
180
+ end
181
+ else
182
+ process_send(command)
183
+ end
184
+ else
185
+ process_next_send
186
+ end
187
+ rescue => e
188
+ EM.defer do
189
+ AutomateEm.print_error(logger, e, {
190
+ :message => "module #{@parent.class} in device_connection.rb, base : error in send loop",
191
+ :level => Logger::ERROR
192
+ })
193
+ end
194
+ ensure
195
+ ActiveRecord::Base.clear_active_connections!
196
+ @wait_queue.pop &@wait_queue_proc
197
+ end
198
+
199
+ end
200
+ }
201
+ end
202
+ end
203
+
204
+ @wait_queue.push(nil) # Don't start paused
205
+ @wait_queue.pop &@wait_queue_proc
206
+ end
207
+
208
+
209
+ def process_send(command) # this is on the reactor thread
210
+ begin
211
+ if @connection.nil?
212
+ @connection = EventMachine::HttpRequest.new(@uri, @config)
213
+ #
214
+ # TODO:: allow for a block to be passed in too
215
+ #
216
+ if @parent.respond_to?(:use_middleware)
217
+ @parent.use_middleware(@connection)
218
+ end
219
+ end
220
+
221
+ # if command[:custom_client].nil?
222
+ http = @connection.__send__(command[:verb], command)
223
+ =begin else
224
+ http = @connection.__send__(command[:verb], command) do |*args|
225
+ begin
226
+ command[:custom_client].call *args
227
+ rescue => e
228
+ #
229
+ # Save the thread in case of bad data in that send
230
+ #
231
+ EM.defer do
232
+ AutomateEm.print_error(logger, e, {
233
+ :message => "module #{@parent.class} in device_connection.rb, process_send : possible bad data",
234
+ :level => Logger::ERROR
235
+ })
236
+ end
237
+
238
+ process_next_send if command[:wait]
239
+
240
+ raise e # continue propagation
241
+ end
242
+ =end end
243
+ # end
244
+
245
+ @last_sent_at = Time.now.to_f
246
+
247
+ if command[:stream]
248
+ http.stream { |chunk|
249
+ EM.defer {
250
+ @task_queue.push lambda {
251
+ if command[:callback].present?
252
+ command[:callback].call(chunk, command)
253
+ elsif @parent.respond_to?(:received)
254
+ @parent.received(chunk, command)
255
+ end
256
+ }
257
+ }
258
+ }
259
+ http.callback {
260
+ #
261
+ # streaming has finished
262
+ #
263
+ if logger.debug?
264
+ EM.defer do
265
+ logger.debug "Stream closed by remote"
266
+ end
267
+ end
268
+ on_stream_close(http, command)
269
+ if command[:wait]
270
+ process_next_send
271
+ end
272
+ }
273
+ else
274
+ http.callback {
275
+ process_response(http, command)
276
+ }
277
+ end
278
+
279
+ if command[:headers].present?
280
+ http.headers { |hash|
281
+ EM.defer {
282
+ @task_queue.push lambda {
283
+ command[:headers].call(hash)
284
+ }
285
+ }
286
+ }
287
+ end
288
+
289
+ if command[:wait]
290
+ http.errback do
291
+ @connection = nil
292
+
293
+ if !command[:stream]
294
+ process_result(:failed, command)
295
+
296
+ EM.defer do
297
+ logger.info "module #{@parent.class} error: #{http.error}"
298
+ logger.info "A response was not received for the command: #{command[:path]}"
299
+ end
300
+ else
301
+ if logger.debug?
302
+ EM.defer do
303
+ logger.debug "Stream connection dropped"
304
+ end
305
+ end
306
+ on_stream_close(http, command)
307
+ process_next_send
308
+ end
309
+ end
310
+ elsif command[:stream]
311
+ http.errback do
312
+ @connection = nil
313
+ if logger.debug?
314
+ EM.defer do
315
+ logger.debug "Stream connection dropped"
316
+ end
317
+ end
318
+ on_stream_close(http, command)
319
+ end
320
+ process_next_send
321
+ else
322
+ http.errback do
323
+ @connection = nil
324
+ end
325
+ process_next_send
326
+ end
327
+ rescue => e
328
+ #
329
+ # Save the thread in case of bad data in that send
330
+ #
331
+ EM.defer do
332
+ AutomateEm.print_error(logger, e, {
333
+ :message => "module #{@parent.class} in device_connection.rb, process_send : possible bad data",
334
+ :level => Logger::ERROR
335
+ })
336
+ end
337
+
338
+ process_next_send
339
+ ensure
340
+ @connection = nil unless command[:keepalive]
341
+ ActiveRecord::Base.clear_active_connections!
342
+ end
343
+ end
344
+
345
+ def process_next_send
346
+ EM.next_tick do
347
+ @wait_queue.push(nil) # Allows next response to process
348
+ end
349
+ end
350
+
351
+ def on_stream_close(http, command)
352
+ if command[:stream_closed].present?
353
+ EM.defer {
354
+ begin
355
+ command[:stream_closed].call(http, command)
356
+ rescue => e
357
+ #
358
+ # save from bad user code (don't want to deplete thread pool)
359
+ # This error should be logged in some consistent manner
360
+ #
361
+ AutomateEm.print_error(logger, e, {
362
+ :message => "module #{@parent.class} error whilst calling: stream closed",
363
+ :level => Logger::ERROR
364
+ })
365
+ ensure
366
+ ActiveRecord::Base.clear_active_connections!
367
+ end
368
+ }
369
+ end
370
+ end
371
+
372
+
373
+ #
374
+ # Caled from recieve
375
+ #
376
+ def process_response(response, command)
377
+ EM.defer do
378
+ @received_lock.synchronize { # This lock protects the send queue lock when we are emiting status
379
+ @send_monitor.mon_synchronize {
380
+ do_process_response(response, command)
381
+ }
382
+ }
383
+ end
384
+ end
385
+
386
+ def do_process_response(response, command)
387
+ return if @shutting_down.value
388
+
389
+ result = :abort
390
+ begin
391
+ @parent.mark_emit_start(command[:emit]) if command[:emit].present?
392
+
393
+ if command[:callback].present?
394
+ result = command[:callback].call(response, command)
395
+ elsif @parent.respond_to?(:received)
396
+ result = @parent.received(response, command)
397
+ else
398
+ result = true
399
+ end
400
+ rescue => e
401
+ #
402
+ # save from bad user code (don't want to deplete thread pool)
403
+ # This error should be logged in some consistent manner
404
+ #
405
+ AutomateEm.print_error(logger, e, {
406
+ :message => "module #{@parent.class} error whilst calling: received",
407
+ :level => Logger::ERROR
408
+ })
409
+ ensure
410
+ @parent.mark_emit_end if command[:emit].present?
411
+ ActiveRecord::Base.clear_active_connections!
412
+ end
413
+
414
+ if command[:wait]
415
+ EM.schedule do
416
+ process_result(result, command)
417
+ end
418
+ end
419
+ end
420
+
421
+
422
+ def process_result(result, command)
423
+ if [false, :failed].include?(result) && command[:retries] > 0 # assume command failed, we need to retry
424
+ command[:retries] -= 1
425
+ @send_queue.push(command, command[:priority] - @config[:priority_bonus])
426
+ end
427
+
428
+ #else result == :abort || result == :success || result == true || waits and retries exceeded
429
+
430
+ if command[:delay_on_recieve] > 0.0
431
+ delay_for = (@last_recieve_at + command[:delay_on_recieve] - Time.now.to_f)
432
+
433
+ if delay_for > 0.0
434
+ EM.add_timer delay_for do
435
+ process_next_send
436
+ end
437
+ else
438
+ process_next_send
439
+ end
440
+ else
441
+ process_next_send
442
+ end
443
+ end
444
+
445
+
446
+
447
+ #
448
+ # ----------------------------------------------------------------
449
+ # Everything below here is called from a deferred thread
450
+ #
451
+ #
452
+ def logger
453
+ @parent.logger
454
+ end
455
+
456
+ def received_lock
457
+ @send_monitor # for monitor use
458
+ end
459
+
460
+
461
+ #
462
+ # Processes sends in strict order
463
+ #
464
+ def do_send_request(path, options = {}, *args, &block)
465
+
466
+ begin
467
+ @status_lock.synchronize {
468
+ options = @default_request_options.merge(options)
469
+ }
470
+ options[:path] = path unless path.nil?
471
+ options[:retries] = 0 if options[:wait] == false
472
+
473
+ if options[:callback].nil? && (args.length > 0 || block.present?)
474
+ options[:callback] = args[0] unless args.empty? || args[0].class != Proc
475
+ options[:callback] = block unless block.nil?
476
+ end
477
+
478
+ if options[:name].present?
479
+ options[:name] = options[:name].to_sym
480
+ end
481
+ rescue => e
482
+ AutomateEm.print_error(logger, e, {
483
+ :message => "module #{@parent.class} in device_connection.rb, send : possible bad data or options hash",
484
+ :level => Logger::ERROR
485
+ })
486
+
487
+ return true
488
+ end
489
+
490
+
491
+ #
492
+ # Use a monitor here to allow for re-entrant locking
493
+ # This allows for a priority queue and we guarentee order of operations
494
+ #
495
+ bonus = false
496
+ begin
497
+ @send_monitor.mon_exit
498
+ @send_monitor.mon_enter
499
+ bonus = true
500
+ rescue
501
+ end
502
+
503
+ EM.schedule do
504
+ if bonus
505
+ options[:priority] -= @config[:priority_bonus]
506
+ end
507
+
508
+ add = true
509
+ if options[:name].present?
510
+ name = options[:name]
511
+ if @named_commands[name].nil?
512
+ @named_commands[name] = [[options[:priority]], options] #TODO:: we need to deal with the old commands emit values!
513
+ elsif @named_commands[name][0][-1] > options[:priority]
514
+ @named_commands[name][0].push(options[:priority])
515
+ @named_commands[name][1] = options #TODO:: we need to deal with the old commands emit values!
516
+ else
517
+ @named_commands[name][1] = options #TODO:: we need to deal with the old commands emit values!
518
+ add = false
519
+ end
520
+ end
521
+
522
+ @send_queue.push(options, options[:priority]) if add
523
+ end
524
+
525
+ return false
526
+ rescue => e
527
+ #
528
+ # Save from a fatal error
529
+ #
530
+ AutomateEm.print_error(logger, e, {
531
+ :message => "module #{@parent.class} in device_connection.rb, send : something went terribly wrong to get here",
532
+ :level => Logger::ERROR
533
+ })
534
+ return true
535
+ end
536
+
537
+
538
+
539
+ def default_request_options= (options)
540
+ @status_lock.synchronize {
541
+ @default_request_options.merge!(options)
542
+ }
543
+ end
544
+
545
+ def config= (options)
546
+ EM.schedule do
547
+ @config.merge!(options)
548
+ @connection = nil
549
+ end
550
+ end
551
+
552
+
553
+
554
+ def shutdown(system)
555
+ if @parent.leave_system(system) == 0
556
+ @shutting_down.value = true
557
+ @wait_queue.push(:shutdown)
558
+ @send_queue.push(:shutdown, -32768)
559
+ @task_queue.push(nil)
560
+
561
+ EM.defer do
562
+ begin
563
+ if @parent.respond_to?(:on_unload)
564
+ @task_lock.synchronize {
565
+ @parent.on_unload
566
+ }
567
+ end
568
+ rescue => e
569
+ #
570
+ # save from bad user code (don't want to deplete thread pool)
571
+ #
572
+ AutomateEm.print_error(logger, e, {
573
+ :message => "module #{@parent.class} error whilst calling: on_unload on shutdown",
574
+ :level => Logger::ERROR
575
+ })
576
+ ensure
577
+ @parent.clear_active_timers
578
+ end
579
+ end
580
+ end
581
+ end
582
+
583
+ end
584
+ end