cosmos 5.0.3 → 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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/bin/cosmos +1 -1
  3. data/lib/cosmos/api/api.rb +1 -25
  4. data/lib/cosmos/api/cmd_api.rb +6 -6
  5. data/lib/cosmos/api/config_api.rb +10 -4
  6. data/lib/cosmos/api/limits_api.rb +1 -1
  7. data/lib/cosmos/api/settings_api.rb +19 -7
  8. data/lib/cosmos/api/target_api.rb +2 -2
  9. data/lib/cosmos/api/tlm_api.rb +8 -8
  10. data/lib/cosmos/config/config_parser.rb +2 -2
  11. data/lib/cosmos/config/meta_config_parser.rb +1 -1
  12. data/lib/cosmos/logs/log_writer.rb +2 -2
  13. data/lib/cosmos/microservices/decom_microservice.rb +1 -1
  14. data/lib/cosmos/microservices/interface_microservice.rb +0 -1
  15. data/lib/cosmos/microservices/microservice.rb +2 -2
  16. data/lib/cosmos/microservices/reducer_microservice.rb +12 -10
  17. data/lib/cosmos/models/cvt_model.rb +6 -6
  18. data/lib/cosmos/models/gem_model.rb +2 -2
  19. data/lib/cosmos/models/info_model.rb +1 -1
  20. data/lib/cosmos/models/interface_status_model.rb +1 -1
  21. data/lib/cosmos/models/metadata_model.rb +42 -216
  22. data/lib/cosmos/models/metric_model.rb +2 -2
  23. data/lib/cosmos/models/microservice_model.rb +1 -1
  24. data/lib/cosmos/models/microservice_status_model.rb +1 -1
  25. data/lib/cosmos/models/model.rb +16 -16
  26. data/lib/cosmos/models/note_model.rb +124 -0
  27. data/lib/cosmos/models/ping_model.rb +2 -1
  28. data/lib/cosmos/models/plugin_model.rb +1 -1
  29. data/lib/cosmos/models/process_status_model.rb +1 -1
  30. data/lib/cosmos/models/scope_model.rb +9 -6
  31. data/lib/cosmos/models/settings_model.rb +55 -0
  32. data/lib/cosmos/models/sorted_model.rb +165 -0
  33. data/lib/cosmos/models/target_model.rb +20 -20
  34. data/lib/cosmos/models/tool_config_model.rb +38 -0
  35. data/lib/cosmos/models/tool_model.rb +1 -1
  36. data/lib/cosmos/models/widget_model.rb +1 -1
  37. data/lib/cosmos/operators/microservice_operator.rb +2 -1
  38. data/lib/cosmos/script/calendar.rb +26 -15
  39. data/lib/cosmos/system/system.rb +2 -1
  40. data/lib/cosmos/top_level.rb +5 -1
  41. data/lib/cosmos/topics/autonomic_topic.rb +2 -2
  42. data/lib/cosmos/topics/calendar_topic.rb +1 -1
  43. data/lib/cosmos/topics/command_decom_topic.rb +31 -1
  44. data/lib/cosmos/topics/command_topic.rb +6 -4
  45. data/lib/cosmos/topics/interface_topic.rb +8 -8
  46. data/lib/cosmos/topics/limits_event_topic.rb +5 -3
  47. data/lib/cosmos/topics/notifications_topic.rb +1 -1
  48. data/lib/cosmos/topics/router_topic.rb +9 -9
  49. data/lib/cosmos/topics/telemetry_decom_topic.rb +1 -1
  50. data/lib/cosmos/topics/telemetry_topic.rb +1 -1
  51. data/lib/cosmos/topics/timeline_topic.rb +1 -1
  52. data/lib/cosmos/topics/topic.rb +21 -16
  53. data/lib/cosmos/utilities/logger.rb +3 -3
  54. data/lib/cosmos/utilities/metric.rb +32 -26
  55. data/lib/cosmos/utilities/s3.rb +1 -1
  56. data/lib/cosmos/utilities/s3_file_cache.rb +12 -6
  57. data/lib/cosmos/utilities/store.rb +1 -0
  58. data/lib/cosmos/utilities/store_autoload.rb +27 -126
  59. data/lib/cosmos/version.rb +5 -5
  60. metadata +7 -4
  61. data/lib/cosmos/models/narrative_model.rb +0 -280
@@ -34,38 +34,38 @@ module Cosmos
34
34
 
35
35
  def self.receive_commands(interface, scope:)
36
36
  while true
37
- Store.read_topics(InterfaceTopic.topics(interface, scope: scope)) do |topic, msg_id, msg_hash, redis|
37
+ Topic.read_topics(InterfaceTopic.topics(interface, scope: scope)) do |topic, msg_id, msg_hash, redis|
38
38
  result = yield topic, msg_hash
39
39
  ack_topic = topic.split("__")
40
40
  ack_topic[1] = 'ACK' + ack_topic[1]
41
41
  ack_topic = ack_topic.join("__")
42
- Store.write_topic(ack_topic, { 'result' => result, 'id' => msg_id }, '*', 100)
42
+ Topic.write_topic(ack_topic, { 'result' => result, 'id' => msg_id }, '*', 100)
43
43
  end
44
44
  end
45
45
  end
46
46
 
47
47
  def self.write_raw(interface_name, data, scope:)
48
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'raw' => data }, '*', 100)
48
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'raw' => data }, '*', 100)
49
49
  end
50
50
 
51
51
  def self.connect_interface(interface_name, scope:)
52
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'connect' => 'true' }, '*', 100)
52
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'connect' => 'true' }, '*', 100)
53
53
  end
54
54
 
55
55
  def self.disconnect_interface(interface_name, scope:)
56
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'disconnect' => 'true' }, '*', 100)
56
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'disconnect' => 'true' }, '*', 100)
57
57
  end
58
58
 
59
59
  def self.start_raw_logging(interface_name, scope:)
60
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'log_raw' => 'true' }, '*', 100)
60
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'log_raw' => 'true' }, '*', 100)
61
61
  end
62
62
 
63
63
  def self.stop_raw_logging(interface_name, scope:)
64
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'log_raw' => 'false' }, '*', 100)
64
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'log_raw' => 'false' }, '*', 100)
65
65
  end
66
66
 
67
67
  def self.shutdown(interface, scope:)
68
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface.name}", { 'shutdown' => 'true' }, '*', 100)
68
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface.name}", { 'shutdown' => 'true' }, '*', 100)
69
69
  sleep 1 # Give some time for the interface to shutdown
70
70
  InterfaceTopic.clear_topics(InterfaceTopic.topics(interface, scope: scope))
71
71
  end
@@ -52,13 +52,13 @@ module Cosmos
52
52
  raise "Invalid limits event type '#{event[:type]}'"
53
53
  end
54
54
 
55
- Store.write_topic("#{scope}__cosmos_limits_events", event, '*', 1000)
55
+ Topic.write_topic("#{scope}__cosmos_limits_events", event, '*', 1000)
56
56
  end
57
57
 
58
58
  def self.read(offset = nil, count: 100, scope:)
59
59
  topic = "#{scope}__cosmos_limits_events"
60
60
  if offset
61
- result = Store.xread(topic, offset, count: count)
61
+ result = Topic.read_topics([topic], [offset], nil, count)
62
62
  if result.empty?
63
63
  [] # We want to return an empty array rather than an empty hash
64
64
  else
@@ -67,7 +67,9 @@ module Cosmos
67
67
  result[topic]
68
68
  end
69
69
  else
70
- Store.xrevrange(topic, count: 1)
70
+ result = Topic.get_newest_message(topic)
71
+ return [result] if result
72
+ return []
71
73
  end
72
74
  end
73
75
 
@@ -22,7 +22,7 @@ require 'cosmos/topics/topic'
22
22
  module Cosmos
23
23
  class NotificationsTopic < Topic
24
24
  def self.write_notification(notification, scope:)
25
- Store.write_topic("#{scope}__cosmos_notifications", notification)
25
+ Topic.write_topic("#{scope}__cosmos_notifications", notification)
26
26
  end
27
27
  end
28
28
  end
@@ -36,13 +36,13 @@ module Cosmos
36
36
 
37
37
  def self.receive_telemetry(router, scope:)
38
38
  while true
39
- Store.read_topics(RouterTopic.topics(router, scope: scope)) do |topic, msg_id, msg_hash, redis|
39
+ Topic.read_topics(RouterTopic.topics(router, scope: scope)) do |topic, msg_id, msg_hash, redis|
40
40
  result = yield topic, msg_hash
41
41
  if /CMD}ROUTER/.match?(topic)
42
42
  ack_topic = topic.split("__")
43
43
  ack_topic[1] = 'ACK' + ack_topic[1]
44
44
  ack_topic = ack_topic.join("__")
45
- Store.write_topic(ack_topic, { 'result' => result }, msg_id, 100)
45
+ Topic.write_topic(ack_topic, { 'result' => result }, msg_id, 100)
46
46
  end
47
47
  end
48
48
  end
@@ -51,33 +51,33 @@ module Cosmos
51
51
  def self.route_command(packet, target_names, scope:)
52
52
  if packet.identified?
53
53
  topic = "{#{scope}__CMD}TARGET__#{packet.target_name}"
54
- Store.write_topic(topic, { 'target_name' => packet.target_name, 'cmd_name' => packet.packet_name, 'cmd_buffer' => packet.buffer(false) }, '*', 100)
54
+ Topic.write_topic(topic, { 'target_name' => packet.target_name, 'cmd_name' => packet.packet_name, 'cmd_buffer' => packet.buffer(false) }, '*', 100)
55
55
  elsif target_names.length == 1
56
56
  topic = "{#{scope}__CMD}TARGET__#{target_names[0]}"
57
- Store.write_topic(topic, { 'target_name' => packet.target_name, 'cmd_name' => 'UNKNOWN', 'cmd_buffer' => packet.buffer(false) }, '*', 100)
57
+ Topic.write_topic(topic, { 'target_name' => packet.target_name, 'cmd_name' => 'UNKNOWN', 'cmd_buffer' => packet.buffer(false) }, '*', 100)
58
58
  else
59
59
  raise "No route for command: #{packet.target_name} #{packet.packet_name}"
60
60
  end
61
61
  end
62
62
 
63
63
  def self.connect_router(router_name, scope:)
64
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'connect' => true }, '*', 100)
64
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'connect' => true }, '*', 100)
65
65
  end
66
66
 
67
67
  def self.disconnect_router(router_name, scope:)
68
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'disconnect' => true }, '*', 100)
68
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'disconnect' => true }, '*', 100)
69
69
  end
70
70
 
71
71
  def self.start_raw_logging(router_name, scope:)
72
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'log_raw' => 'true' }, '*', 100)
72
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'log_raw' => 'true' }, '*', 100)
73
73
  end
74
74
 
75
75
  def self.stop_raw_logging(router_name, scope:)
76
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'log_raw' => 'false' }, '*', 100)
76
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'log_raw' => 'false' }, '*', 100)
77
77
  end
78
78
 
79
79
  def self.shutdown(router, scope:)
80
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router.name}", { 'shutdown' => 'true' }, '*', 100)
80
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router.name}", { 'shutdown' => 'true' }, '*', 100)
81
81
  sleep 1 # Give some time for the interface to shutdown
82
82
  RouterTopic.clear_topics(RouterTopic.topics(router, scope: scope))
83
83
  end
@@ -43,7 +43,7 @@ module Cosmos
43
43
  :received_count => packet.received_count,
44
44
  :json_data => JSON.generate(json_hash.as_json),
45
45
  }
46
- Store.write_topic("#{scope}__DECOM__{#{packet.target_name}}__#{packet.packet_name}", msg_hash, id)
46
+ Topic.write_topic("#{scope}__DECOM__{#{packet.target_name}}__#{packet.packet_name}", msg_hash, id)
47
47
  # Also update the current value table with the latest decommutated data
48
48
  CvtModel.set(json_hash, target_name: packet.target_name, packet_name: packet.packet_name, scope: scope)
49
49
  end
@@ -30,7 +30,7 @@ module Cosmos
30
30
  :received_count => packet.received_count,
31
31
  :buffer => packet.buffer(false),
32
32
  }
33
- Store.write_topic("#{scope}__TELEMETRY__{#{packet.target_name}}__#{packet.packet_name}", msg_hash)
33
+ Topic.write_topic("#{scope}__TELEMETRY__{#{packet.target_name}}__#{packet.packet_name}", msg_hash)
34
34
  end
