orchestrator 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/orchestrator/api/dependencies_controller.rb +2 -1
  3. data/app/controllers/orchestrator/api/logs_controller.rb +37 -0
  4. data/app/controllers/orchestrator/api/systems_controller.rb +14 -12
  5. data/app/controllers/orchestrator/api/users_controller.rb +76 -0
  6. data/app/controllers/orchestrator/api/zones_controller.rb +2 -1
  7. data/app/controllers/orchestrator/api_controller.rb +2 -1
  8. data/app/controllers/orchestrator/base.rb +24 -7
  9. data/app/models/orchestrator/access_log.rb +10 -4
  10. data/app/models/orchestrator/edge_control.rb +25 -0
  11. data/app/models/user.rb +8 -0
  12. data/config/routes.rb +4 -0
  13. data/lib/orchestrator/control.rb +24 -3
  14. data/lib/orchestrator/core/mixin.rb +10 -4
  15. data/lib/orchestrator/core/module_manager.rb +3 -3
  16. data/lib/orchestrator/core/request_proxy.rb +24 -2
  17. data/lib/orchestrator/core/requests_proxy.rb +57 -3
  18. data/lib/orchestrator/core/system_proxy.rb +12 -6
  19. data/lib/orchestrator/device/processor.rb +7 -0
  20. data/lib/orchestrator/engine.rb +1 -0
  21. data/lib/orchestrator/logger.rb +2 -1
  22. data/lib/orchestrator/logic/manager.rb +2 -2
  23. data/lib/orchestrator/logic/mixin.rb +1 -1
  24. data/lib/orchestrator/remote/edge.rb +30 -0
  25. data/lib/orchestrator/remote/master.rb +150 -0
  26. data/lib/orchestrator/remote/proxy.rb +24 -0
  27. data/lib/orchestrator/status.rb +39 -4
  28. data/lib/orchestrator/system.rb +8 -0
  29. data/lib/orchestrator/utilities/constants.rb +4 -2
  30. data/lib/orchestrator/utilities/transcoder.rb +6 -2
  31. data/lib/orchestrator/version.rb +1 -1
  32. data/lib/orchestrator/websocket_manager.rb +121 -31
  33. metadata +10 -3
@@ -45,11 +45,17 @@ module Orchestrator
45
45
 
46
46
 
47
47
  class RequestProxy
48
- def initialize(thread, mod)
48
+ def initialize(thread, mod, user = nil)
49
49
  @mod = mod
50
50
  @thread = thread
51
+ @user = user
52
+ @trace = []
51
53
  end
52
54
 
55
+
56
+ attr_reader :trace
57
+
58
+
53
59
  # Simplify access to status variables as they are thread safe
54
60
  def [](name)
55
61
  @mod.instance[name]
@@ -77,6 +83,11 @@ module Orchestrator
77
83
  end
78
84
  end
79
85
 
86
+ # Looks up the arity of a method
87
+ def arity(method)
88
+ @mod.instance.method(method.to_sym).arity
89
+ end
90
+
80
91
  # All other method calls are wrapped in a promise
81
92
  def method_missing(name, *args, &block)
82
93
  defer = @thread.defer
@@ -90,14 +101,25 @@ module Orchestrator
90
101
  defer.reject(err)
91
102
  @mod.logger.warn(err.message)
92
103
  else
104
+ @trace = caller
105
+
93
106
  @mod.thread.schedule do
107
+ # Keep track of previous in case of recursion
108
+ previous = nil
94
109
  begin
110
+ if @user
111
+ previous = @mod.current_user
112
+ @mod.current_user = @user
113
+ end
114
+
95
115
  defer.resolve(
96
116
  @mod.instance.public_send(name, *args, &block)
97
117
  )
98
118
  rescue => e
99
- @mod.logger.print_error(e)
119
+ @mod.logger.print_error(e, '', @trace)
100
120
  defer.reject(e)
121
+ ensure
122
+ @mod.current_user = previous if @user
101
123
  end
102
124
  end
103
125
  end
@@ -1,22 +1,65 @@
1
+ require 'forwardable'
2
+
1
3
  module Orchestrator
