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,294 @@
1
+
2
+ module Orchestrator
3
+ module Api
4
+ class SystemsController < ApiController
5
+ respond_to :json
6
+ # state, funcs, count and types are available to authenticated users
7
+ before_action :check_admin, only: [:create, :update, :destroy, :remove, :start, :stop]
8
+ before_action :check_support, only: [:index, :show, :exec]
9
+ before_action :find_system, only: [:show, :update, :destroy, :remove, :start, :stop]
10
+
11
+
12
+ @@elastic ||= Elastic.new(ControlSystem)
13
+
14
+
15
+ def index
16
+ query = @@elastic.query(params)
17
+ query.sort = [{name: "asc"}]
18
+
19
+ # Filter systems via zone_id
20
+ if params.has_key? :zone_id
21
+ zone_id = params.permit(:zone_id)[:zone_id]
22
+ query.filter({
23
+ zones: [zone_id]
24
+ })
25
+ end
26
+
27
+ # filter via module_id
28
+ if params.has_key? :module_id
29
+ module_id = params.permit(:module_id)[:module_id]
30
+ query.filter({
31
+ modules: [module_id]
32
+ })
33
+ end
34
+
35
+ respond_with @@elastic.search(query)
36
+ end
37
+
38
+ def show
39
+ if params.has_key? :complete
40
+ respond_with @cs, {
41
+ methods: [:module_data, :zone_data]
42
+ }
43
+ else
44
+ respond_with @cs
45
+ end
46
+ end
47
+
48
+ def update
49
+ @cs.update(safe_params)
50
+ #save_and_respond(@cs) # save deletes the system cache
51
+ respond_with :api, @cs
52
+ end
53
+
54
+ # Removes the module from the system and deletes it if not used elsewhere
55
+ def remove
56
+ module_id = params.permit(:module_id)[:module_id]
57
+ mod = ::Orchestrator::Module.find module_id
58
+
59
+ if @cs.modules.include? module_id
60
+ remove = true
61
+
62
+ @cs.modules.delete(module_id)
63
+ @cs.save!
64
+
65
+ ControlSystem.using_module(module_id).each do |cs|
66
+ if cs.id != @cs.id
67
+ remove = false
68
+ break
69
+ end
70
+ end
71
+
72
+ mod.delete if remove
73
+ end
74
+ render :nothing => true
75
+ end
76
+
77
+ def create
78
+ cs = ControlSystem.new(safe_params)
79
+ save_and_respond cs
80
+ end
81
+
82
+ def destroy
83
+ @cs.delete # expires the cache in after callback
84
+ render :nothing => true
85
+ end
86
+
87
+
88
+ ##
89
+ # Additional Functions:
90
+ ##
91
+
92
+ def start
93
+ # Start all modules in the system
94
+ @cs.modules.each do |mod_id|
95
+ load_and_start mod_id
96
+ end
97
+ render :nothing => true
98
+ end
99
+
100
+ def stop
101
+ # Stop all modules in the system (shared or not)
102
+ @cs.modules.each do |mod_id|
103
+ mod = control.loaded? mod_id
104
+ if mod
105
+ mod.thread.next_tick do
106
+ mod.stop
107
+ end
108
+ end
109
+ end
110
+ render :nothing => true
111
+ end
112
+
113
+ def exec
114
+ # Run a function in a system module (async request)
115
+ params.require(:module)
116
+ params.require(:method)
117
+ sys = System.get(id)
118
+ if sys
119
+ para = params.permit(:module, :index, :method, {args: []}).tap do |whitelist|
120
+ whitelist[:args] = params[:args]
121
+ end
122
+ index = para[:index]
123
+ mod = sys.get(para[:module].to_sym, index.nil? ? 0 : (index.to_i - 1))
124
+ if mod
125
+ mod.thread.schedule do
126
+ perform_exec(mod, para)
127
+ end
128
+ throw :async
129
+ else
130
+ render nothing: true, status: :not_found
131
+ end
132
+ else
133
+ render nothing: true, status: :not_found
134
+ end
135
+ end
136
+
137
+ def state
138
+ # Status defined as a system module
139
+ params.require(:module)
140
+ params.require(:lookup)
141
+ sys = System.get(id)
142
+ if sys
143
+ para = params.permit(:module, :index, :lookup)
144
+ index = para[:index]
145
+ mod = sys.get(para[:module].to_sym, index.nil? ? 0 : (index.to_i - 1))
146
+ if mod
147
+ render json: mod.status[para[:lookup].to_sym]
148
+ else
149
+ render nothing: true, status: :not_found
150
+ end
151
+ else
152
+ render nothing: true, status: :not_found
153
+ end
154
+ end
155
+
156
+ # returns a list of functions available to call
157
+ def funcs
158
+ params.require(:module)
159
+ sys = System.get(id)
160
+ if sys
161
+ para = params.permit(:module, :index)
162
+ index = para[:index]
163
+ index = index.nil? ? 0 : (index.to_i - 1);
164
+
165
+ mod = sys.get(para[:module].to_sym, index)
166
+ if mod
167
+ funcs = mod.instance.public_methods(false)
168
+ priv = []
169
+ funcs.each do |func|
170
+ if ::Orchestrator::Core::PROTECTED[func]
171
+ priv << func
172
+ end
173
+ end
174
+ render json: (funcs - priv)
175
+ else
176
+ render nothing: true, status: :not_found
177
+ end
178
+ else
179
+ render nothing: true, status: :not_found
180
+ end
181
+ end
182
+
183
+ # return the count of a module type in a system
184
+ def count
185
+ params.require(:module)
186
+ sys = System.get(id)
187
+ if sys
188
+ mod = params.permit(:module)[:module]
189
+ render json: {count: sys.count(mod)}
190
+ else
191
+ render nothing: true, status: :not_found
192
+ end
193
+ end
194
+
195
+ # return the list of a module types in a system
196
+ def types
197
+ sys = System.get(id)
198
+ if sys
199
+ render json: sys.modules
200
+ else
201
+ render nothing: true, status: :not_found
202
+ end
203
+ end
204
+
205
+
206
+ protected
207
+
208
+
209
+ # Better performance as don't need to create the object each time
210
+ CS_PARAMS = [
211
+ :name, :description, :support_url,
212
+ {
213
+ zones: [],
214
+ modules: []
215
+ }
216
+ ]
217
+ # We need to support an arbitrary settings hash so have to
218
+ # work around safe params as per
219
+ # http://guides.rubyonrails.org/action_controller_overview.html#outside-the-scope-of-strong-parameters
220
+ def safe_params
221
+ settings = params[:settings]
222
+ {
223
+ modules: [],
224
+ zones: [],
225
+ settings: settings.is_a?(::Hash) ? settings : {}
226
+ }.merge(params.permit(CS_PARAMS))
227
+ end
228
+
229
+ def find_system
230
+ # Find will raise a 404 (not found) if there is an error
231
+ sys = ::Orchestrator::ControlSystem.bucket.get("sysname-#{id.downcase}", {quiet: true}) || id
232
+ @cs = ControlSystem.find(sys)
233
+ end
234
+
235
+ def load_and_start(mod_id)
236
+ mod = control.loaded? mod_id
237
+ if mod
238
+ mod.thread.next_tick do
239
+ mod.start
240
+ end
241
+ else # attempt to load module
242
+ config = ::Orchestrator::Module.find(mod_id)
243
+ control.load(config).then(
244
+ proc { |mod|
245
+ mod.thread.next_tick do
246
+ mod.start
247
+ end
248
+ }
249
+ )
250
+ end
251
+ end
252
+
253
+ # Called on the module thread
254
+ def perform_exec(mod, para)
255
+ defer = mod.thread.defer
256
+
257
+ req = Core::RequestProxy.new(mod.thread, mod)
258
+ args = para[:args] || []
259
+ result = req.send(para[:method].to_sym, *args)
260
+
261
+ # timeout in case message is queued
262
+ timeout = mod.thread.scheduler.in(5000) do
263
+ defer.resolve('Wait time exceeded. Command may have been queued.')
264
+ end
265
+
266
+ result.finally do
267
+ timeout.cancel # if we have our answer
268
+ defer.resolve(result)
269
+ end
270
+
271
+ defer.promise.then(proc { |res|
272
+ output = ''
273
+ begin
274
+ output = ::JSON.generate([res])
275
+ rescue Exception => e
276
+ # respond with nil if object cannot be converted
277
+ # TODO:: need a better way of dealing with this
278
+ # ALSO in websocket manager
279
+ end
280
+ env['async.callback'].call([200, {
281
+ 'Content-Length' => output.bytesize,
282
+ 'Content-Type' => 'application/json'
283
+ }, [output]])
284
+ }, proc { |err|
285
+ output = err.message
286
+ env['async.callback'].call([500, {
287
+ 'Content-Length' => output.bytesize,
288
+ 'Content-Type' => 'text/plain'
289
+ }, [output]])
290
+ })
291
+ end
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,62 @@
1
+
2
+ module Orchestrator
3
+ module Api
4
+ class ZonesController < ApiController
5
+ respond_to :json
6
+ before_action :check_admin
7
+ before_action :find_zone, only: [:show, :update, :destroy]
8
+
9
+
10
+ @@elastic ||= Elastic.new(Zone)
11
+
12
+
13
+ def index
14
+ query = @@elastic.query(params)
15
+ query.sort = [{name: "asc"}]
16
+
17
+ respond_with @@elastic.search(query)
18
+ end
19
+
20
+ def show
21
+ respond_with @zone
22
+ end
23
+
24
+ def update
25
+ @zone.update_attributes(safe_params)
26
+ save_and_respond @zone
27
+ end
28
+
29
+ def create
30
+ zone = Zone.new(safe_params)
31
+ save_and_respond zone
32
+ end
33
+
34
+ def destroy
35
+ # delete will update CS and zone caches
36
+ @zone.delete
37
+ render :nothing => true
38
+ end
39
+
40
+
41
+ protected
42
+
43
+
44
+ ZONE_PARAMS = [
45
+ :name, :description,
46
+ {groups: []}
47
+ ]
48
+ def safe_params
49
+ settings = params[:settings]
50
+ {
51
+ settings: settings.is_a?(::Hash) ? settings : {},
52
+ groups: []
53
+ }.merge(params.permit(ZONE_PARAMS))
54
+ end
55
+
56
+ def find_zone
57
+ # Find will raise a 404 (not found) if there is an error
58
+ @zone = Zone.find(id)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,13 @@
1
+
2
+ module Orchestrator
3
+ class ApiController < ::AcaEngineBase
4
+
5
+ protected
6
+
7
+
8
+ # Access to the control system controller
9
+ def control
10
+ @@__control__ ||= ::Orchestrator::Control.instance
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,59 @@
1
+
2
+ module Orchestrator
3
+ class Base < ::ActionController::Base
4
+ layout nil
5
+ rescue_from Couchbase::Error::NotFound, with: :entry_not_found
6
+
7
+
8
+ # Add headers to allow for CORS requests to the API
9
+ before_filter :allow_cors
10
+
11
+
12
+ # This is a preflight OPTIONS request
13
+ def options
14
+ render nothing: true
15
+ end
16
+
17
+
18
+ protected
19
+
20
+
21
+ # Don't keep re-creating these objects for every request
22
+ ALLOW_ORIGIN = 'Access-Control-Allow-Origin'.freeze
23
+ ALLOW_METHODS = 'Access-Control-Allow-Methods'.freeze
24
+ ALLOW_HEADERS = 'Access-Control-Allow-Headers'.freeze
25
+ MAX_AGE = 'Access-Control-Max-Age'.freeze
26
+ ANY_ORIGIN = '*'.freeze
27
+ ANY_METHOD = 'GET, POST, PUT, DELETE, OPTIONS, PATCH'.freeze
28
+ COMMON_HEADERS = 'Origin, Accept, Content-Type, X-Requested-With, Authorization, X-Frame-Options'.freeze
29
+ ONE_DAY = '1728000'.freeze
30
+
31
+ def allow_cors
32
+ headers[ALLOW_ORIGIN] = ANY_ORIGIN
33
+ headers[ALLOW_METHODS] = ANY_METHOD
34
+ headers[ALLOW_HEADERS] = COMMON_HEADERS
35
+ headers[MAX_AGE] = ONE_DAY
36
+ end
37
+
38
+
39
+ # Couchbase catch all
40
+ def entry_not_found(err)
41
+ logger.warn err.message
42
+ logger.warn err.backtrace.join("\n") if err.respond_to?(:backtrace) && err.backtrace
43
+ render nothing: true, status: :not_found # 404
44
+ end
45
+
46
+ # Helper for extracting the id from the request
47
+ def id
48
+ return @id if @id
49
+ params.require(:id)
50
+ @id = params.permit(:id)[:id]
51
+ end
52
+
53
+ # Used to save and respond to all model requests
54
+ def save_and_respond(model)
55
+ yield if model.save && block_given?
56
+ respond_with :api, model
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,29 @@
1
+ require 'spider-gazelle/upgrades/websocket'
2
+
3
+
4
+ module Orchestrator
5
+ class PersistenceController < ApiController
6
+ CONTROL = Control.instance
7
+
8
+
9
+ # Supply a bearer_token param for oauth
10
+ def websocket
11
+ hijack = request.env['rack.hijack']
12
+ if hijack && CONTROL.ready
13
+ promise = hijack.call
14
+
15
+ # grab user for authorization checks in the web socket
16
+ user = current_user
17
+ promise.then do |hijacked|
18
+ ws = ::SpiderGazelle::Websocket.new(hijacked.socket, hijacked.env)
19
+ WebsocketManager.new(ws, user)
20
+ ws.start
21
+ end
22
+
23
+ throw :async # to prevent rails from complaining
24
+ else
25
+ render nothing: true, status: :method_not_allowed
26
+ end
27
+ end
28
+ end
29
+ end