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