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
+
2
+ module Orchestrator
3
+ class Zone < Couchbase::Model
4
+ design_document :zone
5
+ include ::CouchbaseId::Generator
6
+
7
+
8
+ attribute :name
9
+ attribute :description
10
+ attribute :settings, default: lambda { {} }
11
+
12
+ attribute :created_at, default: lambda { Time.now.to_i }
13
+
14
+
15
+
16
+ # Loads all the zones
17
+ def self.all
18
+ all(stale: false)
19
+ end
20
+ view :all
21
+
22
+
23
+ protected
24
+
25
+
26
+ validates :name, presence: true
27
+
28
+
29
+ before_delete :remove_zone
30
+ def remove_zone
31
+ ::Orchestrator::Control.instance.zones.delete(self.id)
32
+ ::Orchestrator::ControlSystem.in_zone(self.id).each do |cs|
33
+ cs.zones.delete(self.id)
34
+ cs.save
35
+ end
36
+ end
37
+
38
+ # Expire both the zone cache and any systems that use the zone
39
+ after_save :expire_caches
40
+ def expire_caches
41
+ ::Orchestrator::Control.instance.zones[self.id] = self
42
+ ::Orchestrator::ControlSystem.in_zone(self.id).each do |cs|
43
+ cs.expire_cache
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+
2
+ function(doc, meta) {
3
+ if(doc.type == "zone") {
4
+ emit(meta.id, null);
5
+ }
6
+ }
@@ -0,0 +1,43 @@
1
+ Orchestrator::Engine.routes.draw do
2
+
3
+ match '/*path' => 'api#options', :via => :options
4
+
5
+ # Restful access to services
6
+ namespace :api do
7
+ # Allows multiple routes to resolve to the one controller
8
+ concern :mods do
9
+ resources :modules do # modules have settings
10
+ post 'start', on: :member
11
+ post 'stop', on: :member
12
+ get 'state', on: :member
13
+ end
14
+ end
15
+
16
+ # Trusted Sessions - Create Trust (returns id), Update Session and Destroy Trust
17
+ resources :trusts
18
+
19
+ resources(:systems, {as: :control_system}) do # systems have settings and define what zone they are in
20
+ post 'remove', on: :member
21
+ post 'start', on: :member
22
+ post 'stop', on: :member
23
+ post 'exec', on: :member
24
+ get 'state', on: :member
25
+ get 'funcs', on: :member
26
+ get 'count', on: :member
27
+ get 'types', on: :member
28
+
29
+ concerns :mods
30
+ end
31
+ resources :dependencies do # dependencies have settings
32
+ post 'reload', on: :member
33
+ end
34
+ resources :groups # users define the groups they are in
35
+ resources :zones # zones define what groups can access them
36
+
37
+ concerns :mods
38
+ end
39
+
40
+ # These are non-restful endpoints
41
+ # Websockets and Eventsources
42
+ get 'websocket', to: 'persistence#websocket', via: :all
43
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generates a module scaffold for you
3
+
4
+ Example:
5
+ rails generate module manufacturer/category/module_name
6
+
7
+ This will create:
8
+ app/modules/manufacturer/category/module_name.rb
@@ -0,0 +1,52 @@
1
+ class ModuleGenerator < Rails::Generators::NamedBase
2
+ #source_root File.expand_path('../templates', __FILE__)
3
+
4
+ def create_module_file
5
+
6
+ name = file_name.downcase.gsub(/\s|-/, '_')
7
+ param = class_path
8
+ param.map! {|item| item.downcase.gsub(/\s|-/, '_')}
9
+
10
+ path = File.join('app/modules', *param)
11
+
12
+ scope = []
13
+ text = ""
14
+ param.map! {|item|
15
+ item = item.camelcase
16
+ scope << item
17
+ text += "module #{scope.join('::')}; end\n"
18
+ item
19
+ }
20
+ param << name.camelcase
21
+ scope = param.join('::')
22
+
23
+
24
+ create_file File.join(path, "#{name}.rb") do
25
+ text += <<-FILE
26
+
27
+
28
+ class #{scope}
29
+ include ::Orchestrator::Constants # On, Off and other useful constants
30
+ include ::Orchestrator::Transcoder # binary, hex and string helper methods
31
+ # For stream tokenization use ::UV::BufferedTokenizer or ::UV::AbstractTokenizer
32
+
33
+ def on_load
34
+ # module has been started
35
+ end
36
+
37
+ def on_unload
38
+ # module has been stopped
39
+ end
40
+
41
+ # Called when class updated at runtime
42
+ def on_update
43
+ end
44
+ end
45
+
46
+ FILE
47
+
48
+ text
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,52 @@
1
+ require 'orchestrator/engine'
2
+
3
+
4
+ # Gems
5
+ require 'couchbase-id'
6
+ require 'uv-rays'
7
+ require 'co-elastic-query'
8
+
9
+ # Optional utility modules
10
+ require 'orchestrator/utilities/transcoder' # functions for data manipulation
11
+ require 'orchestrator/utilities/constants' # constants for readable code
12
+
13
+ # System Main
14
+ require 'orchestrator/dependency_manager' # Manages code loading
15
+ require 'orchestrator/websocket_manager' # Websocket interface
16
+ require 'orchestrator/datagram_server' # UDP abstraction management
17
+ require 'orchestrator/control' # Module control and system loader
18
+ require 'orchestrator/version' # orchestrator version
19
+ require 'orchestrator/system' # This is the source of truth for all system information
20
+ require 'orchestrator/status' # Manages status subscriptions across threads
21
+ require 'orchestrator/logger' # Logs events of interest as well as coordinating live log feedback
22
+ require 'orchestrator/errors' # A list of errors that can occur within the system
23
+
24
+ # Core Abstractions
25
+ require 'orchestrator/core/module_manager' # Base class of logic, device and service managers
26
+ require 'orchestrator/core/schedule_proxy' # Common proxy for all module schedules
27
+ require 'orchestrator/core/requests_proxy' # Sends a command to all modules of that type
28
+ require 'orchestrator/core/request_proxy' # Sends a command to a single module
29
+ require 'orchestrator/core/system_proxy' # prevents stale system objects (maintains loose coupling)
30
+ require 'orchestrator/core/mixin' # Common mixin functions for modules classes
31
+
32
+ # Logic abstractions
33
+ require 'orchestrator/logic/manager' # control system manager for logic modules
34
+ require 'orchestrator/logic/mixin' # helper functions for logic module classes
35
+
36
+ # Device abstractions
37
+ require 'orchestrator/device/transport_makebreak'
38
+ require 'orchestrator/device/command_queue'
39
+ require 'orchestrator/device/transport_tcp'
40
+ require 'orchestrator/device/transport_udp'
41
+ require 'orchestrator/device/processor'
42
+ require 'orchestrator/device/manager'
43
+ require 'orchestrator/device/mixin'
44
+
45
+ # Service abstractions
46
+ require 'orchestrator/service/transport_http'
47
+ require 'orchestrator/service/manager'
48
+ require 'orchestrator/service/mixin'
49
+
50
+
51
+ module Orchestrator
52
+ end
@@ -0,0 +1,303 @@
1
+ require 'set'
2
+ require 'logger'
3
+
4
+
5
+ module Orchestrator
6
+ class Control
7
+ include Singleton
8
+
9
+ #
10
+ #
11
+ # 1. Load the modules allocated to this node
12
+ # 2. Allocate modules to CPUs
13
+ # * Modules load dependencies as required
14
+ # * Logics are streamed in after devices and services
15
+ #
16
+ # Logic modules will fetch their system when they interact with other modules.
17
+ # Devices and services do not have a system associated with them
18
+ # This makes systems very loosely coupled to the modules
19
+ # which should make distributing the system slightly simpler
20
+ #
21
+ #
22
+
23
+ def initialize
24
+ # critical sections
25
+ @critical = ::Mutex.new
26
+ @loaded = ::ThreadSafe::Cache.new
27
+ @zones = ::ThreadSafe::Cache.new
28
+ @loader = DependencyManager.instance
29
+ @loop = ::Libuv::Loop.default
30
+ @exceptions = method(:log_unhandled_exception)
31
+
32
+ @ready = false
33
+
34
+ if Rails.env.production?
35
+ logger = ::Logger.new(::Rails.root.join('log/control.log').to_s, 10, 4194304)
36
+ else
37
+ logger = ::Logger.new(STDOUT)
38
+ end
39
+ logger.formatter = proc { |severity, datetime, progname, msg|
40
+ "#{datetime.strftime("%d/%m/%Y @ %I:%M%p")} #{severity}: #{progname} - #{msg}\n"
41
+ }
42
+ @logger = ::ActiveSupport::TaggedLogging.new(logger)
43
+ end
44
+
45
+
46
+ attr_reader :logger, :loop, :ready, :zones
47
+
48
+
49
+ # Start the control reactor
50
+ def mount
51
+ return @server.loaded if @server
52
+
53
+ @critical.synchronize {
54
+ return if @server # Protect against multiple mounts
55
+
56
+ # Cache all the zones in the system
57
+ ::Orchestrator::Zone.all.each do |zone|
58
+ @zones[zone.id] = zone
59
+ end
60
+
61
+ @server = ::SpiderGazelle::Spider.instance
62
+ @server.loaded.then do
63
+ # Share threads with SpiderGazelle (one per core)
64
+ if @server.mode == :thread
65
+ @threads = @server.threads
66
+ else # We are either running no_ipc or process (unsupported for control)
67
+ @threads = Set.new
68
+
69
+ cpus = ::Libuv.cpu_count || 1
70
+ cpus.times &method(:start_thread)
71
+
72
+ @loop.signal :INT, method(:kill_workers)
73
+ end
74
+
75
+ @selector = @threads.cycle
76
+ end
77
+ }
78
+
79
+ return @server.loaded
80
+ end
81
+
82
+ # Boot the control system, running all defined modules
83
+ def boot(*args)
84
+ # Only boot if running as a server
85
+ Thread.new &method(:load_all)
86
+ end
87
+
88
+ # Load the modules on the loop references in round robin
89
+ # This method is thread safe.
90
+ def load(mod_settings)
91
+ mod_id = mod_settings.id.to_sym
92
+ defer = @loop.defer
93
+ mod = @loaded[mod_id]
94
+
95
+ if mod
96
+ defer.resolve(mod)
97
+ else
98
+ defer.resolve(
99
+ @loader.load(mod_settings.dependency).then(proc { |klass|
100
+ # We will always be on the default loop here
101
+ thread = @selector.next
102
+
103
+ # We'll resolve the promise if the module loads on the deferred thread
104
+ defer = @loop.defer
105
+ thread.schedule do
106
+ defer.resolve(start_module(thread, klass, mod_settings))
107
+ end
108
+
109
+ # update the module cache
110
+ defer.promise.then do |mod_manager|
111
+ @loaded[mod_id] = mod_manager
112
+ end
113
+ defer.promise
114
+ }, @exceptions)
115
+ )
116
+ end
117
+ defer.promise
118
+ end
119
+
120
+ # Checks if a module with the ID specified is loaded
121
+ def loaded?(mod_id)
122
+ @loaded[mod_id.to_sym]
123
+ end
124
+
125
+ # Starts a module running
126
+ def start(mod_id)
127
+ defer = @loop.defer
128
+
129
+ mod = loaded? mod_id
130
+ if mod
131
+ mod.thread.schedule do
132
+ mod.start
133
+ defer.resolve(true)
134
+ end
135
+ else
136
+ err = Error::ModuleNotFound.new "unable to start module '#{mod_id}', not found"
137
+ defer.reject(err)
138
+ @logger.warn err.message
139
+ end
140
+
141
+ defer.promise
142
+ end
143
+
144
+ # Stops a module running
145
+ def stop(mod_id)
146
+ defer = @loop.defer
147
+
148
+ mod = loaded? mod_id
149
+ if mod
150
+ mod.thread.schedule do
151
+ mod.stop
152
+ defer.resolve(true)
153
+ end
154
+ else
155
+ err = Error::ModuleNotFound.new "unable to stop module '#{mod_id}', not found"
156
+ defer.reject(err)
157
+ @logger.warn err.message
158
+ end
159
+
160
+ defer.promise
161
+ end
162
+
163
+ # Stop the module gracefully
164
+ # Then remove it from @loaded
165
+ def unload(mod_id)
166
+ stop(mod_id).then(proc {
167
+ @loaded.delete(mod_id.to_sym)
168
+ true # promise response
169
+ })
170
+ end
171
+
172
+ # Unload then
173
+ # Get a fresh version of the settings from the database
174
+ # load the module
175
+ def update(mod_id)
176
+ unload(mod_id).then(proc {
177
+ # Grab database model in the thread pool
178
+ res = @loop.work do
179
+ ::Orchestrator::Module.find(mod_id)
180
+ end
181
+
182
+ # Load the module if model found
183
+ res.then(proc { |config|
184
+ load(config) # Promise chaining to here
185
+ })
186
+ })
187
+ end
188
+
189
+ def reload(dep_id)
190
+ @loop.work do
191
+ reload_dep(dep_id)
192
+ end
193
+ end
194
+
195
+ def notify_ready
196
+ # Clear the system cache (in case it has been populated at all)
197
+ System.clear_cache
198
+ @ready = true
199
+ end
200
+
201
+ def log_unhandled_exception(*args)
202
+ msg = ''
203
+ err = args[-1]
204
+ if err && err.respond_to?(:backtrace)
205
+ msg << "exception: #{err.message} (#{args[0..-2]})"
206
+ msg << "\n#{err.backtrace.join("\n")}" if err.respond_to?(:backtrace) && err.backtrace
207
+ else
208
+ msg << "unhandled exception: #{args}"
209
+ end
210
+ @logger.error msg
211
+ ::Libuv::Q.reject(@loop, msg)
212
+ end
213
+
214
+
215
+ protected
216
+
217
+
218
+ # This will always be called on the thread reactor here
219
+ def start_module(thread, klass, settings)
220
+ # Initialize the connection / logic / service handler here
221
+ case settings.dependency.role
222
+ when :device
223
+ Device::Manager.new(thread, klass, settings)
224
+ when :service
225
+ Service::Manager.new(thread, klass, settings)
226
+ else
227
+ Logic::Manager.new(thread, klass, settings)
228
+ end
229
+ end
230
+
231
+
232
+ # Grab the modules from the database and load them
233
+ def load_all
234
+ loading = []
235
+ wait = nil
236
+
237
+ modules = ::Orchestrator::Module.all
238
+ modules.each do |mod|
239
+ if mod.role < 3
240
+ loading << load(mod) # modules are streamed in
241
+ else
242
+ if wait.nil?
243
+ wait = ::Libuv::Q.finally(@loop, *loading)
244
+ loading.clear
245
+
246
+ # Clear here in case rest api calls have built the cache
247
+ System.clear_cache
248
+ end
249
+
250
+ loading << mod
251
+ end
252
+ end
253
+
254
+ # In case there were no logic modules
255
+ if wait.nil?
256
+ wait = ::Libuv::Q.finally(@loop, *loading)
257
+ loading.clear
258
+ end
259
+
260
+ # Mark system as ready
261
+ wait.finally do
262
+ continue_loading(loading)
263
+ end
264
+ end
265
+
266
+ # Load all the logic modules after the device modules are complete
267
+ def continue_loading(modules)
268
+ loading = []
269
+
270
+ modules.each do |mod|
271
+ loading << load(mod) # grab the load promises
272
+ end
273
+
274
+ # Once load is complete we'll accept websockets
275
+ ::Libuv::Q.finally(@loop, *loading).finally method(:notify_ready)
276
+ end
277
+
278
+
279
+
280
+ ##
281
+ # Methods called when we manage the threads:
282
+ def start_thread(num)
283
+ thread = Libuv::Loop.new
284
+ @threads << thread
285
+ Thread.new do
286
+ thread.run do |promise|
287
+ promise.progress @exceptions
288
+
289
+ thread.async do
290
+ p 'noop'
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ def kill_workers(*args)
297
+ @threads.each do |thread|
298
+ thread.stop
299
+ end
300
+ @loop.stop
301
+ end
302
+ end
303
+ end