35
35
  end
36
36
  end
@@ -39,7 +39,7 @@ module Cosmos
39
39
  # }
40
40
  # ```
41
41
  def self.write_activity(activity, scope:)
42
- Store.write_topic("#{scope}#{PRIMARY_KEY}", activity, '*', 1000)
42
+ Topic.write_topic("#{scope}#{PRIMARY_KEY}", activity, '*', 1000)
43
43
  end
44
44
  end
45
45
  end
@@ -21,28 +21,33 @@ require 'cosmos/utilities/store'
21
21
 
22
22
  module Cosmos
23
23
  class Topic
24
- def self.initialize_streams(topics)
25
- Store.initialize_streams(topics)
26
- end
27
-
28
- def self.read_topics(topics, offsets = nil, timeout_ms = 1000, &block)
29
- Store.read_topics(topics, offsets, timeout_ms, &block)
24
+ if RUBY_VERSION < "3"
25
+ # Delegate all unknown class methods to delegate to the EphemeralStore
26
+ def self.method_missing(message, *args, &block)
27
+ EphemeralStore.public_send(message, *args, &block)
28
+ end
29
+ else
30
+ # Delegate all unknown class methods to delegate to the EphemeralStore
31
+ def self.method_missing(message, *args, **kwargs, &block)
32
+ EphemeralStore.public_send(message, *args, **kwargs, &block)
33
+ end
30
34
  end
31
35
 
32
36
  def self.clear_topics(topics, maxlen = 0)
33
- topics.each do |topic|
34
- Store.xtrim(topic, maxlen)
35
- end
37
+ topics.each { |topic| EphemeralStore.xtrim(topic, maxlen) }
36
38
  end
37
39
 
38
40
  def self.topics(scope, key)
39
- topics = []
40
- loop do
41
- token, streams = Store.scan(0, :match => "#{scope}__#{key}__*", :count => 1000)
42
- topics.concat(streams)
43
- break if token == 0
44
- end
45
- topics
41
+ EphemeralStore
42
+ .scan_each(match: "#{scope}__#{key}__*", type: 'stream', count: 100)
43
+ .to_a # Change the enumerator into an array
44
+ .uniq # Scan can return duplicates so ensure unique
45
+ .sort # Sort not entirely necessary but nice
46
+ end
47
+
48
+ def self.get_cnt(topic)
49
+ _, packet = EphemeralStore.get_newest_message(topic)
50
+ packet ? packet["received_count"].to_i : 0
46
51
  end
47
52
  end
48
53
  end
@@ -19,7 +19,7 @@
19
19
 
20
20
  require 'cosmos/core_ext/class'
21
21
  require 'cosmos/core_ext/time'
22
- require 'cosmos/utilities/store'
22
+ require 'cosmos/topics/topic'
23
23
  require 'socket'
24
24
  require 'logger'
25
25
  require 'time'
@@ -174,11 +174,11 @@ module Cosmos
174
174
  puts data.to_json if @stdout
175
175
  unless @no_store
176
176
  if scope
177
- Store.write_topic("#{scope}__cosmos_log_messages", data)
177
+ Topic.write_topic("#{scope}__cosmos_log_messages", data)
178
178
  else
179
179
  # The base cosmos_log_messages doesn't have an associated logger
180
180
  # so it must be limited to prevent unbounded stream growth
181
- Store.write_topic("cosmos_log_messages", data, '*', 1000)
181
+ Topic.write_topic("cosmos_log_messages", data, '*', 1000)
182
182
  end
183
183
  end
184
184
  end
@@ -18,6 +18,7 @@
18
18
  # copyright holder
19
19
 
20
20
  require 'cosmos/models/metric_model'
21
+ require 'thread'
21
22
 
22
23
  module Cosmos
23
24
  class Metric
@@ -48,6 +49,7 @@ module Cosmos
48
49
  @scope = scope
49
50
  @microservice = microservice
50
51
  @size = 5000
52
+ @mutex = Mutex.new
51
53
  end
52
54
 
53
55
  def add_sample(name:, value:, labels:)
@@ -69,15 +71,17 @@ module Cosmos
69
71
  # the value is added to @items and the count of the value is increased
70
72
  # if the count of the values exceed the size of the array it sets the
71
73
  # count back to zero and the array will over write older data.
72
- key = "#{name}|" + labels.map { |k, v| "#{k}=#{v}" }.join(',')
73
- if not @items.has_key?(key)
74
- Logger.debug("new data for #{@scope}, #{key}")
75
- @items[key] = { 'values' => Array.new(@size), 'count' => 0 }
74
+ @mutex.synchronize do
75
+ key = "#{name}|" + labels.map { |k, v| "#{k}=#{v}" }.join(',')
76
+ if not @items.has_key?(key)
77
+ Logger.debug("new data for #{@scope}, #{key}")
78
+ @items[key] = { 'values' => Array.new(@size), 'count' => 0 }
79
+ end
80
+ count = @items[key]['count']
81
+ # Logger.info("adding data for #{@scope}, #{count} #{key}, #{value}")
82
+ @items[key]['values'][count] = value
83
+ @items[key]['count'] = count + 1 >= @size ? 0 : count + 1
76
84
  end
77
- count = @items[key]['count']
78
- # Logger.info("adding data for #{@scope}, #{count} #{key}, #{value}")
79
- @items[key]['values'][count] = value
80
- @items[key]['count'] = count + 1 >= @size ? 0 : count + 1
81
85
  end
82
86
 
83
87
  def percentile(sorted_values, percentile)
@@ -106,24 +110,26 @@ module Cosmos
106
110
  # array. to store the array as the value with the metric name again joined
107
111
  # with the @microservice and @scope.
108
112
  Logger.debug("#{@microservice} #{@scope} sending metrics to redis, #{@items.length}") if @items.length > 0
109
- @items.each do |key, values|
110
- label_list = []
111
- name, labels = key.split('|')
112
- metric_labels = labels.nil? ? {} : labels.split(',').map { |x| x.split('=') }.map { |k, v| { k => v } }.reduce({}, :merge)
113
- sorted_values = values['values'].compact.sort
114
- for percentile_value in [10, 50, 90, 95, 99]
115
- percentile_result = percentile(sorted_values, percentile_value)
116
- labels = metric_labels.clone.merge({ 'scope' => @scope, 'microservice' => @microservice })
117
- labels['percentile'] = percentile_value
118
- labels['metric__value'] = percentile_result
119
- label_list.append(labels)
120
- end
121
- begin
122
- Logger.debug("sending metrics summary to redis key: #{@microservice}")
123
- metric = MetricModel.new(name: @microservice, scope: @scope, metric_name: name, label_list: label_list)
124
- metric.create(force: true)
125
- rescue RuntimeError
126
- Logger.error("failed attempt to update metric, #{key}, #{name} #{@scope}")
113
+ @mutex.synchronize do
114
+ @items.each do |key, values|
115
+ label_list = []
116
+ name, labels = key.split('|')
117
+ metric_labels = labels.nil? ? {} : labels.split(',').map { |x| x.split('=') }.map { |k, v| { k => v } }.reduce({}, :merge)
118
+ sorted_values = values['values'].compact.sort
119
+ for percentile_value in [10, 50, 90, 95, 99]
120
+ percentile_result = percentile(sorted_values, percentile_value)
121
+ labels = metric_labels.clone.merge({ 'scope' => @scope, 'microservice' => @microservice })
122
+ labels['percentile'] = percentile_value
123
+ labels['metric__value'] = percentile_result
124
+ label_list.append(labels)
125
+ end
126
+ begin
127
+ Logger.debug("sending metrics summary to redis key: #{@microservice}")
128
+ metric = MetricModel.new(name: @microservice, scope: @scope, metric_name: name, label_list: label_list)
129
+ metric.create(force: true)
130
+ rescue RuntimeError
131
+ Logger.error("failed attempt to update metric, #{key}, #{name} #{@scope}")
132
+ end
127
133
  end
128
134
  end
129
135
  end
@@ -45,7 +45,7 @@ module Cosmos
45
45
  delimiter: '/',
46
46
  continuation_token: token
47
47
  })
48
-
48
+
49
49
  resp.common_prefixes.each do |item|
50
50
  folder_list << item.prefix
51
51
  end
@@ -18,6 +18,7 @@
18
18
  # copyright holder
19
19
 
20
20
  require 'fileutils'
21
+ require 'tmpdir'
21
22
  require 'cosmos'
22
23
  require 'cosmos/utilities/s3'
23
24
 
@@ -48,7 +49,7 @@ class S3File
48
49
 
49
50
  def retrieve
50
51
  local_path = "#{S3FileCache.instance.cache_dir}/#{File.basename(@s3_path)}"
51
- Cosmos::Logger.info "Retrieving #{@s3_path} from logs bucket"
52
+ Cosmos::Logger.debug "Retrieving #{@s3_path} from logs bucket"
52
53
  @rubys3_client.get_object(bucket: "logs", key: @s3_path, response_target: local_path)
53
54
  if File.exist?(local_path)
54
55
  @size = File.size(local_path)
@@ -57,6 +58,7 @@ class S3File
57
58
  rescue => err
58
59
  @error = err
59
60
  Cosmos::Logger.error "Failed to retrieve #{@s3_path}\n#{err.formatted}"
61
+ raise err
60
62
  end
61
63
 
62
64
  def reserve
@@ -165,11 +167,11 @@ class S3FileCache
165
167
  end
166
168
 
167
169
  # Create local file cache location
168
- @cache_dir = File.join(Dir.tmpdir, 'cosmos', 'file_cache', name)
170
+ @cache_dir = Dir.mktmpdir
169
171
  FileUtils.mkdir_p(@cache_dir)
170
-
171
- # Clear out local file cache
172
- FileUtils.rm_f Dir.glob("#{@cache_dir}/*")
172
+ at_exit do
173
+ FileUtils.remove_dir(@cache_dir, true)
174
+ end
173
175
 
174
176
  @cached_files = S3FileCollection.new
175
177
 
@@ -178,7 +180,11 @@ class S3FileCache
178
180
  file = @cached_files.get_next_to_retrieve
179
181
  # Cosmos::Logger.debug "Next file: #{file}"
180
182
  if file and (file.size + @cached_files.current_disk_usage()) <= @max_disk_usage
181
- file.retrieve
183
+ begin
184
+ file.retrieve
185
+ rescue
186
+ # Will be automatically retried
187
+ end
182
188
  else
183
189
  sleep(1)
184
190
  end
@@ -19,4 +19,5 @@
19
19
 
20
20
  module Cosmos
21
21
  autoload(:Store, "cosmos/utilities/store_autoload.rb")
22
+ autoload(:EphemeralStore, "cosmos/utilities/store_autoload.rb")
22
23
  end
@@ -31,7 +31,7 @@ end
31
31
  module Cosmos
32
32
  class Store
33
33
  # Variable that holds the singleton instance
34
- @@instance = nil
34
+ @instance = nil
35
35
 
36
36
  # Mutex used to ensure that only one instance is created
37
37
  @@instance_mutex = Mutex.new
@@ -42,11 +42,11 @@ module Cosmos
42
42
  # Get the singleton instance
43
43
  def self.instance(pool_size = 100)
44
44
  # Logger.level = Logger::DEBUG
45
- return @@instance if @@instance
45
+ return @instance if @instance
46
46
 
47
47
  @@instance_mutex.synchronize do
48
- @@instance ||= self.new(pool_size)
49
- return @@instance
48
+ @instance ||= self.new(pool_size)
49
+ return @instance
50
50
  end
51
51
  end
52
52
 
@@ -87,44 +87,10 @@ module Cosmos
87
87
  end
88
88
  end
89
89
 
90
- def get_cmd_item(target_name, packet_name, param_name, type: :WITH_UNITS, scope: $cosmos_scope)
91
- msg_id, msg_hash = read_topic_last("#{scope}__DECOMCMD__{#{target_name}}__#{packet_name}")
92
- if msg_id
93
- # TODO: We now have these reserved items directly on command packets
94
- # Do we still calculate from msg_hash['time'] or use the times directly?
95
- #
96
- # if param_name == 'RECEIVED_TIMESECONDS' || param_name == 'PACKET_TIMESECONDS'
97
- # Time.from_nsec_from_epoch(msg_hash['time'].to_i).to_f
98
- # elsif param_name == 'RECEIVED_TIMEFORMATTED' || param_name == 'PACKET_TIMEFORMATTED'
99
- # Time.from_nsec_from_epoch(msg_hash['time'].to_i).formatted
100
- if param_name == 'RECEIVED_COUNT'
101
- msg_hash['received_count'].to_i
102
- else
103
- json = msg_hash['json_data']
104
- hash = JSON.parse(json)
105
- # Start from the most complex down to the basic raw value
106
- value = hash["#{param_name}__U"]
107
- return value if value && type == :WITH_UNITS
108
-
109
- value = hash["#{param_name}__F"]
110
- return value if value && (type == :WITH_UNITS || type == :FORMATTED)
111
-
112
- value = hash["#{param_name}__C"]
113
- return value if value && (type == :WITH_UNITS || type == :FORMATTED || type == :CONVERTED)
114
-
115
- return hash[param_name]
116
- end
117
- end
118
- end
119
-
120
90
  ###########################################################################
121
91
  # Stream APIs
122
92
  ###########################################################################
123
93
 
124
- def self.initialize_streams(topics)
125
- self.instance.initialize_streams(topics)
126
- end
127
-
128
94
  def initialize_streams(topics)
129
95
  @redis_pool.with do |redis|
130
96
  topics.each do |topic|
@@ -134,48 +100,18 @@ module Cosmos
134
100
  end
135
101
  end
136
102
 
137
- def self.get_oldest_message(topic)
138
- self.instance.get_oldest_message(topic)
139
- end
140
-
141
103
  def get_oldest_message(topic)
142
104
  @redis_pool.with do |redis|
143
105
  result = redis.xrange(topic, count: 1)
144
- return result[0]
145
- end
146
- end
147
-
148
- def self.get_newest_message(topic)
149
- self.instance.get_newest_message(topic)
150
- end
151
-
152
- def get_newest_message(topic)
153
- @redis_pool.with do |redis|
154
- result = redis.xrevrange(topic, count: 1)
155
- return result[0]
156
- end
157
- end
158
-
159
- def self.get_last_offset(topic)
160
- self.instance.get_last_offset(topic)
161
- end
162
-
163
- def get_last_offset(topic)
164
- @redis_pool.with do |redis|
165
- result = redis.xrevrange(topic, count: 1)
166
- if result and result[0] and result[0][0]
167
- result[0][0]
106
+ if result and result.length > 0
107
+ return result[0]
168
108
  else
169
- "0-0"
109
+ return nil
170
110
  end
171
111
  end
172
112
  end
173
113
 
174
- def self.read_topic_last(topic)
175
- self.instance.read_topic_last(topic)
176
- end
177
-
178
- def read_topic_last(topic)
114
+ def get_newest_message(topic)
179
115
  @redis_pool.with do |redis|
180
116
  # Default in xrevrange is range end '+', start '-' which means get all
181
117
  # elements from higher ID to lower ID and since we're limiting to 1
@@ -189,18 +125,15 @@ module Cosmos
189
125
  end
190
126
  end
191
127
 
192
- # TODO: Currently unused
193
- # def decrement_id(id)
194
- # time, sequence = id.split('-')
195
- # if sequence == '0'
196
- # "#{time.to_i - 1}-18446744073709551615"
197
- # else
198
- # "#{time}-#{sequence.to_i - 1}"
199
- # end
200
- # end
201
-
202
- def self.update_topic_offsets(topics)
203
- self.instance.update_topic_offsets(topics)
128
+ def get_last_offset(topic)
129
+ @redis_pool.with do |redis|
130
+ result = redis.xrevrange(topic, count: 1)
131
+ if result and result[0] and result[0][0]
132
+ result[0][0]
133
+ else
134
+ "0-0"
135
+ end
136
+ end
204
137
  end
205
138
 
206
139
  def update_topic_offsets(topics)
@@ -221,15 +154,12 @@ module Cosmos
221
154
  return offsets
222
155
  end
223
156
 
224
- def self.read_topics(topics, offsets = nil, timeout_ms = 1000, &block)
225
- self.instance.read_topics(topics, offsets, timeout_ms, &block)
226
- end
227
157
  unless $enterprise_cosmos
228
- def read_topics(topics, offsets = nil, timeout_ms = 1000)
158
+ def read_topics(topics, offsets = nil, timeout_ms = 1000, count = nil)
229
159
  # Logger.debug "read_topics: #{topics}, #{offsets} pool:#{@redis_pool}"
