streamdal 0.0.1
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 +7 -0
- data/lib/audiences.rb +45 -0
- data/lib/audiences_spec.rb +5 -0
- data/lib/hostfunc.rb +109 -0
- data/lib/hostfunc_spec.rb +5 -0
- data/lib/kv.rb +52 -0
- data/lib/kv_spec.rb +54 -0
- data/lib/metrics.rb +265 -0
- data/lib/metrics_spec.rb +5 -0
- data/lib/schema.rb +47 -0
- data/lib/schema_spec.rb +59 -0
- data/lib/spec_helper.rb +2 -0
- data/lib/streamdal.rb +852 -0
- data/lib/streamdal_spec.rb +5 -0
- data/lib/tail.rb +97 -0
- data/lib/tail_spec.rb +5 -0
- data/lib/validation.rb +88 -0
- data/lib/validation_spec.rb +77 -0
- metadata +59 -0
data/lib/tail.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# TODO: implement token bucket limiter
|
2
|
+
require "bozos_buckets"
|
3
|
+
|
4
|
+
NUM_TAIL_WORKERS = 2
|
5
|
+
MIN_TAIL_RESPONSE_INTERVAL_MS = 100
|
6
|
+
|
7
|
+
module Streamdal
|
8
|
+
class Tail
|
9
|
+
attr_accessor :queue, :active, :request
|
10
|
+
|
11
|
+
def initialize(request, streamdal_url, auth_token, log, metrics, active = false)
|
12
|
+
@request = request
|
13
|
+
@streamdal_url = streamdal_url
|
14
|
+
@auth_token = auth_token
|
15
|
+
@logger = log
|
16
|
+
@metrics = metrics
|
17
|
+
@active = active
|
18
|
+
@last_msg = Time::at(0)
|
19
|
+
@queue = Queue.new
|
20
|
+
@workers = []
|
21
|
+
|
22
|
+
# Only use rate limiting if sample_options is set
|
23
|
+
unless request.sample_options.nil?
|
24
|
+
@limiter = BozosBuckets::Bucket.new(
|
25
|
+
initial_token_count: request.sample_options.sample_rate,
|
26
|
+
refill_rate: request.sample_options.sample_interval_seconds,
|
27
|
+
max_token_count: request.sample_options.sample_rate
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def start_tail_workers
|
33
|
+
NUM_TAIL_WORKERS.times do |worker_id|
|
34
|
+
@workers << Thread.new { start_tail_worker(worker_id + 1) }
|
35
|
+
end
|
36
|
+
|
37
|
+
@active = true
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop_tail
|
41
|
+
@active = false
|
42
|
+
|
43
|
+
sleep(1)
|
44
|
+
|
45
|
+
@workers.each do |worker|
|
46
|
+
if worker.alive?
|
47
|
+
worker.exit
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def start_tail_worker(worker_id)
|
54
|
+
@logger.debug("Starting tail worker #{worker_id}")
|
55
|
+
|
56
|
+
# Each worker gets it's own gRPC connection
|
57
|
+
stub = Streamdal::Protos::Internal::Stub.new(@streamdal_url, :this_channel_is_insecure)
|
58
|
+
|
59
|
+
while @active
|
60
|
+
# If the queue is empty, sleep for a bit and loop again
|
61
|
+
if @queue.empty?
|
62
|
+
sleep(0.1)
|
63
|
+
next
|
64
|
+
end
|
65
|
+
|
66
|
+
if Time::now - @last_msg < MIN_TAIL_RESPONSE_INTERVAL_MS
|
67
|
+
sleep(MIN_TAIL_RESPONSE_INTERVAL_MS)
|
68
|
+
@metrics.incr(Metrics::CounterEntry.new(COUNTER_DROPPED_TAIL_MESSAGES, nil, {}, 1))
|
69
|
+
@logger.debug("Dropped tail message for '#{@request.id}' due to rate limiting")
|
70
|
+
next
|
71
|
+
end
|
72
|
+
|
73
|
+
unless stub.nil?
|
74
|
+
tail_response = @queue.pop(non_block = false)
|
75
|
+
@logger.debug("Sending tail request for '#{tail_response.tail_request_id}'")
|
76
|
+
|
77
|
+
begin
|
78
|
+
stub.send_tail([tail_response], metadata: { "auth-token" => @auth_token })
|
79
|
+
rescue => e
|
80
|
+
@logger.error("Error sending tail request: #{e}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
@logger.debug "Tail worker #{worker_id} exited"
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
def should_send
|
90
|
+
if @limiter.nil?
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
@limiter.use_tokens(1)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/tail_spec.rb
ADDED
data/lib/validation.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
##
|
2
|
+
# Validation mix-in module
|
3
|
+
require "steps/sp_steps_kv_pb"
|
4
|
+
|
5
|
+
module Validation
|
6
|
+
def validate_set_pipelines(cmd)
|
7
|
+
if cmd.nil?
|
8
|
+
raise "cmd is required"
|
9
|
+
end
|
10
|
+
|
11
|
+
if cmd.audience.nil?
|
12
|
+
raise "audience is required"
|
13
|
+
end
|
14
|
+
|
15
|
+
if cmd.set_pipelines.nil?
|
16
|
+
raise "set_pipelines command is required"
|
17
|
+
end
|
18
|
+
|
19
|
+
cmd.set_pipelines.each do |pipeline|
|
20
|
+
if pipeline.id == ""
|
21
|
+
raise "pipeline id is required"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_kv_command(cmd)
|
27
|
+
if cmd.nil?
|
28
|
+
raise "cmd is required"
|
29
|
+
end
|
30
|
+
|
31
|
+
if cmd.kv.nil?
|
32
|
+
raise "kv command is required"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_kv_instruction(inst)
|
37
|
+
if inst.nil?
|
38
|
+
raise "instruction is required"
|
39
|
+
end
|
40
|
+
|
41
|
+
if inst.id == ""
|
42
|
+
raise "instruction id is required"
|
43
|
+
end
|
44
|
+
|
45
|
+
if inst.action.nil?
|
46
|
+
raise "instruction action is required"
|
47
|
+
end
|
48
|
+
|
49
|
+
if inst.action == :KV_ACTION_UNSET
|
50
|
+
raise "instruction action is required"
|
51
|
+
end
|
52
|
+
|
53
|
+
if inst.object.nil? and inst.action != :KV_ACTION_DELETE_ALL
|
54
|
+
raise "instruction object is required"
|
55
|
+
end
|
56
|
+
|
57
|
+
validate_kv_object(inst.object)
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_kv_object(obj)
|
61
|
+
if obj.nil?
|
62
|
+
raise "object is required"
|
63
|
+
end
|
64
|
+
|
65
|
+
if obj.key == ""
|
66
|
+
raise "kv object key is required"
|
67
|
+
end
|
68
|
+
|
69
|
+
if obj.value == ""
|
70
|
+
raise "kv object value is required"
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
def validate_tail_request(cmd)
|
76
|
+
if cmd.nil?
|
77
|
+
raise "cmd is required"
|
78
|
+
end
|
79
|
+
|
80
|
+
if cmd.audience.nil?
|
81
|
+
raise "audience is required"
|
82
|
+
end
|
83
|
+
|
84
|
+
if cmd.tail.nil?
|
85
|
+
raise "tail is required"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'sp_command_pb'
|
3
|
+
require 'sp_kv_pb'
|
4
|
+
require_relative 'spec_helper'
|
5
|
+
require_relative 'validation'
|
6
|
+
|
7
|
+
module Streamdal
|
8
|
+
class TestClient
|
9
|
+
include Validation
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec.describe "Validation" do
|
14
|
+
let(:validation) { Streamdal::TestClient.new }
|
15
|
+
|
16
|
+
context "#validate_kv_command" do
|
17
|
+
it "raises an error if cmd is nil" do
|
18
|
+
expect { validation.validate_kv_command(nil) }.to raise_error("cmd is required")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "raises an error if cmd.kv is nil" do
|
22
|
+
cmd = Streamdal::Protos::Command.new
|
23
|
+
expect { validation.validate_kv_command(cmd) }.to raise_error("kv command is required")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#validate_kv_instruction" do
|
28
|
+
it "raises an error if inst is nil" do
|
29
|
+
expect { validation.validate_kv_instruction(nil) }.to raise_error("instruction is required")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "raises an error if inst.id is empty" do
|
33
|
+
inst = Streamdal::Protos::KVInstruction.new
|
34
|
+
expect { validation.validate_kv_instruction(inst) }.to raise_error("instruction id is required")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises an error if inst.action is KV_ACTION_UNSET" do
|
38
|
+
inst = Streamdal::Protos::KVInstruction.new(id: "id", action: 0)
|
39
|
+
expect { validation.validate_kv_instruction(inst) }.to raise_error("instruction action is required")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "raises an error if inst.object is nil" do
|
43
|
+
inst = Streamdal::Protos::KVInstruction.new(id: "id", action: 1)
|
44
|
+
expect { validation.validate_kv_instruction(inst) }.to raise_error("instruction object is required")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context '#validate_kv_object' do
|
49
|
+
it "raises an error if obj is nil" do
|
50
|
+
expect { validation.validate_kv_object(nil) }.to raise_error("object is required")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "raises an error if obj.key is empty" do
|
54
|
+
obj = Streamdal::Protos::KVObject.new
|
55
|
+
expect { validation.validate_kv_object(obj) }.to raise_error("kv object key is required")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "raises an error if obj.value is empty" do
|
59
|
+
obj = Streamdal::Protos::KVObject.new(key: "key")
|
60
|
+
expect { validation.validate_kv_object(obj) }.to raise_error("kv object value is required")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "#validate_tail_request" do
|
65
|
+
it "raises an error if cmd is nil" do
|
66
|
+
expect { validation.validate_tail_request(nil) }.to raise_error("cmd is required")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "raises an error if cmd.tail_request is nil" do
|
70
|
+
cmd = Streamdal::Protos::Command.new
|
71
|
+
cmd.audience = Streamdal::Protos::Audience.new
|
72
|
+
cmd.tail = nil
|
73
|
+
expect { validation.validate_tail_request(cmd) }.to raise_error("tail is required")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: streamdal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark Gregan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-05-03 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: mark@streamdal.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/audiences.rb
|
20
|
+
- lib/audiences_spec.rb
|
21
|
+
- lib/hostfunc.rb
|
22
|
+
- lib/hostfunc_spec.rb
|
23
|
+
- lib/kv.rb
|
24
|
+
- lib/kv_spec.rb
|
25
|
+
- lib/metrics.rb
|
26
|
+
- lib/metrics_spec.rb
|
27
|
+
- lib/schema.rb
|
28
|
+
- lib/schema_spec.rb
|
29
|
+
- lib/spec_helper.rb
|
30
|
+
- lib/streamdal.rb
|
31
|
+
- lib/streamdal_spec.rb
|
32
|
+
- lib/tail.rb
|
33
|
+
- lib/tail_spec.rb
|
34
|
+
- lib/validation.rb
|
35
|
+
- lib/validation_spec.rb
|
36
|
+
homepage: https://docs.streamdal.com
|
37
|
+
licenses:
|
38
|
+
- Apache-2.0
|
39
|
+
metadata: {}
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - '='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 0.0.1
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubygems_version: 3.3.5
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: Streamdal SDK
|
59
|
+
test_files: []
|