From 159c7372db1c1ce49a0551922e7b9baab6d340df Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Mon, 2 Oct 2017 17:15:23 +0200 Subject: [PATCH] Adding support of Redis Sentinel to Cache, PageCache, and Session. Update version of CM modules requirement. Adding Tests for Redis Session Configuration. Update unit tests for Redis usage with Sentinel. Update version of CM modules requirement. --- .../Session/SaveHandler/Redis/Config.php | 66 +++++++++++++++++- .../Unit/SaveHandler/Redis/ConfigTest.php | 54 +++++++++++++++ .../Setup/Model/ConfigOptionsList/Cache.php | 55 ++++++++++++++- .../Model/ConfigOptionsList/PageCache.php | 63 +++++++++++++++-- .../Setup/Model/ConfigOptionsList/Session.php | 62 ++++++++++++++++- .../Model/ConfigOptionsList/CacheTest.php | 32 +++++++-- .../Model/ConfigOptionsList/PageCacheTest.php | 29 ++++++-- .../Model/ConfigOptionsList/SessionTest.php | 67 ++++++++++++++++--- .../Validator/RedisConnectionValidator.php | 40 +++++++++-- 9 files changed, 433 insertions(+), 35 deletions(-) diff --git a/lib/internal/Magento/Framework/Session/SaveHandler/Redis/Config.php b/lib/internal/Magento/Framework/Session/SaveHandler/Redis/Config.php index e21a92bbbdc24..5bf8714799094 100644 --- a/lib/internal/Magento/Framework/Session/SaveHandler/Redis/Config.php +++ b/lib/internal/Magento/Framework/Session/SaveHandler/Redis/Config.php @@ -100,6 +100,26 @@ class Config implements \Cm\RedisSession\Handler\ConfigInterface */ const PARAM_BREAK_AFTER = 'session/redis/break_after'; + /** + * Configuration path for number of seconds to wait before completely failing to break the lock + */ + const PARAM_FAIL_AFTER = 'session/redis/fail_after'; + + /** + * The name of master to reach via Sentinel servers. + */ + const PARAM_SENTINEL_MASTER = 'session/redis/sentinel_master'; + + /** + * Configuration path indicating if master status should be verified during connections. + */ + const PARAM_SENTINEL_VERIFY_MASTER = 'session/redis/sentinel_master_verify'; + + /** + * Configuration path indicating the number of connect retries for sentinel servers connection. + */ + const PARAM_SENTINEL_CONNECT_RETRIES = 'session/redis/sentinel_connect_retries'; + /** * Cookie lifetime config path */ @@ -300,10 +320,52 @@ public function getLifetime() } /** - * {@inheritdoc} + * Get number of seconds to wait before completely failing to break the lock + * + * @return int */ public function getFailAfter() { - return self::DEFAULT_FAIL_AFTER; + return $this->deploymentConfig->get(self::PARAM_FAIL_AFTER . '_' . $this->appState->getAreaCode()); + } + + /** + * Get list of redis sentinels + * + * @return string + */ + public function getSentinelServers() + { + return $this->getHost(); + } + + /** + * Get sentinel master name + * + * @return string + */ + public function getSentinelMaster() + { + return $this->deploymentConfig->get(self::PARAM_SENTINEL_MASTER); + } + + /** + * Verify master status flag + * + * @return string + */ + public function getSentinelVerifyMaster() + { + return $this->deploymentConfig->get(self::PARAM_SENTINEL_VERIFY_MASTER); + } + + /** + * Connection retries for sentinels + * + * @return string + */ + public function getSentinelConnectRetries() + { + return $this->deploymentConfig->get(self::PARAM_SENTINEL_CONNECT_RETRIES); } } diff --git a/lib/internal/Magento/Framework/Session/Test/Unit/SaveHandler/Redis/ConfigTest.php b/lib/internal/Magento/Framework/Session/Test/Unit/SaveHandler/Redis/ConfigTest.php index 26f3d4c4c4e89..63f696ca41efe 100644 --- a/lib/internal/Magento/Framework/Session/Test/Unit/SaveHandler/Redis/ConfigTest.php +++ b/lib/internal/Magento/Framework/Session/Test/Unit/SaveHandler/Redis/ConfigTest.php @@ -216,6 +216,20 @@ public function testBreakAfter() $this->assertEquals($this->config->getBreakAfter(), $breakAfter); } + public function testFailAfter() + { + $areaCode = 'frontend'; + $breakAfter = 5; + $this->deploymentConfigMock->expects($this->once()) + ->method('get') + ->with(Config::PARAM_FAIL_AFTER . '_' . $areaCode) + ->willReturn($breakAfter); + $this->appStateMock->expects($this->once()) + ->method('getAreaCode') + ->willReturn($areaCode); + $this->assertEquals($this->config->getFailAfter(), $breakAfter); + } + public function testGetLifetimeAdmin() { $areaCode = 'adminhtml'; @@ -246,4 +260,44 @@ public function testGetLifetimeFrontend() ->willReturn($expectedLifetime); $this->assertEquals($this->config->getLifetime(), $expectedLifetime); } + + public function testGetSentinelServers() + { + $expected = 'tcp://127.0.0.1:26379,tcp://127.0.0.2:26379'; + $this->deploymentConfigMock->expects($this->once()) + ->method('get') + ->with(Config::PARAM_HOST) + ->willReturn($expected); + $this->assertEquals($this->config->getSentinelServers(), $expected); + } + + public function testGetSentinelMaster() + { + $expected = 'redismaster'; + $this->deploymentConfigMock->expects($this->once()) + ->method('get') + ->with(Config::PARAM_SENTINEL_MASTER) + ->willReturn($expected); + $this->assertEquals($this->config->getSentinelMaster(), $expected); + } + + public function testGetSentinelVerifyMaster() + { + $expected = '1'; + $this->deploymentConfigMock->expects($this->once()) + ->method('get') + ->with(Config::PARAM_SENTINEL_VERIFY_MASTER) + ->willReturn($expected); + $this->assertEquals($this->config->getSentinelVerifyMaster(), $expected); + } + + public function testGetSentinelConnectRetries() + { + $expected = '3'; + $this->deploymentConfigMock->expects($this->once()) + ->method('get') + ->with(Config::PARAM_SENTINEL_CONNECT_RETRIES) + ->willReturn($expected); + $this->assertEquals($this->config->getSentinelConnectRetries(), $expected); + } } diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php index 1ec9d486a5a22..861deff0fa35c 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php @@ -26,11 +26,19 @@ class Cache implements ConfigOptionsListInterface const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; const INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE = 'cache-backend-redis-db'; const INPUT_KEY_CACHE_BACKEND_REDIS_PORT = 'cache-backend-redis-port'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER = 'cache-backend-redis-sentinel-master'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY = 'cache-backend-redis-sentinel-master-verify'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES= 'cache-backend-redis-sentinel-load-from-slaves'; const CONFIG_PATH_CACHE_BACKEND = 'cache/frontend/default/backend'; const CONFIG_PATH_CACHE_BACKEND_SERVER = 'cache/frontend/default/backend_options/server'; const CONFIG_PATH_CACHE_BACKEND_DATABASE = 'cache/frontend/default/backend_options/database'; const CONFIG_PATH_CACHE_BACKEND_PORT = 'cache/frontend/default/backend_options/port'; + const CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_MASTER = 'cache/frontend/default/backend_options/sentinel_master'; + const CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY = + 'cache/frontend/default/backend_options/sentinel_master_verify'; + const CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES = + 'cache/frontend/default/backend_options/load_from_slaves'; /** * @var array @@ -38,7 +46,10 @@ class Cache implements ConfigOptionsListInterface private $defaultConfigValues = [ self::INPUT_KEY_CACHE_BACKEND_REDIS_SERVER => '127.0.0.1', self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE => '0', - self::INPUT_KEY_CACHE_BACKEND_REDIS_PORT => '6379' + self::INPUT_KEY_CACHE_BACKEND_REDIS_PORT => '6379', + self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER => null, + self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY => null, + self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES => null, ]; /** @@ -55,6 +66,11 @@ class Cache implements ConfigOptionsListInterface self::INPUT_KEY_CACHE_BACKEND_REDIS_SERVER => self::CONFIG_PATH_CACHE_BACKEND_SERVER, self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE => self::CONFIG_PATH_CACHE_BACKEND_DATABASE, self::INPUT_KEY_CACHE_BACKEND_REDIS_PORT => self::CONFIG_PATH_CACHE_BACKEND_PORT, + self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER => self::CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_MASTER, + self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY => + self::CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY, + self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES => + self::CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES, ]; /** @@ -102,7 +118,26 @@ public function getOptions() TextConfigOption::FRONTEND_WIZARD_TEXT, self::CONFIG_PATH_CACHE_BACKEND_PORT, 'Redis server listen port' - ) + ), + new TextConfigOption( + self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_MASTER, + 'Redis sentinel master' + ), + new TextConfigOption( + self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY, + 'Verify connected server is actually master' + ), + new TextConfigOption( + self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES, + 'Using the value \'1\' indicates to only load from slaves and \'2\' ' . + 'to include the master in the random read slave selection' + ), ]; } @@ -191,7 +226,21 @@ private function validateRedisConfig(array $options, DeploymentConfig $deploymen $this->getDefaultConfigValue(self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE) ); - return $this->redisValidator->isValidConnection($config); + $config['sentinel_master'] = isset($options[self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER]) + ? $options[self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER] + : $deploymentConfig->get( + self::CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_MASTER, + $this->getDefaultConfigValue(self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER) + ); + + $config['sentinel_master_verify'] = isset($options[self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY]) + ? $options[self::INPUT_KEY_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY] + : $deploymentConfig->get( + self::CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY, + $this->getDefaultConfigValue(self::CONFIG_PATH_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY) + ); + + return $this->redisValidator->isValidConnection(array_filter($config)); } /** diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php index be1cc5b010185..9e28c283c22ab 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php @@ -27,12 +27,22 @@ class PageCache implements ConfigOptionsListInterface const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE = 'page-cache-redis-db'; const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT = 'page-cache-redis-port'; const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA = 'page-cache-redis-compress-data'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER = 'page-cache-redis-sentinel-master'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY = 'page-cache-redis-sentinel-master-verify'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES= 'page-cache-redis-sentinel-load-from-slaves'; const CONFIG_PATH_PAGE_CACHE_BACKEND = 'cache/frontend/page_cache/backend'; const CONFIG_PATH_PAGE_CACHE_BACKEND_SERVER = 'cache/frontend/page_cache/backend_options/server'; const CONFIG_PATH_PAGE_CACHE_BACKEND_DATABASE = 'cache/frontend/page_cache/backend_options/database'; const CONFIG_PATH_PAGE_CACHE_BACKEND_PORT = 'cache/frontend/page_cache/backend_options/port'; - const CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA = 'cache/frontend/page_cache/backend_options/compress_data'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA = + 'cache/frontend/page_cache/backend_options/compress_data'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER = + 'cache/frontend/page_cache/backend_options/sentinel_master'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY = + 'cache/frontend/page_cache/backend_options/sentinel_master_verify'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES = + 'cache/frontend/page_cache/backend_options/load_from_slaves'; /** * @var array @@ -41,7 +51,10 @@ class PageCache implements ConfigOptionsListInterface self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER => '127.0.0.1', self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE => '1', self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT => '6379', - self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => '0' + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => '0', + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER => null, + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY => null, + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES => null, ]; /** @@ -58,7 +71,13 @@ class PageCache implements ConfigOptionsListInterface self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER => self::CONFIG_PATH_PAGE_CACHE_BACKEND_SERVER, self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE => self::CONFIG_PATH_PAGE_CACHE_BACKEND_DATABASE, self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT => self::CONFIG_PATH_PAGE_CACHE_BACKEND_PORT, - self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => self::CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => self::CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA, + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER => + self::CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER, + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY => + self::CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY, + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES => + self::CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES, ]; /** @@ -112,7 +131,26 @@ public function getOptions() TextConfigOption::FRONTEND_WIZARD_TEXT, self::CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA, 'Set to 1 to compress the full page cache (use 0 to disable)' - ) + ), + new TextConfigOption( + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER, + 'Redis sentinel master' + ), + new TextConfigOption( + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY, + 'Verify connected server is actually master' + ), + new TextConfigOption( + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_LOAD_FROM_SLAVES, + 'Using the value \'1\' indicates to only load from slaves ' . + 'and \'2\' to include the master in the random read slave selection' + ), ]; } @@ -202,7 +240,22 @@ private function validateRedisConfig(array $options, DeploymentConfig $deploymen $this->getDefaultConfigValue(self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE) ); - return $this->redisValidator->isValidConnection($config); + $config['sentinel_master'] = isset($options[self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER]) + ? $options[self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER] + : $deploymentConfig->get( + self::CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER, + null + ); + + $config['sentinel_master_verify'] = + isset($options[self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY]) + ? $options[self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY] + : $deploymentConfig->get( + self::CONFIG_PATH_PAGE_CACHE_BACKEND_REDIS_SENTINEL_MASTER_VERIFY, + null + ); + + return $this->redisValidator->isValidConnection(array_filter($config)); } /** diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Session.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Session.php index 3b3fbf33a02e2..1b51b21f8c648 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Session.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Session.php @@ -31,12 +31,18 @@ class Session implements ConfigOptionsListInterface const INPUT_KEY_SESSION_REDIS_MAX_CONCURRENCY = 'session-save-redis-max-concurrency'; const INPUT_KEY_SESSION_REDIS_BREAK_AFTER_FRONTEND = 'session-save-redis-break-after-frontend'; const INPUT_KEY_SESSION_REDIS_BREAK_AFTER_ADMINHTML = 'session-save-redis-break-after-adminhtml'; + const INPUT_KEY_SESSION_REDIS_FAIL_AFTER_FRONTEND = 'session-save-redis-fail-after-frontend'; + const INPUT_KEY_SESSION_REDIS_FAIL_AFTER_ADMINHTML = 'session-save-redis-fail-after-adminhtml'; const INPUT_KEY_SESSION_REDIS_FIRST_LIFETIME = 'session-save-redis-first-lifetime'; const INPUT_KEY_SESSION_REDIS_BOT_FIRST_LIFETIME = 'session-save-redis-bot-first-lifetime'; const INPUT_KEY_SESSION_REDIS_BOT_LIFETIME = 'session-save-redis-bot-lifetime'; const INPUT_KEY_SESSION_REDIS_DISABLE_LOCKING = 'session-save-redis-disable-locking'; const INPUT_KEY_SESSION_REDIS_MIN_LIFETIME = 'session-save-redis-min-lifetime'; const INPUT_KEY_SESSION_REDIS_MAX_LIFETIME = 'session-save-redis-max-lifetime'; + const INPUT_KEY_SESSION_REDIS_SENTINEL_MASTER = 'session-save-redis-sentinel-master'; + const INPUT_KEY_SESSION_REDIS_SENTINEL_MASTER_VERIFY = 'session-save-redis-sentinel-master-verify'; + const INPUT_KEY_SESSION_REDIS_SENTINEL_LOAD_FROM_SLAVES= 'session-save-redis-sentinel-load-from-slaves'; + const INPUT_KEY_SESSION_REDIS_SENTINEL_CONNECT_RETRIES= 'session-save-redis-sentinel-connect-retries'; const CONFIG_PATH_SESSION_REDIS = 'session/redis'; const CONFIG_PATH_SESSION_REDIS_HOST = 'session/redis/host'; @@ -51,12 +57,18 @@ class Session implements ConfigOptionsListInterface const CONFIG_PATH_SESSION_REDIS_MAX_CONCURRENCY = 'session/redis/max_concurrency'; const CONFIG_PATH_SESSION_REDIS_BREAK_AFTER_FRONTEND = 'session/redis/break_after_frontend'; const CONFIG_PATH_SESSION_REDIS_BREAK_AFTER_ADMINHTML = 'session/redis/break_after_adminhtml'; + const CONFIG_PATH_SESSION_REDIS_FAIL_AFTER_FRONTEND = 'session/redis/fail_after_frontend'; + const CONFIG_PATH_SESSION_REDIS_FAIL_AFTER_ADMINHTML = 'session/redis/fail_after_adminhtml'; const CONFIG_PATH_SESSION_REDIS_FIRST_LIFETIME = 'session/redis/first_lifetime'; const CONFIG_PATH_SESSION_REDIS_BOT_FIRST_LIFETIME = 'session/redis/bot_first_lifetime'; const CONFIG_PATH_SESSION_REDIS_BOT_LIFETIME = 'session/redis/bot_lifetime'; const CONFIG_PATH_SESSION_REDIS_DISABLE_LOCKING = 'session/redis/disable_locking'; const CONFIG_PATH_SESSION_REDIS_MIN_LIFETIME = 'session/redis/min_lifetime'; const CONFIG_PATH_SESSION_REDIS_MAX_LIFETIME = 'session/redis/max_lifetime'; + const CONFIG_PATH_SESSION_REDIS_SENTINEL_MASTER = 'session/redis/sentinel_master'; + const CONFIG_PATH_SESSION_REDIS_SENTINEL_MASTER_VERIFY = 'session/redis/sentinel_master_verify'; + const CONFIG_PATH_SESSION_REDIS_SENTINEL_LOAD_FROM_SLAVES = 'session/redis/load_from_slaves'; + const CONFIG_PATH_SESSION_REDIS_SENTINEL_CONNECT_RETRIES = 'session/redis/sentinel_connect_retries'; /** * @var array @@ -75,12 +87,15 @@ class Session implements ConfigOptionsListInterface self::INPUT_KEY_SESSION_REDIS_MAX_CONCURRENCY => '6', self::INPUT_KEY_SESSION_REDIS_BREAK_AFTER_FRONTEND => '5', self::INPUT_KEY_SESSION_REDIS_BREAK_AFTER_ADMINHTML => '30', + self::INPUT_KEY_SESSION_REDIS_FAIL_AFTER_FRONTEND => '1', + self::INPUT_KEY_SESSION_REDIS_FAIL_AFTER_ADMINHTML => '10', self::INPUT_KEY_SESSION_REDIS_FIRST_LIFETIME => '600', self::INPUT_KEY_SESSION_REDIS_BOT_FIRST_LIFETIME => '60', self::INPUT_KEY_SESSION_REDIS_BOT_LIFETIME => '7200', self::INPUT_KEY_SESSION_REDIS_DISABLE_LOCKING => '0', self::INPUT_KEY_SESSION_REDIS_MIN_LIFETIME => '60', - self::INPUT_KEY_SESSION_REDIS_MAX_LIFETIME => '2592000' + self::INPUT_KEY_SESSION_REDIS_MAX_LIFETIME => '2592000', + self::INPUT_KEY_SESSION_REDIS_SENTINEL_CONNECT_RETRIES => 3, ]; /** @@ -115,12 +130,20 @@ class Session implements ConfigOptionsListInterface self::INPUT_KEY_SESSION_REDIS_MAX_CONCURRENCY => self::CONFIG_PATH_SESSION_REDIS_MAX_CONCURRENCY, self::INPUT_KEY_SESSION_REDIS_BREAK_AFTER_FRONTEND => self::CONFIG_PATH_SESSION_REDIS_BREAK_AFTER_FRONTEND, self::INPUT_KEY_SESSION_REDIS_BREAK_AFTER_ADMINHTML => self::CONFIG_PATH_SESSION_REDIS_BREAK_AFTER_ADMINHTML, + self::INPUT_KEY_SESSION_REDIS_FAIL_AFTER_FRONTEND => self::CONFIG_PATH_SESSION_REDIS_FAIL_AFTER_FRONTEND, + self::INPUT_KEY_SESSION_REDIS_FAIL_AFTER_ADMINHTML => self::CONFIG_PATH_SESSION_REDIS_FAIL_AFTER_ADMINHTML, self::INPUT_KEY_SESSION_REDIS_FIRST_LIFETIME => self::CONFIG_PATH_SESSION_REDIS_FIRST_LIFETIME, self::INPUT_KEY_SESSION_REDIS_BOT_FIRST_LIFETIME => self::CONFIG_PATH_SESSION_REDIS_BOT_FIRST_LIFETIME, self::INPUT_KEY_SESSION_REDIS_BOT_LIFETIME => self::CONFIG_PATH_SESSION_REDIS_BOT_LIFETIME, self::INPUT_KEY_SESSION_REDIS_DISABLE_LOCKING => self::CONFIG_PATH_SESSION_REDIS_DISABLE_LOCKING, self::INPUT_KEY_SESSION_REDIS_MIN_LIFETIME => self::CONFIG_PATH_SESSION_REDIS_MIN_LIFETIME, self::INPUT_KEY_SESSION_REDIS_MAX_LIFETIME => self::CONFIG_PATH_SESSION_REDIS_MAX_LIFETIME, + self::INPUT_KEY_SESSION_REDIS_SENTINEL_MASTER => self::CONFIG_PATH_SESSION_REDIS_SENTINEL_MASTER, + self::INPUT_KEY_SESSION_REDIS_SENTINEL_MASTER_VERIFY => self::CONFIG_PATH_SESSION_REDIS_SENTINEL_MASTER_VERIFY, + self::INPUT_KEY_SESSION_REDIS_SENTINEL_LOAD_FROM_SLAVES + => self::CONFIG_PATH_SESSION_REDIS_SENTINEL_LOAD_FROM_SLAVES, + self::INPUT_KEY_SESSION_REDIS_SENTINEL_CONNECT_RETRIES + => self::CONFIG_PATH_SESSION_REDIS_SENTINEL_CONNECT_RETRIES, ]; /** @@ -210,6 +233,18 @@ public function getOptions() self::CONFIG_PATH_SESSION_REDIS_BREAK_AFTER_ADMINHTML, 'Number of seconds to wait before trying to break a lock for Admin session' ), + new TextConfigOption( + self::INPUT_KEY_SESSION_REDIS_FAIL_AFTER_FRONTEND, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_SESSION_REDIS_FAIL_AFTER_FRONTEND, + 'Number of seconds to wait before completely failing to break the lock for frontend session' + ), + new TextConfigOption( + self::INPUT_KEY_SESSION_REDIS_FAIL_AFTER_ADMINHTML, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_SESSION_REDIS_FAIL_AFTER_ADMINHTML, + 'Number of seconds to wait before completely failing to break the lock for Admin session' + ), new TextConfigOption( self::INPUT_KEY_SESSION_REDIS_FIRST_LIFETIME, TextConfigOption::FRONTEND_WIZARD_TEXT, @@ -246,6 +281,31 @@ public function getOptions() self::CONFIG_PATH_SESSION_REDIS_MAX_LIFETIME, 'Redis max session lifetime, in seconds' ), + new TextConfigOption( + self::INPUT_KEY_SESSION_REDIS_SENTINEL_MASTER, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_SESSION_REDIS_SENTINEL_MASTER, + 'Redis sentinel master' + ), + new TextConfigOption( + self::INPUT_KEY_SESSION_REDIS_SENTINEL_MASTER_VERIFY, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_SESSION_REDIS_SENTINEL_MASTER_VERIFY, + 'Verify connected server is actually master' + ), + new TextConfigOption( + self::INPUT_KEY_SESSION_REDIS_SENTINEL_LOAD_FROM_SLAVES, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_SESSION_REDIS_SENTINEL_LOAD_FROM_SLAVES, + 'Using the value \'1\' indicates to only load from slaves and ' . + ' \'2\' to include the master in the random read slave selection' + ), + new TextConfigOption( + self::INPUT_KEY_SESSION_REDIS_SENTINEL_CONNECT_RETRIES, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_SESSION_REDIS_SENTINEL_CONNECT_RETRIES, + 'The number of attempts when contacting Redis Sentinel servers.' + ), ]; } diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php index ef0ea3e988364..e3b2e0d2df601 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php @@ -39,7 +39,7 @@ protected function setUp() public function testGetOptions() { $options = $this->configOptionsList->getOptions(); - $this->assertCount(4, $options); + $this->assertCount(7, $options); $this->assertArrayHasKey(0, $options); $this->assertInstanceOf(SelectConfigOption::class, $options[0]); @@ -56,6 +56,18 @@ public function testGetOptions() $this->assertArrayHasKey(3, $options); $this->assertInstanceOf(TextConfigOption::class, $options[3]); $this->assertEquals('cache-backend-redis-port', $options[3]->getName()); + + $this->assertArrayHasKey(4, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[4]); + $this->assertEquals('cache-backend-redis-sentinel-master', $options[4]->getName()); + + $this->assertArrayHasKey(5, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[5]); + $this->assertEquals('cache-backend-redis-sentinel-master-verify', $options[5]->getName()); + + $this->assertArrayHasKey(2, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[6]); + $this->assertEquals('cache-backend-redis-sentinel-load-from-slaves', $options[6]->getName()); } public function testCreateConfigCacheRedis() @@ -70,7 +82,10 @@ public function testCreateConfigCacheRedis() 'backend_options' => [ 'server' => '', 'port' => '', - 'database' => '' + 'database' => '', + 'sentinel_master' => '', + 'sentinel_master_verify' => '', + 'load_from_slaves' => '', ] ] ] @@ -92,17 +107,24 @@ public function testCreateConfigWithRedisConfig() 'backend_options' => [ 'server' => 'localhost', 'port' => '1234', - 'database' => '5' + 'database' => '5', + 'sentinel_master' => 'redismaster', + 'sentinel_master_verify' => '1', + 'load_from_slaves' => '2', ] ] ] ] ]; + $options = [ 'cache-backend' => 'redis', 'cache-backend-redis-server' => 'localhost', 'cache-backend-redis-port' => '1234', - 'cache-backend-redis-db' => '5' + 'cache-backend-redis-db' => '5', + 'cache-backend-redis-sentinel-master' => 'redismaster', + 'cache-backend-redis-sentinel-master-verify' => '1', + 'cache-backend-redis-sentinel-load-from-slaves' => '2', ]; $configData = $this->configOptionsList->createConfig($options, $this->deploymentConfigMock); @@ -118,7 +140,7 @@ public function testValidateWithValidInput() ]; $this->validatorMock->expects($this->once()) ->method('isValidConnection') - ->with(['host'=>'localhost', 'db'=>'', 'port'=>'']) + ->with(['host'=>'localhost']) ->willReturn(true); $errors = $this->configOptionsList->validate($options, $this->deploymentConfigMock); diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php index e654bea9ac1c5..9edd2be443fc3 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php @@ -39,7 +39,7 @@ protected function setUp() public function testGetOptions() { $options = $this->configList->getOptions(); - $this->assertCount(5, $options); + $this->assertCount(8, $options); $this->assertArrayHasKey(0, $options); $this->assertInstanceOf(SelectConfigOption::class, $options[0]); @@ -60,6 +60,18 @@ public function testGetOptions() $this->assertArrayHasKey(4, $options); $this->assertInstanceOf(TextConfigOption::class, $options[4]); $this->assertEquals('page-cache-redis-compress-data', $options[4]->getName()); + + $this->assertArrayHasKey(4, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[5]); + $this->assertEquals('page-cache-redis-sentinel-master', $options[5]->getName()); + + $this->assertArrayHasKey(4, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[6]); + $this->assertEquals('page-cache-redis-sentinel-master-verify', $options[6]->getName()); + + $this->assertArrayHasKey(4, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[7]); + $this->assertEquals('page-cache-redis-sentinel-load-from-slaves', $options[7]->getName()); } public function testCreateConfigWithRedis() @@ -75,7 +87,10 @@ public function testCreateConfigWithRedis() 'server'=> '', 'port' => '', 'database' => '', - 'compress_data' => '' + 'compress_data' => '', + 'sentinel_master' => '', + 'sentinel_master_verify' => '', + 'load_from_slaves' => '', ] ] ] @@ -98,7 +113,10 @@ public function testCreateConfigWithRedisConfiguration() 'server' => 'foo.bar', 'port' => '9000', 'database' => '6', - 'compress_data' => '1' + 'compress_data' => '1', + 'sentinel_master' => 'redismaster', + 'sentinel_master_verify' => '1', + 'load_from_slaves' => '2', ] ] ] @@ -110,7 +128,10 @@ public function testCreateConfigWithRedisConfiguration() 'page-cache-redis-server' => 'foo.bar', 'page-cache-redis-port' => '9000', 'page-cache-redis-db' => '6', - 'page-cache-redis-compress-data' => '1' + 'page-cache-redis-compress-data' => '1', + 'page-cache-redis-sentinel-master' => 'redismaster', + 'page-cache-redis-sentinel-master-verify' => '1', + 'page-cache-redis-sentinel-load-from-slaves' => '2', ]; $configData = $this->configList->createConfig($options, $this->deploymentConfigMock); diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/SessionTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/SessionTest.php index d37c07e715482..a7588cdf8e00e 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/SessionTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/SessionTest.php @@ -29,10 +29,13 @@ protected function setUp() $this->deploymentConfigMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function testGetOptions() { $options = $this->configList->getOptions(); - $this->assertCount(19, $options); + $this->assertCount(25, $options); $this->assertArrayHasKey(0, $options); $this->assertInstanceOf(SelectConfigOption::class, $options[0]); @@ -88,27 +91,51 @@ public function testGetOptions() $this->assertArrayHasKey(13, $options); $this->assertInstanceOf(TextConfigOption::class, $options[13]); - $this->assertEquals('session-save-redis-first-lifetime', $options[13]->getName()); + $this->assertEquals('session-save-redis-fail-after-frontend', $options[13]->getName()); $this->assertArrayHasKey(14, $options); $this->assertInstanceOf(TextConfigOption::class, $options[14]); - $this->assertEquals('session-save-redis-bot-first-lifetime', $options[14]->getName()); + $this->assertEquals('session-save-redis-fail-after-adminhtml', $options[14]->getName()); $this->assertArrayHasKey(15, $options); $this->assertInstanceOf(TextConfigOption::class, $options[15]); - $this->assertEquals('session-save-redis-bot-lifetime', $options[15]->getName()); + $this->assertEquals('session-save-redis-first-lifetime', $options[15]->getName()); $this->assertArrayHasKey(16, $options); $this->assertInstanceOf(TextConfigOption::class, $options[16]); - $this->assertEquals('session-save-redis-disable-locking', $options[16]->getName()); + $this->assertEquals('session-save-redis-bot-first-lifetime', $options[16]->getName()); $this->assertArrayHasKey(17, $options); $this->assertInstanceOf(TextConfigOption::class, $options[17]); - $this->assertEquals('session-save-redis-min-lifetime', $options[17]->getName()); + $this->assertEquals('session-save-redis-bot-lifetime', $options[17]->getName()); $this->assertArrayHasKey(18, $options); $this->assertInstanceOf(TextConfigOption::class, $options[18]); - $this->assertEquals('session-save-redis-max-lifetime', $options[18]->getName()); + $this->assertEquals('session-save-redis-disable-locking', $options[18]->getName()); + + $this->assertArrayHasKey(19, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[19]); + $this->assertEquals('session-save-redis-min-lifetime', $options[19]->getName()); + + $this->assertArrayHasKey(20, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[20]); + $this->assertEquals('session-save-redis-max-lifetime', $options[20]->getName()); + + $this->assertArrayHasKey(21, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[21]); + $this->assertEquals('session-save-redis-sentinel-master', $options[21]->getName()); + + $this->assertArrayHasKey(22, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[22]); + $this->assertEquals('session-save-redis-sentinel-master-verify', $options[22]->getName()); + + $this->assertArrayHasKey(23, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[23]); + $this->assertEquals('session-save-redis-sentinel-load-from-slaves', $options[23]->getName()); + + $this->assertArrayHasKey(24, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[24]); + $this->assertEquals('session-save-redis-sentinel-connect-retries', $options[24]->getName()); } public function testCreateConfig() @@ -156,7 +183,13 @@ public function testCreateConfigWithSessionSaveRedis() 'bot_lifetime' => '', 'disable_locking' => '', 'min_lifetime' => '', - 'max_lifetime' => '' + 'max_lifetime' => '', + 'fail_after_frontend' => '', + 'fail_after_adminhtml' => '', + 'sentinel_connect_retries' => '', + 'sentinel_master' => '', + 'sentinel_master_verify' => '', + 'load_from_slaves' => '', ] ] @@ -186,6 +219,10 @@ public function testCreateConfigWithRedisInput() 'session-save-redis-log-level' => '4', 'session-save-redis-min-lifetime' => '60', 'session-save-redis-max-lifetime' => '3600', + 'session-save-redis-sentinel-master' => 'redismaster', + 'session-save-redis-sentinel-master-verify' => '1', + 'session-save-redis-sentinel-load-from-slaves'=> '2', + 'session-save-redis-sentinel-connect-retries'=> '3', ]; $expectedConfigData = [ @@ -209,7 +246,13 @@ public function testCreateConfigWithRedisInput() 'bot_lifetime' => '', 'disable_locking' => '', 'min_lifetime' => '60', - 'max_lifetime' => '3600' + 'max_lifetime' => '3600', + 'sentinel_master' => 'redismaster', + 'sentinel_master_verify' => '1', + 'load_from_slaves' => '2', + 'fail_after_frontend' => '', + 'fail_after_adminhtml' => '', + 'sentinel_connect_retries' => 3, ] ], @@ -277,12 +320,18 @@ public function redisOptionProvider() ['session-save-redis-max-concurrency', 'max_concurrency', '3'], ['session-save-redis-break-after-frontend', 'break_after_frontend', '10'], ['session-save-redis-break-after-adminhtml', 'break_after_adminhtml', '20'], + ['session-save-redis-fail-after-frontend', 'fail_after_frontend', '1'], + ['session-save-redis-fail-after-adminhtml', 'fail_after_adminhtml', '10'], ['session-save-redis-first-lifetime', 'first_lifetime', '300'], ['session-save-redis-bot-first-lifetime', 'bot_first_lifetime', '30'], ['session-save-redis-bot-lifetime', 'bot_lifetime', '3600'], ['session-save-redis-disable-locking', 'disable_locking', '1'], ['session-save-redis-min-lifetime', 'min_lifetime', '20'], ['session-save-redis-max-lifetime', 'max_lifetime', '12000'], + ['session-save-redis-sentinel-master', 'sentinel_master', 'redismaster'], + ['session-save-redis-sentinel-master-verify', 'sentinel_master_verify', '1'], + ['session-save-redis-sentinel-load-from-slaves', 'load_from_slaves', '2'], + ['session-save-redis-sentinel-connect-retries', 'sentinel_connect_retries', '1'], ]; } diff --git a/setup/src/Magento/Setup/Validator/RedisConnectionValidator.php b/setup/src/Magento/Setup/Validator/RedisConnectionValidator.php index 58696b9abd46d..e590f9378d0d5 100644 --- a/setup/src/Magento/Setup/Validator/RedisConnectionValidator.php +++ b/setup/src/Magento/Setup/Validator/RedisConnectionValidator.php @@ -15,22 +15,49 @@ class RedisConnectionValidator * Validate redis connection * * @param array $redisOptions + * * @return bool */ public function isValidConnection(array $redisOptions) { $default = [ - 'host' => '', - 'port' => '', - 'db' => '', - 'password' => null, - 'timeout' => null, - 'persistent' => '' + 'host' => '', + 'port' => '', + 'db' => '', + 'password' => null, + 'timeout' => null, + 'persistent' => '', + 'sentinel_master' => null, + 'sentinel_master_verify' => null, ]; $config = array_merge($default, $redisOptions); try { + // If Redis is set to use sentinel, try to retrieve master from Sentinel servers, then try connecting to it. + if (isset($config['sentinel_master'])) { + $sentinelClient = new \Credis_Client( + $config['host'], + $config['port'], + $config['timeout'], + $config['persistent'] + ); + $sentinelClient->forceStandalone(); + $sentinelClient->setMaxConnectRetries(0); + + $sentinel = new \Credis_Sentinel($sentinelClient); + $sentinel + ->setClientTimeout($config['timeout']) + ->setClientPersistent($config['persistent']); + + $redisClient = $sentinel->getMasterClient($config['sentinel_master']); + $redisClient->setMaxConnectRetries(1); + $redisClient->connect(); + + return true; + } + + // When not using sentinel mode, just process standard check. $redisClient = new \Credis_Client( $config['host'], $config['port'], @@ -39,6 +66,7 @@ public function isValidConnection(array $redisOptions) $config['db'], $config['password'] ); + $redisClient->setMaxConnectRetries(1); $redisClient->connect(); } catch (\CredisException $e) {