From 8f94d8389795fbe890c6742a503f595ef75202ea Mon Sep 17 00:00:00 2001 From: webchick Date: Tue, 25 Aug 2015 14:55:42 -0700 Subject: [PATCH] Issue #2545672 by mikeryan, benjy, phenaproxima: Handle various migration interruption scenarios --- core/modules/migrate/src/Entity/Migration.php | 8 ++ .../migrate/src/Entity/MigrationInterface.php | 9 +++ .../modules/migrate/src/MigrateExecutable.php | 7 ++ .../migrate/src/Tests/MigrateEventsTest.php | 13 +-- .../src/Tests/MigrateInterruptionTest.php | 81 +++++++++++++++++++ .../src/Plugin/migrate/source/DataSource.php | 10 ++- 6 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 core/modules/migrate/src/Tests/MigrateInterruptionTest.php diff --git a/core/modules/migrate/src/Entity/Migration.php b/core/modules/migrate/src/Entity/Migration.php index cb79a3ef238..735e8833276 100644 --- a/core/modules/migrate/src/Entity/Migration.php +++ b/core/modules/migrate/src/Entity/Migration.php @@ -458,6 +458,14 @@ class Migration extends ConfigEntityBase implements MigrationInterface, Requirem return \Drupal::keyValue('migrate_result')->get($this->id(), static::RESULT_INCOMPLETE); } + /** + * {@inheritdoc} + */ + public function interruptMigration($result) { + $this->setStatus(MigrationInterface::STATUS_STOPPING); + $this->setMigrationResult($result); + } + /** * {@inheritdoc} */ diff --git a/core/modules/migrate/src/Entity/MigrationInterface.php b/core/modules/migrate/src/Entity/MigrationInterface.php index 5a6192de1a1..b39ae6a6dae 100644 --- a/core/modules/migrate/src/Entity/MigrationInterface.php +++ b/core/modules/migrate/src/Entity/MigrationInterface.php @@ -206,6 +206,15 @@ interface MigrationInterface extends ConfigEntityInterface { */ public function getMigrationResult(); + /** + * Signal that the migration should be interrupted with the specified result + * code. + * + * @param int $result + * One of the MigrationInterface::RESULT_* constants. + */ + public function interruptMigration($result); + /** * Get the normalized process pipeline configuration describing the process * plugins. diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php index f7cbd3f7390..fccb667bfcd 100644 --- a/core/modules/migrate/src/MigrateExecutable.php +++ b/core/modules/migrate/src/MigrateExecutable.php @@ -298,10 +298,17 @@ class MigrateExecutable implements MigrateExecutableInterface { unset($sourceValues, $destinationValues); $this->sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED; + // Check for memory exhaustion. if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) { break; } + // If anyone has requested we stop, return the requested result. + if ($this->migration->getStatus() == MigrationInterface::STATUS_STOPPING) { + $return = $this->migration->getMigrationResult(); + break; + } + try { $source->next(); } diff --git a/core/modules/migrate/src/Tests/MigrateEventsTest.php b/core/modules/migrate/src/Tests/MigrateEventsTest.php index 65070d464f2..aa1aafafb53 100644 --- a/core/modules/migrate/src/Tests/MigrateEventsTest.php +++ b/core/modules/migrate/src/Tests/MigrateEventsTest.php @@ -95,8 +95,9 @@ class MigrateEventsTest extends KernelTestBase { $event = $this->state->get('migrate_events_test.map_save_event', []); $this->assertIdentical($event['event_name'], MigrateEvents::MAP_SAVE); - $this->assertIdentical($event['fields']['sourceid1'], 'dummy value'); - $this->assertIdentical($event['fields']['destid1'], 'dummy value'); + // Validating the last row processed. + $this->assertIdentical($event['fields']['sourceid1'], 'dummy value2'); + $this->assertIdentical($event['fields']['destid1'], 'dummy value2'); $this->assertIdentical($event['fields']['source_row_status'], 0); $event = $this->state->get('migrate_events_test.map_delete_event', []); @@ -105,13 +106,15 @@ class MigrateEventsTest extends KernelTestBase { $event = $this->state->get('migrate_events_test.pre_row_save_event', []); $this->assertIdentical($event['event_name'], MigrateEvents::PRE_ROW_SAVE); $this->assertIdentical($event['migration']->id(), $migration->id()); - $this->assertIdentical($event['row']->getSourceProperty('data'), 'dummy value'); + // Validating the last row processed. + $this->assertIdentical($event['row']->getSourceProperty('data'), 'dummy value2'); $event = $this->state->get('migrate_events_test.post_row_save_event', []); $this->assertIdentical($event['event_name'], MigrateEvents::POST_ROW_SAVE); $this->assertIdentical($event['migration']->id(), $migration->id()); - $this->assertIdentical($event['row']->getSourceProperty('data'), 'dummy value'); - $this->assertIdentical($event['destination_id_values']['value'], 'dummy value'); + // Validating the last row processed. + $this->assertIdentical($event['row']->getSourceProperty('data'), 'dummy value2'); + $this->assertIdentical($event['destination_id_values']['value'], 'dummy value2'); // Generate a map delete event. $migration->getIdMap()->delete(['data' => 'dummy value']); diff --git a/core/modules/migrate/src/Tests/MigrateInterruptionTest.php b/core/modules/migrate/src/Tests/MigrateInterruptionTest.php new file mode 100644 index 00000000000..ece13feb0ee --- /dev/null +++ b/core/modules/migrate/src/Tests/MigrateInterruptionTest.php @@ -0,0 +1,81 @@ +addListener(MigrateEvents::POST_ROW_SAVE, + array($this, 'postRowSaveEventRecorder')); + } + + /** + * Tests migration interruptions. + */ + public function testMigrateEvents() { + // Run a simple little migration, which should trigger one of each event + // other than map_delete. + $config = [ + 'id' => 'sample_data', + 'migration_tags' => ['Event test'], + 'source' => ['plugin' => 'data'], + 'process' => ['value' => 'data'], + 'destination' => ['plugin' => 'dummy'], + 'load' => ['plugin' => 'null'], + ]; + + $migration = Migration::create($config); + + /** @var MigrationInterface $migration */ + $executable = new MigrateExecutable($migration, new MigrateMessage); + // When the import runs, the first row imported will trigger an interruption. + $result = $executable->import(); + + $this->assertEqual($result, MigrationInterface::RESULT_INCOMPLETE); + + // The status should have been reset to IDLE. + $this->assertEqual($migration->getStatus(), MigrationInterface::STATUS_IDLE); + } + + /** + * Reacts to post-row-save event. + * + * @param \Drupal\Migrate\Event\MigratePostRowSaveEvent $event + * The migration event. + * @param string $name + * The event name. + */ + public function postRowSaveEventRecorder(MigratePostRowSaveEvent $event, $name) { + $event->getMigration()->interruptMigration(MigrationInterface::RESULT_INCOMPLETE); + } + +} diff --git a/core/modules/migrate/tests/modules/migrate_events_test/src/Plugin/migrate/source/DataSource.php b/core/modules/migrate/tests/modules/migrate_events_test/src/Plugin/migrate/source/DataSource.php index da74c5a861a..3e9fb014d88 100644 --- a/core/modules/migrate/tests/modules/migrate_events_test/src/Plugin/migrate/source/DataSource.php +++ b/core/modules/migrate/tests/modules/migrate_events_test/src/Plugin/migrate/source/DataSource.php @@ -31,11 +31,15 @@ class DataSource extends SourcePluginBase { * {@inheritdoc} */ public function initializeIterator() { - return new \ArrayIterator(array(array('data' => 'dummy value'))); + return new \ArrayIterator([ + ['data' => 'dummy value'], + ['data' => 'dummy value2'], + ]); + } public function __toString() { - return ''; + return 'Sample data for testing'; } /** @@ -50,7 +54,7 @@ class DataSource extends SourcePluginBase { * {@inheritdoc} */ public function count() { - return 1; + return 2; } }