230
160
  @redis_pool.with do |redis|
231
161
  offsets = update_topic_offsets(topics) unless offsets
232
- result = redis.xread(topics, offsets, block: timeout_ms)
162
+ result = redis.xread(topics, offsets, block: timeout_ms, count: count)
233
163
  if result and result.length > 0
234
164
  result.each do |topic, messages|
235
165
  messages.each do |msg_id, msg_hash|
@@ -244,26 +174,6 @@ module Cosmos
244
174
  end
245
175
  end
246
176
 
247
- # Add new entry to the redis stream.
248
- # > https://www.rubydoc.info/github/redis/redis-rb/Redis:xadd
249
- #
250
- # @example Without options
251
- # COSMOS::Store().write_topic('MANGO__TOPIC', {'message' => 'something'})
252
- # @example With options
253
- # COSMOS::Store().write_topic('MANGO__TOPIC', {'message' => 'something'}, id: '0-0', maxlen: 1000, approximate: false)
254
- #
255
- # @param topic [String] the stream / topic
256
- # @param msg_hash [Hash] one or multiple field-value pairs
257
- #
258
- # @option opts [String] :id the entry id, default value is `*`, it means auto generation
259
- # @option opts [Integer] :maxlen max length of entries
260
- # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
261
- #
262
- # @return [String] the entry id
263
- def self.write_topic(topic, msg_hash, id = '*', maxlen = nil, approximate = true)
264
- self.instance.write_topic(topic, msg_hash, id, maxlen, approximate)
265
- end
266
-
267
177
  # Add new entry to the redis stream.
268
178
  # > https://www.rubydoc.info/github/redis/redis-rb/Redis:xadd
269
179
  #
@@ -289,23 +199,6 @@ module Cosmos
289
199
  end
290
200
  end
291
201
 
292
- # Trims older entries of the redis stream if needed.
293
- # > https://www.rubydoc.info/github/redis/redis-rb/Redis:xtrim
294
- #
295
- # @example Without options
296
- # COSMOS::Store.trim_topic('MANGO__TOPIC', 1000)
297
- # @example With options
298
- # COSMOS::Store.trim_topic('MANGO__TOPIC', 1000, approximate: true, limit: 0)
299
- #
300
- # @param topic [String] the stream key
301
- # @param minid [Integer] max length of entries to trim
302
- # @param limit [Boolean] whether to add `~` modifier of maxlen or not
303
- #
304
- # @return [Integer] the number of entries actually deleted
305
- def self.trim_topic(topic, minid, approximate = true, limit: 0)
306
- self.instance.trim_topic(topic, minid, approximate, limit: limit)
307
- end
308
-
309
202
  # Trims older entries of the redis stream if needed.
310
203
  # > https://www.rubydoc.info/github/redis/redis-rb/Redis:xtrim
311
204
  #
@@ -325,6 +218,14 @@ module Cosmos
325
218
  end
326
219
  end
327
220
  end
221
+
222
+ class EphemeralStore < Store
223
+ def initialize(pool_size = 10)
224
+ super(pool_size)
225
+ @redis_url = "redis://#{ENV['COSMOS_REDIS_EPHEMERAL_HOSTNAME']}:#{ENV['COSMOS_REDIS_EPHEMERAL_PORT']}"
226
+ @redis_pool = ConnectionPool.new(size: pool_size) { build_redis() }
227
+ end
228
+ end
328
229
  end
329
230
 
330
231
  class Redis
@@ -1,14 +1,14 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- COSMOS_VERSION = '5.0.3'
3
+ COSMOS_VERSION = '5.0.4'
4
4
  module Cosmos
5
5
  module Version
6
6
  MAJOR = '5'
7
7
  MINOR = '0'
8
- PATCH = '3'
8
+ PATCH = '4'
9
9
  OTHER = ''
10
- BUILD = '715c106240ff43bb59be2d17d1f5aed50595fbc9'
10
+ BUILD = '84d6dcebd4bc21e5559963be4e5fde2ddb7e9822'
11
11
  end
12
- VERSION = '5.0.3'
13
- GEM_VERSION = '5.0.3'
12
+ VERSION = '5.0.4'
13
+ GEM_VERSION = '5.0.4'
14
14
  end