-
Notifications
You must be signed in to change notification settings - Fork 43
feat: read backup file and bootstrap for streaming #255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
e33e8cd
2959297
0690871
425c1c4
3ab22d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
require 'unleash/configuration' | ||
require 'json' | ||
|
||
module Unleash | ||
class BackupFileReader | ||
def self.read! | ||
Unleash.logger.debug "read!()" | ||
|
||
backup_file = Unleash.configuration.backup_file | ||
return nil unless File.exist?(backup_file) | ||
|
||
File.read(backup_file) | ||
rescue IOError => e | ||
# :nocov: | ||
Unleash.logger.error "Unable to read the backup_file: #{e}" | ||
# :nocov: | ||
nil | ||
rescue JSON::ParserError => e | ||
|
||
# :nocov: | ||
Unleash.logger.error "Unable to parse JSON from existing backup_file: #{e}" | ||
# :nocov: | ||
nil | ||
rescue StandardError => e | ||
# :nocov: | ||
Unleash.logger.error "Unable to extract valid data from backup_file. Exception thrown: #{e}" | ||
# :nocov: | ||
nil | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,4 +1,6 @@ | ||||||
require 'unleash/streaming_event_processor' | ||||||
require 'unleash/bootstrap/handler' | ||||||
require 'unleash/backup_file_reader' | ||||||
require 'unleash/util/event_source_wrapper' | ||||||
|
||||||
module Unleash | ||||||
|
@@ -10,6 +12,20 @@ def initialize(name, engine) | |||||
self.event_source = nil | ||||||
self.event_processor = Unleash::StreamingEventProcessor.new(engine) | ||||||
self.running = false | ||||||
|
||||||
begin | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this was missing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. similar setup to toggle fetcher in polling mode but we don't make first request to the server and instead go straight to either bootstrap or a backup file. Then streaming starts and takes over from there |
||||||
# if bootstrap configuration is available, initialize. Otherwise read backup file | ||||||
if Unleash.configuration.use_bootstrap? | ||||||
bootstrap(engine) | ||||||
else | ||||||
read_backup_file!(engine) | ||||||
end | ||||||
rescue StandardError => e | ||||||
# fail back to reading the backup file | ||||||
|
# fail back to reading the backup file | |
# fall back to reading the backup file |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
RSpec.describe Unleash::StreamingClientExecutor do | ||
unless RUBY_ENGINE == 'jruby' | ||
before do | ||
Unleash.configure do |config| | ||
config.url = 'http://streaming-test-url/' | ||
config.app_name = 'streaming-test-app' | ||
config.instance_id = 'rspec/streaming' | ||
config.disable_metrics = true | ||
config.experimental_mode = { type: 'streaming' } | ||
end | ||
|
||
WebMock.stub_request(:post, "http://streaming-test-url/client/register") | ||
.to_return(status: 200, body: "", headers: {}) | ||
|
||
Unleash.logger = Unleash.configuration.logger | ||
end | ||
|
||
after do | ||
WebMock.reset! | ||
File.delete(Unleash.configuration.backup_file) if File.exist?(Unleash.configuration.backup_file) | ||
|
||
# Reset configuration to prevent interference with other tests | ||
Unleash.configuration.bootstrap_config = nil | ||
Unleash.configuration.experimental_mode = nil | ||
Unleash.configuration.disable_metrics = false | ||
end | ||
|
||
describe '.new' do | ||
let(:engine) { YggdrasilEngine.new } | ||
let(:executor_name) { 'streaming_client_executor_spec' } | ||
|
||
context 'when there are problems connecting to streaming endpoint' do | ||
let(:backup_toggles) do | ||
{ | ||
version: 1, | ||
features: [ | ||
{ | ||
name: "backup-feature", | ||
description: "Feature from backup", | ||
enabled: true, | ||
strategies: [{ | ||
"name": "default" | ||
}] | ||
} | ||
] | ||
} | ||
end | ||
|
||
let(:streaming_executor) { described_class.new(executor_name, engine) } | ||
|
||
before do | ||
backup_file = Unleash.configuration.backup_file | ||
|
||
# manually create a stub cache on disk, so we can test that we read it correctly later. | ||
File.open(backup_file, "w") do |file| | ||
file.write(backup_toggles.to_json) | ||
end | ||
|
||
# Simulate streaming connection failure | ||
WebMock.stub_request(:get, "http://streaming-test-url/client/streaming") | ||
.to_return(status: 500, body: "Internal Server Error", headers: {}) | ||
|
||
streaming_executor | ||
end | ||
|
||
it 'reads the backup file for values' do | ||
enabled = engine.enabled?('backup-feature', {}) | ||
expect(enabled).to eq(true) | ||
end | ||
end | ||
|
||
context 'when bootstrap is configured' do | ||
let(:bootstrap_data) do | ||
{ | ||
version: 1, | ||
features: [ | ||
{ | ||
name: "bootstrap-feature", | ||
enabled: true, | ||
strategies: [{ name: "default" }] | ||
} | ||
] | ||
} | ||
end | ||
|
||
let(:bootstrap_config) do | ||
Unleash::Bootstrap::Configuration.new({ | ||
'data' => bootstrap_data.to_json | ||
}) | ||
end | ||
|
||
let(:streaming_executor) { described_class.new(executor_name, engine) } | ||
|
||
before do | ||
Unleash.configuration.bootstrap_config = bootstrap_config | ||
|
||
# Streaming connection might succeed or fail, doesn't matter for bootstrap | ||
WebMock.stub_request(:get, "http://streaming-test-url/client/streaming") | ||
.to_return(status: 200, body: "", headers: {}) | ||
|
||
streaming_executor | ||
end | ||
|
||
after do | ||
Unleash.configuration.bootstrap_config = nil | ||
end | ||
|
||
it 'uses bootstrap data on initialization' do | ||
enabled = engine.enabled?('bootstrap-feature', {}) | ||
expect(enabled).to eq(true) | ||
end | ||
|
||
it 'clears bootstrap config after use' do | ||
expect(Unleash.configuration.bootstrap_config).to be_nil | ||
end | ||
end | ||
|
||
context 'when bootstrap fails and backup file exists' do | ||
let(:invalid_bootstrap_config) do | ||
Unleash::Bootstrap::Configuration.new({ | ||
'data' => 'invalid json' | ||
}) | ||
end | ||
|
||
let(:fallback_toggles) do | ||
{ | ||
version: 1, | ||
features: [ | ||
{ | ||
name: "fallback-feature", | ||
enabled: true, | ||
strategies: [{ name: "default" }] | ||
} | ||
] | ||
} | ||
end | ||
|
||
let(:streaming_executor) { described_class.new(executor_name, engine) } | ||
|
||
before do | ||
backup_file = Unleash.configuration.backup_file | ||
|
||
File.open(backup_file, "w") do |file| | ||
file.write(fallback_toggles.to_json) | ||
end | ||
|
||
Unleash.configuration.bootstrap_config = invalid_bootstrap_config | ||
|
||
# Streaming connection failure doesn't matter here | ||
WebMock.stub_request(:get, "http://streaming-test-url/client/streaming") | ||
.to_return(status: 500, body: "", headers: {}) | ||
|
||
streaming_executor | ||
end | ||
|
||
after do | ||
Unleash.configuration.bootstrap_config = nil | ||
end | ||
|
||
it 'falls back to reading backup file when bootstrap fails' do | ||
enabled = engine.enabled?('fallback-feature', {}) | ||
expect(enabled).to eq(true) | ||
end | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is extracted from toggle_fetcher
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I really like this