Problem/Motivation

We noticed this problem on #3063856-130: Add ability to view migrate_message table data.

A migration plugin that uses a source derived from SqlBase cannot be instantiated if the configured database connection is disabled. Although the plugin cannot be executed in this case, there are other reasons for instantiating it. For example, it gives access to the map and message tables.

Steps to reproduce

  1. Install Drupal 7 with the devel_generate module (part of the devel project, at least for D7).
  2. Generate some sample content: vocabularies, taxonomy terms, users, menu links, and nodes.
  3. Install Drupal 10, with the patch from #3063856-120: Add ability to view migrate_message table data, and enable the migrate_drupal_ui module.
  4. Migrate from the D7 site, starting at /upgrade on the D10 site.
  5. Review the migration messages using the pages added in this issue.
  6. Shut down the database server for the D7 site.
  7. Repeat Step 5.

Alternatively,

  1. Install Drupal 10.1.x with the Standard profile.
  2. Define $databases['migrate']['default'] in settings.php with a fake database host or one that is not running.
  3. Enable the migrate_drupal module.
  4. Execute this code: $migration = Drupal::service ('plugin.manager.migration')->createInstance('d7_node_type';. For example, use drush php or add that code to some module.
  5. You should get a message something like this: "PDOException SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for ddev-drupal7-db failed: Temporary failure in name resolution."

Proposed resolution

Catch the exception.

Drupal\migrate\Plugin\migrate\source\SqlBase::setUpDatabase() already catches ConnectionNotDefinedException and re-throws it as a RequirementsException.

  1. Move that try/catch block to Drupal\migrate\Plugin\migrate\source\SqlBase::checkRequirements().
  2. Update SqlBase::checkRequirements() it so that it catches \PDOException and DatabaseException.
  3. Update SqlTestBase as needed because of the changes in (2).
  4. Add test coverage for the exceptions to SqlTestBase.
  5. Add new kernel tests to the migrate and migrate_drupal modules.

Remaining tasks

  1. Decide whether to move the existing catch block, as in #11, or just add a new one.
  2. Decide how much of the new test coverage to keep.
  3. Add a change record.

User interface changes

No more WSOD when using #3063856: Add ability to view migrate_message table data.

API changes

I do not think this counts as an API change.

Data model changes

None

Release notes snippet

N/A

Comments

benjifisher created an issue. See original summary.

quietone’s picture

StatusFileSize
new606 bytes

Not sure about the message here but here is a start. Unfortunately, I've not figured out how to test this.

Ideas, or patches welcome!

benjifisher’s picture

Status: Active » Needs work
Issue tags: +Needs tests

Since we need to test this, let's put the status at NW and add the appropriate tag.

mikelutz’s picture

Queuing a test to see if that breaks any existing tests as well.

benjifisher’s picture

Status: Needs work » Needs review
StatusFileSize
new1.19 KB
new1.91 KB

I stole copied some code from SqlBaseTest::testConnectionTypes(). In a stand-alone test, it looks a little odd that I did not change any of the strings. If anyone cares, then I am happy to change that.

I am also happy to add comments. It all seems so obvious after staring at testConnectionTypes() for half an hour.

From the several scenarios in that test, I chose the one that leads to the shortest decision path in SqlBase::getDatabase().

After the setup, the test calls getDatabase() and checks the exception. Easy peasy.

I am attaching a new patch and a test-only patch, which is also the interdiff.

P.S. There are two reasons to add a new test instead of adding a few lines to testConnectionTypes():

You can only use expectException() once in a test. If you want to test two exceptions, then use two tests or put at least one of them in a try/catch block.
testConnectionTypes() is all about testing the code paths in getDatabase(), and only incidentally ends with a test for an exception. The new test is looking for something different.

The last submitted patch, 5: 3312733-5-test-only.patch, failed testing. View results

benjifisher’s picture

The test-only patch failed as expected:

There was 1 failure:

1) Drupal\Tests\migrate\Kernel\SqlBaseTest::testBrokenConnection
Failed asserting that exception of type "PDOException" matches expected exception "Drupal\migrate\Exception\RequirementsException". Message was: "SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for no_such_host failed: Name or service not known" at
/var/www/html/core/modules/mysql/src/Driver/Database/mysql/Connection.php:165

benjifisher’s picture

Issue summary: View changes
quietone’s picture

Priority: Normal » Major
Issue tags: +blocker

This was brought up at the migrate meeting today. This is blocking a Major issue, so adding tag and changing status.

mikelutz’s picture

