cosmos 5.0.2 → 5.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/bin/cosmos +183 -42
  3. data/data/config/microservice.yaml +47 -35
  4. data/data/config/plugins.yaml +10 -147
  5. data/data/config/target.yaml +70 -0
  6. data/data/config/tool.yaml +37 -31
  7. data/ext/cosmos/ext/cosmos_io/cosmos_io.c +14 -14
  8. data/ext/cosmos/ext/packet/packet.c +3 -3
  9. data/ext/cosmos/ext/structure/structure.c +31 -31
  10. data/lib/cosmos/api/api.rb +1 -25
  11. data/lib/cosmos/api/cmd_api.rb +17 -6
  12. data/lib/cosmos/api/config_api.rb +10 -4
  13. data/lib/cosmos/api/limits_api.rb +1 -1
  14. data/lib/cosmos/api/settings_api.rb +19 -7
  15. data/lib/cosmos/api/target_api.rb +2 -2
  16. data/lib/cosmos/api/tlm_api.rb +65 -41
  17. data/lib/cosmos/config/config_parser.rb +19 -22
  18. data/lib/cosmos/config/meta_config_parser.rb +1 -1
  19. data/lib/cosmos/conversions/generic_conversion.rb +2 -2
  20. data/lib/cosmos/conversions/polynomial_conversion.rb +5 -8
  21. data/lib/cosmos/conversions/segmented_polynomial_conversion.rb +26 -9
  22. data/lib/cosmos/io/json_drb.rb +5 -1
  23. data/lib/cosmos/logs/log_writer.rb +78 -29
  24. data/lib/cosmos/microservices/cleanup_microservice.rb +28 -29
  25. data/lib/cosmos/microservices/decom_microservice.rb +1 -1
  26. data/lib/cosmos/microservices/interface_microservice.rb +0 -16
  27. data/lib/cosmos/microservices/microservice.rb +3 -3
  28. data/lib/cosmos/microservices/reducer_microservice.rb +12 -10
  29. data/lib/cosmos/models/cvt_model.rb +6 -6
  30. data/lib/cosmos/models/gem_model.rb +9 -3
  31. data/lib/cosmos/models/info_model.rb +1 -1
  32. data/lib/cosmos/models/interface_model.rb +16 -7
  33. data/lib/cosmos/models/interface_status_model.rb +1 -1
  34. data/lib/cosmos/models/metadata_model.rb +69 -219
  35. data/lib/cosmos/models/metric_model.rb +2 -2
  36. data/lib/cosmos/models/microservice_model.rb +7 -4
  37. data/lib/cosmos/models/microservice_status_model.rb +1 -1
  38. data/lib/cosmos/models/model.rb +23 -16
  39. data/lib/cosmos/models/note_model.rb +122 -0
  40. data/lib/cosmos/models/ping_model.rb +2 -1
  41. data/lib/cosmos/models/plugin_model.rb +108 -48
  42. data/lib/cosmos/models/process_status_model.rb +1 -1
  43. data/lib/cosmos/models/scope_model.rb +10 -25
  44. data/lib/cosmos/models/settings_model.rb +55 -0
  45. data/lib/cosmos/models/sorted_model.rb +167 -0
  46. data/lib/cosmos/models/target_model.rb +143 -27
  47. data/lib/cosmos/models/tool_config_model.rb +38 -0
  48. data/lib/cosmos/models/tool_model.rb +9 -9
  49. data/lib/cosmos/models/widget_model.rb +11 -11
  50. data/lib/cosmos/operators/microservice_operator.rb +2 -1
  51. data/lib/cosmos/packets/packet.rb +24 -1
  52. data/lib/cosmos/packets/packet_config.rb +2 -2
  53. data/lib/cosmos/packets/packet_item.rb +57 -0
  54. data/lib/cosmos/packets/packet_item_limits.rb +14 -2
  55. data/lib/cosmos/packets/parsers/packet_item_parser.rb +1 -1
  56. data/lib/cosmos/packets/parsers/packet_parser.rb +1 -1
  57. data/lib/cosmos/packets/parsers/xtce_parser.rb +1 -1
  58. data/lib/cosmos/packets/structure.rb +30 -33
  59. data/lib/cosmos/packets/structure_item.rb +10 -1
  60. data/lib/cosmos/script/api_shared.rb +30 -25
  61. data/lib/cosmos/script/calendar.rb +37 -15
  62. data/lib/cosmos/script/commands.rb +5 -7
  63. data/lib/cosmos/script/script.rb +19 -39
  64. data/lib/cosmos/script/storage.rb +92 -105
  65. data/lib/cosmos/system/system.rb +2 -1
  66. data/lib/cosmos/tools/table_manager/table_config.rb +16 -1
  67. data/lib/cosmos/tools/table_manager/table_item.rb +1 -1
  68. data/lib/cosmos/tools/table_manager/table_manager_core.rb +213 -309
  69. data/lib/cosmos/top_level.rb +5 -1
  70. data/lib/cosmos/topics/autonomic_topic.rb +2 -2
  71. data/lib/cosmos/topics/calendar_topic.rb +1 -1
  72. data/lib/cosmos/topics/command_decom_topic.rb +35 -1
  73. data/lib/cosmos/topics/command_topic.rb +6 -4
  74. data/lib/cosmos/topics/config_topic.rb +68 -0
  75. data/lib/cosmos/topics/interface_topic.rb +8 -8
  76. data/lib/cosmos/topics/limits_event_topic.rb +5 -3
  77. data/lib/cosmos/topics/notifications_topic.rb +1 -1
  78. data/lib/cosmos/topics/router_topic.rb +9 -9
  79. data/lib/cosmos/topics/telemetry_decom_topic.rb +5 -1
  80. data/lib/cosmos/topics/telemetry_topic.rb +1 -1
  81. data/lib/cosmos/topics/timeline_topic.rb +1 -1
  82. data/lib/cosmos/topics/topic.rb +23 -8
  83. data/lib/cosmos/utilities/logger.rb +4 -3
  84. data/lib/cosmos/utilities/metric.rb +32 -26
  85. data/lib/cosmos/utilities/s3.rb +61 -0
  86. data/lib/cosmos/utilities/s3_file_cache.rb +12 -6
  87. data/lib/cosmos/utilities/store.rb +1 -0
  88. data/lib/cosmos/utilities/store_autoload.rb +25 -134
  89. data/lib/cosmos/version.rb +5 -4
  90. data/templates/plugin-template/plugin.gemspec +0 -2
  91. metadata +12 -10
  92. data/bin/xtce_converter +0 -92
  93. data/lib/cosmos/models/narrative_model.rb +0 -280
