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) {