diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php index b4f7e43387d0efd50604054cc69bbb8ea74748d8..ee1df8f23424d7fc14b65da13fa461b3304e10e4 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php @@ -87,6 +87,7 @@ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuild ->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()) ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->order('t.min_price ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php index f018e2b148f156a01006f187a8595345a8a2b9f3..8841b6059c46ff2b024edd9b90d4e431ce7f0466 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php @@ -95,6 +95,7 @@ class LinkedProductSelectBuilderByBasePrice implements LinkedProductSelectBuilde ->where('t.attribute_id = ?', $priceAttribute->getAttributeId()) ->where('t.value IS NOT NULL') ->order('t.value ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php index b4459cd1eea078e65aa3d3b7a20f58096c1ec39b..5c47185a85bf407d2c13215c379611f2d144cb0d 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php @@ -139,6 +139,7 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui 'special_to.value IS NULL OR ' . $connection->getDatePartSql('special_to.value') .' >= ?', $currentDate )->order('t.value ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $specialPrice = $this->baseSelectProcessor->process($specialPrice); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php index 79323e57b033e36c6ede529e66b1d52cab9ef656..37281193d6a1b3509779bd18ec8a2008b14c0305 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php @@ -97,6 +97,7 @@ class LinkedProductSelectBuilderByTierPrice implements LinkedProductSelectBuilde ->where('t.all_groups = 1 OR customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.qty = ?', 1) ->order('t.value ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php index 6b908d317aa5b815e36f608f376a8d970b9a7d75..cec862ee9661f0aac6ca7d23c9ca17a1338a8b7c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php @@ -84,7 +84,7 @@ class LinkedProductSelectBuilderByIndexPriceTest extends \PHPUnit\Framework\Test $select->expects($this->any())->method('from')->willReturnSelf(); $select->expects($this->any())->method('joinInner')->willReturnSelf(); $select->expects($this->any())->method('where')->willReturnSelf(); - $select->expects($this->once())->method('order')->willReturnSelf(); + $select->expects($this->exactly(2))->method('order')->willReturnSelf(); $select->expects($this->once())->method('limit')->willReturnSelf(); $this->resourceMock->expects($this->any())->method('getConnection')->willReturn($connection); $this->metadataPoolMock->expects($this->once())->method('getMetadata')->willReturn($metadata); diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php index 00808f38c913263d2dc2ddfade74416069f4326f..3f396cacd37dab442e332343586d6f27c880b72f 100644 --- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php @@ -105,6 +105,7 @@ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelec ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.rule_date = ?', $currentDate) ->order('t.rule_price ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index 4ebdcc58240a1ee16216ba0c28fcd3a8c104357e..af690f1899e7beb485740509cd2d8015cf2711f8 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -66,6 +66,14 @@ class AdminSessionsManager */ private $remoteAddress; + /** + * Max lifetime for session prolong to be valid (sec) + * + * Means that after session was prolonged + * all other prolongs will be ignored within this period + */ + private $maxIntervalBetweenConsecutiveProlongs = 60; + /** * @param ConfigInterface $securityConfig * @param \Magento\Backend\Model\Auth\Session $authSession @@ -124,11 +132,16 @@ class AdminSessionsManager */ public function processProlong() { - $this->getCurrentSession()->setData( - 'updated_at', - $this->authSession->getUpdatedAt() - ); - $this->getCurrentSession()->save(); + if ($this->lastProlongIsOldEnough()) { + $this->getCurrentSession()->setData( + 'updated_at', + date( + \Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT, + $this->authSession->getUpdatedAt() + ) + ); + $this->getCurrentSession()->save(); + } return $this; } @@ -298,4 +311,45 @@ class AdminSessionsManager { return $this->adminSessionInfoCollectionFactory->create(); } + + /** + * Calculates diff between now and last session updated_at + * and decides whether new prolong must be triggered or not + * + * This is done to limit amount of session prolongs and updates to database + * within some period of time - X + * X - is calculated in getIntervalBetweenConsecutiveProlongs() + * + * @see getIntervalBetweenConsecutiveProlongs() + * @return bool + */ + private function lastProlongIsOldEnough() + { + $lastProlongTimestamp = strtotime($this->getCurrentSession()->getUpdatedAt()); + $nowTimestamp = $this->authSession->getUpdatedAt(); + + $diff = $nowTimestamp - $lastProlongTimestamp; + + return (float) $diff > $this->getIntervalBetweenConsecutiveProlongs(); + } + + /** + * Calculates lifetime for session prolong to be valid + * + * Calculation is based on admin session lifetime + * Calculated result is in seconds and is in the interval + * between 1 (including) and MAX_INTERVAL_BETWEEN_CONSECUTIVE_PROLONGS (including) + * + * @return float + */ + private function getIntervalBetweenConsecutiveProlongs() + { + return (float) max( + 1, + min( + 4 * log((float)$this->securityConfig->getAdminSessionLifetime()), + $this->maxIntervalBetweenConsecutiveProlongs + ) + ); + } } diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php index d81264f661762ca2a29cad90d203684b7ce01339..ddfeaa59ac224f72bab2cf3aa12537c693e8ac9a 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php @@ -99,7 +99,8 @@ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase 'setIsOtherSessionsTerminated', 'save', 'getUserId', - 'getSessionId' + 'getSessionId', + 'getUpdatedAt' ]); $this->securityConfigMock = $this->getMockBuilder(\Magento\Security\Model\ConfigInterface::class) @@ -216,7 +217,8 @@ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase public function testProcessProlong() { $sessionId = 50; - $updatedAt = '2015-12-31 23:59:59'; + $lastUpdatedAt = '2015-12-31 23:59:59'; + $newUpdatedAt = '2016-01-01 00:00:30'; $this->adminSessionInfoFactoryMock->expects($this->any()) ->method('create') @@ -230,13 +232,21 @@ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase ->method('load') ->willReturnSelf(); - $this->authSessionMock->expects($this->once()) + $this->currentSessionMock->expects($this->once()) + ->method('getUpdatedAt') + ->willReturn($lastUpdatedAt); + + $this->authSessionMock->expects($this->exactly(2)) ->method('getUpdatedAt') - ->willReturn($updatedAt); + ->willReturn(strtotime($newUpdatedAt)); + + $this->securityConfigMock->expects($this->once()) + ->method('getAdminSessionLifetime') + ->willReturn(100); $this->currentSessionMock->expects($this->once()) ->method('setData') - ->with('updated_at', $updatedAt) + ->with('updated_at', $newUpdatedAt) ->willReturnSelf(); $this->currentSessionMock->expects($this->once()) diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php index 7166779621001bec29d4cb552362ad6835d9dcbc..52268dc96d8a325720b1f824cc0687b28d706a5f 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php @@ -40,6 +40,11 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase */ protected $dateTime; + /** + * @var \Magento\Security\Model\ConfigInterface + */ + protected $securityConfig; + /** * Set up */ @@ -54,8 +59,9 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase $this->authSession = $this->objectManager->create(\Magento\Backend\Model\Auth\Session::class); $this->adminSessionInfo = $this->objectManager->create(\Magento\Security\Model\AdminSessionInfo::class); $this->auth->setAuthStorage($this->authSession); - $this->adminSessionsManager = $this->objectManager->create(\Magento\Security\Model\AdminSessionsManager::class); + $this->adminSessionsManager = $this->objectManager->get(\Magento\Security\Model\AdminSessionsManager::class); $this->dateTime = $this->objectManager->create(\Magento\Framework\Stdlib\DateTime::class); + $this->securityConfig = $this->objectManager->create(\Magento\Security\Model\ConfigInterface::class); } /** @@ -73,7 +79,42 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase /** * Test of prolong user action + * session manager will not trigger new prolong if previous prolong was less than X sec ago + * X - is calculated based on current admin session lifetime * + * @see \Magento\Security\Model\AdminSessionsManager::lastProlongIsOldEnough + * @magentoDbIsolation enabled + */ + public function testConsecutiveProcessProlong() + { + $this->auth->login( + \Magento\TestFramework\Bootstrap::ADMIN_NAME, + \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD + ); + $sessionId = $this->authSession->getSessionId(); + $prolongsDiff = log($this->securityConfig->getAdminSessionLifetime()) - 2; // X from comment above + $dateInPast = $this->dateTime->formatDate($this->authSession->getUpdatedAt() - $prolongsDiff); + $this->adminSessionsManager->getCurrentSession() + ->setData( + 'updated_at', + $dateInPast + ) + ->save(); + $this->adminSessionInfo->load($sessionId, 'session_id'); + $oldUpdatedAt = $this->adminSessionInfo->getUpdatedAt(); + $this->authSession->prolong(); + $this->adminSessionInfo->load($sessionId, 'session_id'); + $updatedAt = $this->adminSessionInfo->getUpdatedAt(); + + $this->assertSame(strtotime($oldUpdatedAt), strtotime($updatedAt)); + } + + /** + * Test of prolong user action + * session manager will trigger new prolong if previous prolong was more than X sec ago + * X - is calculated based on current admin session lifetime + * + * @see \Magento\Security\Model\AdminSessionsManager::lastProlongIsOldEnough * @magentoDbIsolation enabled */ public function testProcessProlong() @@ -83,7 +124,8 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD ); $sessionId = $this->authSession->getSessionId(); - $dateInPast = $this->dateTime->formatDate($this->authSession->getUpdatedAt() - 100); + $prolongsDiff = 4 * log($this->securityConfig->getAdminSessionLifetime()) + 2; // X from comment above + $dateInPast = $this->dateTime->formatDate($this->authSession->getUpdatedAt() - $prolongsDiff); $this->adminSessionsManager->getCurrentSession() ->setData( 'updated_at', @@ -95,6 +137,7 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase $this->authSession->prolong(); $this->adminSessionInfo->load($sessionId, 'session_id'); $updatedAt = $this->adminSessionInfo->getUpdatedAt(); - $this->assertGreaterThan($oldUpdatedAt, $updatedAt); + + $this->assertGreaterThan(strtotime($oldUpdatedAt), strtotime($updatedAt)); } } diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 154915cd3a4fcb7b9abd2bf5cb304625a50470a3..ef407b9206c0f9ccc899b3ced8c8367fb99ac870 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -12976,6 +12976,9 @@ vars.put("admin_user", adminUser); //Index of the current product from the cluster Random random = new Random(); + if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); + } int iterator = random.nextInt(clusterLength); if (iterator == 0) { iterator = 1; @@ -32318,7 +32321,7 @@ vars.put("admin_user", adminUser); <stringProp name="RegexExtractor.regex">actions":\{"edit":\{"href":"(?:http|https):\\/\\/(.*?)\\/customer\\/index\\/edit\\/id\\/(\d+)\\/",</stringProp> <stringProp name="RegexExtractor.template">/customer/index/edit/id/$2$/</stringProp> <stringProp name="RegexExtractor.default"/> - <stringProp name="RegexExtractor.match_number">0</stringProp> + <stringProp name="RegexExtractor.match_number">1</stringProp> </RegexExtractor> <hashTree/> <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert customer edit url" enabled="true"> @@ -34535,6 +34538,9 @@ vars.put("admin_user", adminUser); //Index of the current product from the cluster Random random = new Random(); + if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); + } int iterator = random.nextInt(clusterLength); if (iterator == 0) { iterator = 1; diff --git a/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php b/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php index c817d2e07660a35e478747f5156a0d21225089a8..d4f192255c2096768addd41678e0458792112302 100644 --- a/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php +++ b/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php @@ -96,8 +96,10 @@ class GenerateFixturesCommand extends Command $indexerRegistry = $fixtureModel->getObjectManager() ->create(\Magento\Framework\Indexer\IndexerRegistry::class); + $indexersState = []; foreach ($indexerListIds as $indexerId) { $indexer = $indexerRegistry->get($indexerId['indexer_id']); + $indexersState[$indexerId['indexer_id']] = $indexer->isScheduled(); $indexer->setScheduled(true); } @@ -107,6 +109,12 @@ class GenerateFixturesCommand extends Command $this->clearChangelog(); + foreach ($indexerListIds as $indexerId) { + /** @var $indexer \Magento\Indexer\Model\Indexer */ + $indexer = $indexerRegistry->get($indexerId['indexer_id']); + $indexer->setScheduled($indexersState[$indexerId['indexer_id']]); + } + /** @var \Magento\Setup\Fixtures\IndexersStatesApplyFixture $indexerFixture */ $indexerFixture = $fixtureModel ->getFixtureByName(\Magento\Setup\Fixtures\IndexersStatesApplyFixture::class);