orchestrator 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +158 -0
  3. data/README.md +13 -0
  4. data/Rakefile +7 -0
  5. data/app/controllers/orchestrator/api/dependencies_controller.rb +109 -0
  6. data/app/controllers/orchestrator/api/modules_controller.rb +183 -0
  7. data/app/controllers/orchestrator/api/systems_controller.rb +294 -0
  8. data/app/controllers/orchestrator/api/zones_controller.rb +62 -0
  9. data/app/controllers/orchestrator/api_controller.rb +13 -0
  10. data/app/controllers/orchestrator/base.rb +59 -0
  11. data/app/controllers/orchestrator/persistence_controller.rb +29 -0
  12. data/app/models/orchestrator/access_log.rb +35 -0
  13. data/app/models/orchestrator/control_system.rb +160 -0
  14. data/app/models/orchestrator/dependency.rb +87 -0
  15. data/app/models/orchestrator/mod/by_dependency/map.js +6 -0
  16. data/app/models/orchestrator/mod/by_module_type/map.js +6 -0
  17. data/app/models/orchestrator/module.rb +127 -0
  18. data/app/models/orchestrator/sys/by_modules/map.js +9 -0
  19. data/app/models/orchestrator/sys/by_zones/map.js +9 -0
  20. data/app/models/orchestrator/zone.rb +47 -0
  21. data/app/models/orchestrator/zone/all/map.js +6 -0
  22. data/config/routes.rb +43 -0
  23. data/lib/generators/module/USAGE +8 -0
  24. data/lib/generators/module/module_generator.rb +52 -0
  25. data/lib/orchestrator.rb +52 -0
  26. data/lib/orchestrator/control.rb +303 -0
  27. data/lib/orchestrator/core/mixin.rb +123 -0
  28. data/lib/orchestrator/core/module_manager.rb +258 -0
  29. data/lib/orchestrator/core/request_proxy.rb +109 -0
  30. data/lib/orchestrator/core/requests_proxy.rb +47 -0
  31. data/lib/orchestrator/core/schedule_proxy.rb +49 -0
  32. data/lib/orchestrator/core/system_proxy.rb +153 -0
  33. data/lib/orchestrator/datagram_server.rb +114 -0
  34. data/lib/orchestrator/dependency_manager.rb +131 -0
  35. data/lib/orchestrator/device/command_queue.rb +213 -0
  36. data/lib/orchestrator/device/manager.rb +83 -0
  37. data/lib/orchestrator/device/mixin.rb +35 -0
  38. data/lib/orchestrator/device/processor.rb +441 -0
  39. data/lib/orchestrator/device/transport_makebreak.rb +221 -0
  40. data/lib/orchestrator/device/transport_tcp.rb +139 -0
  41. data/lib/orchestrator/device/transport_udp.rb +89 -0
  42. data/lib/orchestrator/engine.rb +70 -0
  43. data/lib/orchestrator/errors.rb +23 -0
  44. data/lib/orchestrator/logger.rb +115 -0
  45. data/lib/orchestrator/logic/manager.rb +18 -0
  46. data/lib/orchestrator/logic/mixin.rb +11 -0
  47. data/lib/orchestrator/service/manager.rb +63 -0
  48. data/lib/orchestrator/service/mixin.rb +56 -0
  49. data/lib/orchestrator/service/transport_http.rb +55 -0
  50. data/lib/orchestrator/status.rb +229 -0
  51. data/lib/orchestrator/system.rb +108 -0
  52. data/lib/orchestrator/utilities/constants.rb +41 -0
  53. data/lib/orchestrator/utilities/transcoder.rb +57 -0
  54. data/lib/orchestrator/version.rb +3 -0
  55. data/lib/orchestrator/websocket_manager.rb +425 -0
  56. data/orchestrator.gemspec +35 -0
  57. data/spec/orchestrator/queue_spec.rb +200 -0
  58. metadata +271 -0
@@ -0,0 +1,47 @@
1
+ module Orchestrator
2
+ module Core
3
+ class RequestsProxy
4
+ def initialize(thread, modules)
5
+ if modules.nil?
6
+ @modules = []
7
+ else
8
+ @modules = modules.is_a?(Array) ? modules : [modules]
9
+ end
10
+ @thread = thread
11
+ end
12
+
13
+ # Returns true if there is no object to proxy
14
+ #
15
+ # @return [true|false]
16
+ def nil?
17
+ @modules.empty?
18
+ end
19
+ alias_method :empty?, :nil?
20
+
21
+ def method_missing(name, *args, &block)
22
+ if ::Orchestrator::Core::PROTECTED[name]
23
+ err = Error::ProtectedMethod.new "attempt to access a protected method '#{name}' in multiple modules"
24
+ ::Libuv::Q.reject(@thread, err)
25
+ # TODO:: log warning err.message
26
+ else
27
+ promises = @modules.map do |mod|
28
+ defer = mod.thread.defer
29
+ mod.thread.schedule do
30
+ begin
31
+ defer.resolve(
32
+ mod.instance.public_send(name, *args, &block)
33
+ )
34
+ rescue => e
35
+ mod.logger.print_error(e)
36
+ defer.reject(e)
37
+ end
38
+ end
39
+ defer.promise
40
+ end
41
+
42
+ @thread.finally(*promises)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,49 @@
1
+ require 'set'
2
+
3
+
4
+ module Orchestrator
5
+ module Core
6
+ class ScheduleProxy
7
+ def initialize(thread)
8
+ @scheduler = thread.scheduler
9
+ @schedules = Set.new
10
+ end
11
+
12
+ attr_reader :schedules
13
+
14
+ def every(*args, &block)
15
+ add_schedule @scheduler.every(*args, &block)
16
+ end
17
+
18
+ def in(*args, &block)
19
+ add_schedule @scheduler.in(*args, &block)
20
+ end
21
+
22
+ def at(*args, &block)
23
+ add_schedule @scheduler.at(*args, &block)
24
+ end
25
+
26
+ def cron(*args, &block)
27
+ add_schedule @scheduler.cron(*args, &block)
28
+ end
29
+
30
+ def clear
31
+ @schedules.each do |schedule|
32
+ schedule.cancel
33
+ end
34
+ end
35
+
36
+
37
+ protected
38
+
39
+
40
+ def add_schedule(schedule)
41
+ @schedules.add(schedule)
42
+ schedule.finally do
43
+ @schedules.delete(schedule)
44
+ end
45
+ schedule
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,153 @@
1
+ require 'set'
2
+
3
+
4
+ module Orchestrator
5
+ module Core
6
+ class SystemProxy
7
+ def initialize(thread, sys_id, origin = nil)
8
+ @system = sys_id.to_sym
9
+ @thread = thread
10
+ @origin = origin # This is the module that requested the proxy
11
+ end
12
+
13
+ # Alias for get
14
+ def [](mod)
15
+ get mod
16
+ end
17
+
18
+ # Provides a proxy to a module for a safe way to communicate across threads
19
+ #
20
+ # @param module [String, Symbol] the name of the module in the system
21
+ # @param index [Integer] the index of the desired module (starting at 1)
22
+ # @return [::Orchestrator::Core::RequestsProxy] proxies requests to a single module
23
+ def get(mod, index = 1)
24
+ index -= 1 # Get the real index
25
+ name = mod.to_sym
26
+
27
+ RequestProxy.new(@thread, system.get(name, index))
28
+ end
29
+
30
+ # Checks for the existence of a particular module
31
+ #
32
+ # @param module [String, Symbol] the name of the module in the system
33
+ # @param index [Integer] the index of the desired module (starting at 1)
34
+ # @return [true, false] does the module exist?
35
+ def exists?(mod, index = 1)
36
+ index -= 1 # Get the real index
37
+ name = mod.to_sym
38
+
39
+ !system.get(name, index).nil?
40
+ end
41
+
42
+ # Provides a proxy to multiple modules. A simple way to send commands to multiple devices
43
+ #
44
+ # @param module [String, Symbol] the name of the module in the system
45
+ # @return [::Orchestrator::Core::RequestsProxy] proxies requests to multiple modules
46
+ def all(mod)
47
+ name = mod.to_sym
48
+ RequestsProxy.new(@thread, system.all(name))
49
+ end
50
+
51
+ # Iterates over the modules in the system. Can also specify module types.
52
+ #
53
+ # @param mod_name [String, Symbol] the optional names of modules to iterate over
54
+ # @yield [Module Instance, Symbol, Integer] yields the modules with their name and index
55
+ def each(*args)
56
+ mods = args.empty? ? modules : args
57
+ mods.each do |mod|
58
+ number = count(mod)
59
+ (1..number).each do |index|
60
+ yield(get(mod, index), mod, index)
61
+ end
62
+ end
63
+ end
64
+
65
+ # Grabs the number of a particular device type
66
+ #
67
+ # @param module [String, Symbol] the name of the module in the system
68
+ # @return [Integer] the number of modules with a shared name
69
+ def count(mod)
70
+ name = mod.to_sym
71
+ system.count(name)
72
+ end
73
+
74
+ # Returns a list of all the module names in the system
75
+ #
76
+ # @return [Array] a list of all the module names
77
+ def modules
78
+ system.modules
79
+ end
80
+
81
+ # Returns the system name as defined in the database
82
+ #
83
+ # @return [String] the name of the system
84
+ def name
85
+ system.config.name
86
+ end
87
+
88
+ # Used to be notified when an update to a status value occurs
89
+ #
90
+ # @param module [String, Symbol] the name of the module in the system
91
+ # @param index [Integer] the index of the module as there may be more than one
92
+ # @param status [String, Symbol] the name of the status variable
93
+ # @param callback [Proc] method, block, proc or lambda to be called when a change occurs
94
+ # @return [Object] a reference to the subscription for un-subscribing
95
+ def subscribe(mod_name, index, status = nil, callback = nil, &block)
96
+ # Allow index to be optional
97
+ if not index.is_a?(Integer)
98
+ callback = status || block
99
+ status = index.to_sym
100
+ index = 1
101
+ else
102
+ callback ||= block
103
+ end
104
+ mod_name = mod_name.to_sym
105
+
106
+ raise 'callback required' unless callback.respond_to? :call
107
+
108
+ # We need to get the system to schedule threads
109
+ sys = system
110
+ options = {
111
+ sys_id: @system,
112
+ sys_name: sys.config.name,
113
+ mod_name: mod_name,
114
+ index: index,
115
+ status: status,
116
+ callback: callback,
117
+ on_thread: @thread
118
+ }
119
+
120
+ # if the module exists, subscribe on the correct thread
121
+ # use a bit of promise magic as required
122
+ mod_man = sys.get(mod_name, index - 1)
123
+ sub = if mod_man
124
+ defer = @thread.defer
125
+
126
+ options[:mod_id] = mod_man.settings.id.to_sym
127
+ options[:mod] = mod_man
128
+ thread = mod_man.thread
129
+ thread.schedule do
130
+ defer.resolve (
131
+ thread.observer.subscribe(options)
132
+ )
133
+ end
134
+
135
+ defer.promise
136
+ else
137
+ @thread.observer.subscribe(options)
138
+ end
139
+
140
+ @origin.add_subscription sub if @origin
141
+ sub
142
+ end
143
+
144
+
145
+ protected
146
+
147
+
148
+ def system
149
+ ::Orchestrator::System.get(@system)
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,114 @@
1
+
2
+ module Orchestrator
3
+ class UdpService < ::UV::DatagramConnection
4
+ def initialize(*args)
5
+ super(*args)
6
+
7
+ @callbacks = {
8
+ # ip => port => callback
9
+ }
10
+ end
11
+
12
+ def attach(ip, port, callback)
13
+ @loop.schedule do
14
+ ports = @callbacks[ip.to_sym] ||= {}
15
+ ports[port.to_i] = callback
16
+ end
17
+ end
18
+
19
+ def detach(ip_raw, port)
20
+ @loop.schedule do
21
+ ip = ip_raw.to_sym
22
+ ip_ports = @callbacks[ip]
23
+ if ip_ports
24
+ ip_ports.delete(port.to_i)
25
+ @callbacks.delete(ip) if ip_ports.empty?
26
+ end
27
+ end
28
+ end
29
+
30
+ def on_read(data, ip, port, transport)
31
+ ip_ports = @callbacks[ip.to_sym]
32
+ if ip_ports
33
+ callback = ip_ports[port.to_i]
34
+ if callback
35
+ callback.call(data)
36
+ end
37
+ end
38
+ end
39
+
40
+ def send(ip, port, data)
41
+ @loop.schedule do
42
+ send_datagram(data, ip, port)
43
+ end
44
+ end
45
+ end
46
+
47
+
48
+ class UdpBroadcast < ::UV::DatagramConnection
49
+ def post_init
50
+ @transport.enable_broadcast
51
+ end
52
+
53
+ def send(ip, port, data)
54
+ @loop.schedule do
55
+ send_datagram(data, ip, port)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+
62
+ module Libuv
63
+ class Loop
64
+ def udp_service
65
+ if @udp_service
66
+ @udp_service
67
+ else
68
+ CRITICAL.synchronize {
69
+ return @udp_service if @udp_service
70
+
71
+ port = Rails.configuration.orchestrator.datagram_port || 0
72
+
73
+ if port == 0
74
+ @udp_service = ::UV.open_datagram_socket(::Orchestrator::UdpService)
75
+ elsif defined? @@udp_service
76
+ @udp_service = @@udp_service
77
+ else # define a class variable at the specified port
78
+ @udp_service = ::UV.open_datagram_socket(::Orchestrator::UdpService, '0.0.0.0', port)
79
+ @@udp_service = @udp_service
80
+ end
81
+ }
82
+ end
83
+ end
84
+
85
+ def udp_broadcast(data, port = 9, ip = '<broadcast>')
86
+ if @udp_broadcast
87
+ @udp_broadcast.send(ip, port, data)
88
+ else
89
+ CRITICAL.synchronize {
90
+ return @udp_broadcast.send(ip, port, data) if @udp_broadcast
91
+
92
+ port = Rails.configuration.orchestrator.broadcast_port || 0
93
+
94
+ if port == 0
95
+ @udp_broadcast = ::UV.open_datagram_socket(::Orchestrator::UdpBroadcast)
96
+ elsif defined? @@udp_broadcast
97
+ @udp_broadcast = @@udp_broadcast
98
+ else # define a class variable at the specified port
99
+ @udp_broadcast = ::UV.open_datagram_socket(::Orchestrator::UdpBroadcast, '0.0.0.0', port)
100
+ @@udp_broadcast = @udp_broadcast
101
+ end
102
+
103
+ @udp_broadcast.send(ip, port, data)
104
+ }
105
+ end
106
+ end
107
+
108
+ def wake_device(mac, ip = '<broadcast>')
109
+ mac = mac.gsub(/(0x|[^0-9A-Fa-f])*/, "").scan(/.{2}/).pack("H*H*H*H*H*H*")
110
+ magicpacket = (0xff.chr) * 6 + mac * 16
111
+ udp_broadcast(magicpacket, 9, ip)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,131 @@
1
+ require 'thread' # For Mutex
2
+ require 'set'
3
+
4
+
5
+ module Orchestrator
6
+ class DependencyManager
7
+ include Singleton
8
+
9
+
10
+ def initialize
11
+ @load_mutex = Mutex.new
12
+ @dependencies = ThreadSafe::Cache.new
13
+ @loop = ::Libuv::Loop.default
14
+ @loop.next_tick do
15
+ @logger = ::Orchestrator::Control.instance.logger
16
+ end
17
+ end
18
+
19
+
20
+ def load(dependency, force = false)
21
+ defer = @loop.defer
22
+
23
+ classname = dependency.class_name
24
+ class_lookup = classname.to_sym
25
+ class_object = @dependencies[class_lookup]
26
+
27
+ if class_object && force == false
28
+ defer.resolve(class_object)
29
+ else
30
+ # We need to ensure only one file loads at a time
31
+ @load_mutex.synchronize {
32
+ perform_load(dependency, defer, classname, class_lookup, force)
33
+ }
34
+ end
35
+
36
+ defer.promise
37
+ end
38
+
39
+ def force_load(file)
40
+ defer = @loop.defer
41
+
42
+ if File.exists?(file)
43
+ begin
44
+ @load_mutex.synchronize {
45
+ load file
46
+ }
47
+ defer.resolve(file)
48
+ rescue Exception => e
49
+ defer.reject(e)
50
+ print_error(e, 'force load failed')
51
+ end
52
+ else
53
+ defer.reject(Error::FileNotFound.new("could not find '#{file}'"))
54
+ end
55
+
56
+ defer.promise
57
+ end
58
+
59
+
60
+ protected
61
+
62
+
63
+ # Always called from within a Mutex
64
+ def perform_load(dependency, defer, classname, class_lookup, force)
65
+ if force == false
66
+ class_object = @dependencies[class_lookup]
67
+ if class_object
68
+ defer.resolve(class_object)
69
+ return
70
+ end
71
+ end
72
+
73
+ begin
74
+ file = "#{classname.underscore}.rb"
75
+ class_object = nil
76
+
77
+ ::Rails.configuration.orchestrator.module_paths.each do |path|
78
+ if ::File.exists?("#{path}/#{file}")
79
+
80
+ ::Kernel.load "#{path}/#{file}"
81
+ class_object = classname.constantize
82
+
83
+ case dependency.role
84
+ when :device
85
+ include_device(class_object)
86
+ when :service
87
+ include_service(class_object)
88
+ else
89
+ include_logic(class_object)
90
+ end
91
+
92
+ @dependencies[class_lookup] = class_object
93
+ defer.resolve(class_object)
94
+ break
95
+ end
96
+ end
97
+
98
+ if class_object.nil?
99
+ defer.reject(Error::FileNotFound.new("could not find '#{file}'"))
100
+ end
101
+ rescue Exception => e
102
+ defer.reject(e)
103
+ print_error(e, 'error loading dependency')
104
+ end
105
+ end
106
+
107
+ def include_logic(klass)
108
+ klass.class_eval do
109
+ include ::Orchestrator::Logic::Mixin
110
+ end
111
+ end
112
+
113
+ def include_device(klass)
114
+ klass.class_eval do
115
+ include ::Orchestrator::Device::Mixin
116
+ end
117
+ end
118
+
119
+ def include_service(klass)
120
+ klass.class_eval do
121
+ include ::Orchestrator::Service::Mixin
122
+ end
123
+ end
124
+
125
+ def print_error(e, msg = '')
126
+ msg << "\n#{e.message}"
127
+ msg << "\n#{e.backtrace.join("\n")}" if e.respond_to? :backtrace
128
+ @logger.error(msg)
129
+ end
130
+ end
131
+ end