automate-em 0.0.1

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 (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