2
4
  module Core
3
5
  class RequestsProxy
4
- def initialize(thread, modules)
6
+ extend Forwardable
7
+
8
+
9
+ def initialize(thread, modules, user = nil)
5
10
  if modules.nil?
6
11
  @modules = []
7
12
  else
8
13
  @modules = modules.is_a?(Array) ? modules : [modules]
9
14
  end
10
15
  @thread = thread
16
+ @user = user
17
+ @trace = []
18
+ end
19
+
20
+
21
+ attr_reader :trace
22
+
23
+
24
+ # Provide Enumerable support
25
+ def each
26
+ return enum_for(:each) unless block_given?
27
+
28
+ @modules.each do |mod|
29
+ yield RequestProxy.new(@thread, mod, @user)
30
+ end
11
31
  end
12
32
 
33
+ # Provide some helper methods
34
+ def_delegators :@modules, :count, :length, :empty?, :each_index
35
+
36
+ def last
37
+ mod = @modules.last
38
+ return nil unless mod
39
+ return RequestProxy.new(@thread, mod, @user)
40
+ end
41
+
42
+ def first
43
+ mod = @modules.first
44
+ return nil unless mod
45
+ return RequestProxy.new(@thread, mod, @user)
46
+ end
47
+
48
+ def [](index)
49
+ mod = @modules[index]
50
+ return nil unless mod
51
+ return RequestProxy.new(@thread, mod, @user)
52
+ end
53
+ alias_method :at, :[]
54
+
13
55
  # Returns true if there is no object to proxy
56
+ # Allows RequestProxy and RequestsProxy to be used interchangably
14
57
  #
15
58
  # @return [true|false]
16
59
  def nil?
17
60
  @modules.empty?
18
61
  end
19
- alias_method :empty?, :nil?
62
+
20
63
 
21
64
  def method_missing(name, *args, &block)
22
65
  if ::Orchestrator::Core::PROTECTED[name]
@@ -24,16 +67,27 @@ module Orchestrator
24
67
  ::Libuv::Q.reject(@thread, err)
25
68
  # TODO:: log warning err.message
26
69
  else
70
+ @trace = caller
71
+
27
72
  promises = @modules.map do |mod|
28
73
  defer = mod.thread.defer
29
74
  mod.thread.schedule do
75
+ # Keep track of previous in case of recursion
76
+ previous = nil
30
77
  begin
78
+ if @user
79
+ previous = mod.current_user
80
+ mod.current_user = @user
81
+ end
82
+
31
83
  defer.resolve(
32
84
  mod.instance.public_send(name, *args, &block)
33
85
  )
34
86
  rescue => e
35
- mod.logger.print_error(e)
87
+ mod.logger.print_error(e, '', @trace)
36
88
  defer.reject(e)
89
+ ensure
90
+ mod.current_user = previous if @user
37
91
  end
38
92
  end
39
93
  defer.promise
@@ -4,10 +4,11 @@ require 'set'
4
4
  module Orchestrator
5
5
  module Core
6
6
  class SystemProxy
7
- def initialize(thread, sys_id, origin = nil)
7
+ def initialize(thread, sys_id, origin = nil, user = nil)
8
8
  @system = sys_id.to_sym
9
9
  @thread = thread
10
10
  @origin = origin # This is the module that requested the proxy
11
+ @user = user
11
12
  end
12
13
 
13
14
  # Alias for get
@@ -24,7 +25,7 @@ module Orchestrator
24
25
  index -= 1 # Get the real index
25
26
  name = mod.to_sym
26
27
 
27
- RequestProxy.new(@thread, system.get(name, index))
28
+ RequestProxy.new(@thread, system.get(name, index), @user)
28
29
  end
29
30
 
30
31
  # Checks for the existence of a particular module
@@ -44,8 +45,7 @@ module Orchestrator
44
45
  # @param module [String, Symbol] the name of the module in the system
45
46
  # @return [::Orchestrator::Core::RequestsProxy] proxies requests to multiple modules
46
47
  def all(mod)
