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,35 @@
|
|
1
|
+
|
2
|
+
module Orchestrator
|
3
|
+
class AccessLog < Couchbase::Model
|
4
|
+
design_document :alog
|
5
|
+
include ::CouchbaseId::Generator
|
6
|
+
|
7
|
+
|
8
|
+
TTL = Rails.env.production? ? 2.weeks.to_i : 120
|
9
|
+
|
10
|
+
|
11
|
+
belongs_to :user, class_name: "::User"
|
12
|
+
attribute :systems, default: lambda { [] }
|
13
|
+
|
14
|
+
attribute :persisted, default: false
|
15
|
+
attribute :suspected, default: false
|
16
|
+
attribute :notes
|
17
|
+
|
18
|
+
attribute :created_at
|
19
|
+
attribute :ended_at, default: lambda { Time.now.to_i }
|
20
|
+
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
super
|
24
|
+
self.created_at = Time.now.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
if self.persisted
|
29
|
+
super
|
30
|
+
else
|
31
|
+
super(ttl: TTL)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'addressable/uri'
|
3
|
+
|
4
|
+
|
5
|
+
module Orchestrator
|
6
|
+
class ControlSystem < Couchbase::Model
|
7
|
+
design_document :sys
|
8
|
+
include ::CouchbaseId::Generator
|
9
|
+
|
10
|
+
# Allows us to lookup systems by names
|
11
|
+
before_save :update_name
|
12
|
+
after_save :expire_cache
|
13
|
+
|
14
|
+
before_delete :cleanup_modules
|
15
|
+
after_delete :expire_cache
|
16
|
+
|
17
|
+
|
18
|
+
attribute :name
|
19
|
+
define_attribute_methods :name # dirty attributes for name!
|
20
|
+
|
21
|
+
attribute :description
|
22
|
+
|
23
|
+
attribute :zones, default: lambda { [] }
|
24
|
+
attribute :modules, default: lambda { [] }
|
25
|
+
attribute :settings, default: lambda { {} }
|
26
|
+
|
27
|
+
attribute :created_at, default: lambda { Time.now.to_i }
|
28
|
+
|
29
|
+
# Provide a field for simplifying support
|
30
|
+
attribute :support_url
|
31
|
+
|
32
|
+
|
33
|
+
def self.find_by_name(name)
|
34
|
+
id = ControlSystem.bucket.get("sysname-#{self.name.downcase}", {quiet: true})
|
35
|
+
ControlSystem.find_by_id(id) if id
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def name=(new_name)
|
40
|
+
new_name.strip!
|
41
|
+
write_attribute(:name, new_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def expire_cache(noUpdate = nil)
|
45
|
+
::Orchestrator::System.expire(self.id || @old_id)
|
46
|
+
ctrl = ::Orchestrator::Control.instance
|
47
|
+
|
48
|
+
# If not deleted and control is running
|
49
|
+
# then we want to trigger updates on the logic modules
|
50
|
+
if !@old_id && noUpdate.nil? && ctrl.ready
|
51
|
+
(::Orchestrator::Module.find_by_id(self.modules) || []).each do |mod|
|
52
|
+
if mod.control_system_id
|
53
|
+
manager = ctrl.loaded? mod.id
|
54
|
+
manager.reloaded(mod) if manager
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def self.using_module(mod_id)
|
62
|
+
by_modules({key: mod_id, stale: false})
|
63
|
+
end
|
64
|
+
view :by_modules
|
65
|
+
|
66
|
+
def self.in_zone(zone_id)
|
67
|
+
by_zones({key: zone_id, stale: false})
|
68
|
+
end
|
69
|
+
view :by_zones
|
70
|
+
|
71
|
+
|
72
|
+
# Methods for obtaining the modules and zones as objects
|
73
|
+
def module_data
|
74
|
+
(::Orchestrator::Module.find_by_id(modules) || []).collect do |mod|
|
75
|
+
mod.as_json({
|
76
|
+
include: {
|
77
|
+
dependency: {
|
78
|
+
only: [:name, :module_name]
|
79
|
+
}
|
80
|
+
}
|
81
|
+
})
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def zone_data
|
86
|
+
::Orchestrator::Zone.find_by_id(zones) || []
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
|
93
|
+
# Zones and settings are only required for confident coding
|
94
|
+
validates :name, presence: true
|
95
|
+
validates :zones, presence: true
|
96
|
+
|
97
|
+
validate :support_link
|
98
|
+
|
99
|
+
def support_link
|
100
|
+
if self.support_url.nil? || self.support_url.empty?
|
101
|
+
self.support_url = nil
|
102
|
+
else
|
103
|
+
begin
|
104
|
+
url = Addressable::URI.parse(self.support_url)
|
105
|
+
url.scheme && url.host && url
|
106
|
+
rescue
|
107
|
+
errors.add(:support_url, 'is an invalid URI')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
validate :name_unique
|
113
|
+
|
114
|
+
def name_unique
|
115
|
+
return false if self.name.blank?
|
116
|
+
|
117
|
+
result = ControlSystem.bucket.get("sysname-#{name.downcase}", {quiet: true})
|
118
|
+
if result != nil && result != self.id
|
119
|
+
errors.add(:name, 'has already been taken')
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def update_name
|
124
|
+
if self.name_changed?
|
125
|
+
old_name = self.name_was
|
126
|
+
old_name.downcase! if old_name
|
127
|
+
elsif not self.exists?
|
128
|
+
old_name = false
|
129
|
+
else
|
130
|
+
return
|
131
|
+
end
|
132
|
+
|
133
|
+
current_name = self.name.downcase
|
134
|
+
|
135
|
+
if old_name != current_name
|
136
|
+
bucket = ControlSystem.bucket
|
137
|
+
bucket.delete("sysname-#{old_name}", {quiet: true}) if old_name
|
138
|
+
bucket.set("sysname-#{current_name}", self.id)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# 1. Find systems that have each of the modules specified
|
143
|
+
# 2. If this is the last system we remove the modules
|
144
|
+
def cleanup_modules
|
145
|
+
ControlSystem.bucket.delete("sysname-#{self.name.downcase}", {quiet: true})
|
146
|
+
|
147
|
+
self.modules.each do |mod_id|
|
148
|
+
systems = ControlSystem.using_module(mod_id).fetch_all
|
149
|
+
|
150
|
+
if systems.length <= 1
|
151
|
+
# We don't use the model's delete method as it looks up control systems
|
152
|
+
::Orchestrator::Control.instance.unload(mod_id)
|
153
|
+
::Orchestrator::Module.bucket.delete(mod_id, {quiet: true})
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
@old_id = self.id # not sure if required
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
|
4
|
+
module Orchestrator
|
5
|
+
class Dependency < Couchbase::Model
|
6
|
+
design_document :dep
|
7
|
+
include ::CouchbaseId::Generator
|
8
|
+
|
9
|
+
|
10
|
+
after_save :update_modules
|
11
|
+
before_delete :cleanup_modules
|
12
|
+
|
13
|
+
|
14
|
+
ROLES = Set.new([:device, :service, :logic])
|
15
|
+
|
16
|
+
|
17
|
+
attribute :name
|
18
|
+
attribute :role
|
19
|
+
attribute :description
|
20
|
+
attribute :default # default data (port or URI)
|
21
|
+
|
22
|
+
# Override default role accessors
|
23
|
+
def role
|
24
|
+
@role ||= self.attributes[:role].to_sym if self.attributes[:role]
|
25
|
+
end
|
26
|
+
def role=(name)
|
27
|
+
@role = name.to_sym
|
28
|
+
self.attributes[:role] = name
|
29
|
+
end
|
30
|
+
|
31
|
+
attribute :class_name
|
32
|
+
attribute :module_name
|
33
|
+
attribute :settings, default: lambda { {} }
|
34
|
+
|
35
|
+
attribute :created_at, default: lambda { Time.now.to_i }
|
36
|
+
|
37
|
+
|
38
|
+
# Find the modules that rely on this dependency
|
39
|
+
def modules
|
40
|
+
::Orchestrator::Module.dependent_on(self.id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_port=(port)
|
44
|
+
self.role = :device
|
45
|
+
self.default = port
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_uri=(uri)
|
49
|
+
self.role = :service
|
50
|
+
self.default = uri
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
|
57
|
+
# Validations
|
58
|
+
validates :name, presence: true
|
59
|
+
validates :class_name, presence: true
|
60
|
+
validates :module_name, presence: true
|
61
|
+
validate :role_exists
|
62
|
+
|
63
|
+
|
64
|
+
def role_exists
|
65
|
+
if self.role && ROLES.include?(self.role.to_sym)
|
66
|
+
self.role = self.role.to_s
|
67
|
+
else
|
68
|
+
errors.add(:role, 'is not valid')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Delete all the module references relying on this dependency
|
73
|
+
def cleanup_modules
|
74
|
+
modules.each do |mod|
|
75
|
+
mod.delete
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Reload all modules to update their settings
|
80
|
+
def update_modules
|
81
|
+
modules.each do |mod|
|
82
|
+
manager = ctrl.loaded? mod.id
|
83
|
+
manager.reloaded(mod) if manager
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
3
|
+
|
4
|
+
module Orchestrator
|
5
|
+
class Module < Couchbase::Model
|
6
|
+
design_document :mod
|
7
|
+
include ::CouchbaseId::Generator
|
8
|
+
|
9
|
+
|
10
|
+
# The classes / files that this module requires to execute
|
11
|
+
# Defines module type
|
12
|
+
# Requires dependency_id to be set
|
13
|
+
belongs_to :dependency, :class_name => "Orchestrator::Dependency"
|
14
|
+
belongs_to :control_system, :class_name => "Orchestrator::ControlSystem"
|
15
|
+
|
16
|
+
|
17
|
+
# Device module
|
18
|
+
def hostname; ip; end
|
19
|
+
def hostname=(host); ip = host; end
|
20
|
+
attribute :ip
|
21
|
+
attribute :tls
|
22
|
+
attribute :udp
|
23
|
+
attribute :port
|
24
|
+
attribute :makebreak, default: false
|
25
|
+
|
26
|
+
# HTTP Service module
|
27
|
+
attribute :uri
|
28
|
+
|
29
|
+
# Custom module names (in addition to what is defined in the dependency)
|
30
|
+
attribute :custom_name
|
31
|
+
attribute :settings, default: lambda { {} }
|
32
|
+
|
33
|
+
attribute :created_at, default: lambda { Time.now.to_i }
|
34
|
+
attribute :role # cache the dependency role locally for load order
|
35
|
+
|
36
|
+
# Connected state in model so we can filter and search on it
|
37
|
+
attribute :connected, default: true
|
38
|
+
attribute :running, default: false
|
39
|
+
attribute :notes
|
40
|
+
|
41
|
+
|
42
|
+
# helper method for looking up the manager
|
43
|
+
def manager
|
44
|
+
::Orchestrator::Control.instance.loaded? self.id
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Loads all the modules for this node
|
49
|
+
def self.all
|
50
|
+
# ascending order by default (device, service then logic)
|
51
|
+
by_module_type(stale: false)
|
52
|
+
end
|
53
|
+
view :by_module_type
|
54
|
+
|
55
|
+
# Finds all the modules belonging to a particular dependency
|
56
|
+
def self.dependent_on(dep_id)
|
57
|
+
by_dependency({key: dep_id, stale: false})
|
58
|
+
end
|
59
|
+
view :by_dependency
|
60
|
+
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
|
65
|
+
validates :dependency, presence: true
|
66
|
+
validate :configuration
|
67
|
+
|
68
|
+
def configuration
|
69
|
+
return unless dependency
|
70
|
+
case dependency.role
|
71
|
+
when :device
|
72
|
+
self.role = 1
|
73
|
+
self.port = (self.port || dependency.default).to_i
|
74
|
+
|
75
|
+
errors.add(:ip, 'cannot be blank') if self.ip.blank?
|
76
|
+
errors.add(:port, 'is invalid') unless self.port.between?(1, 65535)
|
77
|
+
|
78
|
+
# Ensure tls and upd values are correct
|
79
|
+
# can't have udp + tls
|
80
|
+
self.udp = !!self.udp
|
81
|
+
if self.udp
|
82
|
+
self.tls = false
|
83
|
+
else
|
84
|
+
self.tls = !!self.tls
|
85
|
+
end
|
86
|
+
|
87
|
+
begin
|
88
|
+
url = Addressable::URI.parse("http://#{self.ip}:#{self.port}/")
|
89
|
+
url.scheme && url.host && url
|
90
|
+
rescue
|
91
|
+
errors.add(:ip, 'address / hostname or port are not valid')
|
92
|
+
end
|
93
|
+
when :service
|
94
|
+
self.role = 2
|
95
|
+
|
96
|
+
self.tls = nil
|
97
|
+
self.udp = nil
|
98
|
+
|
99
|
+
begin
|
100
|
+
self.uri ||= dependency.default
|
101
|
+
url = Addressable::URI.parse(self.uri)
|
102
|
+
url.scheme && url.host && url
|
103
|
+
rescue
|
104
|
+
errors.add(:uri, 'is an invalid URI')
|
105
|
+
end
|
106
|
+
else # logic
|
107
|
+
self.connected = true # it is connectionless
|
108
|
+
self.tls = nil
|
109
|
+
self.udp = nil
|
110
|
+
self.role = 3
|
111
|
+
if control_system.nil?
|
112
|
+
errors.add(:control_system, 'must be associated')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
before_delete :unload_module
|
118
|
+
def unload_module
|
119
|
+
::Orchestrator::Control.instance.unload(self.id)
|
120
|
+
# Find all the systems with this module ID and remove it
|
121
|
+
ControlSystem.using_module(self.id).each do |cs|
|
122
|
+
cs.modules.delete(self.id)
|
123
|
+
cs.save
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|