@@ -0,0 +1,167 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 Ball Aerospace & Technologies Corp.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU Affero General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # This program may also be used under the terms of a commercial or
17
+ # enterprise edition license of COSMOS if purchased from the
18
+ # copyright holder
19
+
20
+ # https://www.rubydoc.info/gems/redis/Redis/Commands/SortedSets
21
+ # https://redis.io/docs/manual/data-types/data-types-tutorial/#sorted-sets
22
+
23
+ require 'cosmos/models/model'
24
+ require 'cosmos/topics/calendar_topic'
25
+
26
+ module Cosmos
27
+ # Put these under the Cosmos module so they are easily accessed in the controller as
28
+ # Cosmos::SortedError vs Cosmos::SortedModel::Error
29
+ class SortedError < StandardError; end
30
+ class SortedInputError < SortedError; end
31
+ class SortedOverlapError < SortedError; end
32
+
33
+ class SortedModel < Model
34
+ SORTED_TYPE = 'sorted'.freeze # To be overriden by base class
35
+ PRIMARY_KEY = '__SORTED'.freeze # To be overriden by base class
36
+
37
+ # MUST be overriden by any subclasses
38
+ def self.pk(scope)
39
+ "#{scope}#{PRIMARY_KEY}"
40
+ end
41
+
42
+ # @return [String|nil] String of the saved json or nil if start not found
43
+ def self.get(start:, scope:)
44
+ result = Store.zrangebyscore(self.pk(scope), start, start)
45
+ return JSON.parse(result[0]) unless result.empty?
46
+ nil
47
+ end
48
+
49
+ # @return [Array<Hash>] Array up to the limit of the models (as Hash objects) stored under the primary key
50
+ def self.all(scope:, limit: 100)
51
+ result = Store.zrevrangebyscore(self.pk(scope), '+inf', '-inf', limit: [0, limit])
52
+ result.map { |item| JSON.parse(item) }
53
+ end
54
+
55
+ # @return [String|nil] json or nil if metadata empty
56
+ def self.get_current_value(scope:)
57
+ start = Time.now.to_i
58
+ array = Store.zrevrangebyscore(self.pk(scope), start, '-inf', limit: [0, 1])
59
+ return nil if array.empty?
60
+ return array[0]
61
+ end
62
+
63
+ # @param start [Integer] Start time to return values (inclusive)
64
+ # @param stop [Integer] Stop time to return values (inclusive)
65
+ # @return [Array|nil] Array up to 100 of this model or empty array
66
+ def self.range(start:, stop:, scope:, limit: 100)
67
+ if start > stop
68
+ raise SortedInputError.new "start: #{start} must be before stop: #{stop}"
69
+ end
70
+ result = Store.zrangebyscore(self.pk(scope), start, stop, limit: [0, limit])
71
+ result.map { |item| JSON.parse(item) }
72
+ end
73
+
74
+ # @return [Integer] count of the members stored under the primary key
75
+ def self.count(scope:)
76
+ Store.zcard(self.pk(scope))
77
+ end
78
+
79
+ # Remove member from a sorted set
80
+ # @return [Integer] count of the members removed, 0 if not found
81
+ def self.destroy(scope:, start:)
82
+ Store.zremrangebyscore(self.pk(scope), start, start)
83
+ end
84
+
85
+ # Remove members from min to max of the sorted set.
86
+ # @return [Integer] count of the members removed
87
+ def self.range_destroy(scope:, start:, stop:)
88
+ Store.zremrangebyscore(self.pk(scope), start, stop)
89
+ end
90
+
91
+ attr_reader :start
92
+
93
+ # @param [Integer] start - start used to store data
94
+ # @param [String] scope - Cosmos scope to track event to
95
+ # @param [Anything] kwargs - Any kwargs to store in the JSON
96
+ def initialize(start:, scope:, type: SORTED_TYPE, **kwargs)
97
+ # Name becomes the start in the base class
98
+ super(self.class.pk(scope), name: start.to_s, scope: scope, **kwargs)
99
+ @type = type # For the as_json, from_json round trip
100
+ @start = start
101
+ end
102
+
103
+ # start MUST be a positive integer
104
+ def validate_start(update: false)
105
+ unless @start.is_a?(Integer)
106
+ raise SortedInputError.new "start must be integer: #{@start}"
107
+ end
108
+ if @start.to_i < 0
109
+ raise SortedInputError.new "start must be positive: #{@start}"
110
+ end
111
+ if !update and self.class.get(start: @start, scope: @scope)
112
+ raise SortedOverlapError.new "duplicate, existing data at #{@start}"
113
+ end
114
+ @start = @start.to_i
115
+ end
116
+
117
+ # Update the Redis hash at primary_key based on the initial passed start
118
+ # The member is set to the JSON generated via calling as_json
119
+ def create(update: false)
120
+ validate_start(update: update)
121
+ @updated_at = Time.now.to_nsec_from_epoch
122
+ SortedModel.destroy(scope: @scope, start: update) if update
123
+ Store.zadd(@primary_key, @start, JSON.generate(as_json()))
124
+ if update
125
+ notify(kind: 'updated')
126
+ else
127
+ notify(kind: 'created')
128
+ end
129
+ end
130
+
131
+ # Update the Redis hash at primary_key
132
+ def update(start:)
133
+ orig_start = @start
134
+ @start = start
135
+ create(update: orig_start)
136
+ end
137
+
138
+ # destroy the activity from the redis database
139
+ def destroy
140
+ self.class.destroy(scope: @scope, start: @start)
141
+ notify(kind: 'deleted')
142
+ end
143
+
144
+ # @return [] update the redis stream / timeline topic that something has changed
145
+ def notify(kind:, extra: nil)
146
+ notification = {
147
+ 'data' => JSON.generate(as_json()),
148
+ 'kind' => kind,
149
+ 'type' => 'calendar',
150
+ }
151
+ notification['extra'] = extra unless extra.nil?
152
+ begin
153
+ CalendarTopic.write_entry(notification, scope: @scope)
154
+ rescue StandardError => e
155
+ raise SortedError.new "Failed to write to stream: #{notification}, #{e}"
156
+ end
157
+ end
158
+
159
+ # @return [Hash] JSON encoding of this model
160
+ def as_json
161
+ { **super(),
162
+ 'start' => @start,
163
+ 'type' => SORTED_TYPE,
164
+ }
165
+ end
166
+ end
167
+ end
@@ -22,6 +22,7 @@ require 'cosmos/models/model'
22
22
  require 'cosmos/models/cvt_model'
