cosmos 5.0.3 → 5.0.4

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