Skip to content
Snippets Groups Projects
Commit 91310036 authored by Ted Bowman's avatar Ted Bowman Committed by Adam G-H
Browse files

Issue #3265057 by tedbow, phenaproxima: Take out of maintenance mode when done with an update

parent f3c421e7
No related branches found
No related tags found
No related merge requests found
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* Contains hook implementations for Automatic Updates. * Contains hook implementations for Automatic Updates.
*/ */
use Drupal\automatic_updates\BatchProcessor;
use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\automatic_updates\CronUpdater; use Drupal\automatic_updates\CronUpdater;
use Drupal\automatic_updates\UpdateRecommender; use Drupal\automatic_updates\UpdateRecommender;
...@@ -12,6 +13,7 @@ use Drupal\automatic_updates\Validation\AdminReadinessMessages; ...@@ -12,6 +13,7 @@ use Drupal\automatic_updates\Validation\AdminReadinessMessages;
use Drupal\Core\Extension\ExtensionVersion; use Drupal\Core\Extension\ExtensionVersion;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\system\Controller\DbUpdateController;
use Drupal\update\ProjectSecurityData; use Drupal\update\ProjectSecurityData;
/** /**
...@@ -200,3 +202,16 @@ function automatic_updates_local_tasks_alter(array &$local_tasks) { ...@@ -200,3 +202,16 @@ function automatic_updates_local_tasks_alter(array &$local_tasks) {
} }
} }
} }
/**
* Implements hook_batch_alter().
*
* @todo Remove this in https://www.drupal.org/i/3267817.
*/
function automatic_updates_batch_alter(array &$batch): void {
foreach ($batch['sets'] as &$batch_set) {
if (!empty($batch_set['finished']) && $batch_set['finished'] === [DbUpdateController::class, 'batchFinished']) {
$batch_set['finished'] = [BatchProcessor::class, 'dbUpdateBatchFinished'];
}
}
}
...@@ -112,6 +112,9 @@ class TestSubscriber implements EventSubscriberInterface { ...@@ -112,6 +112,9 @@ class TestSubscriber implements EventSubscriberInterface {
public function handleEvent(StageEvent $event): void { public function handleEvent(StageEvent $event): void {
$results = $this->state->get(static::STATE_KEY . '.' . get_class($event), []); $results = $this->state->get(static::STATE_KEY . '.' . get_class($event), []);
// Record that value of maintenance mode for each event.
$this->state->set(get_class($event) . '.' . 'system.maintenance_mode', $this->state->get('system.maintenance_mode'));
if ($results instanceof \Throwable) { if ($results instanceof \Throwable) {
throw $results; throw $results;
} }
......
...@@ -4,6 +4,7 @@ namespace Drupal\automatic_updates; ...@@ -4,6 +4,7 @@ namespace Drupal\automatic_updates;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\Exception\StageValidationException;
use Drupal\system\Controller\DbUpdateController;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
/** /**
...@@ -18,6 +19,13 @@ class BatchProcessor { ...@@ -18,6 +19,13 @@ class BatchProcessor {
*/ */
public const STAGE_ID_SESSION_KEY = '_automatic_updates_stage_id'; public const STAGE_ID_SESSION_KEY = '_automatic_updates_stage_id';
/**
* The session key which indicates if the update is done in maintenance mode.
*
* @var string
*/
public const MAINTENANCE_MODE_SESSION_KEY = '_automatic_updates_maintenance_mode';
/** /**
* Gets the updater service. * Gets the updater service.
* *
...@@ -200,4 +208,39 @@ class BatchProcessor { ...@@ -200,4 +208,39 @@ class BatchProcessor {
} }
} }
/**
* Reset maintenance mode after update.php.
*
* This wraps \Drupal\system\Controller\DbUpdateController::batchFinished()
* because that function would leave the site in maintenance mode if we
* redirected the user to update.php already in maintenance mode. We need to
* take the site out of maintenance mode, if it was not enabled before they
* submitted our confirmation form.
*
* @param bool $success
* Whether the batch API tasks were all completed successfully.
* @param array $results
* An array of all the results.
* @param array $operations
* A list of the operations that had not been completed by the batch API.
*
* @todo Remove the need for this workaround in
* https://www.drupal.org/i/3267817.
*
* @see \Drupal\update\Form\UpdateReady::submitForm()
* @see automatic_updates_batch_alter()
*/
public static function dbUpdateBatchFinished(bool $success, array $results, array $operations) {
DbUpdateController::batchFinished($success, $results, $operations);
// Now that the update is done, we can put the site back online if it was
// previously not in maintenance mode.
// \Drupal\system\Controller\DbUpdateController::batchFinished() will not
// unset maintenance mode if the site was in maintenance mode when the user
// was redirected to update.php by
// \Drupal\automatic_updates\Controller\UpdateController::onFinish().
if (!\Drupal::request()->getSession()->remove(static::MAINTENANCE_MODE_SESSION_KEY)) {
\Drupal::state()->set('system.maintenance_mode', FALSE);
}
}
} }
...@@ -2,11 +2,14 @@ ...@@ -2,11 +2,14 @@
namespace Drupal\automatic_updates\Controller; namespace Drupal\automatic_updates\Controller;
use Drupal\automatic_updates\BatchProcessor;
use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\package_manager\Validator\PendingUpdatesValidator; use Drupal\package_manager\Validator\PendingUpdatesValidator;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
/** /**
* Defines a controller to handle various stages of an automatic update. * Defines a controller to handle various stages of an automatic update.
...@@ -28,9 +31,12 @@ class UpdateController extends ControllerBase { ...@@ -28,9 +31,12 @@ class UpdateController extends ControllerBase {
* *
* @param \Drupal\package_manager\Validator\PendingUpdatesValidator $pending_updates_validator * @param \Drupal\package_manager\Validator\PendingUpdatesValidator $pending_updates_validator
* The pending updates validator. * The pending updates validator.
* @param \Drupal\Core\State\StateInterface $state
* The state service.
*/ */
public function __construct(PendingUpdatesValidator $pending_updates_validator) { public function __construct(PendingUpdatesValidator $pending_updates_validator, StateInterface $state) {
$this->pendingUpdatesValidator = $pending_updates_validator; $this->pendingUpdatesValidator = $pending_updates_validator;
$this->stateService = $state;
} }
/** /**
...@@ -38,7 +44,8 @@ class UpdateController extends ControllerBase { ...@@ -38,7 +44,8 @@ class UpdateController extends ControllerBase {
*/ */
public static function create(ContainerInterface $container) { public static function create(ContainerInterface $container) {
return new static( return new static(
$container->get('package_manager.validator.pending_updates') $container->get('package_manager.validator.pending_updates'),
$container->get('state')
); );
} }
...@@ -49,10 +56,13 @@ class UpdateController extends ControllerBase { ...@@ -49,10 +56,13 @@ class UpdateController extends ControllerBase {
* update.php to run those. Otherwise, they are redirected to the status * update.php to run those. Otherwise, they are redirected to the status
* report. * report.
* *
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse * @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect to the appropriate destination. * A redirect to the appropriate destination.
*/ */
public function onFinish(): RedirectResponse { public function onFinish(Request $request): RedirectResponse {
if ($this->pendingUpdatesValidator->updatesExist()) { if ($this->pendingUpdatesValidator->updatesExist()) {
$message = $this->t('Please apply database updates to complete the update process.'); $message = $this->t('Please apply database updates to complete the update process.');
$url = Url::fromRoute('system.db_update'); $url = Url::fromRoute('system.db_update');
...@@ -60,6 +70,11 @@ class UpdateController extends ControllerBase { ...@@ -60,6 +70,11 @@ class UpdateController extends ControllerBase {
else { else {
$message = $this->t('Update complete!'); $message = $this->t('Update complete!');
$url = Url::fromRoute('update.status'); $url = Url::fromRoute('update.status');
// Now that the update is done, we can put the site back online if it was
// previously not in maintenance mode.
if (!$request->getSession()->remove(BatchProcessor::MAINTENANCE_MODE_SESSION_KEY)) {
$this->state()->set('system.maintenance_mode', FALSE);
}
} }
$this->messenger()->addStatus($message); $this->messenger()->addStatus($message);
return new RedirectResponse($url->setAbsolute()->toString()); return new RedirectResponse($url->setAbsolute()->toString());
......
...@@ -196,12 +196,13 @@ class UpdateReady extends FormBase { ...@@ -196,12 +196,13 @@ class UpdateReady extends FormBase {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function submitForm(array &$form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) {
$session = $this->getRequest()->getSession();
// Store maintenance_mode setting so we can restore it when done. // Store maintenance_mode setting so we can restore it when done.
$session->set('maintenance_mode', $this->state->get('system.maintenance_mode')); $this->getRequest()
->getSession()
->set(BatchProcessor::MAINTENANCE_MODE_SESSION_KEY, $this->state->get('system.maintenance_mode'));
if ($form_state->getValue('maintenance_mode')) { if ($form_state->getValue('maintenance_mode')) {
$this->state->set('system.maintenance_mode', TRUE); $this->state->set('system.maintenance_mode', TRUE);
// @todo unset after updater. After db update?
} }
$stage_id = $form_state->getValue('stage_id'); $stage_id = $form_state->getValue('stage_id');
$batch = (new BatchBuilder()) $batch = (new BatchBuilder())
......
...@@ -344,13 +344,35 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -344,13 +344,35 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$assert_session->pageTextContains($cancelled_message); $assert_session->pageTextContains($cancelled_message);
} }
/**
* Data provider for testStagedDatabaseUpdates().
*
* @return bool[][]
* The test cases.
*/
public function providerStagedDatabaseUpdates() {
return [
'maintenance mode on' => [TRUE],
'maintenance mode off' => [FALSE],
];
}
/** /**
* Tests the update form when staged modules have database updates. * Tests the update form when staged modules have database updates.
*
* @param bool $maintenance_mode_on
* Whether the site should be in maintenance mode at the beginning of the
* update process.
*
* @dataProvider providerStagedDatabaseUpdates
*/ */
public function testStagedDatabaseUpdates(): void { public function testStagedDatabaseUpdates(bool $maintenance_mode_on): void {
$this->setCoreVersion('9.8.0'); $this->setCoreVersion('9.8.0');
$this->checkForUpdates(); $this->checkForUpdates();
$state = $this->container->get('state');
$state->set('system.maintenance_mode', $maintenance_mode_on);
// Flag a warning, which will not block the update but should be displayed // Flag a warning, which will not block the update but should be displayed
// on the updater form. // on the updater form.
$this->createTestValidationResults(); $this->createTestValidationResults();
...@@ -371,7 +393,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -371,7 +393,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
// Simulate a staged database update in the automatic_updates_test module. // Simulate a staged database update in the automatic_updates_test module.
// We must do this after the update has started, because the pending updates // We must do this after the update has started, because the pending updates
// validator will prevent an update from starting. // validator will prevent an update from starting.
$this->container->get('state')->set('automatic_updates_test.new_update', TRUE); $state->set('automatic_updates_test.new_update', TRUE);
// The warning from the updater form should be not be repeated, but we // The warning from the updater form should be not be repeated, but we
// should see a warning about pending database updates, and once the staged // should see a warning about pending database updates, and once the staged
// changes have been applied, we should be redirected to update.php, where // changes have been applied, we should be redirected to update.php, where
...@@ -380,12 +402,55 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -380,12 +402,55 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$possible_update_message = 'Possible database updates were detected in the following modules; you may be redirected to the database update page in order to complete the update process.'; $possible_update_message = 'Possible database updates were detected in the following modules; you may be redirected to the database update page in order to complete the update process.';
$assert_session->pageTextContains($possible_update_message); $assert_session->pageTextContains($possible_update_message);
$assert_session->pageTextContains('System'); $assert_session->pageTextContains('System');
$assert_session->checkboxChecked('maintenance_mode');
$page->pressButton('Continue'); $page->pressButton('Continue');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
// Confirm that the site was in maintenance before the update was applied.
// @see \Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber::handleEvent()
$this->assertTrue($state->get(PreApplyEvent::class . '.system.maintenance_mode'));
// Confirm the site remains in maintenance more when redirected to
// update.php.
$this->assertTrue($state->get('system.maintenance_mode'));
$assert_session->addressEquals('/update.php'); $assert_session->addressEquals('/update.php');
$assert_session->pageTextNotContains(reset($messages)); $assert_session->pageTextNotContains(reset($messages));
$assert_session->pageTextNotContains($possible_update_message); $assert_session->pageTextNotContains($possible_update_message);
$assert_session->pageTextContainsOnce('Please apply database updates to complete the update process.'); $assert_session->pageTextContainsOnce('Please apply database updates to complete the update process.');
$this->assertTrue($state->get('system.maintenance_mode'));
$page->clickLink('Continue');
// @see automatic_updates_update_9001()
$assert_session->pageTextContains('Dynamic automatic_updates_update_9001');
$page->clickLink('Apply pending updates');
$this->checkForMetaRefresh();
$assert_session->pageTextContains('Updates were attempted.');
// Confirm the site was returned to the original maintenance module state.
$this->assertSame($state->get('system.maintenance_mode'), $maintenance_mode_on);
}
/**
* Data provider for testSuccessfulUpdate().
*
* @return string[][]
* Test case parameters.
*/
public function providerSuccessfulUpdate(): array {
return [
'Modules page, maintenance mode on' => [
'/admin/modules/automatic-update',
TRUE,
],
'Modules page, maintenance mode off' => [
'/admin/modules/automatic-update',
FALSE,
],
'Reports page, maintenance mode on' => [
'/admin/reports/updates/automatic-update',
TRUE,
],
'Reports page, maintenance mode off' => [
'/admin/reports/updates/automatic-update',
FALSE,
],
];
} }
/** /**
...@@ -393,12 +458,16 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -393,12 +458,16 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
* *
* @param string $update_form_url * @param string $update_form_url
* The URL of the update form to visit. * The URL of the update form to visit.
* @param bool $maintenance_mode_on
* Whether maintenance should be on at the beginning of the update.
* *
* @dataProvider providerUpdateFormReferringUrl * @dataProvider providerSuccessfulUpdate
*/ */
public function testSuccessfulUpdate(string $update_form_url): void { public function testSuccessfulUpdate(string $update_form_url, bool $maintenance_mode_on): void {
$this->setCoreVersion('9.8.0'); $this->setCoreVersion('9.8.0');
$this->checkForUpdates(); $this->checkForUpdates();
$state = $this->container->get('state');
$state->set('system.maintenance_mode', $maintenance_mode_on);
$page = $this->getSession()->getPage(); $page = $this->getSession()->getPage();
$this->drupalGet($update_form_url); $this->drupalGet($update_form_url);
...@@ -407,17 +476,18 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -407,17 +476,18 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$this->assertUpdateStagedTimes(1); $this->assertUpdateStagedTimes(1);
$this->assertUpdateReady('9.8.1'); $this->assertUpdateReady('9.8.1');
$this->assertNotTrue($this->container->get('state')->get('system.maintenance_mode')); // Confirm that the site was put into maintenance mode if needed.
$this->assertSame($state->get('system.maintenance_mode'), $maintenance_mode_on);
$page->pressButton('Continue'); $page->pressButton('Continue');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$assert_session = $this->assertSession(); $assert_session = $this->assertSession();
$assert_session->addressEquals('/admin/reports/updates'); $assert_session->addressEquals('/admin/reports/updates');
// Assert that the site was put into maintenance mode. // Confirm that the site was in maintenance before the update was applied.
// @todo Add test coverage to ensure that site is taken back out of // @see \Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber::handleEvent()
// maintenance if it was not originally in maintenance mode when the $this->assertTrue($state->get(PreApplyEvent::class . '.system.maintenance_mode'));
// update started in https://www.drupal.org/i/3265057.
$this->assertTrue($this->container->get('state')->get('system.maintenance_mode'));
$assert_session->pageTextContainsOnce('Update complete!'); $assert_session->pageTextContainsOnce('Update complete!');
// Confirm the site was returned to the original maintenance mode state.
$this->assertSame($state->get('system.maintenance_mode'), $maintenance_mode_on);
} }
/** /**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment