Skip to content

Commit 269b47a

Browse files
authored
Merge pull request #4688 from magento-thunder/MC-19250
[thunder] MC-19250: The stuck deployment on the Cloud because of consumers
2 parents 7cf99d1 + 73de315 commit 269b47a

File tree

8 files changed

+339
-15
lines changed

8 files changed

+339
-15
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\MessageQueue\Setup;
9+
10+
use Magento\Framework\Setup\ConfigOptionsListInterface;
11+
use Magento\Framework\Setup\Option\SelectConfigOption;
12+
use Magento\Framework\App\DeploymentConfig;
13+
use Magento\Framework\Config\Data\ConfigData;
14+
use Magento\Framework\Config\File\ConfigFilePool;
15+
16+
/**
17+
* Deployment configuration consumers options needed for Setup application
18+
*/
19+
class ConfigOptionsList implements ConfigOptionsListInterface
20+
{
21+
/**
22+
* Input key for the option
23+
*/
24+
const INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES ='consumers-wait-for-messages';
25+
26+
/**
27+
* Path to the value in the deployment config
28+
*/
29+
const CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES = 'queue/consumers_wait_for_messages';
30+
31+
/**
32+
* Default value
33+
*/
34+
const DEFAULT_CONSUMERS_WAIT_FOR_MESSAGES = 1;
35+
36+
/**
37+
* The available configuration values
38+
*
39+
* @var array
40+
*/
41+
private $selectOptions = [0, 1];
42+
43+
/**
44+
* @inheritdoc
45+
*/
46+
public function getOptions()
47+
{
48+
return [
49+
new SelectConfigOption(
50+
self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES,
51+
SelectConfigOption::FRONTEND_WIZARD_SELECT,
52+
$this->selectOptions,
53+
self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES,
54+
'Should consumers wait for a message from the queue? 1 - Yes, 0 - No',
55+
self::DEFAULT_CONSUMERS_WAIT_FOR_MESSAGES
56+
),
57+
];
58+
}
59+
60+
/**
61+
* @inheritdoc
62+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
63+
*/
64+
public function createConfig(array $data, DeploymentConfig $deploymentConfig)
65+
{
66+
$configData = new ConfigData(ConfigFilePool::APP_ENV);
67+
68+
if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES)) {
69+
$configData->set(
70+
self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES,
71+
(int)$data[self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES]
72+
);
73+
}
74+
75+
return [$configData];
76+
}
77+
78+
/**
79+
* @inheritdoc
80+
*/
81+
public function validate(array $options, DeploymentConfig $deploymentConfig)
82+
{
83+
$errors = [];
84+
85+
if (!$this->isDataEmpty($options, self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES)
86+
&& !in_array($options[self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES], $this->selectOptions)) {
87+
$errors[] = 'You can use only 1 or 0 for ' . self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES . ' option';
88+
}
89+
90+
return $errors;
91+
}
92+
93+
/**
94+
* Check if data ($data) with key ($key) is empty
95+
*
96+
* @param array $data
97+
* @param string $key
98+
* @return bool
99+
*/
100+
private function isDataEmpty(array $data, $key)
101+
{
102+
if (isset($data[$key]) && $data[$key] !== '') {
103+
return false;
104+
}
105+
106+
return true;
107+
}
108+
}

dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,24 @@ class QueueTestCaseAbstract extends \PHPUnit\Framework\TestCase
4545
/**
4646
* @var PublisherConsumerController
4747
*/
48-
private $publisherConsumerController;
48+
protected $publisherConsumerController;
4949

50+
/**
51+
* @inheritdoc
52+
*/
5053
protected function setUp()
5154
{
5255
$this->objectManager = Bootstrap::getObjectManager();
5356
$this->logFilePath = TESTS_TEMP_DIR . "/MessageQueueTestLog.txt";
54-
$this->publisherConsumerController = $this->objectManager->create(PublisherConsumerController::class, [
55-
'consumers' => $this->consumers,
56-
'logFilePath' => $this->logFilePath,
57-
'maxMessages' => $this->maxMessages,
58-
'appInitParams' => \Magento\TestFramework\Helper\Bootstrap::getInstance()->getAppInitParams()
59-
]);
57+
$this->publisherConsumerController = $this->objectManager->create(
58+
PublisherConsumerController::class,
59+
[
60+
'consumers' => $this->consumers,
61+
'logFilePath' => $this->logFilePath,
62+
'maxMessages' => $this->maxMessages,
63+
'appInitParams' => \Magento\TestFramework\Helper\Bootstrap::getInstance()->getAppInitParams()
64+
]
65+
);
6066

6167
try {
6268
$this->publisherConsumerController->initialize();
@@ -70,6 +76,9 @@ protected function setUp()
7076
$this->publisher = $this->publisherConsumerController->getPublisher();
7177
}
7278

79+
/**
80+
* @inheritdoc
81+
*/
7382
protected function tearDown()
7483
{
7584
$this->publisherConsumerController->stopConsumers();
@@ -85,25 +94,35 @@ protected function waitForAsynchronousResult($expectedLinesCount, $logFilePath)
8594
{
8695
try {
8796
//$expectedLinesCount, $logFilePath
88-
$this->publisherConsumerController->waitForAsynchronousResult([$this, 'checkLogsExists'], [
89-
$expectedLinesCount, $logFilePath
90-
]);
97+
$this->publisherConsumerController->waitForAsynchronousResult(
98+
[$this, 'checkLogsExists'],
99+
[$expectedLinesCount, $logFilePath]
100+
);
91101
} catch (PreconditionFailedException $e) {
92102
$this->fail($e->getMessage());
93103
}
94104
}
95105

106+
/**
107+
* Checks that logs exist
108+
*
109+
* @param int $expectedLinesCount
110+
* @return bool
111+
*/
96112
public function checkLogsExists($expectedLinesCount)
97113
{
114+
//phpcs:ignore Magento2.Functions.DiscouragedFunction
98115
$actualCount = file_exists($this->logFilePath) ? count(file($this->logFilePath)) : 0;
99116
return $expectedLinesCount === $actualCount;
100117
}
101118

102119
/**
103120
* Workaround for https://bugs.php.net/bug.php?id=72286
121+
* phpcs:disable Magento2.Functions.StaticFunction
104122
*/
105123
public static function tearDownAfterClass()
106124
{
125+
// phpcs:enable Magento2.Functions.StaticFunction
107126
if (version_compare(phpversion(), '7') == -1) {
108127
$closeConnection = new \ReflectionMethod(\Magento\Amqp\Model\Config::class, 'closeConnection');
109128
$closeConnection->setAccessible(true);
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\MessageQueue\UseCase;
7+
8+
use Magento\Framework\App\DeploymentConfig\FileReader;
9+
use Magento\TestModuleAsyncAmqp\Model\AsyncTestData;
10+
use Magento\Framework\App\DeploymentConfig\Writer;
11+
use Magento\Framework\Config\File\ConfigFilePool;
12+
use Magento\Framework\Filesystem;
13+
14+
class WaitAndNotWaitMessagesTest extends QueueTestCaseAbstract
15+
{
16+
/**
17+
* @var FileReader
18+
*/
19+
private $reader;
20+
21+
/**
22+
* @var Filesystem
23+
*/
24+
private $filesystem;
25+
26+
/**
27+
* @var array
28+
*/
29+
private $config;
30+
31+
/**
32+
* @var AsyncTestData
33+
*/
34+
protected $msgObject;
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
protected $consumers = ['mixed.sync.and.async.queue.consumer'];
40+
41+
/**
42+
* @var string[]
43+
*/
44+
protected $messages = ['message1', 'message2', 'message3'];
45+
46+
/**
47+
* @var int|null
48+
*/
49+
protected $maxMessages = 4;
50+
51+
/**
52+
* @inheritdoc
53+
*/
54+
protected function setUp()
55+
{
56+
parent::setUp();
57+
$this->msgObject = $this->objectManager->create(AsyncTestData::class);
58+
$this->reader = $this->objectManager->get(FileReader::class);
59+
$this->filesystem = $this->objectManager->get(Filesystem::class);
60+
$this->config = $this->loadConfig();
61+
}
62+
63+
/**
64+
* Check if consumers wait for messages from the queue
65+
*/
66+
public function testWaitForMessages()
67+
{
68+
$this->assertArraySubset(['queue' => ['consumers_wait_for_messages' => 1]], $this->config);
69+
70+
foreach ($this->messages as $message) {
71+
$this->publishMessage($message);
72+
}
73+
74+
$this->waitForAsynchronousResult(count($this->messages), $this->logFilePath);
75+
76+
foreach ($this->messages as $item) {
77+
$this->assertContains($item, file_get_contents($this->logFilePath));
78+
}
79+
80+
$this->publishMessage('message4');
81+
$this->waitForAsynchronousResult(count($this->messages) + 1, $this->logFilePath);
82+
$this->assertContains('message4', file_get_contents($this->logFilePath));
83+
}
84+
85+
/**
86+
* Check if consumers do not wait for messages from the queue and die
87+
*/
88+
public function testNotWaitForMessages(): void
89+
{
90+
$this->publisherConsumerController->stopConsumers();
91+
92+
$config = $this->config;
93+
$config['queue']['consumers_wait_for_messages'] = 0;
94+
$this->writeConfig($config);
95+
96+
$this->assertArraySubset(['queue' => ['consumers_wait_for_messages' => 0]], $this->loadConfig());
97+
foreach ($this->messages as $message) {
98+
$this->publishMessage($message);
99+
}
100+
101+
$this->publisherConsumerController->startConsumers();
102+
$this->waitForAsynchronousResult(count($this->messages), $this->logFilePath);
103+
104+
foreach ($this->messages as $item) {
105+
$this->assertContains($item, file_get_contents($this->logFilePath));
106+
}
107+
108+
// Checks that consumers do not wait 4th message and die
109+
$this->assertArraySubset(
110+
['mixed.sync.and.async.queue.consumer' => []],
111+
$this->publisherConsumerController->getConsumersProcessIds()
112+
);
113+
}
114+
115+
/**
116+
* @param string $message
117+
*/
118+
private function publishMessage(string $message): void
119+
{
120+
$this->msgObject->setValue($message);
121+
$this->msgObject->setTextFilePath($this->logFilePath);
122+
$this->publisher->publish('multi.topic.queue.topic.c', $this->msgObject);
123+
}
124+
125+
/**
126+
* @return array
127+
*/
128+
private function loadConfig(): array
129+
{
130+
return $this->reader->load(ConfigFilePool::APP_ENV);
131+
}
132+
133+
/**
134+
* @param array $config
135+
*/
136+
private function writeConfig(array $config): void
137+
{
138+
$writer = $this->objectManager->get(Writer::class);
139+
$writer->saveConfig([ConfigFilePool::APP_ENV => $config], true);
140+
}
141+
142+
/**
143+
* @inheritdoc
144+
*/
145+
protected function tearDown()
146+
{
147+
parent::tearDown();
148+
$this->writeConfig($this->config);
149+
}
150+
}

dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ protected function setUp()
5050
/**
5151
* test for \Magento\MediaStorage\Model\File\Storage\Database::deleteFolder()
5252
*
53-
* @magentoDataFixture Magento/MediaStorage/_files/database_mode.php
54-
* @magentoDbIsolation disabled
53+
* @magentoDataFixtureBeforeTransaction Magento/MediaStorage/_files/database_mode.php
5554
*/
5655
public function testDeleteFolder()
5756
{

dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
$database = $objectManager->get(\Magento\MediaStorage\Helper\File\Storage\Database::class);
1313
$database->getStorageDatabaseModel()->init();
1414

15+
/** @var Magento\Framework\App\Config\ConfigResource\ConfigInterface $config */
1516
$config = $objectManager->get(Magento\Framework\App\Config\ConfigResource\ConfigInterface::class);
1617
$config->saveConfig('system/media_storage_configuration/media_storage', '1');
1718
$config->saveConfig('system/media_storage_configuration/media_database', 'default_setup');
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
/** @var $objectManager \Magento\TestFramework\ObjectManager */
9+
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
10+
11+
/** @var Magento\Framework\App\Config\ConfigResource\ConfigInterface $config */
12+
$config = $objectManager->get(Magento\Framework\App\Config\ConfigResource\ConfigInterface::class);
13+
$config->deleteConfig('system/media_storage_configuration/media_storage');
14+
$config->deleteConfig('system/media_storage_configuration/media_database');
15+
$objectManager->get(Magento\Framework\App\Config\ReinitableConfigInterface::class)->reinit();

0 commit comments

Comments
 (0)