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,70 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
|
4
|
+
module Orchestrator
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
isolate_namespace Orchestrator
|
7
|
+
|
8
|
+
|
9
|
+
# NOTE:: if we ever have any tasks
|
10
|
+
#rake_tasks do
|
11
|
+
# load "tasks/orchestrator_tasks.rake"
|
12
|
+
#end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Define the application configuration
|
16
|
+
#
|
17
|
+
config.before_initialize do |app| # Rails.configuration
|
18
|
+
app.config.orchestrator = ActiveSupport::OrderedOptions.new
|
19
|
+
app.config.orchestrator.module_paths = []
|
20
|
+
|
21
|
+
# Clearance levels defined in code
|
22
|
+
#app.config.orchestrator.clearance_levels = Set.new([:Admin, :Support, :User, :Public])
|
23
|
+
|
24
|
+
# Access checking callback - used at the system level
|
25
|
+
# Will always be passed a system id and the user attempting to access
|
26
|
+
app.config.orchestrator.check_access = proc { |system, user|
|
27
|
+
true
|
28
|
+
}
|
29
|
+
|
30
|
+
# if not zero all UDP sockets must be transmitted from a single thread
|
31
|
+
app.config.orchestrator.datagram_port = 0 # ephemeral port (random selection)
|
32
|
+
app.config.orchestrator.broadcast_port = 0 # ephemeral port (random selection)
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Discover the possible module location paths after initialization is complete
|
37
|
+
#
|
38
|
+
config.after_initialize do |app|
|
39
|
+
|
40
|
+
ActiveSupport::Dependencies.autoload_paths.each do |path|
|
41
|
+
Pathname.new(path).ascend do |v|
|
42
|
+
if ['app', 'vendor'].include?(v.basename.to_s)
|
43
|
+
app.config.orchestrator.module_paths << "#{v.to_s}/modules"
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
app.config.orchestrator.module_paths.uniq!
|
50
|
+
|
51
|
+
# Force design documents
|
52
|
+
temp = ::Couchbase::Model::Configuration.design_documents_paths
|
53
|
+
::Couchbase::Model::Configuration.design_documents_paths = [File.expand_path(File.join(File.expand_path("../", __FILE__), '../../app/models/orchestrator'))]
|
54
|
+
::Orchestrator::ControlSystem.ensure_design_document!
|
55
|
+
::Orchestrator::Module.ensure_design_document!
|
56
|
+
::Orchestrator::Zone.ensure_design_document!
|
57
|
+
::Couchbase::Model::Configuration.design_documents_paths = temp
|
58
|
+
|
59
|
+
# Start the control system by initializing it
|
60
|
+
ctrl = ::Orchestrator::Control.instance
|
61
|
+
|
62
|
+
# Don't auto-load if running in the console or as a rake task
|
63
|
+
unless ENV['ORC_NO_BOOT'] || defined?(Rails::Console) || Rails.env.test? || defined?(Rake)
|
64
|
+
ctrl.loop.next_tick do
|
65
|
+
ctrl.mount.then ctrl.method(:boot)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Orchestrator
|
2
|
+
class Error < StandardError
|
3
|
+
|
4
|
+
# Called from:
|
5
|
+
# * request_proxy
|
6
|
+
# * requests_proxy
|
7
|
+
class ProtectedMethod < Error; end
|
8
|
+
class ModuleUnavailable < Error; end
|
9
|
+
|
10
|
+
# Called from:
|
11
|
+
# * dependency_manager
|
12
|
+
class FileNotFound < Error; end
|
13
|
+
|
14
|
+
# Called from:
|
15
|
+
# * control
|
16
|
+
class ModuleNotFound < Error; end
|
17
|
+
|
18
|
+
# Called from:
|
19
|
+
# * Device -> Processor
|
20
|
+
class CommandFailure < Error; end
|
21
|
+
class CommandCanceled < Error; end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
|
4
|
+
# Note:: We should be logging the User id in the log
|
5
|
+
# see: http://pastebin.com/Wrjp8b8e (rails log_tags)
|
6
|
+
module Orchestrator
|
7
|
+
class Logger
|
8
|
+
LEVEL = {
|
9
|
+
debug: 0,
|
10
|
+
info: 1,
|
11
|
+
warn: 2,
|
12
|
+
error: 3,
|
13
|
+
fatal: 4
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
# TODO:: Make this a config item
|
17
|
+
DEFAULT_LEVEL = 1
|
18
|
+
|
19
|
+
def initialize(loop, mod)
|
20
|
+
@loop = loop
|
21
|
+
@mod_id = mod.id
|
22
|
+
if mod.respond_to? :dependency
|
23
|
+
@klass = mod.dependency.class_name
|
24
|
+
else
|
25
|
+
@klass = 'User' # Filter by user driven events and behavior
|
26
|
+
end
|
27
|
+
@level = DEFAULT_LEVEL
|
28
|
+
@listeners = Set.new
|
29
|
+
@logger = ::Orchestrator::Control.instance.logger
|
30
|
+
end
|
31
|
+
|
32
|
+
def level=(level)
|
33
|
+
@level = LEVEL[level] || level
|
34
|
+
end
|
35
|
+
attr_reader :level
|
36
|
+
|
37
|
+
# Add listener
|
38
|
+
def add(listener)
|
39
|
+
@loop.schedule do
|
40
|
+
@listeners.add listener
|
41
|
+
end
|
42
|
+
listener.promise.finally do
|
43
|
+
@loop.schedule do
|
44
|
+
@listeners.delete listener
|
45
|
+
end
|
46
|
+
end
|
47
|
+
listener
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete(listener)
|
51
|
+
@loop.schedule do
|
52
|
+
@listeners.delete listener
|
53
|
+
if @listeners.size == 0
|
54
|
+
level = DEFAULT_LEVEL # back to efficient logging
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def debug(msg)
|
61
|
+
if @level <= 0
|
62
|
+
log(:debug, msg)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def info(msg)
|
67
|
+
if @level <= 1
|
68
|
+
log(:info, msg)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def warn(msg)
|
73
|
+
if @level <= 2
|
74
|
+
log(:warn, msg)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def error(msg)
|
79
|
+
if @level <= 3
|
80
|
+
log(:error, msg)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def fatal(msg)
|
85
|
+
if @level <= 4
|
86
|
+
log(:fatal, msg)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def print_error(e, msg = '')
|
91
|
+
msg << "\n#{e.message}"
|
92
|
+
msg << "\n#{e.backtrace.join("\n")}" if e.respond_to?(:backtrace) && e.backtrace
|
93
|
+
error(msg)
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
|
100
|
+
def log(level, msg)
|
101
|
+
@loop.schedule do
|
102
|
+
if LEVEL[level] >= DEFAULT_LEVEL
|
103
|
+
@loop.work do
|
104
|
+
@logger.tagged(@klass, @mod_id) {
|
105
|
+
@logger.send(level, msg)
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
@listeners.each do |listener|
|
110
|
+
listener.notify(@klass, @mod_id, level, msg)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Orchestrator
|
2
|
+
module Logic
|
3
|
+
class Manager < ::Orchestrator::Core::ModuleManager
|
4
|
+
def initialize(*args)
|
5
|
+
super(*args)
|
6
|
+
|
7
|
+
# Do we want to start here?
|
8
|
+
# Should be ok.
|
9
|
+
@thread.next_tick method(:start)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Access to other modules in the same control system
|
13
|
+
def system
|
14
|
+
@system ||= ::Orchestrator::Core::SystemProxy.new(@thread, @settings.control_system_id)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Orchestrator
|
2
|
+
module Service
|
3
|
+
class Manager < ::Orchestrator::Core::ModuleManager
|
4
|
+
def initialize(*args)
|
5
|
+
super(*args)
|
6
|
+
|
7
|
+
# Do we want to start here?
|
8
|
+
# Should be ok.
|
9
|
+
@thread.next_tick method(:start)
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :processor, :connection
|
13
|
+
|
14
|
+
def start
|
15
|
+
@processor = Orchestrator::Device::Processor.new(self)
|
16
|
+
|
17
|
+
super # Calls on load (allows setting of tls certs)
|
18
|
+
|
19
|
+
@connection = TransportHttp.new(self, @processor)
|
20
|
+
@processor.transport = @connection
|
21
|
+
end
|
22
|
+
|
23
|
+
def stop
|
24
|
+
super
|
25
|
+
@processor.terminate
|
26
|
+
@processor = nil
|
27
|
+
@connection.terminate
|
28
|
+
@connection = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# NOTE:: Same as Device::Manager:-------
|
32
|
+
# TODO:: Need to have a guess about when a device may be off line
|
33
|
+
|
34
|
+
def notify_connected
|
35
|
+
if @instance.respond_to? :connected, true
|
36
|
+
begin
|
37
|
+
@instance.__send__(:connected)
|
38
|
+
rescue => e
|
39
|
+
@logger.print_error(e, 'error in module connected callback')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
update_connected_status(true)
|
44
|
+
end
|
45
|
+
|
46
|
+
def notify_received(data, resolve, command = nil)
|
47
|
+
begin
|
48
|
+
blk = command.nil? ? nil : command[:on_receive]
|
49
|
+
if blk.respond_to? :call
|
50
|
+
blk.call(data, resolve, command)
|
51
|
+
elsif @instance.respond_to? :received, true
|
52
|
+
@instance.__send__(:received, data, resolve, command)
|
53
|
+
else
|
54
|
+
@logger.warn('no received function provided')
|
55
|
+
:abort
|
56
|
+
end
|
57
|
+
rescue => e
|
58
|
+
@logger.print_error(e, 'error in received callback')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Orchestrator
|
2
|
+
module Service
|
3
|
+
module Mixin
|
4
|
+
include ::Orchestrator::Core::Mixin
|
5
|
+
|
6
|
+
def request(method, path, options = {}, &blk)
|
7
|
+
defer = @__config__.thread.defer
|
8
|
+
options[:method] = method
|
9
|
+
options[:path] = path
|
10
|
+
options[:defer] = defer
|
11
|
+
options[:max_waits] = 0 # HTTP will only ever respond to a request
|
12
|
+
options[:on_receive] = blk if blk # on command success
|
13
|
+
@__config__.thread.schedule do
|
14
|
+
@__config__.processor.queue_command(options)
|
15
|
+
end
|
16
|
+
defer.promise
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(path, options = {}, &blk)
|
20
|
+
request(:get, path, options, &blk)
|
21
|
+
end
|
22
|
+
|
23
|
+
def post(path, options = {}, &blk)
|
24
|
+
request(:post, path, options, &blk)
|
25
|
+
end
|
26
|
+
|
27
|
+
def put(path, options = {}, &blk)
|
28
|
+
request(:put, path, options, &blk)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(path, options = {}, &blk)
|
32
|
+
request(:delete, path, options, &blk)
|
33
|
+
end
|
34
|
+
|
35
|
+
def config(options)
|
36
|
+
@__config__.thread.schedule do
|
37
|
+
@__config__.processor.config = options
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def defaults(options)
|
42
|
+
@__config__.thread.schedule do
|
43
|
+
@__config__.processor.send_options(options)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def clear_cookies
|
48
|
+
# TODO::
|
49
|
+
end
|
50
|
+
|
51
|
+
def use_middleware(klass)
|
52
|
+
# TODO::
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
module Orchestrator
|
3
|
+
module Service
|
4
|
+
class TransportHttp
|
5
|
+
def initialize(manager, processor)
|
6
|
+
@manager = manager
|
7
|
+
@settings = @manager.settings
|
8
|
+
@processor = processor
|
9
|
+
|
10
|
+
# Load http endpoint after module has had a chance to update the config
|
11
|
+
config = @processor.config
|
12
|
+
config[:tls] ||= @settings.tls
|
13
|
+
config[:tokenize] = false
|
14
|
+
@server = UV::HttpEndpoint.new @settings.uri, config
|
15
|
+
|
16
|
+
@manager.thread.next_tick do
|
17
|
+
# Call connected (we only need to do this once)
|
18
|
+
# We may never be connected, this is just to signal that we are ready
|
19
|
+
@processor.connected
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def transmit(cmd)
|
24
|
+
return if @terminated
|
25
|
+
|
26
|
+
# TODO:: Support multiple simultaneous requests (multiple servers)
|
27
|
+
|
28
|
+
@server.request(cmd[:method], cmd).then(
|
29
|
+
proc { |result|
|
30
|
+
# Make sure the request information is always available
|
31
|
+
result[:request] = cmd
|
32
|
+
@processor.buffer(result)
|
33
|
+
nil
|
34
|
+
},
|
35
|
+
proc { |failure|
|
36
|
+
# Fail fast (no point waiting for the timeout)
|
37
|
+
if @processor.queue.waiting #== cmd
|
38
|
+
@processor.__send__(:resp_failure, :failed)
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO:: Log failure with more detail
|
42
|
+
nil
|
43
|
+
}
|
44
|
+
)
|
45
|
+
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def terminate
|
50
|
+
@terminated = true
|
51
|
+
@server.close_connection(:after_writing)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
|
4
|
+
module Orchestrator
|
5
|
+
Subscription = Struct.new(:sys_name, :sys_id, :mod_name, :mod_id, :index, :status, :callback, :on_thread) do
|
6
|
+
def initialize(*args)
|
7
|
+
super(*args)
|
8
|
+
|
9
|
+
@do_callback = method(:do_callback)
|
10
|
+
end
|
11
|
+
|
12
|
+
def notify(update)
|
13
|
+
if update != @last_update
|
14
|
+
@last_update = update
|
15
|
+
on_thread.schedule @do_callback
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def value
|
20
|
+
@last_update
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
|
27
|
+
# This is always called on the subscribing thread
|
28
|
+
def do_callback
|
29
|
+
callback.call(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Status
|
34
|
+
def initialize(thread)
|
35
|
+
@thread = thread
|
36
|
+
@controller = ::Orchestrator::Control.instance
|
37
|
+
|
38
|
+
@find_subscription = method(:find_subscription)
|
39
|
+
|
40
|
+
# {:mod_id => {status => Subscriptions}}
|
41
|
+
@subscriptions = {}
|
42
|
+
# {:system_id => Subscriptions}
|
43
|
+
@systems = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
attr_reader :thread
|
48
|
+
|
49
|
+
|
50
|
+
# Subscribes to updates from a system module
|
51
|
+
# Modules do not have to exist and updates will be triggered as soon as they are
|
52
|
+
def subscribe(opt) # sys_name, mod_name, index, status, callback, on_thread
|
53
|
+
if opt[:sys_name] && !opt[:sys_id]
|
54
|
+
@thread.work(proc {
|
55
|
+
id = ::Orchestrator::ControlSystem.bucket.get("sysname-#{sys_name}")
|
56
|
+
opt[:sys_id] = id
|
57
|
+
|
58
|
+
# Grabbing system here as thread-safe and has the potential to block
|
59
|
+
::Orchestrator::System.get(id)
|
60
|
+
}).then(proc { |sys|
|
61
|
+
mod = sys.get(opt[:mod_name], opt[:index] - 1)
|
62
|
+
if mod
|
63
|
+
opt[:mod_id] = mod.settings.id.to_sym
|
64
|
+
opt[:mod] = mod
|
65
|
+
end
|
66
|
+
|
67
|
+
do_subscribe(opt)
|
68
|
+
})
|
69
|
+
else
|
70
|
+
do_subscribe(opt)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Removes subscription callback from the lookup
|
75
|
+
def unsubscribe(sub)
|
76
|
+
if sub.is_a? ::Libuv::Q::Promise
|
77
|
+
sub.then @find_subscription
|
78
|
+
else
|
79
|
+
find_subscription(sub)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Triggers an update to be sent to listening callbacks
|
84
|
+
def update(mod_id, status, value)
|
85
|
+
mod = @subscriptions[mod_id]
|
86
|
+
if mod
|
87
|
+
subscribed = mod[status]
|
88
|
+
if subscribed
|
89
|
+
subscribed.each do |subscription|
|
90
|
+
begin
|
91
|
+
subscription.notify(value)
|
92
|
+
rescue => e
|
93
|
+
@controller.log_unhandled_exception(e)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# TODO:: we also need the system class to contact each of the threads
|
101
|
+
def reloaded_system(sys_id)
|
102
|
+
subscriptions = @systems[sys_id]
|
103
|
+
if subscriptions
|
104
|
+
sys = ::Orchestrator::System.get(@system)
|
105
|
+
|
106
|
+
subscriptions.each do |sub|
|
107
|
+
old_id = sub.mod_id
|
108
|
+
|
109
|
+
# re-index the subscription
|
110
|
+
mod = sys.get(sub.mod_name, sub.index - 1)
|
111
|
+
sub.mod_id = mod ? mod.settings.id.to_sym : nil
|
112
|
+
|
113
|
+
# Check for changes (order, removal, replacement)
|
114
|
+
if old_id != sub.mod_id
|
115
|
+
@subscriptions[old_id][sub.status].delete(sub)
|
116
|
+
|
117
|
+
# Update to the new module
|
118
|
+
if sub.mod_id
|
119
|
+
@subscriptions[sub.mod_id] ||= {}
|
120
|
+
@subscriptions[sub.mod_id][sub.status] ||= Set.new
|
121
|
+
@subscriptions[sub.mod_id][sub.status].add(sub)
|
122
|
+
|
123
|
+
# Check for existing status to send to subscriber
|
124
|
+
value = mod.status[sub.status]
|
125
|
+
sub.notify(value) unless value.nil?
|
126
|
+
end
|
127
|
+
|
128
|
+
# Perform any required cleanup
|
129
|
+
if @subscriptions[old_id][sub.status].empty?
|
130
|
+
@subscriptions[old_id].delete(sub.status)
|
131
|
+
if @subscriptions[old_id].empty?
|
132
|
+
@subscriptions.delete(old_id)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# NOTE:: Only to be called from subscription thread
|
142
|
+
def exec_unsubscribe(sub)
|
143
|
+
# Update the system lookup if a system was specified
|
144
|
+
if sub.sys_id
|
145
|
+
subscriptions = @systems[sub.sys_id]
|
146
|
+
if subscriptions
|
147
|
+
subscriptions.delete(sub)
|
148
|
+
|
149
|
+
if subscriptions.empty?
|
150
|
+
@systems.delete(sub.sys_id)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Update the module lookup
|
156
|
+
statuses = @subscriptions[sub.mod_id]
|
157
|
+
if statuses
|
158
|
+
subscriptions = statuses[sub.status]
|
159
|
+
if subscriptions
|
160
|
+
subscriptions.delete(sub)
|
161
|
+
|
162
|
+
if subscriptions.empty?
|
163
|
+
statuses.delete(sub.status)
|
164
|
+
|
165
|
+
if statuses.empty?
|
166
|
+
@subscriptions.delete(sub.mod_id)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
protected
|
175
|
+
|
176
|
+
|
177
|
+
def do_subscribe(opt)
|
178
|
+
# Build the subscription object (as loosely coupled as we can)
|
179
|
+
sub = Subscription.new(opt[:sys_name], opt[:sys_id], opt[:mod_name], opt[:mod_id], opt[:index], opt[:status], opt[:callback], opt[:on_thread])
|
180
|
+
|
181
|
+
if sub.sys_id
|
182
|
+
@systems[sub.sys_id] ||= Set.new
|
183
|
+
@systems[sub.sys_id].add(sub)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Now if the module is added later we'll still receive updates
|
187
|
+
# and also support direct module status bindings
|
188
|
+
if sub.mod_id
|
189
|
+
@subscriptions[sub.mod_id] ||= {}
|
190
|
+
@subscriptions[sub.mod_id][sub.status] ||= Set.new
|
191
|
+
@subscriptions[sub.mod_id][sub.status].add(sub)
|
192
|
+
|
193
|
+
# Check for existing status to send to subscriber
|
194
|
+
value = opt[:mod].status[sub.status]
|
195
|
+
sub.notify(value) unless value.nil?
|
196
|
+
end
|
197
|
+
|
198
|
+
# return the subscription
|
199
|
+
sub
|
200
|
+
end
|
201
|
+
|
202
|
+
def find_subscription(sub)
|
203
|
+
# Find module thread
|
204
|
+
if sub.mod_id
|
205
|
+
manager = @controller.loaded?(sub.mod_id)
|
206
|
+
if manager
|
207
|
+
thread = manager.thread
|
208
|
+
thread.schedule do
|
209
|
+
thread.observer.exec_unsubscribe(sub)
|
210
|
+
end
|
211
|
+
else
|
212
|
+
# Should be in our schedule
|
213
|
+
exec_unsubscribe(sub)
|
214
|
+
end
|
215
|
+
else
|
216
|
+
exec_unsubscribe(sub)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
module Libuv
|
223
|
+
class Loop
|
224
|
+
def observer
|
225
|
+
@observer ||= ::Orchestrator::Status.new(@loop)
|
226
|
+
@observer
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|