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