spec/chef/cookbooks/redis/recipes/redis_spec.rb (521 lines of code) (raw):
require 'chef_helper'
RSpec.describe 'redis' do
let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(redis_service runit_service)).converge('gitlab::default') }
let(:redis_conf) { '/var/opt/gitlab/redis/redis.conf' }
before do
allow(Gitlab).to receive(:[]).and_call_original
end
context 'by default' do
let(:gitlab_redis_cli_rc) do
<<-EOF
redis_dir='/var/opt/gitlab/redis'
redis_host=''
redis_port='0'
redis_tls_port=''
redis_tls_auth_clients='optional'
redis_tls_cacert_file='/opt/gitlab/embedded/ssl/certs/cacert.pem'
redis_tls_cacert_dir='/opt/gitlab/embedded/ssl/certs/'
redis_tls_cert_file=''
redis_tls_key_file=''
redis_socket='/var/opt/gitlab/redis/redis.socket'
EOF
end
it 'enables the redis service' do
expect(chef_run).to create_redis_service('redis')
end
it 'creates redis config with default values' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content { |content|
expect(content).to match(/client-output-buffer-limit normal 0 0 0/)
expect(content).to match(/client-output-buffer-limit replica 256mb 64mb 60/)
expect(content).to match(/client-output-buffer-limit pubsub 32mb 8mb 60/)
expect(content).to match(/^hz 10/)
expect(content).to match(/^save 900 1/)
expect(content).to match(/^save 300 10/)
expect(content).to match(/^save 60 10000/)
expect(content).to match(/^maxmemory 0/)
expect(content).to match(/^maxmemory-policy noeviction/)
expect(content).to match(/^maxmemory-samples 5/)
expect(content).to match(/^tcp-backlog 511/)
expect(content).to match(/^rename-command KEYS ""$/)
expect(content).to match(/^lazyfree-lazy-eviction no$/)
expect(content).to match(/^lazyfree-lazy-expire no$/)
expect(content).to match(/^io-threads 1$/)
expect(content).to match(/^io-threads-do-reads no$/)
expect(content).to match(/^stop-writes-on-bgsave-error yes$/)
expect(content).not_to match(/^replicaof/)
expect(content).not_to match(/^tls-/)
}
end
it 'creates redis user and group' do
expect(chef_run).to create_account('user and group for redis').with(username: 'gitlab-redis', groupname: 'gitlab-redis')
end
it_behaves_like 'enabled runit service', 'redis', 'root', 'root'
it 'uses a 0 second startup delay' do
expect(chef_run).to render_file('/opt/gitlab/sv/redis/run')
.with_content { |content|
expect(content).to match(/^sleep 0$/)
}
end
it 'creates gitlab-redis-cli-rc' do
expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
.with_content(gitlab_redis_cli_rc)
end
describe 'pending restart check' do
context 'when running version is same as installed version' do
before do
allow_any_instance_of(RedisHelper::Server).to receive(:running_version).and_return('3.2.12')
allow_any_instance_of(RedisHelper::Server).to receive(:installed_version).and_return('3.2.12')
end
it 'does not raise a warning' do
expect(chef_run).not_to run_ruby_block('warn pending redis restart')
end
end
context 'when running version is different than installed version' do
before do
allow_any_instance_of(RedisHelper::Server).to receive(:running_version).and_return('3.2.12')
allow_any_instance_of(RedisHelper::Server).to receive(:installed_version).and_return('5.0.9')
end
it 'raises a warning' do
expect(chef_run).to run_ruby_block('warn pending redis restart')
end
end
end
end
context 'with user specified values' do
before do
stub_gitlab_rb(
redis: {
client_output_buffer_limit_normal: "5 5 5",
client_output_buffer_limit_replica: "512mb 128mb 120",
client_output_buffer_limit_pubsub: "64mb 16mb 120",
save: ["10 15000"],
maxmemory: "32gb",
maxmemory_policy: "allkeys-url",
maxmemory_samples: 10,
tcp_backlog: 1024,
hz: 100,
username: 'foo',
group: 'bar',
rename_commands: {
"FAKE_COMMAND" => "RENAMED_FAKE_COMMAND",
"DISABLED_FAKE_COMMAND" => ""
},
'startup_delay': 10,
open_files_ulimit: 60000
}
)
end
it 'creates redis config with custom values' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/client-output-buffer-limit normal 5 5 5/)
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/client-output-buffer-limit replica 512mb 128mb 120/)
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/client-output-buffer-limit pubsub 64mb 16mb 120/)
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^save 10 15000/)
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^maxmemory 32gb/)
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^maxmemory-policy allkeys-url/)
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^maxmemory-samples 10/)
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^tcp-backlog 1024/)
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^hz 100/)
end
it 'does not include the default renamed keys in redis.conf' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content { |content|
expect(content).not_to match(/^rename-command KEYS ""$/)
expect(content).to match(/^rename-command FAKE_COMMAND "RENAMED_FAKE_COMMAND"$/)
expect(content).to match(/^rename-command DISABLED_FAKE_COMMAND ""$/)
}
end
it 'creates redis user and group' do
expect(chef_run).to create_account('user and group for redis').with(username: 'foo', groupname: 'bar')
end
it_behaves_like 'enabled runit service', 'redis', 'root', 'root'
it 'uses a 10 second startup delay' do
expect(chef_run).to render_file('/opt/gitlab/sv/redis/run')
.with_content { |content|
expect(content).to match(/^sleep 10$/)
}
end
it 'sets a filehandle limit' do
expect(chef_run).to render_file('/opt/gitlab/sv/redis/run')
.with_content { |content|
expect(content).to match(/^ulimit -n 60000$/)
}
end
end
context 'with snapshotting disabled' do
before do
stub_gitlab_rb(
redis: {
save: []
}
)
end
it 'creates redis config without save setting' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
expect(chef_run).not_to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^save/)
end
end
context 'with snapshotting cleared' do
before do
stub_gitlab_rb(
redis: {
save: [""]
}
)
end
it 'creates redis config without save setting' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^save ""/)
end
end
context 'with multiple bind addresses' do
let(:redis_host) { '1.2.3.4 5.6.7.8' }
let(:redis_port) { 6370 }
let(:master_ip) { '10.0.0.0' }
let(:master_port) { 6371 }
let(:gitlab_redis_cli_rc) do
<<-EOF
redis_dir='/var/opt/gitlab/redis'
redis_host='1.2.3.4'
redis_port='6370'
redis_tls_port=''
redis_tls_auth_clients='optional'
redis_tls_cacert_file='/opt/gitlab/embedded/ssl/certs/cacert.pem'
redis_tls_cacert_dir='/opt/gitlab/embedded/ssl/certs/'
redis_tls_cert_file=''
redis_tls_key_file=''
redis_socket=''
EOF
end
before do
stub_gitlab_rb(
redis: {
bind: redis_host,
port: redis_port,
master_ip: master_ip,
master_port: master_port,
master_password: 'password',
master: false
}
)
end
it 'creates gitlab-redis-cli-rc' do
expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
.with_content(gitlab_redis_cli_rc)
end
end
context 'with a replica configured' do
let(:redis_host) { '1.2.3.4' }
let(:redis_port) { 6370 }
let(:master_ip) { '10.0.0.0' }
let(:master_port) { 6371 }
let(:gitlab_redis_cli_rc) do
<<-EOF
redis_dir='/var/opt/gitlab/redis'
redis_host='1.2.3.4'
redis_port='6370'
redis_tls_port=''
redis_tls_auth_clients='optional'
redis_tls_cacert_file='/opt/gitlab/embedded/ssl/certs/cacert.pem'
redis_tls_cacert_dir='/opt/gitlab/embedded/ssl/certs/'
redis_tls_cert_file=''
redis_tls_key_file=''
redis_socket=''
EOF
end
before do
stub_gitlab_rb(
redis: {
bind: redis_host,
port: redis_port,
master_ip: master_ip,
master_port: master_port,
master_password: 'password',
master: false
}
)
end
it 'includes replicaof' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^replicaof #{master_ip} #{master_port}/)
end
it 'creates gitlab-redis-cli-rc' do
expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
.with_content(gitlab_redis_cli_rc)
end
end
context 'in HA mode with Sentinels' do
let(:redis_host) { '1.2.3.4' }
let(:redis_port) { 6370 }
let(:master_ip) { '10.0.0.0' }
let(:master_port) { 6371 }
let(:gitlab_redis_cli_rc) do
<<-EOF
redis_dir='/var/opt/gitlab/redis'
redis_host='1.2.3.4'
redis_port='6370'
redis_tls_port=''
redis_tls_auth_clients='optional'
redis_tls_cacert_file='/opt/gitlab/embedded/ssl/certs/cacert.pem'
redis_tls_cacert_dir='/opt/gitlab/embedded/ssl/certs/'
redis_tls_cert_file=''
redis_tls_key_file=''
redis_socket=''
EOF
end
before do
stub_gitlab_rb(
redis: {
bind: redis_host,
port: redis_port,
ha: true,
master_ip: master_ip,
master_port: master_port,
master_password: 'password',
master: false
}
)
end
it 'omits replicaof' do
expect(chef_run).not_to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content(/^replicaof/)
end
it_behaves_like 'started down runit service', 'redis'
it 'creates gitlab-redis-cli-rc' do
expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
.with_content(gitlab_redis_cli_rc)
end
context 'with Sentinels configured' do
before do
stub_gitlab_rb(
redis: {
bind: redis_host,
port: redis_port,
ha: true,
master_ip: master_ip,
master_port: master_port,
master_password: 'password',
master: false
},
gitlab_rails: {
redis_sentinels: [
{ 'host' => '127.0.0.1', 'port' => 2637 }
]
}
)
end
it_behaves_like 'started down runit service', 'redis'
it 'creates gitlab-redis-cli-rc' do
expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
.with_content(gitlab_redis_cli_rc)
end
end
end
context 'with rename_commands disabled' do
before do
stub_gitlab_rb(
redis: {
rename_commands: {}
}
)
end
it 'should not rename any commands' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content { |content|
expect(content).not_to match(/^rename-command/)
}
end
end
context 'with lazy eviction enabled' do
before do
stub_gitlab_rb(
redis: {
lazyfree_lazy_eviction: true
}
)
end
it 'creates redis config with lazyfree-lazy-eviction yes' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content { |content|
expect(content).to match(/^lazyfree-lazy-eviction yes$/)
expect(content).to match(/^lazyfree-lazy-expire no$/)
}
end
end
context 'with lazy eviction enabled' do
before do
stub_gitlab_rb(
redis: {
io_threads: 4,
io_threads_do_reads: true
}
)
end
it 'creates redis config with lazyfree-lazy-eviction yes' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content { |content|
expect(content).to match(/^io-threads 4$/)
expect(content).to match(/^io-threads-do-reads yes$/)
}
end
end
context 'with stop writes on bgsave error disabled' do
before do
stub_gitlab_rb(
redis: {
stop_writes_on_bgsave_error: false
}
)
end
it 'creates redis config with stop-writes-on-bgsave-error no' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content { |content|
expect(content).to match(/^stop-writes-on-bgsave-error no$/)
}
end
end
context 'with tls settings specified' do
let(:gitlab_redis_cli_rc) do
<<-EOF
redis_dir='/var/opt/gitlab/redis'
redis_host='127.0.0.1'
redis_port='0'
redis_tls_port='6380'
redis_tls_auth_clients='no'
redis_tls_cacert_file='/etc/gitlab/ssl/redis-ca.crt'
redis_tls_cacert_dir='/opt/gitlab/embedded/ssl/certs'
redis_tls_cert_file='/etc/gitlab/ssl/redis.crt'
redis_tls_key_file='/etc/gitlab/ssl/redis.key'
redis_socket=''
EOF
end
before do
stub_gitlab_rb(
redis: {
tls_port: 6380,
tls_cert_file: '/etc/gitlab/ssl/redis.crt',
tls_key_file: '/etc/gitlab/ssl/redis.key',
tls_dh_params_file: '/etc/gitlab/ssl/redis-dhparams',
tls_ca_cert_file: '/etc/gitlab/ssl/redis-ca.crt',
tls_ca_cert_dir: '/opt/gitlab/embedded/ssl/certs',
tls_auth_clients: 'no',
tls_replication: 'yes',
tls_cluster: 'yes',
tls_protocols: 'TLSv1.2 TLSv1.3',
tls_ciphers: 'DEFAULT:!MEDIUM',
tls_ciphersuites: 'TLS_CHACHA20_POLY1305_SHA256',
tls_prefer_server_ciphers: 'yes',
tls_session_caching: 'no',
tls_session_cache_size: 10000,
tls_session_cache_timeout: 120
}
)
end
it 'renders redis config with tls settings' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content { |content|
expect(content).to match(%r{^tls-port 6380$})
expect(content).to match(%r{^tls-cert-file /etc/gitlab/ssl/redis.crt$})
expect(content).to match(%r{^tls-key-file /etc/gitlab/ssl/redis.key$})
expect(content).to match(%r{^tls-dh-params-file /etc/gitlab/ssl/redis-dhparams$})
expect(content).to match(%r{^tls-ca-cert-file /etc/gitlab/ssl/redis-ca.crt$})
expect(content).to match(%r{^tls-ca-cert-dir /opt/gitlab/embedded/ssl/certs$})
expect(content).to match(%r{^tls-auth-clients no$})
expect(content).to match(%r{^tls-replication yes$})
expect(content).to match(%r{^tls-cluster yes$})
expect(content).to match(%r{^tls-protocols "TLSv1.2 TLSv1.3"$})
expect(content).to match(%r{^tls-ciphers DEFAULT:!MEDIUM$})
expect(content).to match(%r{^tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256$})
expect(content).to match(%r{^tls-prefer-server-ciphers yes$})
expect(content).to match(%r{^tls-session-caching no$})
expect(content).to match(%r{^tls-session-cache-size 10000$})
expect(content).to match(%r{^tls-session-cache-timeout 120$})
}
end
it 'creates gitlab-redis-cli-rc' do
expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
.with_content(gitlab_redis_cli_rc)
end
end
context 'log directory and runit group' do
context 'default values' do
it_behaves_like 'enabled logged service', 'redis', true, { log_directory_owner: 'gitlab-redis' }
end
context 'custom values' do
before do
stub_gitlab_rb(
redis: {
log_group: 'fugee'
}
)
end
it_behaves_like 'enabled logged service', 'redis', true, { log_directory_owner: 'gitlab-redis', log_group: 'fugee' }
end
end
context 'extra config command' do
context 'when extra_config_command points to a file that does not exist' do
before do
stub_gitlab_rb(
redis: {
extra_config_command: '/tmp/a-file-that-does-not-exist'
}
)
end
it 'raises error' do
expect { chef_run }.to raise_error(Redis::CommandExecutionError).with_message("Redis: Execution of `/tmp/a-file-that-does-not-exist` failed. File does not exist.")
end
end
context 'extra_config_command exits with a non-zero code' do
before do
stub_gitlab_rb(
redis: {
extra_config_command: 'bash /tmp/a-file-that-does-not-exist'
}
)
end
it 'raises error' do
expect { chef_run }.to raise_error(Redis::CommandExecutionError).with_message(/Redis.*exit code 127.*No such file or directory/)
end
end
context 'extra_config_command runs successfully' do
let(:code_file) { Tempfile.new(['redis-code', '.sh']) }
before do
file_content = <<~MSG
#!/usr/bin/env bash
echo 'requirepass "toomanysecrets"'
MSG
File.write(code_file.path, file_content)
stub_gitlab_rb(
redis: {
extra_config_command: "bash #{code_file.path}"
}
)
end
after do
code_file.close
code_file.unlink
end
it 'populates redis.conf with output from command' do
expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
.with_content { |content|
expect(content).to match(/requirepass "toomanysecrets"/)
}
end
end
end
context 'with redis disabled' do
before do
stub_gitlab_rb(redis: { enable: false })
end
it_behaves_like 'disabled runit service', 'redis', 'root', 'root'
end
end