cosmos 5.0.4 → 5.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|