47
- name = mod.to_sym
48
- RequestsProxy.new(@thread, system.all(name))
48
+ RequestsProxy.new(@thread, system.all(mod.to_sym), @user)
49
49
  end
50
50
 
51
51
  # Iterates over the modules in the system. Can also specify module types.
@@ -67,8 +67,7 @@ module Orchestrator
67
67
  # @param module [String, Symbol] the name of the module in the system
68
68
  # @return [Integer] the number of modules with a shared name
69
69
  def count(mod)
70
- name = mod.to_sym
71
- system.count(name)
70
+ system.count(mod.to_sym)
72
71
  end
73
72
 
74
73
  # Returns a list of all the module names in the system
@@ -85,6 +84,13 @@ module Orchestrator
85
84
  system.config.name
86
85
  end
87
86
 
87
+ # Returns the system id as defined in the database
88
+ #
89
+ # @return [Symbol] the id of the system
90
+ def id
91
+ @system
92
+ end
93
+
88
94
  # Used to be notified when an update to a status value occurs
89
95
  #
90
96
  # @param module [String, Symbol] the name of the module in the system
@@ -34,6 +34,7 @@ module Orchestrator
34
34
  # Other options include:
35
35
  # * emit callback to occur once command complete (may be discarded if a named command)
36
36
  # * on_receive (alternative to received function)
37
+ # * clear_queue (clear further commands once this has run)
37
38
  }
38
39
 
