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,221 @@
|
|
1
|
+
|
2
|
+
module Orchestrator
|
3
|
+
module Device
|
4
|
+
class MakebreakConnection < ::UV::OutboundConnection
|
5
|
+
def post_init(manager, processor, tls)
|
6
|
+
@manager = manager
|
7
|
+
@processor = processor
|
8
|
+
@config = @processor.config
|
9
|
+
@tls = tls
|
10
|
+
|
11
|
+
@connected = false
|
12
|
+
@changing_state = true
|
13
|
+
@disconnecting = false
|
14
|
+
@last_retry = 0
|
15
|
+
|
16
|
+
|
17
|
+
@activity = nil # Activity timer
|
18
|
+
@connecting = nil # Connection timer
|
19
|
+
@retries = 2 # Connection retries
|
20
|
+
@write_queue = []
|
21
|
+
|
22
|
+
@timeout = method(:timeout)
|
23
|
+
@reset_timeout = method(:reset_timeout)
|
24
|
+
end
|
25
|
+
|
26
|
+
def transmit(cmd)
|
27
|
+
return if @terminated
|
28
|
+
|
29
|
+
data = cmd[:data]
|
30
|
+
|
31
|
+
if @connected
|
32
|
+
promise = write(data)
|
33
|
+
reset_timeout
|
34
|
+
if cmd[:wait]
|
35
|
+
promise.catch do |err|
|
36
|
+
if @processor.queue.waiting == cmd
|
37
|
+
# Fail fast
|
38
|
+
@processor.thread.next_tick do
|
39
|
+
@processor.__send__(:resp_failure, err)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
cmd[:defer].reject(err)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
elsif @retries < 2
|
47
|
+
@write_queue << cmd
|
48
|
+
reconnect unless @disconnecting
|
49
|
+
else
|
50
|
+
cmd[:defer].reject(Error::CommandFailure.new "transmit aborted as disconnected")
|
51
|
+
end
|
52
|
+
# discards data when officially disconnected
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_connect(transport)
|
56
|
+
@connected = true
|
57
|
+
@changing_state = false
|
58
|
+
|
59
|
+
if @connecting
|
60
|
+
@connecting.cancel
|
61
|
+
@connecting = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
if @terminated
|
65
|
+
close_connection(:after_writing)
|
66
|
+
else
|
67
|
+
begin
|
68
|
+
use_tls(@config) if @tls
|
69
|
+
rescue => e
|
70
|
+
@manager.logger.print_error(e, 'error starting tls')
|
71
|
+
end
|
72
|
+
|
73
|
+
if @config[:wait_ready]
|
74
|
+
@delaying = ''
|
75
|
+
else
|
76
|
+
init_connection
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def on_close
|
82
|
+
@delaying = false
|
83
|
+
@connected = false
|
84
|
+
@changing_state = false
|
85
|
+
@disconnecting = false
|
86
|
+
|
87
|
+
|
88
|
+
if @connecting
|
89
|
+
@connecting.cancel
|
90
|
+
@connecting = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Prevent re-connect if terminated
|
94
|
+
unless @terminated
|
95
|
+
@retries += 1
|
96
|
+
the_time = @processor.thread.now
|
97
|
+
boundry = @last_retry + @config[:thrashing_threshold]
|
98
|
+
|
99
|
+
# ensure we are not thrashing (rapid connect then disconnect)
|
100
|
+
# This equals a disconnect and requires a warning
|
101
|
+
if @retries == 1 && boundry >= the_time
|
102
|
+
@retries += 1
|
103
|
+
@manager.logger.warn('possible connection thrashing. Disconnecting')
|
104
|
+
end
|
105
|
+
|
106
|
+
@activity.cancel if @activity
|
107
|
+
@activity = nil
|
108
|
+
|
109
|
+
if @retries == 1
|
110
|
+
if @write_queue.length > 0
|
111
|
+
# We reconnect here as there are pending writes
|
112
|
+
@last_retry = the_time
|
113
|
+
reconnect
|
114
|
+
end
|
115
|
+
else # retries > 1
|
116
|
+
@write_queue.clear
|
117
|
+
|
118
|
+
variation = 1 + rand(2000)
|
119
|
+
@connecting = @manager.get_scheduler.in(3000 + variation) do
|
120
|
+
@connecting = nil
|
121
|
+
reconnect
|
122
|
+
end
|
123
|
+
|
124
|
+
# we mark the queue as offline if more than 1 reconnect fails
|
125
|
+
# or if the first connect fails
|
126
|
+
if @retries == 2 || (@retries == 3 && @last_retry == 0)
|
127
|
+
@processor.disconnected
|
128
|
+
@processor.queue.offline(@config[:clear_queue_on_disconnect])
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def on_read(data, *args)
|
135
|
+
if @delaying
|
136
|
+
@delaying << data
|
137
|
+
result = @delaying.split(@config[:wait_ready], 2)
|
138
|
+
if result.length > 1
|
139
|
+
@delaying = false
|
140
|
+
init_connection
|
141
|
+
rem = result[-1]
|
142
|
+
@processor.buffer(rem) unless rem.empty?
|
143
|
+
end
|
144
|
+
else
|
145
|
+
@processor.buffer(data)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def terminate
|
150
|
+
@terminated = true
|
151
|
+
@connecting.cancel if @connecting
|
152
|
+
@activity.cancel if @activity
|
153
|
+
close_connection(:after_writing) if @transport.connected
|
154
|
+
end
|
155
|
+
|
156
|
+
def disconnect
|
157
|
+
@connected = false
|
158
|
+
@disconnecting = true
|
159
|
+
close_connection(:after_writing)
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
|
166
|
+
def timeout(*args)
|
167
|
+
@activity = nil
|
168
|
+
disconnect
|
169
|
+
end
|
170
|
+
|
171
|
+
def reset_timeout
|
172
|
+
return if @terminated
|
173
|
+
|
174
|
+
if @activity
|
175
|
+
@activity.cancel
|
176
|
+
@activity = nil
|
177
|
+
end
|
178
|
+
|
179
|
+
timeout = @config[:inactivity_timeout] || 0
|
180
|
+
if timeout > 0
|
181
|
+
@activity = @manager.get_scheduler.in(timeout, @timeout)
|
182
|
+
else # Wait for until queue complete
|
183
|
+
waiting = @processor.queue.waiting
|
184
|
+
if waiting
|
185
|
+
if waiting[:makebreak_set].nil?
|
186
|
+
waiting[:defer].promise.finally @reset_timeout
|
187
|
+
waiting[:makebreak_set] = true
|
188
|
+
end
|
189
|
+
elsif @processor.queue.length == 0
|
190
|
+
disconnect
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def reconnect
|
196
|
+
return if @changing_state || @connected
|
197
|
+
@changing_state = true
|
198
|
+
super
|
199
|
+
end
|
200
|
+
|
201
|
+
def init_connection
|
202
|
+
# Write pending directly
|
203
|
+
queue = @write_queue
|
204
|
+
@write_queue = []
|
205
|
+
while queue.length > 0
|
206
|
+
transmit(queue.shift)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Notify module
|
210
|
+
if @retries > 1
|
211
|
+
@processor.queue.online
|
212
|
+
@processor.connected
|
213
|
+
end
|
214
|
+
@retries = 0
|
215
|
+
|
216
|
+
# Start inactivity timeout
|
217
|
+
reset_timeout
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
|
2
|
+
module Orchestrator
|
3
|
+
module Device
|
4
|
+
class TcpConnection < ::UV::OutboundConnection
|
5
|
+
def post_init(manager, processor, tls)
|
6
|
+
@manager = manager
|
7
|
+
@processor = processor
|
8
|
+
@config = @processor.config
|
9
|
+
@tls = tls
|
10
|
+
|
11
|
+
# Delay retry by default if connection fails on load
|
12
|
+
@retries = 1 # Connection retries
|
13
|
+
@connecting = nil # Connection timer
|
14
|
+
|
15
|
+
# Last retry shouldn't break any thresholds
|
16
|
+
@last_retry = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def transmit(cmd)
|
20
|
+
return if @terminated
|
21
|
+
promise = write(cmd[:data])
|
22
|
+
if cmd[:wait]
|
23
|
+
promise.catch do |err|
|
24
|
+
if @processor.queue.waiting == cmd
|
25
|
+
# Fail fast
|
26
|
+
@processor.thread.next_tick do
|
27
|
+
@processor.__send__(:resp_failure, err)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
cmd[:defer].reject(err)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_connect(transport)
|
37
|
+
if @terminated
|
38
|
+
close_connection(:after_writing)
|
39
|
+
else
|
40
|
+
begin
|
41
|
+
use_tls(@config) if @tls
|
42
|
+
rescue => e
|
43
|
+
@manager.logger.print_error(e, 'error starting tls')
|
44
|
+
end
|
45
|
+
|
46
|
+
if @config[:wait_ready]
|
47
|
+
@delaying = ''
|
48
|
+
else
|
49
|
+
init_connection
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_close
|
55
|
+
unless @terminated
|
56
|
+
# Clear the connection delay if in use
|
57
|
+
@delaying = false if @delaying
|
58
|
+
@retries += 1
|
59
|
+
the_time = @processor.thread.now
|
60
|
+
boundry = @last_retry + @config[:thrashing_threshold]
|
61
|
+
|
62
|
+
# ensure we are not thrashing (rapid connect then disconnect)
|
63
|
+
# This equals a disconnect and requires a warning
|
64
|
+
if @retries == 1 && boundry >= the_time
|
65
|
+
@retries += 1
|
66
|
+
@processor.disconnected
|
67
|
+
@manager.logger.warn('possible connection thrashing. Disconnecting')
|
68
|
+
end
|
69
|
+
|
70
|
+
if @retries == 1
|
71
|
+
@last_retry = the_time
|
72
|
+
@processor.disconnected
|
73
|
+
reconnect
|
74
|
+
else
|
75
|
+
variation = 1 + rand(2000)
|
76
|
+
@connecting = @manager.get_scheduler.in(3000 + variation) do
|
77
|
+
@connecting = nil
|
78
|
+
reconnect
|
79
|
+
end
|
80
|
+
|
81
|
+
if @retries == 2
|
82
|
+
# NOTE:: edge case if disconnected on first connect
|
83
|
+
@processor.disconnected if @last_retry == 0
|
84
|
+
|
85
|
+
# we mark the queue as offline if more than 1 reconnect fails
|
86
|
+
@processor.queue.offline(@config[:clear_queue_on_disconnect])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def on_read(data, *args)
|
93
|
+
if @delaying
|
94
|
+
# Update last retry so we don't trigger multiple
|
95
|
+
# calls to disconnected as connection is working
|
96
|
+
@last_retry += 1
|
97
|
+
|
98
|
+
@delaying << data
|
99
|
+
result = @delaying.split(@config[:wait_ready], 2)
|
100
|
+
if result.length > 1
|
101
|
+
@delaying = false
|
102
|
+
init_connection
|
103
|
+
rem = result[-1]
|
104
|
+
@processor.buffer(rem) unless rem.empty?
|
105
|
+
end
|
106
|
+
else
|
107
|
+
@processor.buffer(data)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def terminate
|
112
|
+
@terminated = true
|
113
|
+
@connecting.cancel if @connecting
|
114
|
+
close_connection(:after_writing) if @transport.connected
|
115
|
+
end
|
116
|
+
|
117
|
+
def disconnect
|
118
|
+
# Shutdown quickly
|
119
|
+
close_connection
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
protected
|
124
|
+
|
125
|
+
|
126
|
+
def init_connection
|
127
|
+
# Enable keep alive every 30 seconds
|
128
|
+
keepalive(30)
|
129
|
+
|
130
|
+
# We only have to mark a queue online if more than 1 retry was required
|
131
|
+
if @retries > 1
|
132
|
+
@processor.queue.online
|
133
|
+
end
|
134
|
+
@retries = 0
|
135
|
+
@processor.connected
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Orchestrator
|
2
|
+
module Device
|
3
|
+
class UdpConnection
|
4
|
+
def initialize(manager, processor)
|
5
|
+
@manager = manager
|
6
|
+
@loop = manager.thread
|
7
|
+
@processor = processor
|
8
|
+
|
9
|
+
settings = manager.settings
|
10
|
+
@ip = settings.ip
|
11
|
+
@port = settings.port
|
12
|
+
@on_read = method(:on_read)
|
13
|
+
|
14
|
+
# One per loop unless port specified
|
15
|
+
@udp_server = @loop.udp_service
|
16
|
+
|
17
|
+
if IPAddress.valid? @ip
|
18
|
+
@attached_ip = @ip
|
19
|
+
@udp_server.attach(@attached_ip, @port, @on_read)
|
20
|
+
@loop.next_tick do
|
21
|
+
# Call connected (we only need to do this once)
|
22
|
+
@processor.connected
|
23
|
+
end
|
24
|
+
else
|
25
|
+
variation = 1 + rand(60000 * 5) # 5min
|
26
|
+
@checker = @manager.get_scheduler.in(60000 * 5 + variation) do
|
27
|
+
find_ip(@ip)
|
28
|
+
end
|
29
|
+
find_ip(@ip)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def transmit(cmd)
|
34
|
+
return if @terminated
|
35
|
+
@udp_server.send(@attached_ip, @port, cmd[:data])
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_read(data)
|
39
|
+
# We schedule as UDP server may be on a different thread
|
40
|
+
@loop.schedule do
|
41
|
+
@processor.buffer(data)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def terminate
|
46
|
+
#@processor.disconnected # Disconnect should never be called
|
47
|
+
@terminated = true
|
48
|
+
if @searching
|
49
|
+
@searching.cancel
|
50
|
+
@searching = nil
|
51
|
+
else
|
52
|
+
@udp_server.detach(@attached_ip, @port)
|
53
|
+
end
|
54
|
+
|
55
|
+
@checker.cancel if @checker
|
56
|
+
end
|
57
|
+
|
58
|
+
def disconnect; end
|
59
|
+
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
|
64
|
+
def find_ip(hostname)
|
65
|
+
@loop.lookup(hostname).then(proc{ |result|
|
66
|
+
update_ip(result[0][0])
|
67
|
+
}, proc { |failure|
|
68
|
+
variation = 1 + rand(8000)
|
69
|
+
@searching = @manager.get_scheduler.in(8000 + variation) do
|
70
|
+
@searching = nil
|
71
|
+
find_ip(hostname)
|
72
|
+
end
|
73
|
+
})
|
74
|
+
end
|
75
|
+
|
76
|
+
def update_ip(ip)
|
77
|
+
if ip != @attached_ip
|
78
|
+
if @attached_ip
|
79
|
+
@udp_server.detach(@attached_ip, @port)
|
80
|
+
else
|
81
|
+
@processor.connected
|
82
|
+
end
|
83
|
+
@attached_ip = ip
|
84
|
+
@udp_server.attach(@attached_ip, @port, @on_read)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|