orchestrator 1.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 (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