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
- Install Drupal 7 with the
devel_generate module (part of the devel project, at least for D7).
- Generate some sample content: vocabularies, taxonomy terms, users, menu links, and nodes.
- Install Drupal 10, with the patch from #3063856-120: Add ability to view migrate_message table data, and enable the
migrate_drupal_ui module.
- Migrate from the D7 site, starting at
/upgrade on the D10 site.
- Review the migration messages using the pages added in this issue.
- Shut down the database server for the D7 site.
- Repeat Step 5.
Alternatively,
- Install Drupal 10.1.x with the Standard profile.
- Define
$databases['migrate']['default'] in settings.php with a fake database host or one that is not running.
- Enable the
migrate_drupal module.
- 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.
- 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.
Move that try/catch block to Drupal\migrate\Plugin\migrate\source\SqlBase::checkRequirements().
- Update
SqlBase::checkRequirements() it so that it catches \PDOException and DatabaseException.
- Update
SqlTestBase as needed because of the changes in (2).
- Add test coverage for the exceptions to
SqlTestBase.
- Add new kernel tests to the
migrate and migrate_drupal modules.
Remaining tasks
Decide whether to move the existing catch block, as in #11, or just add a new one.
Decide how much of the new test coverage to keep.
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
Comment #2
quietone commentedNot sure about the message here but here is a start. Unfortunately, I've not figured out how to test this.
Ideas, or patches welcome!
Comment #3
benjifisherSince we need to test this, let's put the status at NW and add the appropriate tag.
Comment #4
mikelutzQueuing a test to see if that breaks any existing tests as well.
Comment #5
benjifisherI
stolecopied some code fromSqlBaseTest::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 ingetDatabase(), and only incidentally ends with a test for an exception. The new test is looking for something different.Comment #7
benjifisherThe test-only patch failed as expected:
Comment #8
benjifisherComment #9
quietone commentedThis was brought up at the migrate meeting today. This is blocking a Major issue, so adding tag and changing status.
Comment #10
mikelutzTechnically, 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…
Comment #11
benjifisher@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
nodeandmigrate_drupalmodules are installed. Here is part of the stack trace from #3327401: [meeting] Migrate Meeting 2022-12-22 2100Z:Another point from that meeting:
MigrationPluginManger::createInstance()(not to be confused with the method of the same name inMigratePluginManager) just callscreateInstances().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 thenodeandmigrate_drupalmodules are enabled and the source database is not available.The attached patch catches either a
PDOExceptionor aConnectionNotDefinedExceptionincheckRequirements()and re-throws as aRequirementsException, 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
SqlTestBasseand makes necessary changes to the existing tests, same as the patch in #5. The current patch adds two new kernel tests: one in themigratemodule and one in themigrate_drupalmodule. The tests are very similar, and we probably do not want to keep both. I would like some opinions on that.Comment #13
benjifisherPHPStan 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.
Comment #16
benjifisherI wonder whether fixing this issue will affect the annoying (but harmless) error message
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.
Comment #17
benjifisherFixing 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().
Comment #18
smustgrave commentedThis 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.
Comment #19
benjifisher@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.
Comment #20
benjifisherI added a draft change record.
Comment #23
benjifisherThe test-only patch fails as expected. The full patch fails on
CKEditor5AllowedTagsTest, which is a FJS test. That test does not enable themigratemodule, so it should not be affected by this patch.Back t o NR.
Comment #24
smustgrave commentedThis looks good to me. Reran the tests for the full patch in #19 but seemed to be random ckeditor before.
Comment #26
quietone commentedIt was a random test failure, restoring RTBC
Comment #28
benjifisherThis 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.
Comment #30
longwaveCommitted and pushed to 10.1.x and published the change record, thanks. Not eligible for backport as there is a change to exception handling.
Comment #31
benjifisherThank you, @longwave! Now that this issue is fixed, I un-postponed #3063856: Add ability to view migrate_message table data.
Comment #33
longwaveRolled this back; the tests here don't work on SQLite: https://www.drupal.org/pift-ci-job/2616192
Comment #34
benjifisherMaybe the test fails with SQLite because it starts with the default database and then changes the
hostkey. If so, then always usingmysqlfor the test database connection should fix the test.Comment #35
benjifisherI was in a rush and uploaded the wrong patch. Try again ...
Comment #36
benjifisherOne 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.
Comment #38
quietone commentedSQLite failed on Drupal\Tests\ckeditor5\FunctionalJavascript\CKEditor5AllowedTagsTest. I am retesting
Comment #39
longwaveWould it be better to set
database, instead ofhost, 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.Comment #40
benjifisherI am setting the status to NW for #39. I will update the patch, but maybe not today.
Comment #41
benjifisherIs 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.
Comment #42
benjifisherBoth of the SQLite failures are the same:
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.Comment #44
benjifisherOK, 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.
Comment #45
benjifisherComment #46
quietone commentedI applied the patch in #36 and tested with
That failed with
I then applied the patch in #41 and tested again with
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.
Comment #47
quietone commentedIn 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.
Comment #48
quietone commentedThe 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.
Comment #49
quietone commentedMaybe just add a catch for a DatabaseException in \Drupal\migrate\Plugin\migrate\source\SqlBase::checkRequirements?
Comment #50
benjifisherUsing
git bisectis 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.)Comment #51
benjifisherOn second thought, maybe #49 did mean
DatabaseException, the interface implemented byDatabaseConnectionRefusedException. Here is a patch that implements that, with an interdiff comparing to #41.Comment #52
benjifisherTry again, this time keeping CodeSniffer happy.
Comment #53
quietone commentedI 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.
Comment #55
amber himes matzHiding old patches, as it looks like the patch from #52 is the one!
Comment #58
catchLooks good, simplifies the code and lots more test coverage.
Committed/pushed to 11.x and cherry-picked to 10.2.x, thanks!