orchestrator 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|