39
40
  CONFIG_DEFAULTS = {
@@ -425,6 +426,12 @@ module Orchestrator
425
426
  command[:defer].resolve(:no_wait)
426
427
  call_emit command # the command has been sent
427
428
  end
429
+
430
+ # Useful for emergency stops etc
431
+ if command[:clear_queue]
432
+ @queue.cancel_all("Command #{command[:name]} cleared the queue")
433
+ end
434
+
428
435
  nil # ensure promise chain is not propagated
429
436
  end
430
437
 
@@ -36,6 +36,7 @@ module Orchestrator
36
36
  # Discover the possible module location paths after initialization is complete
37
37
  #
38
38
  config.after_initialize do |app|
39
+ require File.expand_path(File.join(File.expand_path("../", __FILE__), '../../app/models/user'))
39
40
 
40
41
  ActiveSupport::Dependencies.autoload_paths.each do |path|
41
42
  Pathname.new(path).ascend do |v|
@@ -87,9 +87,10 @@ module Orchestrator
87
87
  end
88
88
  end
89
89
 
90
- def print_error(e, msg = '')
90
+ def print_error(e, msg = '', trace = nil)
91
91
  msg << "\n#{e.message}"
92
92
  msg << "\n#{e.backtrace.join("\n")}" if e.respond_to?(:backtrace) && e.backtrace
93
+ msg << "\nCaller backtrace:\n#{trace.join("\n")}" if trace
93
94
  error(msg)
94
95
  end
95
96
 
@@ -10,8 +10,8 @@ module Orchestrator
10
10
  end
11
11
 
12
12
  # Access to other modules in the same control system
13
- def system
14
- @system ||= ::Orchestrator::Core::SystemProxy.new(@thread, @settings.control_system_id)
13
+ def system(user = nil)
14
+ ::Orchestrator::Core::SystemProxy.new(@thread, @settings.control_system_id, nil, user)
15
15
  end
16
16
  end
17
17
  end
@@ -4,7 +4,7 @@ module Orchestrator
4
4
  include ::Orchestrator::Core::Mixin
5
5
 
6
6
  def system
7
- @__config__.system
7
+ @__config__.system(current_user)
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,30 @@
1
+ require 'set'
2
+
3
+
4
+ module Orchestrator
5
+
6
+ class Edge
7
+ def initialize(edge_ip, thread)
8
+ @thread = thread
9
+ @ip = edge_ip
10
+
11
+ reconnect
12
+ end
13
+
14
+
15
+ attr_reader :thread
16
+
17
+
18
+ def exec()
19
+
20
+ end
21
+
22
+
23
+ protected
24
+
25
+
26
+ def reconnect
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,150 @@
1
+ require 'set'
2
+ require 'json'
3
+
4
+
5
+ module Orchestrator
6
+
7
+ class Master
8
+ def initialize(thread)
9
+ @thread = thread
10
+
11
+ @accept_connection = method :accept_connection
12
+ @new_connection = method :new_connection
13
+ @bind_error = method :bind_error
14
+
15
+ @shutdown = true
16
+ @edge_nodes = ::ThreadSafe::Cache.new # id => connection
17
+ @requests = {} # req_id => defer
18
+ @req_map = {} # connection => ::Set.new (req_id)
19
+
20
+ @signal_bind = @thread.async method(:bind_actual)
21
+ @signal_unbind = @thread.async method(:unbind_actual)
22
+
23
+ @request_id = 0
24
+ end
25
+
26
+
27
+ attr_reader :thread
28
+
29
+
30
+ # ping
31
+ # pong
32
+ # exec
33
+ # bind
34
+ # unbind
35
+ # notify
36
+ # status
37
+ # success
38
+ # failure
39
+
40
+
41
+ def request(edge_id, details)
42
+ defer = @thread.defer
43
+
44
+ # Lookup node
45
+ connection = online? id
46
+ if connection
47
+ @thread.schedule do
48
+ if connection.connected
49
+ @request_id += 1
50
+ @requests[@request_id] = defer
51
+ @req_map[connection] ||= ::Set.new
52
+ @req_map[connection] << @request_id
53
+
54
+ # Send the request
55
+ connection.write(::JSON.fast_generate({
56
+ id: @request_id,
57
+
58
+ })).catch do |reason|
59
+ on_failure(defer, edge_id, details)
60
+ end
61
+ else
62
+ on_failure(defer, edge_id, details)
63
+ end
64
+ end
65
+ else
66
+ on_failure(defer, edge_id, details)
67
+ end
68
+
69
+ defer.promise
70
+ end
71
+
72
+ def online?(id)
73
+ edge = @edge_nodes[id]
74
+ edge && edge.connected ? edge : false
75
+ end
76
+
77
+ def unbind
78
+ @signal_unbind.call
79
+ end
80
+
81
+ def bind
82
+ @signal_bind.call
83
+ end
84
+
85
+
86
+ protected
87
+
88
+
89
+ def on_failure(defer, edge_id, details)
90
+ # Failed...
91
+ # Are we loading this device locally or remotely?
92
+ # Do we wait a small amount of time before trying again?
93
+ # When should we fail the request?
94
+ end
95
+
96
+
97
+ # These are async methods.. They could be called more than once
98
+ def unbind_actual(*args)
99
+ return if @shutdown
100
+ @shutdown = true
101
+
102
+ @tcp.close unless @tcp.nil?
103
+ @tcp = nil
104
+ end
105
+
106
+ def bind_actual(*args)
107
+ return unless @shutdown
108
+ @shutdown = false
109
+
110
+ # Bind the socket
111
+ @tcp = @thread.tcp
112
+ @tcp.bind '0.0.0.0', 17838, @new_connection
113
+ @tcp.listen 100 # smallish backlog is all we need
114
+
115
+ # Delegate errors
116
+ @tcp.catch @bind_error
117
+ @tcp
118
+ end
119
+
120
+
121
+ # There is a new connection pending. We accept it
122
+ def new_connection(server)
123
+ server.accept @accept_connection
124
+ end
125
+
126
+ # Once the connection is accepted we disable Nagles Algorithm
127
+ # This improves performance as we are using vectored or scatter/gather IO
128
+ # Then the spider delegates to the gazelle loops
129
+ def accept_connection(client)
130
+ client.enable_nodelay
131
+ # TODO:: auth client and then signal the interested parties
132
+ end
133
+
134
+ # Called when binding is closed due to an error
135
+ def bind_error(err)
136
+ return if @shutdown
137
+
138
+ # TODO:: log the error
139
+
140
+ # Attempt to recover!
141
+ @thread.scheduler.in(1000) do
142
+ bind
143
+ end
144
+ end
145
+
146
+ def process_request(defer, node, request)
147
+
148
+ end
149
+ end
150
+ end