23
23
  require 'cosmos/models/microservice_model'
24
24
  require 'cosmos/topics/limits_event_topic'
25
+ require 'cosmos/topics/config_topic'
25
26
  require 'cosmos/system'
26
27
  require 'cosmos/utilities/s3'
27
28
  require 'cosmos/utilities/zip'
@@ -51,12 +52,20 @@ module Cosmos
51
52
  attr_accessor :id
52
53
  attr_accessor :cmd_log_cycle_time
53
54
  attr_accessor :cmd_log_cycle_size
55
+ attr_accessor :cmd_log_retain_time
54
56
  attr_accessor :cmd_decom_log_cycle_time
55
57
  attr_accessor :cmd_decom_log_cycle_size
58
+ attr_accessor :cmd_decom_log_retain_time
56
59
  attr_accessor :tlm_log_cycle_time
57
60
  attr_accessor :tlm_log_cycle_size
61
+ attr_accessor :tlm_log_retain_time
58
62
  attr_accessor :tlm_decom_log_cycle_time
59
63
  attr_accessor :tlm_decom_log_cycle_size
64
+ attr_accessor :tlm_decom_log_retain_time
65
+ attr_accessor :reduced_minute_log_retain_time
66
+ attr_accessor :reduced_hour_log_retain_time
67
+ attr_accessor :reduced_day_log_retain_time
68
+ attr_accessor :cleanup_poll_time
60
69
  attr_accessor :needs_dependencies
61
70
 
62
71
  # NOTE: The following three class methods are used by the ModelController
@@ -97,6 +106,11 @@ module Cosmos
97
106
  result
98
107
  end
99
108
 
109
+ # @return [Array>Hash>] All packet hashes under the target_name
110
+ def self.all_packet_name_descriptions(target_name, type: :TLM, scope:)
111
+ self.packets(target_name, type: type, scope: scope).map! { |hash| hash.slice("packet_name", "description") }
112
+ end
113
+
100
114
  def self.set_packet(target_name, packet_name, packet, type: :TLM, scope:)
101
115
  raise "Unknown type #{type} for #{target_name} #{packet_name}" unless VALID_TYPES.include?(type)
102
116
 
@@ -113,7 +127,6 @@ module Cosmos
113
127
  packet = packet(target_name, packet_name, type: type, scope: scope)
114
128
  item = packet['items'].find { |item| item['name'] == item_name.to_s }
115
129
  raise "Item '#{packet['target_name']} #{packet['packet_name']} #{item_name}' does not exist" unless item
116
-
117
130
  item
118
131
  end
119
132
 
@@ -149,7 +162,7 @@ module Cosmos
149
162
  when 'TARGET'
150
163
  usage = "#{keyword} <TARGET FOLDER NAME> <TARGET NAME>"
151
164
  parser.verify_num_parameters(2, 2, usage)
152
- parser.verify_parameters_underscores(2) # Target name is the 2nd parameter
165
+ parser.verify_parameter_naming(2) # Target name is the 2nd parameter
153
166
  return self.new(name: parameters[1].to_s.upcase, folder_name: parameters[0].to_s.upcase, plugin: plugin, scope: scope)
