diff --git a/core/modules/migrate/config/schema/migrate.source.schema.yml b/core/modules/migrate/config/schema/migrate.source.schema.yml
index f3dae0f..a4802d5 100644
--- a/core/modules/migrate/config/schema/migrate.source.schema.yml
+++ b/core/modules/migrate/config/schema/migrate.source.schema.yml
@@ -11,3 +11,16 @@ migrate.source.empty:
     provider:
       type: string
       label: 'Provider'
+
+migrate.source.embedded_data:
+  type: migrate_source
+  label: 'Embedded data source'
+  mapping:
+    embedded_data:
+      type: sequence
+      label: 'Embedded data'
+      sequence:
+        type: sequence
+        label: 'Data row'
+        sequence:
+          type: string
diff --git a/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php b/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php
new file mode 100644
index 0000000..49bcc58
--- /dev/null
+++ b/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Plugin\migrate\source\EmbeddedDataSource.
+ */
+
+namespace Drupal\migrate\Plugin\migrate\source;
+use Drupal\migrate\Entity\MigrationInterface;
+
+/**
+ * Source which takes its data directly from the plugin config.
+ *
+ * @MigrateSource(
+ *   id = "embedded_data"
+ * )
+ */
+class EmbeddedDataSource extends SourcePluginBase {
+
+  /**
+   * Data obtained from the source plugin configuration.
+   *
+   * @var array[]
+   *   Array of data rows, each one an array of values keyed by field names.
+   */
+  protected $embeddedData = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
+    $this->embeddedData = $configuration['embedded_data'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    if ($this->count() > 0) {
+      $first_row = reset($this->embeddedData);
+      $field_names = array_keys($first_row);
+      return array_combine($field_names, $field_names);
+    }
+    else {
+      return [];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initializeIterator() {
+    return new \ArrayIterator($this->embeddedData);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return 'Embedded data';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    if ($this->count() > 0) {
+      // We take the first field as the key.
+      $first_row = reset($this->embeddedData);
+      $keys = array_keys($first_row);
+      $id_field_name = reset($keys);
+      $ids[$id_field_name]['type'] = 'string';
+      return $ids;
+    }
+    else {
+      return [];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count() {
+    return count($this->embeddedData);
+  }
+
+}
diff --git a/core/modules/migrate/src/Tests/MigrateEmbeddedDataTest.php b/core/modules/migrate/src/Tests/MigrateEmbeddedDataTest.php
new file mode 100644
index 0000000..ac39b48
--- /dev/null
+++ b/core/modules/migrate/src/Tests/MigrateEmbeddedDataTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Tests\MigrateEmbeddedDataTest.
+ */
+
+namespace Drupal\migrate\Tests;
+
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\MigrateMessage;
+use Drupal\migrate\Entity\MigrationInterface;
+use Drupal\migrate\MigrateExecutable;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Tests the EmbeddedDataSource plugin.
+ *
+ * @group migrate
+ */
+class MigrateEmbeddedDataTest extends KernelTestBase {
+
+  /**
+   * Modules to enable. We borrow the ban modules destination plugin, since it's
+   * one of the simplest.
+   *
+   * @var array
+   */
+  public static $modules = ['migrate', 'ban'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->installSchema('ban', ['ban_ip']);
+  }
+
+  /**
+   * Tests migration events.
+   */
+  public function testEmbeddedData() {
+    $config = [
+      'id' => 'sample_data',
+      'migration_tags' => ['Embedded data test'],
+      'source' => [
+        'plugin' => 'embedded_data',
+        'embedded_data' => [
+          ['id' => '1', 'banned_ip_address' => '12.34.56.78'],
+          ['id' => '2', 'banned_ip_address' => '123.45.67.89'],
+        ],
+      ],
+      'process' => ['ip' => 'banned_ip_address'],
+      'destination' => ['plugin' => 'blocked_ip'],
+      'load' => ['plugin' => 'null'],
+    ];
+
+    $migration = Migration::create($config);
+
+    /** @var MigrationInterface $migration */
+    $executable = new MigrateExecutable($migration, new MigrateMessage);
+    // As the import runs, events will be dispatched, recording the received
+    // information in state.
+    $executable->import();
+
+    $ip_manager = \Drupal::service('ban.ip_manager');
+    $this->assertTrue($ip_manager->isBanned('12.34.56.78'));
+    $this->assertTrue($ip_manager->isBanned('123.45.67.89'));
+    $this->assertFalse($ip_manager->isBanned('98.76.54.32'));
+  }
+
+}
diff --git a/core/modules/migrate/src/Tests/MigrateEventsTest.php b/core/modules/migrate/src/Tests/MigrateEventsTest.php
index 65070d4..2381e47 100644
--- a/core/modules/migrate/src/Tests/MigrateEventsTest.php
+++ b/core/modules/migrate/src/Tests/MigrateEventsTest.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\migrate\Tests;
 
-use Drupal\Core\State\State;
 use Drupal\migrate\Entity\Migration;
 use Drupal\migrate\Event\MigrateImportEvent;
 use Drupal\migrate\Event\MigrateMapDeleteEvent;
@@ -70,7 +69,12 @@ public function testMigrateEvents() {
     $config = [
       'id' => 'sample_data',
       'migration_tags' => ['Event test'],
-      'source' => ['plugin' => 'data'],
+      'source' => [
+        'plugin' => 'embedded_data',
+        'embedded_data' => [
+          ['data' => 'dummy value'],
+        ],
+      ],
       'process' => ['value' => 'data'],
       'destination' => ['plugin' => 'dummy'],
       'load' => ['plugin' => 'null'],
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
deleted file mode 100644
index da74c5a..0000000
--- a/core/modules/migrate/tests/modules/migrate_events_test/src/Plugin/migrate/source/DataSource.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\migrate_events_test\Plugin\migrate\source\DataSource.
- */
-
-namespace Drupal\migrate_events_test\Plugin\migrate\source;
-
-use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
-
-/**
- * Source returning a single hard-coded data row.
- *
- * @MigrateSource(
- *   id = "data"
- * )
- */
-class DataSource extends SourcePluginBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fields() {
-    return array(
-      'data' => t('Data'),
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function initializeIterator() {
-    return new \ArrayIterator(array(array('data' => 'dummy value')));
-  }
-
-  public function __toString() {
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getIds() {
-    $ids['data']['type'] = 'string';
-    return $ids;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function count() {
-    return 1;
-  }
-
-}