Technically, both the connectionNotDefinedException and the PDOExceptions should be bubbled up as-is in setUpDatabase. They should be caught and converted into RequirementsExceptions only in SqlBase::checkRequirements because that’s the only place the Requirements exception means anything, and is documented to be thrown. In reality, that’s the first place getDatabase() is called, which will call setUpDatabase() and cache it if it finds it, and will never call setUpDatabase again, so we end up only throwing the RequirementsException in checkRequirements anyway, but that’s just coincidence. If setUpDatabase were to be called from anywhere else, we would want to bubble up the original exceptions, not a unexpected and undocumented RequirementsException…

benjifisher’s picture

Title: SQL source plugins throw exceptions if database is not available » SQL migrations cannot be instantiated if database is not available and Node, Migrate Drupal modules are enabled
Issue summary: View changes
Issue tags: -Needs tests +Needs change record
StatusFileSize
new8.68 KB
new11.05 KB

@mikelutz:

Thanks for sharing Comment #10 here even though (as you were the first to admit) it was not entirely thought out. The comment is copied from #3326468: [meeting] Migrate Meeting 2022-12-15 1400Z.

It took me a while to make sense of "In reality, that’s the first place getDatabase() is called". It turns out that this is true when the node and migrate_drupal modules are installed. Here is part of the stack trace from #3327401: [meeting] Migrate Meeting 2022-12-22 2100Z:

Drupal\mysql\Driver\Database\mysql\Connection::open(Array) (Line: 445)
Drupal\Core\Database\Database::openConnection('upgrade', 'default') (Line: 188)
Drupal\Core\Database\Database::getConnection('default', 'upgrade') (Line: 203)
Drupal\migrate\Plugin\migrate\source\SqlBase->setUpDatabase(Array) (Line: 156)
Drupal\migrate\Plugin\migrate\source\SqlBase->getDatabase() (Line: 224)
Drupal\migrate\Plugin\migrate\source\SqlBase->checkRequirements() (Line: 105)
Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase->checkRequirements() (Line: 81)
Drupal\node\Plugin\migrate\D6NodeDeriver->getDerivativeDefinitions(Array) (Line: 101)
Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator->getDerivatives(Array) (Line: 87)
Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator->getDefinitions() (Line: 255)
Drupal\migrate\Plugin\MigrationPluginManager->findDefinitions() (Line: 181)
Drupal\Core\Plugin\DefaultPluginManager->getDefinitions() (Line: 102)
Drupal\migrate\Plugin\MigrationPluginManager->createInstances(Array) (Line: 83)

Another point from that meeting: MigrationPluginManger::createInstance() (not to be confused with the method of the same name in MigratePluginManager) just calls createInstances().

Based on that, I am updating the title of this issue. Also the steps to reproduce in the issue summary. The problem is that createInstances() does not work for a SQL migration when the node and migrate_drupal modules are enabled and the source database is not available.

The attached patch catches either a PDOException or a ConnectionNotDefinedException in checkRequirements() and re-throws as a RequirementsException, as suggested in #10. This is different enough from the approach in #5 that I am not attaching an interdiff.

The patch also adds a test to SqlTestBasse and makes necessary changes to the existing tests, same as the patch in #5. The current patch adds two new kernel tests: one in the migrate module and one in the migrate_drupal module. The tests are very similar, and we probably do not want to keep both. I would like some opinions on that.

The last submitted patch, 11: 3312733-11-test-only.patch, failed testing. View results

benjifisher’s picture

Issue summary: View changes
StatusFileSize
new3.08 KB
new13.27 KB
new8.49 KB
new2.33 KB
new10.12 KB

PHPStan caught a serious mistake on my part: copy/pasting code and forgetting to define one of the referenced variables.

We could do some restructuring, introducing a new, protected method. For future reference, I have done this in the attached "radical" patch. If we do that much, then we should probably go further: deprecate, then remove the protected method setUpDatabase(). But I think that is out of scope for this issue. I was already nervous about the changes to that function, from a BC point of view.

So the non-radical patch attached to this comment reverts some of the previous changes. The original try/catch is back where it started, and the corresponding change to the existing test is reverted.

I have attached interdiffs for both of the new patches, and also a new test-only patch.

The last submitted patch, 13: 3312733-13-test-only.patch, failed testing. View results

The last submitted patch, 13: 3312733-13-radical.patch, failed testing. View results

benjifisher’s picture

I wonder whether fixing this issue will affect the annoying (but harmless) error message

Failed to connect to your database server. The server reports the following message: No database connection configured for source plugin variable.

discussed in #3198339: Improve error reporting from migrate_drupal_migration_plugins_alter() and #3221087: Suppress error about missing requirements for migrating variables when using Drupal 8+ as a source. I am adding those as related issues.

benjifisher’s picture

Fixing this issue does not affect the error message I mentioned in #16. For more details on my testing, see #3198339-15: Improve error reporting from migrate_drupal_migration_plugins_alter().

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

This issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.

Can the new functions in MigrateMissingDatabaseSource be typehinted.

Also was previously tagged for a change record.

benjifisher’s picture

Status: Needs work » Needs review
StatusFileSize
new8.58 KB
new3.37 KB
new10.22 KB

@smustgrave:

Thanks for the review!

I am uploading a new patch (also a new test-only patch and an interdiff). I added return-type declarations to all the new functions: those in the new test module and also new test methods. I will add a change record next.

benjifisher’s picture

Issue tags: -Needs change record

I added a draft change record.

The last submitted patch, 19: 3312733-19-test-only.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 19: 3312733-19.patch, failed testing. View results

benjifisher’s picture

Status: Needs work » Needs review

The test-only patch fails as expected. The full patch fails on CKEditor5AllowedTagsTest, which is a FJS test. That test does not enable the migrate module, so it should not be affected by this patch.

Back t o NR.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

This looks good to me. Reran the tests for the full patch in #19 but seemed to be random ckeditor before.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 19: 3312733-19.patch, failed testing. View results

quietone’s picture

Status: Needs work » Reviewed & tested by the community

It was a random test failure, restoring RTBC

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 19: 3312733-19.patch, failed testing. View results

benjifisher’s picture

Status: Needs work » Reviewed & tested by the community

This is another FJS test failure. It is the same test mentioned in #3055983: Locks on SQLite - consistent fails on PHP 8.4 and PHP 8.5. Back to RTBC.

  • longwave committed 3cad0ee9 on 10.1.x
    Issue #3312733 by benjifisher, quietone, mikelutz, smustgrave: SQL...
longwave’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed to 10.1.x and published the change record, thanks. Not eligible for backport as there is a change to exception handling.

benjifisher’s picture

Thank you, @longwave! Now that this issue is fixed, I un-postponed #3063856: Add ability to view migrate_message table data.

  • longwave committed 312e5891 on 10.1.x
    Revert "Issue #3312733 by benjifisher, quietone, mikelutz, smustgrave:...
longwave’s picture

Status: Fixed » Needs work

Rolled this back; the tests here don't work on SQLite: https://www.drupal.org/pift-ci-job/2616192

1) Drupal\Tests\migrate\Kernel\MigrateMissingDatabaseTest::testMissingDatabase
Failed asserting that exception message 'Destination plugin 'null' did not meet the requirements' contains 'No database connection available for source plugin migrate_missing_database_test'.

1) Drupal\Tests\migrate\Kernel\SqlBaseTest::testBrokenConnection
Failed asserting that exception of type "Drupal\migrate\Exception\RequirementsException" is thrown.
benjifisher’s picture

Status: Needs work » Needs review
StatusFileSize
new813 bytes
new10.22 KB

Maybe the test fails with SQLite because it starts with the default database and then changes the host key. If so, then always using mysql for the test database connection should fix the test.

benjifisher’s picture

Status: Needs review » Needs work
StatusFileSize
new10.28 KB

I was in a rush and uploaded the wrong patch. Try again ...

benjifisher’s picture

Status: Needs work » Needs review
StatusFileSize
new8.7 KB
new1.64 KB
new10.34 KB

One test fixed, one to go.

I am uploading a new patch, an interdiff comparing it to the patch in #19 (not #35), and a test-only patch.

If this does not work, then I should re-postpone #3063856.

The last submitted patch, 36: 3312733-36-test-only.patch, failed testing. View results

quietone’s picture

SQLite failed on Drupal\Tests\ckeditor5\FunctionalJavascript\CKEditor5AllowedTagsTest. I am retesting

longwave’s picture

Would it be better to set database, instead of host, to some invalid value? That should work on SQLite as well as MySQL and makes the test slightly more valid as it is testing the exception across all drivers.

benjifisher’s picture

Status: Needs review » Needs work

I am setting the status to NW for #39. I will update the patch, but maybe not today.

benjifisher’s picture

Status: Needs work » Needs review
StatusFileSize
new1.42 KB
new10.21 KB

Is this what you had in mind in #39?

I considered using 'deadbeat_dad' as the missing database, but decided on the more literary 'godot' (as in Waiting for ...).

I am attaching an interdiff comparing to the patch in #19, since this patch un-does most of the change between #19 and #36. If this patch passed on MySQL and SQLite, then I will upload a test-only patch.

benjifisher’s picture

Both of the SQLite failures are the same:

Failed asserting that exception of type "Drupal\Core\Database\DatabaseNotFoundException" matches expected exception "Drupal\migrate\Exception\RequirementsException". Message was: "SQLSTATE[HY000] [14] unable to open database file" at
/var/www/html/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php:112