154
167
  else
155
168
  raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Target: #{keyword} #{parameters.join(" ")}")
@@ -171,20 +184,35 @@ module Cosmos
171
184
  plugin: nil,
172
185
  cmd_log_cycle_time: 600,
173
186
  cmd_log_cycle_size: 50_000_000,
187
+ cmd_log_retain_time: nil,
174
188
  cmd_decom_log_cycle_time: 600,
175
189
  cmd_decom_log_cycle_size: 50_000_000,
190
+ cmd_decom_log_retain_time: nil,
176
191
  tlm_log_cycle_time: 600,
177
192
  tlm_log_cycle_size: 50_000_000,
193
+ tlm_log_retain_time: nil,
178
194
  tlm_decom_log_cycle_time: 600,
179
195
  tlm_decom_log_cycle_size: 50_000_000,
196
+ tlm_decom_log_retain_time: nil,
197
+ reduced_minute_log_retain_time: nil,
198
+ reduced_hour_log_retain_time: nil,
199
+ reduced_day_log_retain_time: nil,
200
+ cleanup_poll_time: 900,
180
201
  needs_dependencies: false,
181
202
  scope:
182
203
  )
183
204
  super("#{scope}__#{PRIMARY_KEY}", name: name, plugin: plugin, updated_at: updated_at,
184
205
  cmd_log_cycle_time: cmd_log_cycle_time, cmd_log_cycle_size: cmd_log_cycle_size,
206
+ cmd_log_retain_time: cmd_log_retain_time,
185
207
  cmd_decom_log_cycle_time: cmd_decom_log_cycle_time, cmd_decom_log_cycle_size: cmd_decom_log_cycle_size,
208
+ cmd_decom_log_retain_time: cmd_decom_log_retain_time,
186
209
  tlm_log_cycle_time: tlm_log_cycle_time, tlm_log_cycle_size: tlm_log_cycle_size,
210
+ tlm_log_retain_time: tlm_log_retain_time,
187
211
  tlm_decom_log_cycle_time: tlm_decom_log_cycle_time, tlm_decom_log_cycle_size: tlm_decom_log_cycle_size,
212
+ tlm_decom_log_retain_time: tlm_decom_log_retain_time,
213
+ reduced_minute_log_retain_time: reduced_minute_log_retain_time,
214
+ reduced_hour_log_retain_time: reduced_hour_log_retain_time, reduced_day_log_retain_time: reduced_day_log_retain_time,
215
+ cleanup_poll_time: cleanup_poll_time, needs_dependencies: needs_dependencies,
188
216
  scope: scope)
189
217
  @folder_name = folder_name
190
218
  @requires = requires
@@ -197,12 +225,20 @@ module Cosmos
197
225
  @id = id
198
226
  @cmd_log_cycle_time = cmd_log_cycle_time
199
227
  @cmd_log_cycle_size = cmd_log_cycle_size
228
+ @cmd_log_retain_time = cmd_log_retain_time
200
229
  @cmd_decom_log_cycle_time = cmd_decom_log_cycle_time
201
230
  @cmd_decom_log_cycle_size = cmd_decom_log_cycle_size
231
+ @cmd_decom_log_retain_time = cmd_decom_log_retain_time
202
232
  @tlm_log_cycle_time = tlm_log_cycle_time
203
233
  @tlm_log_cycle_size = tlm_log_cycle_size
234
+ @tlm_log_retain_time = tlm_log_retain_time
204
235
  @tlm_decom_log_cycle_time = tlm_decom_log_cycle_time
205
236
  @tlm_decom_log_cycle_size = tlm_decom_log_cycle_size
237
+ @tlm_decom_log_retain_time = tlm_decom_log_retain_time
238
+ @reduced_minute_log_retain_time = reduced_minute_log_retain_time
239
+ @reduced_hour_log_retain_time = reduced_hour_log_retain_time
240
+ @reduced_day_log_retain_time = reduced_day_log_retain_time
241
+ @cleanup_poll_time = cleanup_poll_time
206
242
  @needs_dependencies = needs_dependencies
207
243
  end
208
244
 
@@ -222,12 +258,20 @@ module Cosmos
222
258
  'plugin' => @plugin,
223
259
  'cmd_log_cycle_time' => @cmd_log_cycle_time,
224
260
  'cmd_log_cycle_size' => @cmd_log_cycle_size,
261
+ 'cmd_log_retain_time' => @cmd_log_retain_time,
225
262
  'cmd_decom_log_cycle_time' => @cmd_decom_log_cycle_time,
226
263
  'cmd_decom_log_cycle_size' => @cmd_decom_log_cycle_size,
264
+ 'cmd_decom_log_retain_time' => @cmd_decom_log_retain_time,
227
265
  'tlm_log_cycle_time' => @tlm_log_cycle_time,
228
266
  'tlm_log_cycle_size' => @tlm_log_cycle_size,
267
+ 'tlm_log_retain_time' => @tlm_log_retain_time,
229
268
  'tlm_decom_log_cycle_time' => @tlm_decom_log_cycle_time,
230
269
  'tlm_decom_log_cycle_size' => @tlm_decom_log_cycle_size,
270
+ 'tlm_decom_log_retain_time' => @tlm_decom_log_retain_time,
271
+ 'reduced_minute_log_retain_time' => @reduced_minute_log_retain_time,
272
+ 'reduced_hour_log_retain_time' => @reduced_hour_log_retain_time,
273
+ 'reduced_day_log_retain_time' => @reduced_day_log_retain_time,
274
+ 'cleanup_poll_time' => @cleanup_poll_time,
231
275
  'needs_dependencies' => @needs_dependencies,
