Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
c66fb99d
Commit
c66fb99d
authored
Jan 11, 2022
by
nmilojevic1
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove redis MultiStore implementation
Changelog: removed
parent
f3a1cbfc
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
12 additions
and
1104 deletions
+12
-1104
config/feature_flags/development/use_primary_and_secondary_stores_for_sessions.yml
...lopment/use_primary_and_secondary_stores_for_sessions.yml
+0
-8
config/feature_flags/development/use_primary_store_as_default_for_sessions.yml
...development/use_primary_store_as_default_for_sessions.yml
+0
-8
lib/gitlab/redis/multi_store.rb
lib/gitlab/redis/multi_store.rb
+0
-232
lib/gitlab/redis/sessions.rb
lib/gitlab/redis/sessions.rb
+3
-33
spec/lib/gitlab/redis/multi_store_spec.rb
spec/lib/gitlab/redis/multi_store_spec.rb
+0
-716
spec/lib/gitlab/redis/sessions_spec.rb
spec/lib/gitlab/redis/sessions_spec.rb
+9
-64
spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb
...gitlab/redis/multi_store_feature_flags_shared_examples.rb
+0
-43
No files found.
config/feature_flags/development/use_primary_and_secondary_stores_for_sessions.yml
deleted
100644 → 0
View file @
f3a1cbfc
---
name
:
use_primary_and_secondary_stores_for_sessions
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73660
rollout_issue_url
:
https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1429
milestone
:
'
14.6'
type
:
development
group
:
group::memory
default_enabled
:
false
config/feature_flags/development/use_primary_store_as_default_for_sessions.yml
deleted
100644 → 0
View file @
f3a1cbfc
---
name
:
use_primary_store_as_default_for_sessions
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75258
rollout_issue_url
:
milestone
:
'
14.6'
type
:
development
group
:
group::memory
default_enabled
:
false
lib/gitlab/redis/multi_store.rb
deleted
100644 → 0
View file @
f3a1cbfc
# frozen_string_literal: true
module
Gitlab
module
Redis
class
MultiStore
include
Gitlab
::
Utils
::
StrongMemoize
class
ReadFromPrimaryError
<
StandardError
def
message
'Value not found on the redis primary store. Read from the redis secondary store successful.'
end
end
class
MethodMissingError
<
StandardError
def
message
'Method missing. Falling back to execute method on the redis secondary store.'
end
end
attr_reader
:primary_store
,
:secondary_store
,
:instance_name
FAILED_TO_READ_ERROR_MESSAGE
=
'Failed to read from the redis primary_store.'
FAILED_TO_WRITE_ERROR_MESSAGE
=
'Failed to write to the redis primary_store.'
SKIP_LOG_METHOD_MISSING_FOR_COMMANDS
=
%i(info)
.
freeze
READ_COMMANDS
=
%i(
get
mget
smembers
scard
)
.
freeze
WRITE_COMMANDS
=
%i(
set
setnx
setex
sadd
srem
del
pipelined
flushdb
)
.
freeze
def
initialize
(
primary_store
,
secondary_store
,
instance_name
)
@primary_store
=
primary_store
@secondary_store
=
secondary_store
@instance_name
=
instance_name
validate_stores!
end
# rubocop:disable GitlabSecurity/PublicSend
READ_COMMANDS
.
each
do
|
name
|
define_method
(
name
)
do
|*
args
,
&
block
|
if
use_primary_and_secondary_stores?
read_command
(
name
,
*
args
,
&
block
)
else
default_store
.
send
(
name
,
*
args
,
&
block
)
end
end
end
WRITE_COMMANDS
.
each
do
|
name
|
define_method
(
name
)
do
|*
args
,
&
block
|
if
use_primary_and_secondary_stores?
write_command
(
name
,
*
args
,
&
block
)
else
default_store
.
send
(
name
,
*
args
,
&
block
)
end
end
end
def
method_missing
(
...
)
return
@instance
.
send
(
...
)
if
@instance
log_method_missing
(
...
)
default_store
.
send
(
...
)
end
# rubocop:enable GitlabSecurity/PublicSend
def
respond_to_missing?
(
command_name
,
include_private
=
false
)
true
end
# This is needed because of Redis::Rack::Connection is requiring Redis::Store
# https://github.com/redis-store/redis-rack/blob/a833086ba494083b6a384a1a4e58b36573a9165d/lib/redis/rack/connection.rb#L15
# Done similarly in https://github.com/lsegal/yard/blob/main/lib/yard/templates/template.rb#L122
def
is_a?
(
klass
)
return
true
if
klass
==
default_store
.
class
super
(
klass
)
end
alias_method
:kind_of?
,
:is_a?
def
to_s
use_primary_and_secondary_stores?
?
primary_store
.
to_s
:
default_store
.
to_s
end
def
use_primary_and_secondary_stores?
feature_table_exists?
&&
Feature
.
enabled?
(
"use_primary_and_secondary_stores_for_
#{
instance_name
.
underscore
}
"
,
default_enabled: :yaml
)
&&
!
same_redis_store?
end
def
use_primary_store_as_default?
feature_table_exists?
&&
Feature
.
enabled?
(
"use_primary_store_as_default_for_
#{
instance_name
.
underscore
}
"
,
default_enabled: :yaml
)
&&
!
same_redis_store?
end
private
# @return [Boolean]
def
feature_table_exists?
Feature
::
FlipperFeature
.
table_exists?
rescue
StandardError
false
end
def
default_store
use_primary_store_as_default?
?
primary_store
:
secondary_store
end
def
log_method_missing
(
command_name
,
*
_args
)
return
if
SKIP_LOG_METHOD_MISSING_FOR_COMMANDS
.
include?
(
command_name
)
log_error
(
MethodMissingError
.
new
,
command_name
)
increment_method_missing_count
(
command_name
)
end
def
read_command
(
command_name
,
*
args
,
&
block
)
if
@instance
send_command
(
@instance
,
command_name
,
*
args
,
&
block
)
else
read_one_with_fallback
(
command_name
,
*
args
,
&
block
)
end
end
def
write_command
(
command_name
,
*
args
,
&
block
)
if
@instance
send_command
(
@instance
,
command_name
,
*
args
,
&
block
)
else
write_both
(
command_name
,
*
args
,
&
block
)
end
end
def
read_one_with_fallback
(
command_name
,
*
args
,
&
block
)
begin
value
=
send_command
(
primary_store
,
command_name
,
*
args
,
&
block
)
rescue
StandardError
=>
e
log_error
(
e
,
command_name
,
multi_store_error_message:
FAILED_TO_READ_ERROR_MESSAGE
)
end
value
||=
fallback_read
(
command_name
,
*
args
,
&
block
)
value
end
def
fallback_read
(
command_name
,
*
args
,
&
block
)
value
=
send_command
(
secondary_store
,
command_name
,
*
args
,
&
block
)
if
value
log_error
(
ReadFromPrimaryError
.
new
,
command_name
)
increment_read_fallback_count
(
command_name
)
end
value
end
def
write_both
(
command_name
,
*
args
,
&
block
)
begin
send_command
(
primary_store
,
command_name
,
*
args
,
&
block
)
rescue
StandardError
=>
e
log_error
(
e
,
command_name
,
multi_store_error_message:
FAILED_TO_WRITE_ERROR_MESSAGE
)
end
send_command
(
secondary_store
,
command_name
,
*
args
,
&
block
)
end
def
same_redis_store?
strong_memoize
(
:same_redis_store
)
do
# <Redis client v4.4.0 for redis:///path_to/redis/redis.socket/5>"
primary_store
.
inspect
==
secondary_store
.
inspect
end
end
# rubocop:disable GitlabSecurity/PublicSend
def
send_command
(
redis_instance
,
command_name
,
*
args
,
&
block
)
if
block_given?
# Make sure that block is wrapped and executed only on the redis instance that is executing the block
redis_instance
.
send
(
command_name
,
*
args
)
do
|*
params
|
with_instance
(
redis_instance
,
*
params
,
&
block
)
end
else
redis_instance
.
send
(
command_name
,
*
args
)
end
end
# rubocop:enable GitlabSecurity/PublicSend
def
with_instance
(
instance
,
*
params
)
@instance
=
instance
yield
(
*
params
)
ensure
@instance
=
nil
end
def
increment_read_fallback_count
(
command_name
)
@read_fallback_counter
||=
Gitlab
::
Metrics
.
counter
(
:gitlab_redis_multi_store_read_fallback_total
,
'Client side Redis MultiStore reading fallback'
)
@read_fallback_counter
.
increment
(
command:
command_name
,
instance_name:
instance_name
)
end
def
increment_method_missing_count
(
command_name
)
@method_missing_counter
||=
Gitlab
::
Metrics
.
counter
(
:gitlab_redis_multi_store_method_missing_total
,
'Client side Redis MultiStore method missing'
)
@method_missing_counter
.
increment
(
command:
command_name
,
instance_name:
instance_name
)
end
def
validate_stores!
raise
ArgumentError
,
'primary_store is required'
unless
primary_store
raise
ArgumentError
,
'secondary_store is required'
unless
secondary_store
raise
ArgumentError
,
'instance_name is required'
unless
instance_name
raise
ArgumentError
,
'invalid primary_store'
unless
primary_store
.
is_a?
(
::
Redis
)
raise
ArgumentError
,
'invalid secondary_store'
unless
secondary_store
.
is_a?
(
::
Redis
)
end
def
log_error
(
exception
,
command_name
,
extra
=
{})
Gitlab
::
ErrorTracking
.
log_exception
(
exception
,
command_name:
command_name
,
extra:
extra
.
merge
(
instance_name:
instance_name
))
end
end
end
end
lib/gitlab/redis/sessions.rb
View file @
c66fb99d
...
...
@@ -9,39 +9,9 @@ module Gitlab
IP_SESSIONS_LOOKUP_NAMESPACE
=
'session:lookup:ip:gitlab2'
OTP_SESSIONS_NAMESPACE
=
'session:otp'
class
<<
self
# The data we store on Sessions used to be stored on SharedState.
def
config_fallback
SharedState
end
private
def
redis
# Don't use multistore if redis.sessions configuration is not provided
return
super
if
config_fallback?
primary_store
=
::
Redis
.
new
(
params
)
secondary_store
=
::
Redis
.
new
(
config_fallback
.
params
)
MultiStore
.
new
(
primary_store
,
secondary_store
,
store_name
)
end
end
def
store
(
extras
=
{})
# Don't use multistore if redis.sessions configuration is not provided
return
super
if
self
.
class
.
config_fallback?
primary_store
=
create_redis_store
(
redis_store_options
,
extras
)
secondary_store
=
create_redis_store
(
self
.
class
.
config_fallback
.
params
,
extras
)
MultiStore
.
new
(
primary_store
,
secondary_store
,
self
.
class
.
store_name
)
end
private
def
create_redis_store
(
options
,
extras
)
::
Redis
::
Store
.
new
(
options
.
merge
(
extras
))
# The data we store on Sessions used to be stored on SharedState.
def
self
.
config_fallback
SharedState
end
end
end
...
...
spec/lib/gitlab/redis/multi_store_spec.rb
deleted
100644 → 0
View file @
f3a1cbfc
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Gitlab
::
Redis
::
MultiStore
do
using
RSpec
::
Parameterized
::
TableSyntax
let_it_be
(
:redis_store_class
)
do
Class
.
new
(
Gitlab
::
Redis
::
Wrapper
)
do
def
config_file_name
config_file_name
=
"spec/fixtures/config/redis_new_format_host.yml"
Rails
.
root
.
join
(
config_file_name
).
to_s
end
def
self
.
name
'Sessions'
end
end
end
let_it_be
(
:primary_db
)
{
1
}
let_it_be
(
:secondary_db
)
{
2
}
let_it_be
(
:primary_store
)
{
create_redis_store
(
redis_store_class
.
params
,
db:
primary_db
,
serializer:
nil
)
}
let_it_be
(
:secondary_store
)
{
create_redis_store
(
redis_store_class
.
params
,
db:
secondary_db
,
serializer:
nil
)
}
let_it_be
(
:instance_name
)
{
'TestStore'
}
let_it_be
(
:multi_store
)
{
described_class
.
new
(
primary_store
,
secondary_store
,
instance_name
)}
subject
{
multi_store
.
send
(
name
,
*
args
)
}
before
do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
after
(
:all
)
do
primary_store
.
flushdb
secondary_store
.
flushdb
end
context
'when primary_store is nil'
do
let
(
:multi_store
)
{
described_class
.
new
(
nil
,
secondary_store
,
instance_name
)}
it
'fails with exception'
do
expect
{
multi_store
}.
to
raise_error
(
ArgumentError
,
/primary_store is required/
)
end
end
context
'when secondary_store is nil'
do
let
(
:multi_store
)
{
described_class
.
new
(
primary_store
,
nil
,
instance_name
)}
it
'fails with exception'
do
expect
{
multi_store
}.
to
raise_error
(
ArgumentError
,
/secondary_store is required/
)
end
end
context
'when instance_name is nil'
do
let
(
:instance_name
)
{
nil
}
let
(
:multi_store
)
{
described_class
.
new
(
primary_store
,
secondary_store
,
instance_name
)}
it
'fails with exception'
do
expect
{
multi_store
}.
to
raise_error
(
ArgumentError
,
/instance_name is required/
)
end
end
context
'when primary_store is not a ::Redis instance'
do
before
do
allow
(
primary_store
).
to
receive
(
:is_a?
).
with
(
::
Redis
).
and_return
(
false
)
end
it
'fails with exception'
do
expect
{
described_class
.
new
(
primary_store
,
secondary_store
,
instance_name
)
}.
to
raise_error
(
ArgumentError
,
/invalid primary_store/
)
end
end
context
'when secondary_store is not a ::Redis instance'
do
before
do
allow
(
secondary_store
).
to
receive
(
:is_a?
).
with
(
::
Redis
).
and_return
(
false
)
end
it
'fails with exception'
do
expect
{
described_class
.
new
(
primary_store
,
secondary_store
,
instance_name
)
}.
to
raise_error
(
ArgumentError
,
/invalid secondary_store/
)
end
end
context
'with READ redis commands'
do
let_it_be
(
:key1
)
{
"redis:{1}:key_a"
}
let_it_be
(
:key2
)
{
"redis:{1}:key_b"
}
let_it_be
(
:value1
)
{
"redis_value1"
}
let_it_be
(
:value2
)
{
"redis_value2"
}
let_it_be
(
:skey
)
{
"redis:set:key"
}
let_it_be
(
:keys
)
{
[
key1
,
key2
]
}
let_it_be
(
:values
)
{
[
value1
,
value2
]
}
let_it_be
(
:svalues
)
{
[
value2
,
value1
]
}
where
(
:case_name
,
:name
,
:args
,
:value
,
:block
)
do
'execute :get command'
|
:get
|
ref
(
:key1
)
|
ref
(
:value1
)
|
nil
'execute :mget command'
|
:mget
|
ref
(
:keys
)
|
ref
(
:values
)
|
nil
'execute :mget with block'
|
:mget
|
ref
(
:keys
)
|
ref
(
:values
)
|
->
(
value
)
{
value
}
'execute :smembers command'
|
:smembers
|
ref
(
:skey
)
|
ref
(
:svalues
)
|
nil
'execute :scard command'
|
:scard
|
ref
(
:skey
)
|
2
|
nil
end
before
(
:all
)
do
primary_store
.
multi
do
|
multi
|
multi
.
set
(
key1
,
value1
)
multi
.
set
(
key2
,
value2
)
multi
.
sadd
(
skey
,
value1
)
multi
.
sadd
(
skey
,
value2
)
end
secondary_store
.
multi
do
|
multi
|
multi
.
set
(
key1
,
value1
)
multi
.
set
(
key2
,
value2
)
multi
.
sadd
(
skey
,
value1
)
multi
.
sadd
(
skey
,
value2
)
end
end
RSpec
.
shared_examples_for
'reads correct value'
do
it
'returns the correct value'
do
if
value
.
is_a?
(
Array
)
# :smembers does not guarantee the order it will return the values (unsorted set)
is_expected
.
to
match_array
(
value
)
else
is_expected
.
to
eq
(
value
)
end
end
end
RSpec
.
shared_examples_for
'fallback read from the secondary store'
do
let
(
:counter
)
{
Gitlab
::
Metrics
::
NullMetric
.
instance
}
before
do
allow
(
Gitlab
::
Metrics
).
to
receive
(
:counter
).
and_return
(
counter
)
end
it
'fallback and execute on secondary instance'
do
expect
(
secondary_store
).
to
receive
(
name
).
with
(
*
args
).
and_call_original
subject
end
it
'logs the ReadFromPrimaryError'
do
expect
(
Gitlab
::
ErrorTracking
).
to
receive
(
:log_exception
).
with
(
an_instance_of
(
Gitlab
::
Redis
::
MultiStore
::
ReadFromPrimaryError
),
hash_including
(
command_name:
name
,
extra:
hash_including
(
instance_name:
instance_name
)))
subject
end
it
'increment read fallback count metrics'
do
expect
(
counter
).
to
receive
(
:increment
).
with
(
command:
name
,
instance_name:
instance_name
)
subject
end
include_examples
'reads correct value'
context
'when fallback read from the secondary instance raises an exception'
do
before
do
allow
(
secondary_store
).
to
receive
(
name
).
with
(
*
args
).
and_raise
(
StandardError
)
end
it
'fails with exception'
do
expect
{
subject
}.
to
raise_error
(
StandardError
)
end
end
end
RSpec
.
shared_examples_for
'secondary store'
do
it
'execute on the secondary instance'
do
expect
(
secondary_store
).
to
receive
(
name
).
with
(
*
args
).
and_call_original
subject
end
include_examples
'reads correct value'
it
'does not execute on the primary store'
do
expect
(
primary_store
).
not_to
receive
(
name
)
subject
end
end
with_them
do
describe
"
#{
name
}
"
do
before
do
allow
(
primary_store
).
to
receive
(
name
).
and_call_original
allow
(
secondary_store
).
to
receive
(
name
).
and_call_original
end
context
'with feature flag :use_primary_and_secondary_stores_for_test_store'
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores_for_test_store:
true
)
end
context
'when reading from the primary is successful'
do
it
'returns the correct value'
do
expect
(
primary_store
).
to
receive
(
name
).
with
(
*
args
).
and_call_original
subject
end
it
'does not execute on the secondary store'
do
expect
(
secondary_store
).
not_to
receive
(
name
)
subject
end
include_examples
'reads correct value'
end
context
'when reading from primary instance is raising an exception'
do
before
do
allow
(
primary_store
).
to
receive
(
name
).
with
(
*
args
).
and_raise
(
StandardError
)
allow
(
Gitlab
::
ErrorTracking
).
to
receive
(
:log_exception
)
end
it
'logs the exception'
do
expect
(
Gitlab
::
ErrorTracking
).
to
receive
(
:log_exception
).
with
(
an_instance_of
(
StandardError
),
hash_including
(
extra:
hash_including
(
:multi_store_error_message
,
instance_name:
instance_name
),
command_name:
name
))
subject
end
include_examples
'fallback read from the secondary store'
end
context
'when reading from primary instance return no value'
do
before
do
allow
(
primary_store
).
to
receive
(
name
).
and_return
(
nil
)
end
include_examples
'fallback read from the secondary store'
end
context
'when the command is executed within pipelined block'
do
subject
do
multi_store
.
pipelined
do
multi_store
.
send
(
name
,
*
args
)
end
end
it
'is executed only 1 time on primary instance'
do
expect
(
primary_store
).
to
receive
(
name
).
with
(
*
args
).
once
subject
end
end
if
params
[
:block
]
subject
do
multi_store
.
send
(
name
,
*
args
,
&
block
)
end
context
'when block is provided'
do
it
'yields to the block'
do
expect
(
primary_store
).
to
receive
(
name
).
and_yield
(
value
)
subject
end
include_examples
'reads correct value'
end
end
end
context
'with feature flag :use_primary_and_secondary_stores_for_test_store'
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores_for_test_store:
false
)
end
context
'with feature flag :use_primary_store_as_default_for_test_store is disabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
false
)
end
it_behaves_like
'secondary store'
end
context
'with feature flag :use_primary_store_as_default_for_test_store is enabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
true
)
end
it
'execute on the primary instance'
do
expect
(
primary_store
).
to
receive
(
name
).
with
(
*
args
).
and_call_original
subject
end
include_examples
'reads correct value'
it
'does not execute on the secondary store'
do
expect
(
secondary_store
).
not_to
receive
(
name
)
subject
end
end
end
context
'with both primary and secondary store using same redis instance'
do
let
(
:primary_store
)
{
create_redis_store
(
redis_store_class
.
params
,
db:
primary_db
,
serializer:
nil
)
}
let
(
:secondary_store
)
{
create_redis_store
(
redis_store_class
.
params
,
db:
primary_db
,
serializer:
nil
)
}
let
(
:multi_store
)
{
described_class
.
new
(
primary_store
,
secondary_store
,
instance_name
)}
it_behaves_like
'secondary store'
end
end
end
end
context
'with WRITE redis commands'
do
let_it_be
(
:key1
)
{
"redis:{1}:key_a"
}
let_it_be
(
:key2
)
{
"redis:{1}:key_b"
}
let_it_be
(
:value1
)
{
"redis_value1"
}
let_it_be
(
:value2
)
{
"redis_value2"
}
let_it_be
(
:key1_value1
)
{
[
key1
,
value1
]
}
let_it_be
(
:key1_value2
)
{
[
key1
,
value2
]
}
let_it_be
(
:ttl
)
{
10
}
let_it_be
(
:key1_ttl_value1
)
{
[
key1
,
ttl
,
value1
]
}
let_it_be
(
:skey
)
{
"redis:set:key"
}
let_it_be
(
:svalues1
)
{
[
value2
,
value1
]
}
let_it_be
(
:svalues2
)
{
[
value1
]
}
let_it_be
(
:skey_value1
)
{
[
skey
,
value1
]
}
let_it_be
(
:skey_value2
)
{
[
skey
,
value2
]
}
where
(
:case_name
,
:name
,
:args
,
:expected_value
,
:verification_name
,
:verification_args
)
do
'execute :set command'
|
:set
|
ref
(
:key1_value1
)
|
ref
(
:value1
)
|
:get
|
ref
(
:key1
)
'execute :setnx command'
|
:setnx
|
ref
(
:key1_value2
)
|
ref
(
:value1
)
|
:get
|
ref
(
:key2
)
'execute :setex command'
|
:setex
|
ref
(
:key1_ttl_value1
)
|
ref
(
:ttl
)
|
:ttl
|
ref
(
:key1
)
'execute :sadd command'
|
:sadd
|
ref
(
:skey_value2
)
|
ref
(
:svalues1
)
|
:smembers
|
ref
(
:skey
)
'execute :srem command'
|
:srem
|
ref
(
:skey_value1
)
|
[]
|
:smembers
|
ref
(
:skey
)
'execute :del command'
|
:del
|
ref
(
:key2
)
|
nil
|
:get
|
ref
(
:key2
)
'execute :flushdb command'
|
:flushdb
|
nil
|
0
|
:dbsize
|
nil
end
before
do
primary_store
.
flushdb
secondary_store
.
flushdb
primary_store
.
multi
do
|
multi
|
multi
.
set
(
key2
,
value1
)
multi
.
sadd
(
skey
,
value1
)
end
secondary_store
.
multi
do
|
multi
|
multi
.
set
(
key2
,
value1
)
multi
.
sadd
(
skey
,
value1
)
end
end
RSpec
.
shared_examples_for
'verify that store contains values'
do
|
store
|
it
"
#{
store
}
redis store contains correct values"
,
:aggregate_errors
do
subject
redis_store
=
multi_store
.
send
(
store
)
if
expected_value
.
is_a?
(
Array
)
# :smembers does not guarantee the order it will return the values
expect
(
redis_store
.
send
(
verification_name
,
*
verification_args
)).
to
match_array
(
expected_value
)
else
expect
(
redis_store
.
send
(
verification_name
,
*
verification_args
)).
to
eq
(
expected_value
)
end
end
end
with_them
do
describe
"
#{
name
}
"
do
let
(
:expected_args
)
{
args
||
no_args
}
before
do
allow
(
primary_store
).
to
receive
(
name
).
and_call_original
allow
(
secondary_store
).
to
receive
(
name
).
and_call_original
end
context
'with feature flag :use_primary_and_secondary_stores_for_test_store'
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores_for_test_store:
true
)
end
context
'when executing on primary instance is successful'
do
it
'executes on both primary and secondary redis store'
,
:aggregate_errors
do
expect
(
primary_store
).
to
receive
(
name
).
with
(
*
expected_args
).
and_call_original
expect
(
secondary_store
).
to
receive
(
name
).
with
(
*
expected_args
).
and_call_original
subject
end
include_examples
'verify that store contains values'
,
:primary_store
include_examples
'verify that store contains values'
,
:secondary_store
end
context
'when executing on the primary instance is raising an exception'
do
before
do
allow
(
primary_store
).
to
receive
(
name
).
with
(
*
expected_args
).
and_raise
(
StandardError
)
allow
(
Gitlab
::
ErrorTracking
).
to
receive
(
:log_exception
)
end
it
'logs the exception and execute on secondary instance'
,
:aggregate_errors
do
expect
(
Gitlab
::
ErrorTracking
).
to
receive
(
:log_exception
).
with
(
an_instance_of
(
StandardError
),
hash_including
(
extra:
hash_including
(
:multi_store_error_message
),
command_name:
name
))
expect
(
secondary_store
).
to
receive
(
name
).
with
(
*
expected_args
).
and_call_original
subject
end
include_examples
'verify that store contains values'
,
:secondary_store
end
context
'when the command is executed within pipelined block'
do
subject
do
multi_store
.
pipelined
do
multi_store
.
send
(
name
,
*
args
)
end
end
it
'is executed only 1 time on each instance'
,
:aggregate_errors
do
expect
(
primary_store
).
to
receive
(
name
).
with
(
*
expected_args
).
once
expect
(
secondary_store
).
to
receive
(
name
).
with
(
*
expected_args
).
once
subject
end
include_examples
'verify that store contains values'
,
:primary_store
include_examples
'verify that store contains values'
,
:secondary_store
end
end
context
'with feature flag :use_primary_and_secondary_stores_for_test_store is disabled'
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores_for_test_store:
false
)
end
context
'with feature flag :use_primary_store_as_default_for_test_store is disabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
false
)
end
it
'executes only on the secondary redis store'
,
:aggregate_errors
do
expect
(
secondary_store
).
to
receive
(
name
).
with
(
*
expected_args
)
expect
(
primary_store
).
not_to
receive
(
name
).
with
(
*
expected_args
)
subject
end
include_examples
'verify that store contains values'
,
:secondary_store
end
context
'with feature flag :use_primary_store_as_default_for_test_store is enabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
true
)
end
it
'executes only on the primary_redis redis store'
,
:aggregate_errors
do
expect
(
primary_store
).
to
receive
(
name
).
with
(
*
expected_args
)
expect
(
secondary_store
).
not_to
receive
(
name
).
with
(
*
expected_args
)
subject
end
include_examples
'verify that store contains values'
,
:primary_store
end
end
end
end
end
context
'with unsupported command'
do
let
(
:counter
)
{
Gitlab
::
Metrics
::
NullMetric
.
instance
}
before
do
primary_store
.
flushdb
secondary_store
.
flushdb
allow
(
Gitlab
::
Metrics
).
to
receive
(
:counter
).
and_return
(
counter
)
end
let_it_be
(
:key
)
{
"redis:counter"
}
subject
{
multi_store
.
incr
(
key
)
}
it
'executes method missing'
do
expect
(
multi_store
).
to
receive
(
:method_missing
)
subject
end
context
'when command is not in SKIP_LOG_METHOD_MISSING_FOR_COMMANDS'
do
it
'logs MethodMissingError'
do
expect
(
Gitlab
::
ErrorTracking
).
to
receive
(
:log_exception
).
with
(
an_instance_of
(
Gitlab
::
Redis
::
MultiStore
::
MethodMissingError
),
hash_including
(
command_name: :incr
,
extra:
hash_including
(
instance_name:
instance_name
)))
subject
end
it
'increments method missing counter'
do
expect
(
counter
).
to
receive
(
:increment
).
with
(
command: :incr
,
instance_name:
instance_name
)
subject
end
end
context
'when command is in SKIP_LOG_METHOD_MISSING_FOR_COMMANDS'
do
subject
{
multi_store
.
info
}
it
'does not log MethodMissingError'
do
expect
(
Gitlab
::
ErrorTracking
).
not_to
receive
(
:log_exception
)
subject
end
it
'does not increment method missing counter'
do
expect
(
counter
).
not_to
receive
(
:increment
)
subject
end
end
context
'with feature flag :use_primary_store_as_default_for_test_store is enabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
true
)
end
it
'fallback and executes only on the secondary store'
,
:aggregate_errors
do
expect
(
primary_store
).
to
receive
(
:incr
).
with
(
key
).
and_call_original
expect
(
secondary_store
).
not_to
receive
(
:incr
)
subject
end
it
'correct value is stored on the secondary store'
,
:aggregate_errors
do
subject
expect
(
secondary_store
.
get
(
key
)).
to
be_nil
expect
(
primary_store
.
get
(
key
)).
to
eq
(
'1'
)
end
end
context
'with feature flag :use_primary_store_as_default_for_test_store is disabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
false
)
end
it
'fallback and executes only on the secondary store'
,
:aggregate_errors
do
expect
(
secondary_store
).
to
receive
(
:incr
).
with
(
key
).
and_call_original
expect
(
primary_store
).
not_to
receive
(
:incr
)
subject
end
it
'correct value is stored on the secondary store'
,
:aggregate_errors
do
subject
expect
(
primary_store
.
get
(
key
)).
to
be_nil
expect
(
secondary_store
.
get
(
key
)).
to
eq
(
'1'
)
end
end
context
'when the command is executed within pipelined block'
do
subject
do
multi_store
.
pipelined
do
multi_store
.
incr
(
key
)
end
end
it
'is executed only 1 time on each instance'
,
:aggregate_errors
do
expect
(
primary_store
).
to
receive
(
:incr
).
with
(
key
).
once
expect
(
secondary_store
).
to
receive
(
:incr
).
with
(
key
).
once
subject
end
it
"both redis stores are containing correct values"
,
:aggregate_errors
do
subject
expect
(
primary_store
.
get
(
key
)).
to
eq
(
'1'
)
expect
(
secondary_store
.
get
(
key
)).
to
eq
(
'1'
)
end
end
end
describe
'#to_s'
do
subject
{
multi_store
.
to_s
}
context
'with feature flag :use_primary_and_secondary_stores_for_test_store is enabled'
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores_for_test_store:
true
)
end
it
'returns same value as primary_store'
do
is_expected
.
to
eq
(
primary_store
.
to_s
)
end
end
context
'with feature flag :use_primary_and_secondary_stores_for_test_store is disabled'
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores_for_test_store:
false
)
end
context
'with feature flag :use_primary_store_as_default_for_test_store is enabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
true
)
end
it
'returns same value as primary_store'
do
is_expected
.
to
eq
(
primary_store
.
to_s
)
end
end
context
'with feature flag :use_primary_store_as_default_for_test_store is disabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
false
)
end
it
'returns same value as primary_store'
do
is_expected
.
to
eq
(
secondary_store
.
to_s
)
end
end
end
end
describe
'#is_a?'
do
it
'returns true for ::Redis::Store'
do
expect
(
multi_store
.
is_a?
(
::
Redis
::
Store
)).
to
be
true
end
end
describe
'#use_primary_and_secondary_stores?'
do
context
'with feature flag :use_primary_and_secondary_stores_for_test_store is enabled'
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores_for_test_store:
true
)
end
it
'multi store is disabled'
do
expect
(
multi_store
.
use_primary_and_secondary_stores?
).
to
be
true
end
end
context
'with feature flag :use_primary_and_secondary_stores_for_test_store is disabled'
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores_for_test_store:
false
)
end
it
'multi store is disabled'
do
expect
(
multi_store
.
use_primary_and_secondary_stores?
).
to
be
false
end
end
context
'with empty DB'
do
before
do
allow
(
Feature
::
FlipperFeature
).
to
receive
(
:table_exists?
).
and_return
(
false
)
end
it
'multi store is disabled'
do
expect
(
multi_store
.
use_primary_and_secondary_stores?
).
to
be
false
end
end
context
'when FF table guard raises'
do
before
do
allow
(
Feature
::
FlipperFeature
).
to
receive
(
:table_exists?
).
and_raise
end
it
'multi store is disabled'
do
expect
(
multi_store
.
use_primary_and_secondary_stores?
).
to
be
false
end
end
end
describe
'#use_primary_store_as_default?'
do
context
'with feature flag :use_primary_store_as_default_for_test_store is enabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
true
)
end
it
'multi store is disabled'
do
expect
(
multi_store
.
use_primary_store_as_default?
).
to
be
true
end
end
context
'with feature flag :use_primary_store_as_default_for_test_store is disabled'
do
before
do
stub_feature_flags
(
use_primary_store_as_default_for_test_store:
false
)
end
it
'multi store is disabled'
do
expect
(
multi_store
.
use_primary_store_as_default?
).
to
be
false
end
end
context
'with empty DB'
do
before
do
allow
(
Feature
::
FlipperFeature
).
to
receive
(
:table_exists?
).
and_return
(
false
)
end
it
'multi store is disabled'
do
expect
(
multi_store
.
use_primary_and_secondary_stores?
).
to
be
false
end
end
context
'when FF table guard raises'
do
before
do
allow
(
Feature
::
FlipperFeature
).
to
receive
(
:table_exists?
).
and_raise
end
it
'multi store is disabled'
do
expect
(
multi_store
.
use_primary_and_secondary_stores?
).
to
be
false
end
end
end
def
create_redis_store
(
options
,
extras
=
{})
::
Redis
::
Store
.
new
(
options
.
merge
(
extras
))
end
end
spec/lib/gitlab/redis/sessions_spec.rb
View file @
c66fb99d
...
...
@@ -6,31 +6,16 @@ RSpec.describe Gitlab::Redis::Sessions do
it_behaves_like
"redis_new_instance_shared_examples"
,
'sessions'
,
Gitlab
::
Redis
::
SharedState
describe
'redis instance used in connection pool'
do
before
do
around
do
|
example
|
clear_pool
end
after
do
example
.
run
ensure
clear_pool
end
context
'when redis.sessions configuration is not provided'
do
it
'uses ::Redis instance'
do
expect
(
described_class
).
to
receive
(
:config_fallback?
).
and_return
(
true
)
described_class
.
pool
.
with
do
|
redis_instance
|
expect
(
redis_instance
).
to
be_instance_of
(
::
Redis
)
end
end
end
context
'when redis.sessions configuration is provided'
do
it
'instantiates an instance of MultiStore'
do
expect
(
described_class
).
to
receive
(
:config_fallback?
).
and_return
(
false
)
described_class
.
pool
.
with
do
|
redis_instance
|
expect
(
redis_instance
).
to
be_instance_of
(
::
Gitlab
::
Redis
::
MultiStore
)
end
it
'uses ::Redis instance'
do
described_class
.
pool
.
with
do
|
redis_instance
|
expect
(
redis_instance
).
to
be_instance_of
(
::
Redis
)
end
end
...
...
@@ -44,49 +29,9 @@ RSpec.describe Gitlab::Redis::Sessions do
describe
'#store'
do
subject
(
:store
)
{
described_class
.
store
(
namespace:
described_class
::
SESSION_NAMESPACE
)
}
context
'when redis.sessions configuration is NOT provided'
do
it
'instantiates ::Redis instance'
do
expect
(
described_class
).
to
receive
(
:config_fallback?
).
and_return
(
true
)
expect
(
store
).
to
be_instance_of
(
::
Redis
::
Store
)
end
end
context
'when redis.sessions configuration is provided'
do
let
(
:config_new_format_host
)
{
"spec/fixtures/config/redis_new_format_host.yml"
}
let
(
:config_new_format_socket
)
{
"spec/fixtures/config/redis_new_format_socket.yml"
}
before
do
redis_clear_raw_config!
(
Gitlab
::
Redis
::
Sessions
)
redis_clear_raw_config!
(
Gitlab
::
Redis
::
SharedState
)
allow
(
described_class
).
to
receive
(
:config_fallback?
).
and_return
(
false
)
end
after
do
redis_clear_raw_config!
(
Gitlab
::
Redis
::
Sessions
)
redis_clear_raw_config!
(
Gitlab
::
Redis
::
SharedState
)
end
# Check that Gitlab::Redis::Sessions is configured as MultiStore with proper attrs.
it
'instantiates an instance of MultiStore'
,
:aggregate_failures
do
expect
(
described_class
).
to
receive
(
:config_file_name
).
and_return
(
config_new_format_host
)
expect
(
::
Gitlab
::
Redis
::
SharedState
).
to
receive
(
:config_file_name
).
and_return
(
config_new_format_socket
)
expect
(
store
).
to
be_instance_of
(
::
Gitlab
::
Redis
::
MultiStore
)
expect
(
store
.
primary_store
.
to_s
).
to
eq
(
"Redis Client connected to test-host:6379 against DB 99 with namespace session:gitlab"
)
expect
(
store
.
secondary_store
.
to_s
).
to
eq
(
"Redis Client connected to /path/to/redis.sock against DB 0 with namespace session:gitlab"
)
expect
(
store
.
instance_name
).
to
eq
(
'Sessions'
)
end
context
'when MultiStore correctly configured'
do
before
do
allow
(
described_class
).
to
receive
(
:config_file_name
).
and_return
(
config_new_format_host
)
allow
(
::
Gitlab
::
Redis
::
SharedState
).
to
receive
(
:config_file_name
).
and_return
(
config_new_format_socket
)
end
it_behaves_like
'multi store feature flags'
,
:use_primary_and_secondary_stores_for_sessions
,
:use_primary_store_as_default_for_sessions
end
# Check that Gitlab::Redis::Sessions is configured as RedisStore.
it
'instantiates an instance of Redis::Store'
do
expect
(
store
).
to
be_instance_of
(
::
Redis
::
Store
)
end
end
end
spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb
deleted
100644 → 0
View file @
f3a1cbfc
# frozen_string_literal: true
RSpec
.
shared_examples
'multi store feature flags'
do
|
use_primary_and_secondary_stores
,
use_primary_store_as_default
|
context
"with feature flag :
#{
use_primary_and_secondary_stores
}
is enabled"
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores
=>
true
)
end
it
'multi store is enabled'
do
expect
(
subject
.
use_primary_and_secondary_stores?
).
to
be
true
end
end
context
"with feature flag :
#{
use_primary_and_secondary_stores
}
is disabled"
do
before
do
stub_feature_flags
(
use_primary_and_secondary_stores
=>
false
)
end
it
'multi store is disabled'
do
expect
(
subject
.
use_primary_and_secondary_stores?
).
to
be
false
end
end
context
"with feature flag :
#{
use_primary_store_as_default
}
is enabled"
do
before
do
stub_feature_flags
(
use_primary_store_as_default
=>
true
)
end
it
'primary store is enabled'
do
expect
(
subject
.
use_primary_store_as_default?
).
to
be
true
end
end
context
"with feature flag :
#{
use_primary_store_as_default
}
is disabled"
do
before
do
stub_feature_flags
(
use_primary_store_as_default
=>
false
)
end
it
'primary store is disabled'
do
expect
(
subject
.
use_primary_store_as_default?
).
to
be
false
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment