diff --git a/core/modules/migrate/src/Entity/Migration.php b/core/modules/migrate/src/Entity/Migration.php
index 42d9a7a..d292b1c 100644
--- a/core/modules/migrate/src/Entity/Migration.php
+++ b/core/modules/migrate/src/Entity/Migration.php
@@ -372,6 +372,24 @@ protected function getEntityManager() {
   /**
    * {@inheritdoc}
    */
+  public function setMigrationStatus($status) {
+    \Drupal::state()->set('migrate.' . $this->id() . '.status', $status);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMigrationStatus() {
+    $status = \Drupal::state()->get('migrate.' . $this->id() . '.status');
+    if (is_null($status)) {
+      $status = MigrationInterface::STATUS_IDLE;
+    }
+    return $status;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function setMigrationResult($result) {
     $migrate_result_store = \Drupal::keyValue('migrate_result');
     $migrate_result_store->set($this->id(), $result);
diff --git a/core/modules/migrate/src/Entity/MigrationInterface.php b/core/modules/migrate/src/Entity/MigrationInterface.php
index 793e719..48d57ed 100644
--- a/core/modules/migrate/src/Entity/MigrationInterface.php
+++ b/core/modules/migrate/src/Entity/MigrationInterface.php
@@ -164,6 +164,22 @@ public function saveHighWater($high_water);
   public function isComplete();
 
   /**
+   * Set the current migration status.
+   *
+   * @param int $result
+   *   One of the STATUS_* constants.
+   */
+  public function setMigrationStatus($status);
+
+  /**
+   * Get the current migration status.
+   *
+   * @return int
+   *   The current migration status. Defaults to STATUS_IDLE.
+   */
+  public function getMigrationStatus();
+
+  /**
    * Set the migration result.
    *
    * @param int $result
diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php
index f33781e..f6dc159 100644
--- a/core/modules/migrate/src/MigrateExecutable.php
+++ b/core/modules/migrate/src/MigrateExecutable.php
@@ -237,6 +237,15 @@ protected function getSource() {
    * {@inheritdoc}
    */
   public function import() {
+    // Only begin the import operation if the migration is currently idle.
+    if ($this->migration->getMigrationStatus() != MigrationInterface::STATUS_IDLE) {
+      $this->message->display(
+        $this->t('Migration @id is busy with another operation', array(
+          '@id' => $this->migration->id(),
+        )), 'error');
+      return MigrationInterface::RESULT_FAILED;
+    }
+
     // Knock off migration if the requirements haven't been met.
     try {
       $this->migration->checkRequirements();
@@ -251,6 +260,7 @@ public function import() {
       return MigrationInterface::RESULT_FAILED;
     }
 
+    $this->migration->setMigrationStatus(MigrationInterface::STATUS_IMPORTING);
     $return = MigrationInterface::RESULT_COMPLETED;
     $source = $this->getSource();
     $id_map = $this->migration->getIdMap();
@@ -261,11 +271,17 @@ public function import() {
     catch (\Exception $e) {
       $this->message->display(
         $this->t('Migration failed with source plugin exception: !e', array('!e' => $e->getMessage())), 'error');
+      $this->migration->setMigrationStatus(MigrationInterface::STATUS_IDLE);
       return MigrationInterface::RESULT_FAILED;
     }
 
     $destination = $this->migration->getDestinationPlugin();
     while ($source->valid()) {
+      // Primarily looking for STATUS_STOPPING here, but we should quit on any
+      // unexpected status.
+      if ($this->migration->getMigrationStatus() != MigrationInterface::STATUS_IMPORTING) {
+        break;
+      }
       $row = $source->current();
       if ($this->sourceIdValues = $row->getSourceIdValues()) {
         // Wipe old messages, and save any new messages.
@@ -335,6 +351,7 @@ public function import() {
         $this->message->display(
           $this->t('Migration failed with source plugin exception: !e',
             array('!e' => $e->getMessage())), 'error');
+        $this->migration->setMigrationStatus(MigrationInterface::STATUS_IDLE);
         return MigrationInterface::RESULT_FAILED;
       }
     }
@@ -345,6 +362,8 @@ public function import() {
     #$this->progressMessage($return);
 
     $this->migration->setMigrationResult($return);
+    $this->migration->setMigrationStatus(MigrationInterface::STATUS_IDLE);
+
     return $return;
   }
 
diff --git a/core/modules/migrate/src/Tests/MigrateStatusTest.php b/core/modules/migrate/src/Tests/MigrateStatusTest.php
new file mode 100644
index 0000000..58941d7
--- /dev/null
+++ b/core/modules/migrate/src/Tests/MigrateStatusTest.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Tests\MigrateStatusTest
+ */
+
+namespace Drupal\migrate\Tests;
+
+use Drupal\migrate\Entity\MigrationInterface;
+
+/**
+ * Test migration status tracking.
+ *
+ * @group migrate
+ */
+class MigrateStatusTest extends MigrateTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->strictConfigSchema = FALSE;
+    parent::setUp();
+  }
+
+  /**
+   * Test different connection types.
+   */
+  public function testStatus() {
+    // Create a minimally valid migration.
+    /** @var MigrationInterface $migration */
+    $migration = entity_create('migration', array());
+    $migration->set('id', 'migration_status_test');
+    $migration->set('migration_groups', array('Testing'));
+    $migration->set('source', array('plugin' => 'empty'));
+    $migration->set('destination', array(
+      'plugin' => 'config',
+      'config_name' => 'migrate_test.settings',
+    ));
+    $migration->set('process', array('foo' => 'bar'));
+    $migration->save();
+
+    // Default status is idle.
+    $status = $migration->getMigrationStatus();
+    $this->assertIdentical($status, MigrationInterface::STATUS_IDLE);
+
+    // Test setting and retrieving all known status values.
+    $status_list = array(
+      MigrationInterface::STATUS_IDLE,
+      MigrationInterface::STATUS_IMPORTING,
+      MigrationInterface::STATUS_ROLLING_BACK,
+      MigrationInterface::STATUS_STOPPING,
+      MigrationInterface::STATUS_DISABLED,
+    );
+    foreach ($status_list as $status) {
+      $migration->setMigrationStatus($status);
+      $this->assertIdentical($migration->getMigrationStatus(), $status);
+    }
+  }
+
+}
diff --git a/core/modules/migrate/tests/src/Unit/MigrateTestCase.php b/core/modules/migrate/tests/src/Unit/MigrateTestCase.php
index b1f8dbc..a6a15b3 100644
--- a/core/modules/migrate/tests/src/Unit/MigrateTestCase.php
+++ b/core/modules/migrate/tests/src/Unit/MigrateTestCase.php
@@ -18,6 +18,11 @@
   protected $migrationConfiguration = array();
 
   /**
+   * @var \Drupal\migrate\Entity\MigrationInterface::STATUS_*
+   */
+  protected $migrationStatus;
+
+  /**
    * Retrieve a mocked migration.
    *
    * @return \Drupal\migrate\Entity\MigrationInterface
@@ -40,6 +45,20 @@ protected function getMigration() {
     $migration->expects($this->any())
       ->method('getIdMap')
       ->will($this->returnValue($this->idMap));
+
+    // We need the state to be toggled throughout the test so we store the value
+    // on the test class and use a return callback.
+    $migration->expects($this->any())
+      ->method('getMigrationStatus')
+      ->will($this->returnCallback(function() {
+        return $this->migrationStatus;
+      }));
+    $migration->expects($this->any())
+      ->method('setMigrationStatus')
+      ->will($this->returnCallback(function($status) {
+        $this->migrationStatus = $status;
+      }));
+
     $configuration = &$this->migrationConfiguration;
     $migration->expects($this->any())->method('get')->will($this->returnCallback(function ($argument) use (&$configuration) {
       return isset($configuration[$argument]) ? $configuration[$argument] : '';