232
276
  }
233
277
  end
@@ -245,31 +289,79 @@ module Cosmos
245
289
  when 'CMD_LOG_CYCLE_SIZE'
246
290
  parser.verify_num_parameters(1, 1, "#{keyword} <Maximum file size in bytes>")
247
291
  @cmd_log_cycle_size = parameters[0].to_i
292
+ when 'CMD_LOG_RETAIN_TIME'
293
+ parser.verify_num_parameters(1, 1, "#{keyword} <Retention time for cmd log files in seconds - nil = Forever>")
294
+ @cmd_log_retain_time = ConfigParser.handle_nil(parameters[0])
295
+ @cmd_log_retain_time = @cmd_log_retain_time.to_i if @cmd_log_retain_time
248
296
  when 'CMD_DECOM_LOG_CYCLE_TIME'
249
297
  parser.verify_num_parameters(1, 1, "#{keyword} <Maximum time between files in seconds>")
250
298
  @cmd_decom_log_cycle_time = parameters[0].to_i
251
299
  when 'CMD_DECOM_LOG_CYCLE_SIZE'
252
300
  parser.verify_num_parameters(1, 1, "#{keyword} <Maximum file size in bytes>")
253
301
  @cmd_decom_log_cycle_size = parameters[0].to_i
302
+ when 'CMD_DECOM_LOG_RETAIN_TIME'
303
+ parser.verify_num_parameters(1, 1, "#{keyword} <Retention time for cmd decom log files in seconds - nil = Forever>")
304
+ @cmd_decom_log_retain_time = ConfigParser.handle_nil(parameters[0])
305
+ @cmd_decom_log_retain_time = @cmd_decom_log_retain_time.to_i if @cmd_decom_log_retain_time
254
306
  when 'TLM_LOG_CYCLE_TIME'
255
307
  parser.verify_num_parameters(1, 1, "#{keyword} <Maximum time between files in seconds>")
256
308
  @tlm_log_cycle_time = parameters[0].to_i
257
309
  when 'TLM_LOG_CYCLE_SIZE'
258
310
  parser.verify_num_parameters(1, 1, "#{keyword} <Maximum file size in bytes>")
259
311
  @tlm_log_cycle_size = parameters[0].to_i
312
+ when 'TLM_LOG_RETAIN_TIME'
313
+ parser.verify_num_parameters(1, 1, "#{keyword} <Retention time for tlm log files in seconds - nil = Forever>")
314
+ @tlm_log_retain_time = ConfigParser.handle_nil(parameters[0])
315
+ @tlm_log_retain_time = @tlm_log_retain_time.to_i if @tlm_log_retain_time
260
316
  when 'TLM_DECOM_LOG_CYCLE_TIME'
261
317
  parser.verify_num_parameters(1, 1, "#{keyword} <Maximum time between files in seconds>")
262
318
  @tlm_decom_log_cycle_time = parameters[0].to_i
263
319
  when 'TLM_DECOM_LOG_CYCLE_SIZE'
264
320
  parser.verify_num_parameters(1, 1, "#{keyword} <Maximum file size in bytes>")
265
321
  @tlm_decom_log_cycle_size = parameters[0].to_i
322
+ when 'TLM_DECOM_LOG_RETAIN_TIME'
323
+ parser.verify_num_parameters(1, 1, "#{keyword} <Retention time for tlm decom log files in seconds - nil = Forever>")
324
+ @tlm_decom_log_retain_time = ConfigParser.handle_nil(parameters[0])
325
+ @tlm_decom_log_retain_time = @tlm_decom_log_retain_time.to_i if @tlm_decom_log_retain_time
326
+ when 'REDUCED_MINUTE_LOG_RETAIN_TIME'
327
+ parser.verify_num_parameters(1, 1, "#{keyword} <Retention time for reduced minute log files in seconds - nil = Forever>")
328
+ @reduced_minute_log_retain_time = ConfigParser.handle_nil(parameters[0])
329
+ @reduced_minute_log_retain_time = @reduced_minute_log_retain_time.to_i if @reduced_minute_log_retain_time
330
+ when 'REDUCED_HOUR_LOG_RETAIN_TIME'
331
+ parser.verify_num_parameters(1, 1, "#{keyword} <Retention time for reduced hour log files in seconds - nil = Forever>")
332
+ @reduced_hour_log_retain_time = ConfigParser.handle_nil(parameters[0])
333
+ @reduced_hour_log_retain_time = @reduced_hour_log_retain_time.to_i if @reduced_hour_log_retain_time
334
+ when 'REDUCED_DAY_LOG_RETAIN_TIME'
335
+ parser.verify_num_parameters(1, 1, "#{keyword} <Retention time for reduced day log files in seconds - nil = Forever>")
336
+ @reduced_day_log_retain_time = ConfigParser.handle_nil(parameters[0])
337
+ @reduced_day_log_retain_time = @reduced_day_log_retain_time.to_i if @reduced_day_log_retain_time
338
+ when 'LOG_RETAIN_TIME'
339
+ parser.verify_num_parameters(1, 1, "#{keyword} <Retention time for all log files in seconds - nil = Forever>")
340
+ log_retain_time = ConfigParser.handle_nil(parameters[0])
341
+ if log_retain_time
342
+ @cmd_log_retain_time = log_retain_time.to_i
343
+ @cmd_decom_log_retain_time = log_retain_time.to_i
344
+ @tlm_log_retain_time = log_retain_time.to_i
345
+ @tlm_decom_log_retain_time = log_retain_time.to_i
346
+ end
347
+ when 'REDUCED_LOG_RETAIN_TIME'
348
+ parser.verify_num_parameters(1, 1, "#{keyword} <Retention time for all reduced log files in seconds - nil = Forever>")
349
+ reduced_log_retain_time = ConfigParser.handle_nil(parameters[0])
350
+ if reduced_log_retain_time
351
+ @reduced_minute_log_retain_time = reduced_log_retain_time.to_i
352
+ @reduced_hour_log_retain_time = reduced_log_retain_time.to_i
353
+ @reduced_day_log_retain_time = reduced_log_retain_time.to_i
354
+ end
355
+ when 'CLEANUP_POLL_TIME'
356
+ parser.verify_num_parameters(1, 1, "#{keyword} <Cleanup polling period in seconds>")
357
+ @cleanup_poll_time = parameters[0].to_i
266
358
  else