I am keeping the status at NR to reconsider the patch in #36.

Alternatively, we could catch additional exceptions (and re-throw them as RequirementsExceptions) or maybe there is another interpretation of the suggestion in #39.

Status: Needs review » Needs work

The last submitted patch, 41: 3312733-41.patch, failed testing. View results

benjifisher’s picture

Issue summary: View changes

OK, the testbot shows that the patch in #41 is really a bad idea. (I wrote #42 while still waiting for the results of the MySQL test.)

Again: NR to reconsider the patch in #36.

While I am at it, I will update the list of remaining tasks in the issue summary.

benjifisher’s picture

Status: Needs work » Needs review
quietone’s picture

I applied the patch in #36 and tested with

phpunit --filter testBrokenConnection core/modules/migrate/tests/src/Kernel/SqlBaseTest.php 

That failed with

1) Drupal\Tests\migrate\Kernel\SqlBaseTest::testBrokenConnection
Failed asserting that exception of type "Drupal\Core\Database\DatabaseConnectionRefusedException" matches expected exception "Drupal\migrate\Exception\RequirementsException". Message was: "SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for no_such_host failed: No address associated with hostname [Tip: This message normally means that there is no MySQL server running on the system or that you are using an incorrect host name or port number when trying to connect to the server. You should also check that the TCP/IP port you are using has not been blocked by a firewall or port blocking service.] " at
/var/www/html/core/modules/mysql/src/Driver/Database/mysql/Connection.php:174
/var/www/html/core/lib/Drupal/Core/Database/Database.php:451
/var/www/html/core/lib/Drupal/Core/Database/Database.php:194
/var/www/html/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php:193
/var/www/html/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php:148
/var/www/html/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php:238
/var/www/html/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php:214
/var/www/html/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php:154
/var/www/html/vendor/phpunit/phpunit/src/Framework/TestResult.php:728

I then applied the patch in #41 and tested again with

phpunit --filter testBrokenConnection core/modules/migrate/tests/src/Kernel/SqlBaseTest.php 

and the test passed.

So, for this one test I am getting opposite results from what testbot reports.

It is too late to investigate further.

quietone’s picture

In Slack @benjifisher said that #36 passes on a checkout from March 10.

I used bisect to determine when the failures started. It was #2610858: Add informative error message for 'Connection refused' errors in MySQL in commit 442c0e41.

I've restarted tests on #36.

quietone’s picture

The patch in #41, passes when run on a checkkout before #2610858: Add informative error message for 'Connection refused' errors in MySQL.

So, the other issue has changed \Drupal\mysql\Driver\Database\mysql\Connection to catch the \PDOException, which we expect here, and rethrows a new DatabaseConnectionRefusedException. A similar change was not made for pgsql or sqllite.

quietone’s picture

Maybe just add a catch for a DatabaseException in \Drupal\migrate\Plugin\migrate\source\SqlBase::checkRequirements?

benjifisher’s picture

Using git bisect is fun, isn't it? I identified the same commit before I reloaded this page and saw the last few comments. I did not get as far as examining what that commit changed.

We understand why the patch in #36 no longer works. But when I uploaded the patch in #41, there were 4 failures and now there is only one. I am still a little confused by that, but I am not going to track it down.

I think #49 is the right idea, if you mean DatabaseConnectionRefusedException. Here is a patch that does that, based on the patch in #41. (Reminder: the only difference between #36 and #41 is the tests.)

benjifisher’s picture

StatusFileSize
new866 bytes
new10.53 KB

On second thought, maybe #49 did mean DatabaseException, the interface implemented by DatabaseConnectionRefusedException. Here is a patch that implements that, with an interdiff comparing to #41.

benjifisher’s picture

Issue summary: View changes
StatusFileSize
new868 bytes
new10.53 KB

Try again, this time keeping CodeSniffer happy.

quietone’s picture

Status: Needs review » Reviewed & tested by the community

I agree that `git bisect` can be fun to use. :-)

Yes, that is the change I was suggesting. I am pleased we agree. And I think that will cover future changes to the exceptions. Back to RTBC.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

amber himes matz’s picture

Hiding old patches, as it looks like the patch from #52 is the one!

  • catch committed fba5e54e on 10.1.x
    Issue #3312733 by benjifisher, quietone, longwave, mikelutz, smustgrave...

  • catch committed c326b923 on 11.x
    Issue #3312733 by benjifisher, quietone, longwave, mikelutz, smustgrave...
catch’s picture

Version: 11.x-dev » 10.1.x-dev
Status: Reviewed & tested by the community » Fixed

Looks good, simplifies the code and lots more test coverage.

Committed/pushed to 11.x and cherry-picked to 10.2.x, thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.