cosmos 5.0.2.pre.beta2 → 5.0.4
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 +1 -1
- data/data/config/microservice.yaml +47 -35
- data/data/config/plugins.yaml +3 -150
- data/data/config/target.yaml +70 -0
- data/data/config/tool.yaml +37 -31
- data/lib/cosmos/api/api.rb +1 -25
- data/lib/cosmos/api/cmd_api.rb +17 -6
- data/lib/cosmos/api/config_api.rb +10 -4
- data/lib/cosmos/api/limits_api.rb +1 -1
- data/lib/cosmos/api/settings_api.rb +19 -7
- data/lib/cosmos/api/target_api.rb +2 -2
- data/lib/cosmos/api/tlm_api.rb +69 -41
- data/lib/cosmos/config/config_parser.rb +19 -22
- data/lib/cosmos/config/meta_config_parser.rb +1 -1
- data/lib/cosmos/conversions/generic_conversion.rb +2 -2
- data/lib/cosmos/conversions/polynomial_conversion.rb +5 -8
- data/lib/cosmos/conversions/segmented_polynomial_conversion.rb +26 -9
- data/lib/cosmos/io/json_drb.rb +5 -1
- data/lib/cosmos/logs/log_writer.rb +2 -2
- data/lib/cosmos/microservices/cleanup_microservice.rb +28 -29
- data/lib/cosmos/microservices/decom_microservice.rb +1 -1
- data/lib/cosmos/microservices/interface_microservice.rb +0 -1
- data/lib/cosmos/microservices/microservice.rb +3 -3
- data/lib/cosmos/microservices/reducer_microservice.rb +12 -10
- data/lib/cosmos/models/cvt_model.rb +6 -6
- data/lib/cosmos/models/gem_model.rb +3 -3
- data/lib/cosmos/models/info_model.rb +1 -1
- data/lib/cosmos/models/interface_status_model.rb +1 -1
- data/lib/cosmos/models/metadata_model.rb +42 -216
- data/lib/cosmos/models/metric_model.rb +2 -2
- data/lib/cosmos/models/microservice_model.rb +1 -1
- data/lib/cosmos/models/microservice_status_model.rb +1 -1
- data/lib/cosmos/models/model.rb +16 -16
- data/lib/cosmos/models/note_model.rb +124 -0
- data/lib/cosmos/models/ping_model.rb +2 -1
- data/lib/cosmos/models/plugin_model.rb +1 -1
- data/lib/cosmos/models/process_status_model.rb +1 -1
- data/lib/cosmos/models/scope_model.rb +9 -26
- data/lib/cosmos/models/settings_model.rb +55 -0
- data/lib/cosmos/models/sorted_model.rb +165 -0
- data/lib/cosmos/models/target_model.rb +120 -13
- data/lib/cosmos/models/tool_config_model.rb +38 -0
- data/lib/cosmos/models/tool_model.rb +1 -1
- data/lib/cosmos/models/widget_model.rb +1 -1
- data/lib/cosmos/operators/microservice_operator.rb +2 -1
- data/lib/cosmos/packets/packet.rb +23 -0
- data/lib/cosmos/packets/packet_config.rb +2 -2
- data/lib/cosmos/packets/packet_item.rb +57 -0
- data/lib/cosmos/packets/packet_item_limits.rb +14 -2
- data/lib/cosmos/packets/parsers/packet_item_parser.rb +1 -1
- data/lib/cosmos/packets/parsers/packet_parser.rb +1 -1
- data/lib/cosmos/packets/parsers/xtce_parser.rb +1 -1
- data/lib/cosmos/packets/structure_item.rb +10 -1
- data/lib/cosmos/script/api_shared.rb +30 -25
- data/lib/cosmos/script/calendar.rb +26 -15
- data/lib/cosmos/script/commands.rb +5 -7
- data/lib/cosmos/script/script.rb +19 -39
- data/lib/cosmos/script/storage.rb +92 -105
- data/lib/cosmos/system/system.rb +2 -1
- data/lib/cosmos/tools/table_manager/table_item.rb +1 -1
- data/lib/cosmos/top_level.rb +5 -1
- data/lib/cosmos/topics/autonomic_topic.rb +2 -2
- data/lib/cosmos/topics/calendar_topic.rb +1 -1
- data/lib/cosmos/topics/command_decom_topic.rb +35 -1
- data/lib/cosmos/topics/command_topic.rb +6 -4
- data/lib/cosmos/topics/interface_topic.rb +8 -8
- data/lib/cosmos/topics/limits_event_topic.rb +5 -3
- data/lib/cosmos/topics/notifications_topic.rb +1 -1
- data/lib/cosmos/topics/router_topic.rb +9 -9
- data/lib/cosmos/topics/telemetry_decom_topic.rb +5 -1
- data/lib/cosmos/topics/telemetry_topic.rb +1 -1
- data/lib/cosmos/topics/timeline_topic.rb +1 -1
- data/lib/cosmos/topics/topic.rb +23 -8
- data/lib/cosmos/utilities/logger.rb +4 -3
- data/lib/cosmos/utilities/metric.rb +32 -26
- data/lib/cosmos/utilities/s3.rb +61 -0
- data/lib/cosmos/utilities/s3_file_cache.rb +12 -6
- data/lib/cosmos/utilities/store.rb +1 -0
- data/lib/cosmos/utilities/store_autoload.rb +25 -134
- data/lib/cosmos/version.rb +6 -5
- data/templates/plugin-template/plugin.gemspec +0 -2
- metadata +9 -6
- data/lib/cosmos/models/narrative_model.rb +0 -280
@@ -96,9 +96,8 @@ module Cosmos
|
|
96
96
|
ReducerModel
|
97
97
|
.all_files(type: :DECOM, target: @target_name, scope: @scope)
|
98
98
|
.each do |file|
|
99
|
-
|
100
|
-
|
101
|
-
end
|
99
|
+
process_file(file, 'minute', MINUTE_ENTRY_SECS, MINUTE_FILE_SECS)
|
100
|
+
ReducerModel.rm_file(file)
|
102
101
|
end
|
103
102
|
end
|
104
103
|
end
|
@@ -108,9 +107,8 @@ module Cosmos
|
|
108
107
|
ReducerModel
|
109
108
|
.all_files(type: :MINUTE, target: @target_name, scope: @scope)
|
110
109
|
.each do |file|
|
111
|
-
|
112
|
-
|
113
|
-
end
|
110
|
+
process_file(file, 'hour', HOUR_ENTRY_SECS, HOUR_FILE_SECS)
|
111
|
+
ReducerModel.rm_file(file)
|
114
112
|
end
|
115
113
|
end
|
116
114
|
end
|
@@ -120,9 +118,8 @@ module Cosmos
|
|
120
118
|
ReducerModel
|
121
119
|
.all_files(type: :HOUR, target: @target_name, scope: @scope)
|
122
120
|
.each do |file|
|
123
|
-
|
124
|
-
|
125
|
-
end
|
121
|
+
process_file(file, 'day', DAY_ENTRY_SECS, DAY_FILE_SECS)
|
122
|
+
ReducerModel.rm_file(file)
|
126
123
|
end
|
127
124
|
end
|
128
125
|
end
|
@@ -250,7 +247,12 @@ module Cosmos
|
|
250
247
|
)
|
251
248
|
true
|
252
249
|
rescue => e
|
253
|
-
|
250
|
+
if file.local_path and File.exist?(file.local_path)
|
251
|
+
Logger.error("Reducer Error: #{filename}:#{File.size(file.local_path)} bytes: \n#{e.formatted}")
|
252
|
+
else
|
253
|
+
Logger.error("Reducer Error: #{filename}:(Not Retrieved): \n#{e.formatted}")
|
254
|
+
end
|
255
|
+
false
|
254
256
|
end
|
255
257
|
|
256
258
|
def reduce(type, data_keys, reduced)
|
@@ -40,12 +40,12 @@ module Cosmos
|
|
40
40
|
|
41
41
|
# Delete the current value table for a target
|
42
42
|
def self.del(target_name:, packet_name:, scope:)
|
43
|
-
|
43
|
+
EphemeralStore.hdel("#{scope}__tlm__#{target_name}", packet_name)
|
44
44
|
end
|
45
45
|
|
46
46
|
# Set the current value table for a target, packet
|
47
47
|
def self.set(hash, target_name:, packet_name:, scope:)
|
48
|
-
|
48
|
+
EphemeralStore.hset("#{scope}__tlm__#{target_name}", packet_name, JSON.generate(hash.as_json))
|
49
49
|
end
|
50
50
|
|
51
51
|
# Set an item in the current value table
|
@@ -62,9 +62,9 @@ module Cosmos
|
|
62
62
|
else
|
63
63
|
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
64
64
|
end
|
65
|
-
hash = JSON.parse(
|
65
|
+
hash = JSON.parse(EphemeralStore.hget("#{scope}__tlm__#{target_name}", packet_name))
|
66
66
|
hash[field] = value
|
67
|
-
|
67
|
+
EphemeralStore.hset("#{scope}__tlm__#{target_name}", packet_name, JSON.generate(hash.as_json))
|
68
68
|
end
|
69
69
|
|
70
70
|
# Get an item from the current value table
|
@@ -86,7 +86,7 @@ module Cosmos
|
|
86
86
|
else
|
87
87
|
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
88
88
|
end
|
89
|
-
hash = JSON.parse(
|
89
|
+
hash = JSON.parse(EphemeralStore.hget("#{scope}__tlm__#{target_name}", packet_name))
|
90
90
|
hash.values_at(*types).each do |result|
|
91
91
|
return result if result
|
92
92
|
end
|
@@ -106,7 +106,7 @@ module Cosmos
|
|
106
106
|
|
107
107
|
lookups.each do |target_packet_key, target_name, packet_name, packet_values|
|
108
108
|
unless packet_lookup[target_packet_key]
|
109
|
-
packet =
|
109
|
+
packet = EphemeralStore.hget("#{scope}__tlm__#{target_name}", packet_name)
|
110
110
|
raise "Packet '#{target_name} #{packet_name}' does not exist" unless packet
|
111
111
|
packet_lookup[target_packet_key] = JSON.parse(packet)
|
112
112
|
end
|
@@ -73,7 +73,7 @@ module Cosmos
|
|
73
73
|
return nil
|
74
74
|
end
|
75
75
|
|
76
|
-
def self.install(name_or_path)
|
76
|
+
def self.install(name_or_path, scope:)
|
77
77
|
temp_dir = Dir.mktmpdir
|
78
78
|
begin
|
79
79
|
if File.exist?(name_or_path)
|
@@ -81,10 +81,10 @@ module Cosmos
|
|
81
81
|
else
|
82
82
|
gem_file_path = get(temp_dir, name_or_path)
|
83
83
|
end
|
84
|
-
rubygems_url = get_setting('rubygems_url')
|
84
|
+
rubygems_url = get_setting('rubygems_url', scope: scope)
|
85
85
|
Gem.sources = [rubygems_url] if rubygems_url
|
86
86
|
Gem.done_installing_hooks.clear
|
87
|
-
Gem.install(gem_file_path,
|
87
|
+
Gem.install(gem_file_path, "> 0.pre", :build_args => ['--no-document'], :prerelease => true)
|
88
88
|
rescue => err
|
89
89
|
message = "Gem file #{gem_file_path} error installing to /gems\n#{err.formatted}"
|
90
90
|
Logger.error message
|
@@ -23,7 +23,7 @@ module Cosmos
|
|
23
23
|
# Stores the status about an interface. This class also implements logic
|
24
24
|
# to handle status for a router since the functionality is identical
|
25
25
|
# (only difference is the Redis key used).
|
26
|
-
class InterfaceStatusModel <
|
26
|
+
class InterfaceStatusModel < EphemeralModel
|
27
27
|
INTERFACES_PRIMARY_KEY = 'cosmos_interface_status'
|
28
28
|
ROUTERS_PRIMARY_KEY = 'cosmos_router_status'
|
29
29
|
|
@@ -19,271 +19,97 @@
|
|
19
19
|
|
20
20
|
# https://www.rubydoc.info/gems/redis/Redis/Commands/SortedSets
|
21
21
|
|
22
|
-
require 'cosmos/
|
22
|
+
require 'cosmos/models/sorted_model'
|
23
23
|
|
24
24
|
module Cosmos
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
class MetadataInputError < MetadataError; end
|
29
|
-
|
30
|
-
class MetadataOverlapError < MetadataError; end
|
31
|
-
|
32
|
-
class MetadataModel < Model
|
33
|
-
|
34
|
-
CHRONICLE_TYPE = 'metadata'.freeze
|
35
|
-
CURRENT_VALUE = '__current.metadata.value'.freeze
|
25
|
+
class MetadataModel < SortedModel
|
26
|
+
METADATA_TYPE = 'metadata'.freeze
|
36
27
|
PRIMARY_KEY = '__METADATA'.freeze
|
37
28
|
|
38
29
|
def self.pk(scope)
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
# @return [String|nil] String of the saved json or nil if score not found under current value
|
43
|
-
def self.get_current_value(target:, scope:)
|
44
|
-
json = Store.hget("#{scope}#{CURRENT_VALUE}", target)
|
45
|
-
return nil unless json
|
46
|
-
return self.from_json(JSON.parse(json), scope: scope)
|
47
|
-
end
|
48
|
-
|
49
|
-
# @return [Array|nil] Array up to 100 of this model or empty array
|
50
|
-
def self.get(start:, stop:, scope:, limit: 100)
|
51
|
-
if start > stop
|
52
|
-
raise MetadataInputError.new "start: #{start} must be before stop: #{stop}"
|
53
|
-
end
|
54
|
-
pk = self.pk(scope)
|
55
|
-
array = Store.zrangebyscore(pk, start, stop, :limit => [0, limit])
|
56
|
-
ret_array = Array.new
|
57
|
-
array.each do |value|
|
58
|
-
ret_array << JSON.parse(value)
|
59
|
-
end
|
60
|
-
return ret_array
|
61
|
-
end
|
62
|
-
|
63
|
-
# @return [Array<Hash>] Array up to the limit of the models (as Hash objects) stored under the primary key
|
64
|
-
def self.all(scope:, limit: 100)
|
65
|
-
pk = self.pk(scope)
|
66
|
-
array = Store.zrange(pk, 0, -1, :limit => [0, limit])
|
67
|
-
ret_array = Array.new
|
68
|
-
array.each do |value|
|
69
|
-
ret_array << JSON.parse(value)
|
70
|
-
end
|
71
|
-
return ret_array
|
72
|
-
end
|
73
|
-
|
74
|
-
# @return [Integer] count of the members stored under the primary key
|
75
|
-
def self.count(scope:)
|
76
|
-
return Store.zcard(self.pk(scope))
|
30
|
+
"#{scope}#{PRIMARY_KEY}"
|
77
31
|
end
|
78
32
|
|
79
|
-
|
80
|
-
def self.score(score:, scope:)
|
81
|
-
pk = self.pk(scope)
|
82
|
-
array = Store.zrangebyscore(pk, score, score, :limit => [0, 1])
|
83
|
-
array.each do |value|
|
84
|
-
return JSON.parse(value)
|
85
|
-
end
|
86
|
-
return nil
|
87
|
-
end
|
88
|
-
|
89
|
-
# Remove member from a sorted set based on the score.
|
90
|
-
# @return [Integer] count of the members removed
|
91
|
-
def self.destroy(scope:, score:)
|
92
|
-
pk = self.pk(scope)
|
93
|
-
Store.zremrangebyscore(pk, score, score)
|
94
|
-
end
|
95
|
-
|
96
|
-
# Remove members from min to max of the sorted set.
|
97
|
-
# @return [Integer] count of the members removed
|
98
|
-
def self.range_destroy(scope:, min:, max:)
|
99
|
-
pk = self.pk(scope)
|
100
|
-
Store.zremrangebyscore(pk, min, max)
|
101
|
-
end
|
102
|
-
|
103
|
-
# @return [MetadataModel] Model generated from the passed JSON
|
104
|
-
def self.from_json(json, scope:)
|
105
|
-
json = JSON.parse(json) if String === json
|
106
|
-
raise "json data is nil" if json.nil?
|
107
|
-
|
108
|
-
json.transform_keys!(&:to_sym)
|
109
|
-
self.new(**json, scope: scope)
|
110
|
-
end
|
111
|
-
|
112
|
-
attr_reader :target, :start, :color, :metadata, :type
|
33
|
+
attr_reader :color, :metadata, :type
|
113
34
|
|
114
|
-
# @param [String] target - should be the target but can be anything
|
115
35
|
# @param [Integer] start - time metadata is active in seconds from Epoch
|
116
36
|
# @param [String] color - The event color
|
117
37
|
# @param [String] metadata - Key value pair object to link to name
|
118
38
|
# @param [String] scope - Cosmos scope to track event to
|
119
39
|
def initialize(
|
120
|
-
|
40
|
+
scope:,
|
121
41
|
start:,
|
122
42
|
color: nil,
|
123
43
|
metadata:,
|
124
|
-
|
125
|
-
type: CHRONICLE_TYPE,
|
44
|
+
type: METADATA_TYPE,
|
126
45
|
updated_at: 0
|
127
46
|
)
|
128
|
-
super(
|
129
|
-
set_input(start: start, color: color, metadata: metadata)
|
130
|
-
@target = target
|
131
|
-
@type = type
|
132
|
-
@updated_at = updated_at
|
133
|
-
end
|
134
|
-
|
135
|
-
# validate color
|
136
|
-
def validate_color(color)
|
137
|
-
if color.nil?
|
138
|
-
color = '#%06x' % (rand * 0xffffff)
|
139
|
-
end
|
140
|
-
valid_color = color =~ /(#*)([0-9,a-f,A-f]{6})/
|
141
|
-
if valid_color.nil?
|
142
|
-
raise MetadataInputError.new "invalid color, must be in hex format, e.g. #FF0000"
|
143
|
-
end
|
144
|
-
|
145
|
-
color = "##{color}" unless color.start_with?('#')
|
146
|
-
return color
|
147
|
-
end
|
148
|
-
|
149
|
-
# validate the input to the rules we have created for timelines.
|
150
|
-
# - An entry's start MUST be valid.
|
151
|
-
# - An entry's start MUST NOT be in the future.
|
152
|
-
# - An entry's metadata MUST a hash/object.
|
153
|
-
def validate_input(start:, color:, metadata:)
|
154
|
-
if start.is_a?(Integer) == false
|
155
|
-
raise MetadataInputError.new "failed validation input must be integer: #{start}"
|
156
|
-
end
|
157
|
-
now = Time.now.strftime('%s%3N').to_i
|
158
|
-
if start > now
|
159
|
-
raise MetadataInputError.new "start can not be in the future: #{start} > #{now}"
|
160
|
-
end
|
161
|
-
validate_color(color)
|
162
|
-
if metadata.is_a?(Hash) == false
|
163
|
-
raise MetadataInputError.new "Metadata must be a hash/object: #{metadata}"
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# Set the values of the instance, @start, @stop, @metadata...
|
168
|
-
def set_input(start:, color:, metadata:)
|
169
|
-
if start.is_a?(Integer) == false
|
170
|
-
raise MetadataInputError.new "start input must be integer: #{start}"
|
171
|
-
end
|
47
|
+
super(start: start, scope: scope, updated_at: updated_at)
|
172
48
|
@start = start
|
173
49
|
@color = color
|
174
50
|
@metadata = metadata
|
51
|
+
@type = type # For the as_json, from_json round trip
|
175
52
|
end
|
176
53
|
|
177
|
-
#
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
# a time when doing an update
|
183
|
-
def validate_time(ignore_score: nil)
|
184
|
-
array = Store.zrangebyscore(@primary_key, @start, @start, :limit => [0, 1])
|
185
|
-
array.each do |value|
|
186
|
-
entry = JSON.parse(value)
|
187
|
-
if ignore_score == entry['start']
|
188
|
-
next
|
189
|
-
else
|
190
|
-
return entry
|
191
|
-
end
|
192
|
-
end
|
193
|
-
return nil
|
54
|
+
# Validates the instance variables: @start, @color, @metadata
|
55
|
+
def validate(update: false)
|
56
|
+
validate_start(update: update)
|
57
|
+
validate_color()
|
58
|
+
validate_metadata()
|
194
59
|
end
|
195
60
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
validate_input(start: @start, color: @color, metadata: @metadata)
|
200
|
-
collision = validate_time()
|
201
|
-
unless collision.nil?
|
202
|
-
raise MetadataOverlapError.new "no chronicle can overlap, collision: #{collision}"
|
61
|
+
def validate_color()
|
62
|
+
if @color.nil?
|
63
|
+
@color = '#%06x' % (rand * 0xffffff)
|
203
64
|
end
|
204
|
-
|
205
|
-
|
206
|
-
Store.zadd(@primary_key, @start, JSON.generate(as_json()))
|
207
|
-
update_current_value()
|
208
|
-
notify(kind: 'created')
|
209
|
-
end
|
210
|
-
|
211
|
-
# Update the Redis hash at primary_key and remove the current activity at the current score
|
212
|
-
# and update the score to the new score equal to the start Epoch time this uses a multi
|
213
|
-
# to execute both the remove and create. The member via the JSON generated via calling as_json
|
214
|
-
def update(start:, color:, metadata:)
|
215
|
-
validate_input(start: start, color: color, metadata: metadata)
|
216
|
-
old_start = @start
|
217
|
-
@updated_at = Time.now.to_nsec_from_epoch
|
218
|
-
set_input(start: start, color: color, metadata: metadata)
|
219
|
-
# copy of create
|
220
|
-
collision = validate_time(ignore_score: old_start)
|
221
|
-
unless collision.nil?
|
222
|
-
raise MetadataOverlapError.new "failed to update #{old_start}, no chronicles can overlap, collision: #{collision}"
|
65
|
+
unless @color =~ /(#*)([0-9,a-f,A-f]{6})/
|
66
|
+
raise SortedInputError.new "invalid color, must be in hex format, e.g. #FF0000"
|
223
67
|
end
|
68
|
+
@color = "##{@color}" unless @color.start_with?('#')
|
69
|
+
end
|
224
70
|
|
225
|
-
|
226
|
-
|
227
|
-
|
71
|
+
def validate_metadata()
|
72
|
+
unless @metadata.is_a?(Hash)
|
73
|
+
raise SortedInputError.new "Metadata must be a hash/object: #{@metadata}"
|
228
74
|
end
|
229
|
-
update_current_value(old_start: old_start)
|
230
|
-
notify(kind: 'updated', extra: old_start)
|
231
|
-
return @start
|
232
75
|
end
|
233
76
|
|
234
|
-
# Update the Redis hash at primary_key
|
235
|
-
#
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
json = Store.hget("#{@scope}#{CURRENT_VALUE}", @target)
|
241
|
-
unless json.nil?
|
242
|
-
model = MetadataModel.from_json(JSON.parse(json), scope: @scope)
|
243
|
-
update = model.start <= @start || model.start == old_start
|
244
|
-
end
|
77
|
+
# Update the Redis hash at primary_key based on the initial passed start
|
78
|
+
# The member is set to the JSON generated via calling as_json
|
79
|
+
def create(update: false)
|
80
|
+
validate(update: update)
|
81
|
+
@updated_at = Time.now.to_nsec_from_epoch
|
82
|
+
Store.zadd(@primary_key, @start, JSON.generate(as_json()))
|
245
83
|
if update
|
246
|
-
|
84
|
+
notify(kind: 'updated')
|
85
|
+
else
|
86
|
+
notify(kind: 'created')
|
247
87
|
end
|
248
88
|
end
|
249
89
|
|
250
|
-
#
|
251
|
-
def
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
# @return [] update the redis stream / timeline topic that something has changed
|
257
|
-
def notify(kind:, extra: nil)
|
258
|
-
notification = {
|
259
|
-
'data' => JSON.generate(as_json()),
|
260
|
-
'kind' => kind,
|
261
|
-
'type' => 'calendar',
|
262
|
-
}
|
263
|
-
notification['extra'] = extra unless extra.nil?
|
264
|
-
begin
|
265
|
-
CalendarTopic.write_entry(notification, scope: @scope)
|
266
|
-
rescue StandardError => e
|
267
|
-
raise MetadataError.new "Failed to write to stream: #{notification}, #{e}"
|
268
|
-
end
|
90
|
+
# Update the Redis hash at primary_key
|
91
|
+
def update(start:, color:, metadata:)
|
92
|
+
@start = start
|
93
|
+
@color = color
|
94
|
+
@metadata = metadata
|
95
|
+
create(update: true)
|
269
96
|
end
|
270
97
|
|
271
98
|
# @return [Hash] generated from the MetadataModel
|
272
99
|
def as_json
|
273
100
|
return {
|
274
|
-
'target' => @target,
|
275
101
|
'scope' => @scope,
|
276
|
-
'updated_at' => @updated_at,
|
277
102
|
'start' => @start,
|
278
103
|
'color' => @color,
|
279
104
|
'metadata' => @metadata,
|
280
|
-
'type' =>
|
105
|
+
'type' => METADATA_TYPE,
|
106
|
+
'updated_at' => @updated_at,
|
281
107
|
}
|
282
108
|
end
|
283
109
|
|
284
110
|
# @return [String] string view of metadata
|
285
111
|
def to_s
|
286
|
-
return "<MetadataModel
|
112
|
+
return "<MetadataModel s: #{@start}, c: #{@color}, m: #{@metadata}>"
|
287
113
|
end
|
288
114
|
end
|
289
115
|
end
|
@@ -20,7 +20,7 @@
|
|
20
20
|
require 'cosmos/models/model'
|
21
21
|
|
22
22
|
module Cosmos
|
23
|
-
class MetricModel <
|
23
|
+
class MetricModel < EphemeralModel
|
24
24
|
PRIMARY_KEY = '__cosmos__metric'.freeze
|
25
25
|
|
26
26
|
# NOTE: The following three class methods are used by the ModelController
|
@@ -38,7 +38,7 @@ module Cosmos
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def self.destroy(scope:, name:)
|
41
|
-
|
41
|
+
EphemeralStore.hdel("#{scope}#{PRIMARY_KEY}", name)
|
42
42
|
end
|
43
43
|
|
44
44
|
def initialize(name:, scope:, metric_name:, label_list:)
|
@@ -186,7 +186,7 @@ module Cosmos
|
|
186
186
|
# Load microservice files
|
187
187
|
data = File.read(filename, mode: "rb")
|
188
188
|
Cosmos.set_working_dir(File.dirname(filename)) do
|
189
|
-
data = ERB.new(data).result(binding.set_variables(variables)) if data.is_printable?
|
189
|
+
data = ERB.new(data, trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable?
|
190
190
|
end
|
191
191
|
rubys3_client.put_object(bucket: 'config', key: key, body: data)
|
192
192
|
end
|
data/lib/cosmos/models/model.rb
CHANGED
@@ -27,12 +27,16 @@ module Cosmos
|
|
27
27
|
attr_accessor :plugin
|
28
28
|
attr_accessor :scope
|
29
29
|
|
30
|
+
def self.store
|
31
|
+
Store
|
32
|
+
end
|
33
|
+
|
30
34
|
# NOTE: The following three methods must be reimplemented by Model subclasses
|
31
35
|
# without primary_key to support other class methods.
|
32
36
|
|
33
37
|
# @return [Hash|nil] Hash of this model or nil if name not found under primary_key
|
34
38
|
def self.get(primary_key, name:)
|
35
|
-
json =
|
39
|
+
json = store.hget(primary_key, name)
|
36
40
|
if json
|
37
41
|
return JSON.parse(json)
|
38
42
|
else
|
@@ -42,12 +46,12 @@ module Cosmos
|
|
42
46
|
|
43
47
|
# @return [Array<String>] All the names stored under the primary key
|
44
48
|
def self.names(primary_key)
|
45
|
-
|
49
|
+
store.hkeys(primary_key).sort
|
46
50
|
end
|
47
51
|
|
48
52
|
# @return [Array<Hash>] All the models (as Hash objects) stored under the primary key
|
49
53
|
def self.all(primary_key)
|
50
|
-
hash =
|
54
|
+
hash = store.hgetall(primary_key)
|
51
55
|
hash.each do |key, value|
|
52
56
|
hash[key] = JSON.parse(value)
|
53
57
|
end
|
@@ -117,16 +121,6 @@ module Cosmos
|
|
117
121
|
raise "must be implemented by subclass"
|
118
122
|
end
|
119
123
|
|
120
|
-
# TODO: Not used
|
121
|
-
# def self.from_config(primary_key, filename)
|
122
|
-
# model = nil
|
123
|
-
# parser = ConfigParser.new
|
124
|
-
# parser.parse_file(filename) do |keyword, parameters|
|
125
|
-
# model = self.handle_config(primary_key, parser, model, keyword, parameters)
|
126
|
-
# end
|
127
|
-
# model
|
128
|
-
# end
|
129
|
-
|
130
124
|
# Store the primary key and keyword arguments
|
131
125
|
def initialize(primary_key, **kw_args)
|
132
126
|
@primary_key = primary_key
|
@@ -140,7 +134,7 @@ module Cosmos
|
|
140
134
|
# to the JSON generated via calling as_json
|
141
135
|
def create(update: false, force: false)
|
142
136
|
unless force
|
143
|
-
existing =
|
137
|
+
existing = self.class.store.hget(@primary_key, @name)
|
144
138
|
if existing
|
145
139
|
raise "#{@primary_key}:#{@name} already exists at create" unless update
|
146
140
|
else
|
@@ -148,7 +142,7 @@ module Cosmos
|
|
148
142
|
end
|
149
143
|
end
|
150
144
|
@updated_at = Time.now.to_nsec_from_epoch
|
151
|
-
|
145
|
+
self.class.store.hset(@primary_key, @name, JSON.generate(self.as_json))
|
152
146
|
end
|
153
147
|
|
154
148
|
# Alias for create(update: true)
|
@@ -170,7 +164,7 @@ module Cosmos
|
|
170
164
|
# Delete the model from the Store
|
171
165
|
def destroy
|
172
166
|
undeploy()
|
173
|
-
|
167
|
+
self.class.store.hdel(@primary_key, @name)
|
174
168
|
end
|
175
169
|
|
176
170
|
# @return [Hash] JSON encoding of this model
|
@@ -186,4 +180,10 @@ module Cosmos
|
|
186
180
|
""
|
187
181
|
end
|
188
182
|
end
|
183
|
+
|
184
|
+
class EphemeralModel < Model
|
185
|
+
def self.store
|
186
|
+
EphemeralStore
|
187
|
+
end
|
188
|
+
end
|
189
189
|
end
|