267
359
  raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Target: #{keyword} #{parameters.join(" ")}")
268
360
  end
269
361
  return nil
270
362
  end
271
363
 
272
- def deploy(gem_path, variables)
364
+ def deploy(gem_path, variables, validate_only: false)
273
365
  rubys3_client = Aws::S3::Client.new
274
366
  variables["target_name"] = @name
275
367
  start_path = "/targets/#{@folder_name}/"
@@ -289,7 +381,7 @@ module Cosmos
289
381
  data = File.read(filename, mode: "rb")
290
382
  begin
291
383
  Cosmos.set_working_dir(File.dirname(filename)) do
292
- data = ERB.new(data).result(binding.set_variables(variables)) if data.is_printable? and File.basename(filename)[0] != '_'
384
+ data = ERB.new(data, trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable? and File.basename(filename)[0] != '_'
293
385
  end
294
386
  rescue => error
295
387
  raise "ERB error parsing: #{filename}: #{error.formatted}"
@@ -298,14 +390,23 @@ module Cosmos
298
390
  FileUtils.mkdir_p(File.dirname(local_path))
299
391
  File.open(local_path, 'wb') { |file| file.write(data) }
300
392
  found = true
301
- rubys3_client.put_object(bucket: 'config', key: key, body: data)
393
+ rubys3_client.put_object(bucket: 'config', key: key, body: data) unless validate_only
302
394
  end
303
395
  raise "No target files found at #{target_path}" unless found
304
396
 
305
397
  target_folder = File.join(temp_dir, @name)
306
- build_target_archive(rubys3_client, temp_dir, target_folder)
307
- system = update_store(temp_dir)
308
- deploy_microservices(gem_path, variables, system)
398
+ # Build a System for just this target
399
+ system = System.new([@name], temp_dir)
400
+ if variables["xtce_output"]
401
+ puts "Converting target #{@name} to .xtce files in #{variables["xtce_output"]}/#{@name}"
402
+ system.packet_config.to_xtce(variables["xtce_output"])
403
+ end
404
+ unless validate_only
405
+ build_target_archive(rubys3_client, temp_dir, target_folder)
406
+ system = update_store(system)
407
+ deploy_microservices(gem_path, variables, system)
408
+ ConfigTopic.write({ kind: 'created', type: 'target', name: @name, plugin: @plugin }, scope: @scope)
409
+ end
309
410
  ensure
310
411
  FileUtils.remove_entry(temp_dir) if temp_dir and File.exist?(temp_dir)
311
412
  end
@@ -322,12 +423,12 @@ module Cosmos
322
423
  Store.hdel("#{@scope}__limits_groups", group)
323
424
  end
324
425
  self.class.packets(@name, type: :CMD, scope: @scope).each do |packet|
325
- Store.del("#{@scope}__COMMAND__{#{@name}}__#{packet['packet_name']}")
326
- Store.del("#{@scope}__DECOMCMD__{#{@name}}__#{packet['packet_name']}")
426
+ Topic.del("#{@scope}__COMMAND__{#{@name}}__#{packet['packet_name']}")
427
+ Topic.del("#{@scope}__DECOMCMD__{#{@name}}__#{packet['packet_name']}")
327
428
  end
328
429
  self.class.packets(@name, scope: @scope).each do |packet|
329
- Store.del("#{@scope}__TELEMETRY__{#{@name}}__#{packet['packet_name']}")
330
- Store.del("#{@scope}__DECOM__{#{@name}}__#{packet['packet_name']}")
430
+ Topic.del("#{@scope}__TELEMETRY__{#{@name}}__#{packet['packet_name']}")
431
+ Topic.del("#{@scope}__DECOM__{#{@name}}__#{packet['packet_name']}")
331
432
  CvtModel.del(target_name: @name, packet_name: packet['packet_name'], scope: @scope)
332
433
  LimitsEventTopic.delete(@name, packet['packet_name'], scope: @scope)
333
434
  end
@@ -335,10 +436,11 @@ module Cosmos
335
436
  Store.del("#{@scope}__cosmoscmd__#{@name}")
336
437
 
337
438
  # Note: these match the names of the services in deploy_microservices
338
- %w(DECOM COMMANDLOG DECOMCMDLOG PACKETLOG DECOMLOG REDUCER).each do |type|
439
+ %w(DECOM COMMANDLOG DECOMCMDLOG PACKETLOG DECOMLOG REDUCER CLEANUP).each do |type|
339
440
  model = MicroserviceModel.get_model(name: "#{@scope}__#{type}__#{@name}", scope: @scope)
340
441
  model.destroy if model
341
442
  end
443
+ ConfigTopic.write({ kind: 'deleted', type: 'target', name: @name, plugin: @plugin }, scope: @scope)
342
444
  end
343
445
 
344
446
  ##################################################
@@ -364,7 +466,7 @@ module Cosmos
364
466
 
365
467
  begin
366
468
  Cosmos.set_working_dir(File.dirname(path)) do
367
- return ERB.new(File.read(path)).result(b)
469
+ return ERB.new(File.read(path), trim_mode: "-").result(b)
368
470
  end
369
471
  rescue => error
370
472
  raise "ERB error parsing: #{path}: #{error.formatted}"
@@ -406,9 +508,7 @@ module Cosmos
406
508
  end
407
509
  end
408
510
 
409
- def update_store(temp_dir)
410
- # Build a System for just this target
411
- system = System.new([@name], temp_dir)
511
+ def update_store(system)
412
512
  target = system.targets[@name]
413
513
 
414
514
  # Add in the information from the target and update
@@ -494,10 +594,10 @@ module Cosmos
494
594
  # No telemetry packets for this target
495
595
  end
496
596
  # It's ok to call initialize_streams with an empty array
497
- Store.initialize_streams(command_topic_list)
498
- Store.initialize_streams(decom_command_topic_list)
499
- Store.initialize_streams(packet_topic_list)
500
- Store.initialize_streams(decom_topic_list)
597
+ Topic.initialize_streams(command_topic_list)
598
+ Topic.initialize_streams(decom_command_topic_list)
599
+ Topic.initialize_streams(packet_topic_list)
600
+ Topic.initialize_streams(decom_topic_list)
501
601
 
502
602
  unless command_topic_list.empty?
503
603
  # CommandLog Microservice
@@ -515,7 +615,7 @@ module Cosmos
515
615
  ],
