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.
- checksums.yaml +7 -0
- data/LICENSE.md +158 -0
- data/README.md +13 -0
- data/Rakefile +7 -0
- data/app/controllers/orchestrator/api/dependencies_controller.rb +109 -0
- data/app/controllers/orchestrator/api/modules_controller.rb +183 -0
- data/app/controllers/orchestrator/api/systems_controller.rb +294 -0
- data/app/controllers/orchestrator/api/zones_controller.rb +62 -0
- data/app/controllers/orchestrator/api_controller.rb +13 -0
- data/app/controllers/orchestrator/base.rb +59 -0
- data/app/controllers/orchestrator/persistence_controller.rb +29 -0
- data/app/models/orchestrator/access_log.rb +35 -0
- data/app/models/orchestrator/control_system.rb +160 -0
- data/app/models/orchestrator/dependency.rb +87 -0
- data/app/models/orchestrator/mod/by_dependency/map.js +6 -0
- data/app/models/orchestrator/mod/by_module_type/map.js +6 -0
- data/app/models/orchestrator/module.rb +127 -0
- data/app/models/orchestrator/sys/by_modules/map.js +9 -0
- data/app/models/orchestrator/sys/by_zones/map.js +9 -0
- data/app/models/orchestrator/zone.rb +47 -0
- data/app/models/orchestrator/zone/all/map.js +6 -0
- data/config/routes.rb +43 -0
- data/lib/generators/module/USAGE +8 -0
- data/lib/generators/module/module_generator.rb +52 -0
- data/lib/orchestrator.rb +52 -0
- data/lib/orchestrator/control.rb +303 -0
- data/lib/orchestrator/core/mixin.rb +123 -0
- data/lib/orchestrator/core/module_manager.rb +258 -0
- data/lib/orchestrator/core/request_proxy.rb +109 -0
- data/lib/orchestrator/core/requests_proxy.rb +47 -0
- data/lib/orchestrator/core/schedule_proxy.rb +49 -0
- data/lib/orchestrator/core/system_proxy.rb +153 -0
- data/lib/orchestrator/datagram_server.rb +114 -0
- data/lib/orchestrator/dependency_manager.rb +131 -0
- data/lib/orchestrator/device/command_queue.rb +213 -0
- data/lib/orchestrator/device/manager.rb +83 -0
- data/lib/orchestrator/device/mixin.rb +35 -0
- data/lib/orchestrator/device/processor.rb +441 -0
- data/lib/orchestrator/device/transport_makebreak.rb +221 -0
- data/lib/orchestrator/device/transport_tcp.rb +139 -0
- data/lib/orchestrator/device/transport_udp.rb +89 -0
- data/lib/orchestrator/engine.rb +70 -0
- data/lib/orchestrator/errors.rb +23 -0
- data/lib/orchestrator/logger.rb +115 -0
- data/lib/orchestrator/logic/manager.rb +18 -0
- data/lib/orchestrator/logic/mixin.rb +11 -0
- data/lib/orchestrator/service/manager.rb +63 -0
- data/lib/orchestrator/service/mixin.rb +56 -0
- data/lib/orchestrator/service/transport_http.rb +55 -0
- data/lib/orchestrator/status.rb +229 -0
- data/lib/orchestrator/system.rb +108 -0
- data/lib/orchestrator/utilities/constants.rb +41 -0
- data/lib/orchestrator/utilities/transcoder.rb +57 -0
- data/lib/orchestrator/version.rb +3 -0
- data/lib/orchestrator/websocket_manager.rb +425 -0
- data/orchestrator.gemspec +35 -0
- data/spec/orchestrator/queue_spec.rb +200 -0
- 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,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
|