cosmos 5.0.4 → 5.0.5
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 +4 -4
- data/bin/cosmos +182 -41
- data/data/config/plugins.yaml +10 -0
- data/ext/cosmos/ext/cosmos_io/cosmos_io.c +14 -14
- data/ext/cosmos/ext/packet/packet.c +3 -3
- data/ext/cosmos/ext/structure/structure.c +31 -31
- data/lib/cosmos/api/tlm_api.rb +4 -4
- data/lib/cosmos/logs/log_writer.rb +76 -27
- data/lib/cosmos/microservices/interface_microservice.rb +0 -15
- data/lib/cosmos/models/gem_model.rb +7 -1
- data/lib/cosmos/models/interface_model.rb +16 -7
- data/lib/cosmos/models/metadata_model.rb +39 -15
- data/lib/cosmos/models/microservice_model.rb +6 -3
- data/lib/cosmos/models/model.rb +7 -0
- data/lib/cosmos/models/note_model.rb +4 -6
- data/lib/cosmos/models/plugin_model.rb +107 -47
- data/lib/cosmos/models/scope_model.rb +2 -0
- data/lib/cosmos/models/sorted_model.rb +3 -1
- data/lib/cosmos/models/target_model.rb +24 -15
- data/lib/cosmos/models/tool_model.rb +8 -8
- data/lib/cosmos/models/widget_model.rb +10 -10
- data/lib/cosmos/packets/packet.rb +1 -1
- data/lib/cosmos/packets/structure.rb +30 -33
- data/lib/cosmos/script/calendar.rb +13 -2
- data/lib/cosmos/tools/table_manager/table_config.rb +16 -1
- data/lib/cosmos/tools/table_manager/table_manager_core.rb +213 -309
- data/lib/cosmos/topics/config_topic.rb +68 -0
- data/lib/cosmos/version.rb +5 -5
- metadata +7 -8
- data/bin/xtce_converter +0 -92
@@ -32,9 +32,41 @@ module Cosmos
|
|
32
32
|
# @return [true/false] Whether logging is enabled
|
33
33
|
attr_reader :logging_enabled
|
34
34
|
|
35
|
+
# @return cycle_time [Integer] The amount of time in seconds before creating
|
36
|
+
# a new log file. This can be combined with cycle_size but is better used
|
37
|
+
# independently.
|
38
|
+
attr_reader :cycle_time
|
39
|
+
|
40
|
+
# @return cycle_hour [Integer] The time at which to cycle the log. Combined with
|
41
|
+
# cycle_minute to cycle the log daily at the specified time. If nil, the log
|
42
|
+
# will be cycled hourly at the specified cycle_minute.
|
43
|
+
attr_reader :cycle_hour
|
44
|
+
|
45
|
+
# @return cycle_minute [Integer] The time at which to cycle the log. See cycle_hour
|
46
|
+
# for more information.
|
47
|
+
attr_reader :cycle_minute
|
48
|
+
|
49
|
+
# @return [Time] Time that the current log file started
|
50
|
+
attr_reader :start_time
|
51
|
+
|
52
|
+
# @return [Mutex] Instance mutex protecting file
|
53
|
+
attr_reader :mutex
|
54
|
+
|
35
55
|
# The cycle time interval. Cycle times are only checked at this level of
|
36
56
|
# granularity.
|
37
|
-
CYCLE_TIME_INTERVAL =
|
57
|
+
CYCLE_TIME_INTERVAL = 10
|
58
|
+
|
59
|
+
# Mutex protecting class variables
|
60
|
+
@@mutex = Mutex.new
|
61
|
+
|
62
|
+
# Array of instances used to keep track of cycling logs
|
63
|
+
@@instances = []
|
64
|
+
|
65
|
+
# Thread used to cycle logs across all log writers
|
66
|
+
@@cycle_thread = nil
|
67
|
+
|
68
|
+
# Sleeper used to delay cycle thread
|
69
|
+
@@cycle_sleeper = nil
|
38
70
|
|
39
71
|
# @param remote_log_directory [String] The s3 path to store the log files
|
40
72
|
# @param logging_enabled [Boolean] Whether to start with logging enabled
|
@@ -89,11 +121,15 @@ module Cosmos
|
|
89
121
|
# each time we create an entry which we do a LOT!
|
90
122
|
@entry = String.new
|
91
123
|
|
92
|
-
@cycle_thread = nil
|
93
124
|
if @cycle_time or @cycle_hour or @cycle_minute
|
94
|
-
|
95
|
-
|
96
|
-
|
125
|
+
@@mutex.synchronize do
|
126
|
+
@@instances << self
|
127
|
+
|
128
|
+
unless @@cycle_thread
|
129
|
+
@@cycle_thread = Cosmos.safe_thread("Log cycle") do
|
130
|
+
cycle_thread_body()
|
131
|
+
end
|
132
|
+
end
|
97
133
|
end
|
98
134
|
end
|
99
135
|
end
|
@@ -113,10 +149,13 @@ module Cosmos
|
|
113
149
|
# Stop all logging, close the current log file, and kill the logging threads.
|
114
150
|
def shutdown
|
115
151
|
stop()
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
152
|
+
@@mutex.synchronize do
|
153
|
+
@@instances.delete(self)
|
154
|
+
if @@instances.length <= 0
|
155
|
+
@@cycle_sleeper.cancel if @@cycle_sleeper
|
156
|
+
Cosmos.kill_thread(self, @@cycle_thread) if @@cycle_thread
|
157
|
+
@@cycle_thread = nil
|
158
|
+
end
|
120
159
|
end
|
121
160
|
end
|
122
161
|
|
@@ -143,28 +182,38 @@ module Cosmos
|
|
143
182
|
end
|
144
183
|
|
145
184
|
def cycle_thread_body
|
185
|
+
@@cycle_sleeper = Sleeper.new
|
146
186
|
while true
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
187
|
+
start_time = Time.now
|
188
|
+
@@mutex.synchronize do
|
189
|
+
@@instances.each do |instance|
|
190
|
+
# The check against start_time needs to be mutex protected to prevent a packet coming in between the check
|
191
|
+
# and closing the file
|
192
|
+
instance.mutex.synchronize do
|
193
|
+
utc_now = Time.now.utc
|
194
|
+
# Logger.debug("start:#{@start_time.to_f} now:#{utc_now.to_f} cycle:#{@cycle_time} new:#{(utc_now - @start_time) > @cycle_time}")
|
195
|
+
if instance.logging_enabled and
|
196
|
+
(
|
197
|
+
# Cycle based on total time logging
|
198
|
+
(instance.cycle_time and (utc_now - instance.start_time) > instance.cycle_time) or
|
199
|
+
|
200
|
+
# Cycle daily at a specific time
|
201
|
+
(instance.cycle_hour and instance.cycle_minute and utc_now.hour == instance.cycle_hour and utc_now.min == instance.cycle_minute and instance.start_time.yday != utc_now.yday) or
|
202
|
+
|
203
|
+
# Cycle hourly at a specific time
|
204
|
+
(instance.cycle_minute and not instance.cycle_hour and utc_now.min == instance.cycle_minute and instance.start_time.hour != utc_now.hour)
|
205
|
+
)
|
206
|
+
instance.close_file(false)
|
207
|
+
end
|
208
|
+
end
|
164
209
|
end
|
165
210
|
end
|
211
|
+
|
166
212
|
# Only check whether to cycle at a set interval
|
167
|
-
|
213
|
+
run_time = Time.now - start_time
|
214
|
+
sleep_time = CYCLE_TIME_INTERVAL - run_time
|
215
|
+
sleep_time = 0 if sleep_time < 0
|
216
|
+
break if @@cycle_sleeper.sleep(sleep_time)
|
168
217
|
end
|
169
218
|
end
|
170
219
|
|
@@ -83,11 +83,6 @@ module Cosmos
|
|
83
83
|
@interface.write_raw(msg_hash['raw'])
|
84
84
|
next 'SUCCESS'
|
85
85
|
end
|
86
|
-
if msg_hash['inject_tlm']
|
87
|
-
Logger.info "#{@interface.name}: Inject telemetry" if msg_hash['log']
|
88
|
-
@tlm.inject_tlm(msg_hash)
|
89
|
-
next 'SUCCESS'
|
90
|
-
end
|
91
86
|
if msg_hash.key?('log_raw')
|
92
87
|
if msg_hash['log_raw'] == 'true'
|
93
88
|
Logger.info "#{@interface.name}: Enable raw logging"
|
@@ -412,16 +407,6 @@ module Cosmos
|
|
412
407
|
TelemetryTopic.write_packet(packet, scope: @scope)
|
413
408
|
end
|
414
409
|
|
415
|
-
def inject_tlm(hash)
|
416
|
-
packet = System.telemetry.packet(hash['target_name'], hash['packet_name']).clone
|
417
|
-
if hash['item_hash']
|
418
|
-
JSON.parse(hash['item_hash']).each do |item, value|
|
419
|
-
packet.write(item.to_s, value, hash['type'].to_sym)
|
420
|
-
end
|
421
|
-
end
|
422
|
-
handle_packet(packet)
|
423
|
-
end
|
424
|
-
|
425
410
|
def handle_connection_failed(connect_error)
|
426
411
|
@error = connect_error
|
427
412
|
Logger.error "#{@interface.name}: Connection Failed: #{connect_error.formatted(false, false)}"
|
@@ -81,7 +81,13 @@ module Cosmos
|
|
81
81
|
else
|
82
82
|
gem_file_path = get(temp_dir, name_or_path)
|
83
83
|
end
|
84
|
-
|
84
|
+
begin
|
85
|
+
rubygems_url = get_setting('rubygems_url', scope: scope)
|
86
|
+
rescue
|
87
|
+
# If Redis isn't running try the ENV, then simply rubygems.org
|
88
|
+
rubygems_url = ENV['RUBYGEMS_URL']
|
89
|
+
rubygems_url ||= 'https://rubygems.org'
|
90
|
+
end
|
85
91
|
Gem.sources = [rubygems_url] if rubygems_url
|
86
92
|
Gem.done_installing_hooks.clear
|
87
93
|
Gem.install(gem_file_path, "> 0.pre", :build_args => ['--no-document'], :prerelease => true)
|
@@ -237,7 +237,7 @@ module Cosmos
|
|
237
237
|
end
|
238
238
|
|
239
239
|
# Creates a MicroserviceModel to deploy the Interface/Router
|
240
|
-
def deploy(gem_path, variables)
|
240
|
+
def deploy(gem_path, variables, validate_only: false)
|
241
241
|
type = self.class._get_type
|
242
242
|
microservice_name = "#{@scope}__#{type}__#{@name}"
|
243
243
|
microservice = MicroserviceModel.new(
|
@@ -249,9 +249,12 @@ module Cosmos
|
|
249
249
|
needs_dependencies: @needs_dependencies,
|
250
250
|
scope: @scope
|
251
251
|
)
|
252
|
-
|
253
|
-
|
254
|
-
|
252
|
+
unless validate_only
|
253
|
+
microservice.create
|
254
|
+
microservice.deploy(gem_path, variables)
|
255
|
+
ConfigTopic.write({ kind: 'created', type: type.downcase, name: @name, plugin: @plugin }, scope: @scope)
|
256
|
+
Logger.info "Configured #{type.downcase} microservice #{microservice_name}"
|
257
|
+
end
|
255
258
|
microservice
|
256
259
|
end
|
257
260
|
|
@@ -259,9 +262,15 @@ module Cosmos
|
|
259
262
|
# should should trigger the operator to kill the microservice that in turn
|
260
263
|
# will destroy the InterfaceStatusModel when a stop is called.
|
261
264
|
def undeploy
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
+
type = self.class._get_type
|
266
|
+
name = "#{@scope}__#{type}__#{@name}"
|
267
|
+
model = MicroserviceModel.get_model(name: name, scope: @scope)
|
268
|
+
if model
|
269
|
+
model.destroy
|
270
|
+
ConfigTopic.write({ kind: 'deleted', type: type.downcase, name: @name, plugin: @plugin }, scope: @scope)
|
271
|
+
end
|
272
|
+
|
273
|
+
if type == 'INTERFACE'
|
265
274
|
status_model = InterfaceStatusModel.get_model(name: @name, scope: @scope)
|
266
275
|
else
|
267
276
|
status_model = RouterStatusModel.get_model(name: @name, scope: @scope)
|
@@ -30,17 +30,19 @@ module Cosmos
|
|
30
30
|
"#{scope}#{PRIMARY_KEY}"
|
31
31
|
end
|
32
32
|
|
33
|
-
attr_reader :color, :metadata, :type
|
33
|
+
attr_reader :color, :metadata, :constraints, :type
|
34
34
|
|
35
|
-
# @param [Integer] start -
|
35
|
+
# @param [Integer] start - Time metadata is active in seconds from Epoch
|
36
36
|
# @param [String] color - The event color
|
37
|
-
# @param [
|
37
|
+
# @param [Hash] metadata - Hash of metadata values
|
38
|
+
# @param [Hash] constraints - Constraints to apply to the metadata
|
38
39
|
# @param [String] scope - Cosmos scope to track event to
|
39
40
|
def initialize(
|
40
41
|
scope:,
|
41
42
|
start:,
|
42
43
|
color: nil,
|
43
44
|
metadata:,
|
45
|
+
constraints: nil,
|
44
46
|
type: METADATA_TYPE,
|
45
47
|
updated_at: 0
|
46
48
|
)
|
@@ -48,6 +50,7 @@ module Cosmos
|
|
48
50
|
@start = start
|
49
51
|
@color = color
|
50
52
|
@metadata = metadata
|
53
|
+
@constraints = constraints if constraints
|
51
54
|
@type = type # For the as_json, from_json round trip
|
52
55
|
end
|
53
56
|
|
@@ -56,6 +59,7 @@ module Cosmos
|
|
56
59
|
validate_start(update: update)
|
57
60
|
validate_color()
|
58
61
|
validate_metadata()
|
62
|
+
validate_constraints() if @constraints
|
59
63
|
end
|
60
64
|
|
61
65
|
def validate_color()
|
@@ -72,6 +76,26 @@ module Cosmos
|
|
72
76
|
unless @metadata.is_a?(Hash)
|
73
77
|
raise SortedInputError.new "Metadata must be a hash/object: #{@metadata}"
|
74
78
|
end
|
79
|
+
# Convert keys to strings. This isn't quite as efficient as symbols
|
80
|
+
# but we store as JSON which is all strings and it makes comparisons easier.
|
81
|
+
@metadata = @metadata.transform_keys(&:to_s)
|
82
|
+
end
|
83
|
+
|
84
|
+
def validate_constraints()
|
85
|
+
unless @constraints.is_a?(Hash)
|
86
|
+
raise SortedInputError.new "Constraints must be a hash/object: #{@constraints}"
|
87
|
+
end
|
88
|
+
# Convert keys to strings. This isn't quite as efficient as symbols
|
89
|
+
# but we store as JSON which is all strings and it makes comparisons easier.
|
90
|
+
@constraints = @constraints.transform_keys(&:to_s)
|
91
|
+
unless (@constraints.keys - @metadata.keys).empty?
|
92
|
+
raise SortedInputError.new "Constraints keys must be subset of metadata: #{@constraints.keys} subset #{@metadata.keys}"
|
93
|
+
end
|
94
|
+
@constraints.each do |key, constraint|
|
95
|
+
unless constraint.include?(@metadata[key])
|
96
|
+
raise SortedInputError.new "Constraint violation! key:#{key} value:#{@metadata[key]} constraint:#{constraint}"
|
97
|
+
end
|
98
|
+
end
|
75
99
|
end
|
76
100
|
|
77
101
|
# Update the Redis hash at primary_key based on the initial passed start
|
@@ -79,6 +103,7 @@ module Cosmos
|
|
79
103
|
def create(update: false)
|
80
104
|
validate(update: update)
|
81
105
|
@updated_at = Time.now.to_nsec_from_epoch
|
106
|
+
MetadataModel.destroy(scope: @scope, start: update) if update
|
82
107
|
Store.zadd(@primary_key, @start, JSON.generate(as_json()))
|
83
108
|
if update
|
84
109
|
notify(kind: 'updated')
|
@@ -87,29 +112,28 @@ module Cosmos
|
|
87
112
|
end
|
88
113
|
end
|
89
114
|
|
90
|
-
# Update the
|
91
|
-
def update(start
|
92
|
-
|
93
|
-
@
|
94
|
-
@
|
95
|
-
|
115
|
+
# Update the model. All arguments are optional, only those set will be updated.
|
116
|
+
def update(start: nil, color: nil, metadata: nil, constraints: nil)
|
117
|
+
orig_start = @start
|
118
|
+
@start = start if start
|
119
|
+
@color = color if color
|
120
|
+
@metadata = metadata if metadata
|
121
|
+
@constraints = constraints if constraints
|
122
|
+
create(update: orig_start)
|
96
123
|
end
|
97
124
|
|
98
125
|
# @return [Hash] generated from the MetadataModel
|
99
126
|
def as_json
|
100
|
-
|
127
|
+
{
|
101
128
|
'scope' => @scope,
|
102
129
|
'start' => @start,
|
103
130
|
'color' => @color,
|
104
131
|
'metadata' => @metadata,
|
132
|
+
'constraints' => @constraints,
|
105
133
|
'type' => METADATA_TYPE,
|
106
134
|
'updated_at' => @updated_at,
|
107
135
|
}
|
108
136
|
end
|
109
|
-
|
110
|
-
# @return [String] string view of metadata
|
111
|
-
def to_s
|
112
|
-
return "<MetadataModel s: #{@start}, c: #{@color}, m: #{@metadata}>"
|
113
|
-
end
|
137
|
+
alias to_s as_json
|
114
138
|
end
|
115
139
|
end
|
@@ -171,11 +171,10 @@ module Cosmos
|
|
171
171
|
return nil
|
172
172
|
end
|
173
173
|
|
174
|
-
def deploy(gem_path, variables)
|
174
|
+
def deploy(gem_path, variables, validate_only: false)
|
175
175
|
return unless @folder_name
|
176
176
|
|
177
177
|
variables["microservice_name"] = @name
|
178
|
-
rubys3_client = Aws::S3::Client.new
|
179
178
|
start_path = "/microservices/#{@folder_name}/"
|
180
179
|
Dir.glob(gem_path + start_path + "**/*") do |filename|
|
181
180
|
next if filename == '.' or filename == '..' or File.directory?(filename)
|
@@ -188,7 +187,10 @@ module Cosmos
|
|
188
187
|
Cosmos.set_working_dir(File.dirname(filename)) do
|
189
188
|
data = ERB.new(data, trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable?
|
190
189
|
end
|
191
|
-
|
190
|
+
unless validate_only
|
191
|
+
Aws::S3::Client.new.put_object(bucket: 'config', key: key, body: data)
|
192
|
+
ConfigTopic.write({ kind: 'created', type: 'microservice', name: @name, plugin: @plugin }, scope: @scope)
|
193
|
+
end
|
192
194
|
end
|
193
195
|
end
|
194
196
|
|
@@ -198,6 +200,7 @@ module Cosmos
|
|
198
200
|
rubys3_client.list_objects(bucket: 'config', prefix: prefix).contents.each do |object|
|
199
201
|
rubys3_client.delete_object(bucket: 'config', key: object.key)
|
200
202
|
end
|
203
|
+
ConfigTopic.write({ kind: 'deleted', type: 'microservice', name: @name, plugin: @plugin }, scope: @scope)
|
201
204
|
end
|
202
205
|
end
|
203
206
|
end
|
data/lib/cosmos/models/model.rb
CHANGED
@@ -128,6 +128,7 @@ module Cosmos
|
|
128
128
|
@updated_at = kw_args[:updated_at]
|
129
129
|
@plugin = kw_args[:plugin]
|
130
130
|
@scope = kw_args[:scope]
|
131
|
+
@destroyed = false
|
131
132
|
end
|
132
133
|
|
133
134
|
# Update the Redis hash at primary_key and set the field "name"
|
@@ -163,10 +164,16 @@ module Cosmos
|
|
163
164
|
|
164
165
|
# Delete the model from the Store
|
165
166
|
def destroy
|
167
|
+
@destroyed = true
|
166
168
|
undeploy()
|
167
169
|
self.class.store.hdel(@primary_key, @name)
|
168
170
|
end
|
169
171
|
|
172
|
+
# Indicate if destroy has been called
|
173
|
+
def destroyed?
|
174
|
+
@destroyed
|
175
|
+
end
|
176
|
+
|
170
177
|
# @return [Hash] JSON encoding of this model
|
171
178
|
def as_json
|
172
179
|
{ 'name' => @name,
|
@@ -86,6 +86,7 @@ module Cosmos
|
|
86
86
|
def create(update: false)
|
87
87
|
validate(update: update)
|
88
88
|
@updated_at = Time.now.to_nsec_from_epoch
|
89
|
+
NoteModel.destroy(scope: @scope, start: update) if update
|
89
90
|
Store.zadd(@primary_key, @start, JSON.generate(as_json()))
|
90
91
|
if update
|
91
92
|
notify(kind: 'updated')
|
@@ -96,11 +97,12 @@ module Cosmos
|
|
96
97
|
|
97
98
|
# Update the Redis hash at primary_key
|
98
99
|
def update(start:, stop:, color:, description:)
|
100
|
+
orig_start = @start
|
99
101
|
@start = start
|
100
102
|
@stop = stop
|
101
103
|
@color = color
|
102
104
|
@description = description
|
103
|
-
create(update:
|
105
|
+
create(update: orig_start)
|
104
106
|
end
|
105
107
|
|
106
108
|
# @return [Hash] generated from the NoteModel
|
@@ -115,10 +117,6 @@ module Cosmos
|
|
115
117
|
'updated_at' => @updated_at,
|
116
118
|
}
|
117
119
|
end
|
118
|
-
|
119
|
-
# @return [String] string view of NoteModel
|
120
|
-
def to_s
|
121
|
-
return "<NoteModel s: #{@start}, x: #{@stop}, c: #{@color}, d: #{@description}>"
|
122
|
-
end
|
120
|
+
alias to_s as_json
|
123
121
|
end
|
124
122
|
end
|