516
616
  topics: command_topic_list,
517
617
  target_names: [@name],
518
- plugin: plugin,
618
+ plugin: @plugin,
519
619
  needs_dependencies: @needs_dependencies,
520
620
  scope: @scope
521
621
  )
@@ -538,7 +638,7 @@ module Cosmos
538
638
  ],
539
639
  topics: decom_command_topic_list,
540
640
  target_names: [@name],
541
- plugin: plugin,
641
+ plugin: @plugin,
542
642
  needs_dependencies: @needs_dependencies,
543
643
  scope: @scope
544
644
  )
@@ -563,7 +663,7 @@ module Cosmos
563
663
  ],
564
664
  topics: packet_topic_list,
565
665
  target_names: [@name],
566
- plugin: plugin,
666
+ plugin: @plugin,
567
667
  needs_dependencies: @needs_dependencies,
568
668
  scope: @scope
569
669
  )
@@ -586,7 +686,7 @@ module Cosmos
586
686
  ],
587
687
  topics: decom_topic_list,
588
688
  target_names: [@name],
589
- plugin: plugin,
689
+ plugin: @plugin,
590
690
  needs_dependencies: @needs_dependencies,
591
691
  scope: @scope
592
692
  )
@@ -603,7 +703,7 @@ module Cosmos
603
703
  work_dir: '/cosmos/lib/cosmos/microservices',
604
704
  topics: packet_topic_list,
605
705
  target_names: [@name],
606
- plugin: plugin,
706
+ plugin: @plugin,
607
707
  needs_dependencies: @needs_dependencies,
608
708
  scope: @scope
609
709
  )
@@ -619,7 +719,7 @@ module Cosmos
619
719
  cmd: ["ruby", "reducer_microservice.rb", microservice_name],
620
720
  work_dir: '/cosmos/lib/cosmos/microservices',
621
721
  topics: decom_topic_list,
622
- plugin: plugin,
722
+ plugin: @plugin,
623
723
  needs_dependencies: @needs_dependencies,
624
724
  scope: @scope
625
725
  )
@@ -627,6 +727,22 @@ module Cosmos
627
727
  microservice.deploy(gem_path, variables)
628
728
  Logger.info "Configured microservice #{microservice_name}"
629
729
  end
730
+
731
+ if @cmd_log_retain_time or @cmd_decom_log_retain_time or @tlm_log_retain_time or @tlm_decom_log_retain_time or
732
+ @reduced_minute_log_retain_time or @reduced_hour_log_retain_time or @reduced_day_log_retain_time
733
+ # Cleanup Microservice
734
+ microservice_name = "#{@scope}__CLEANUP__#{@name}"
735
+ microservice = MicroserviceModel.new(
736
+ name: microservice_name,
737
+ cmd: ["ruby", "cleanup_microservice.rb", microservice_name],
738
+ work_dir: '/cosmos/lib/cosmos/microservices',
739
+ plugin: @plugin,
740
+ scope: @scope
741
+ )
742
+ microservice.create
743
+ microservice.deploy(gem_path, variables)
744
+ Logger.info "Configured microservice #{microservice_name}"
745
+ end
630
746
  end
631
747
  end
632
748
  end
@@ -0,0 +1,38 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 Ball Aerospace & Technologies Corp.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU Affero General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # This program may also be used under the terms of a commercial or
17
+ # enterprise edition license of COSMOS if purchased from the
18
+ # copyright holder
19
+
20
+ module Cosmos
21
+ class ToolConfigModel
22
+ def self.list_configs(tool, scope: $cosmos_scope)
23
+ Store.hkeys("#{scope}__config__#{tool}")
24
+ end
25
+
26
+ def self.load_config(tool, name, scope: $cosmos_scope)
27
+ Store.hget("#{scope}__config__#{tool}", name)
28
+ end
29
+
30
+ def self.save_config(tool, name, data, scope: $cosmos_scope)
31
+ Store.hset("#{scope}__config__#{tool}", name, data)
32
+ end
33
+
34
+ def self.delete_config(tool, name, scope: $cosmos_scope)
35
+ Store.hdel("#{scope}__config__#{tool}", name)
36
+ end
37
+ end
38
+ end
@@ -209,13 +209,11 @@ module Cosmos
209
209
  return nil
210
210
  end
211
211
 
212
- def deploy(gem_path, variables)
212
+ def deploy(gem_path, variables, validate_only: false)
213
213
  return unless @folder_name
214
214
 
215
- rubys3_client = Aws::S3::Client.new
216
-
217
215
  # Ensure tools bucket exists
218
- Cosmos::S3Utilities.ensure_public_bucket('tools')
216
+ Cosmos::S3Utilities.ensure_public_bucket('tools') unless validate_only
219
217
 
220
218
  variables["tool_name"] = @name
221
219
  start_path = "/tools/#{@folder_name}/"
@@ -223,16 +221,17 @@ module Cosmos
223
221
  next if filename == '.' or filename == '..' or File.directory?(filename)
224
222
 
225
223
  key = filename.split(gem_path + '/tools/')[-1]
226
-
227
224
  extension = filename.split('.')[-1]
228
225
  content_type = Rack::Mime.mime_type(".#{extension}")
229
226
 
230
- cache_control = Cosmos::S3Utilities.get_cache_control(filename)
231
-
232
227
  # Load tool files
233
228
  data = File.read(filename, mode: "rb")
234
- data = ERB.new(data).result(binding.set_variables(variables)) if data.is_printable?
235
- rubys3_client.put_object(bucket: 'tools', content_type: content_type, cache_control: cache_control, key: key, body: data)
229
+ data = ERB.new(data, trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable?
230
+ unless validate_only
231
+ cache_control = Cosmos::S3Utilities.get_cache_control(filename)
232
+ Aws::S3::Client.new.put_object(bucket: 'tools', content_type: content_type, cache_control: cache_control, key: key, body: data)
233
+ ConfigTopic.write({ kind: 'created', type: 'tool', name: @folder_name, plugin: @plugin }, scope: @scope)
234
+ end
236
235
  end
237
236
  end
238
237
 
@@ -242,6 +241,7 @@ module Cosmos
242
241
  prefix = "#{@folder_name}/"
243
242
  rubys3_client.list_objects(bucket: 'tools', prefix: prefix).contents.each do |object|
244
243
  rubys3_client.delete_object(bucket: 'tools', key: object.key)
244
+ ConfigTopic.write({ kind: 'deleted', type: 'tool', name: @folder_name, plugin: @plugin }, scope: @scope)
245
245
  end
246
246
  end
247
247
  end
@@ -108,25 +108,25 @@ module Cosmos
108
108
  raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Widget: #{keyword} #{parameters.join(" ")}")
109
109
  end
110
110
 
111
- def deploy(gem_path, variables)
112
- rubys3_client = Aws::S3::Client.new
113
-
111
+ def deploy(gem_path, variables, validate_only: false)
114
112
  # Ensure tools bucket exists
115
- Cosmos::S3Utilities.ensure_public_bucket('tools')
113
+ Cosmos::S3Utilities.ensure_public_bucket('tools') unless validate_only
116
114
 
117
115
  filename = gem_path + "/tools/widgets/" + @full_name + '/' + @filename
118
116
 
119
- cache_control = Cosmos::S3Utilities.get_cache_control(@filename)
120
-
121
117
  # Load widget file
122
118
  data = File.read(filename, mode: "rb")
123
119
  Cosmos.set_working_dir(File.dirname(filename)) do
124
- data = ERB.new(data).result(binding.set_variables(variables)) if data.is_printable?
120
+ data = ERB.new(data, trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable?
121
+ end
122
+ unless validate_only
123
+ cache_control = Cosmos::S3Utilities.get_cache_control(@filename)
124
+ # TODO: support widgets that aren't just a single js file (and its associated map file)
125
+ rubys3_client = Aws::S3::Client.new
126
+ rubys3_client.put_object(bucket: 'tools', content_type: 'application/javascript', cache_control: cache_control, key: @s3_key, body: data)
127
+ data = File.read(filename + '.map', mode: "rb")
128
+ rubys3_client.put_object(bucket: 'tools', content_type: 'application/json', cache_control: cache_control, key: @s3_key + '.map', body: data)
125
129
  end
126
- # TODO: support widgets that aren't just a single js file (and its associated map file)
127
- rubys3_client.put_object(bucket: 'tools', content_type: 'application/javascript', cache_control: cache_control, key: @s3_key, body: data)
128
- data = File.read(filename + '.map', mode: "rb")
129
- rubys3_client.put_object(bucket: 'tools', content_type: 'application/json', cache_control: cache_control, key: @s3_key + '.map', body: data)
130
130
  end
131
131
 
132
132
  def undeploy