diff --git a/core/core.services.yml b/core/core.services.yml
index 872e2ff..d2058ac 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -346,6 +346,9 @@ services:
   entity.query.sql:
     class: Drupal\Core\Entity\Query\Sql\QueryFactory
     arguments: ['@database']
+  entity.query.keyvalue:
+    class: Drupal\Core\Entity\KeyValueStore\Query\QueryFactory
+    arguments: ['@keyvalue']
   router.dumper:
     class: Drupal\Core\Routing\MatcherDumper
     arguments: ['@database']
diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php
new file mode 100644
index 0000000..75ebf9d
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php
@@ -0,0 +1,270 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage.
+ */
+
+namespace Drupal\Core\Entity\KeyValueStore;
+
+use Drupal\Component\Utility\String;
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityMalformedException;
+use Drupal\Core\Entity\EntityStorageControllerBase;
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a key value backend for entities.
+ */
+class KeyValueEntityStorage extends EntityStorageControllerBase {
+
+  /**
+   * The key value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $keyValueStore;
+
+  /**
+   * The UUID service.
+   *
+   * @var \Drupal\Component\Uuid\UuidInterface
+   */
+  protected $uuidService;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a new KeyValueEntityStorage.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $key_value_store
+   *   The key value store.
+   * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
+   *   The UUID service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   */
+  public function __construct(EntityTypeInterface $entity_type, KeyValueStoreInterface $key_value_store, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
+    parent::__construct($entity_type);
+    $this->keyValueStore = $key_value_store;
+    $this->uuidService = $uuid_service;
+    $this->languageManager = $language_manager;
+
+    // Check if the entity type supports UUIDs.
+    $this->uuidKey = $this->entityType->getKey('uuid');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('keyvalue')->get('entity_storage__' . $entity_type->id()),
+      $container->get('uuid'),
+      $container->get('language_manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function create(array $values = array()) {
+    $entity_class = $this->entityType->getClass();
+    $entity_class::preCreate($this, $values);
+
+    // Set default language to site default if not provided.
+    $values += array('langcode' => $this->languageManager->getDefaultLanguage()->id);
+
+    $entity = new $entity_class($values, $this->entityTypeId);
+
+    // @todo This is handled by FieldableEntityStorageControllerBase, which
+    //   assumes ContentEntityInterface. The current approach in
+    //   https://drupal.org/node/1867228 improves this but does not solve it
+    //   completely.
+    if ($entity instanceof ContentEntityInterface) {
+      foreach ($entity as $name => $field) {
+        if (isset($values[$name])) {
+          $entity->$name = $values[$name];
+        }
+        elseif (!array_key_exists($name, $values)) {
+          $entity->get($name)->applyDefaultValue();
+        }
+        unset($values[$name]);
+      }
+    }
+
+    $entity->enforceIsNew();
+
+    // Assign a new UUID if there is none yet.
+    if ($this->uuidKey && !isset($entity->{$this->uuidKey})) {
+      $entity->{$this->uuidKey} = $this->uuidService->generate();
+    }
+
+    $entity->postCreate($this);
+
+    // Modules might need to add or change the data initially held by the new
+    // entity object, for instance to fill-in default values.
+    $this->invokeHook('create', $entity);
+
+    return $entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultiple(array $ids = NULL) {
+    if (empty($ids)) {
+      $entities = $this->keyValueStore->getAll();
+    }
+    else {
+      $entities = $this->keyValueStore->getMultiple($ids);
+    }
+    $this->postLoad($entities);
+    return $entities;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($id) {
+    $entity = $this->keyValueStore->get($id);
+    $entities = array($entity);
+    $this->postLoad($entities);
+    return reset($entities);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function postLoad(array &$queried_entities) {
+    $class = $this->entityType->getClass();
+    $entities = array();
+    foreach ($queried_entities as $entity) {
+      $entity = new $class($entity, $this->entityTypeId);
+      $entities[$entity->id()] = $entity;
+    }
+    $queried_entities = $entities;
+    parent::postLoad($queried_entities);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadRevision($revision_id) {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteRevision($revision_id) {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete(array $entities) {
+    if (!$entities) {
+      // If no IDs or invalid IDs were passed, do nothing.
+      return;
+    }
+
+    $entity_class = $this->entityType->getClass();
+    $entity_class::preDelete($this, $entities);
+    foreach ($entities as $entity) {
+      $this->invokeHook('predelete', $entity);
+    }
+
+    $entity_ids = array();
+    foreach ($entities as $entity) {
+      $entity_ids[] = $entity->id();
+    }
+    $this->keyValueStore->deleteMultiple($entity_ids);
+
+    $entity_class::postDelete($this, $entities);
+    foreach ($entities as $entity) {
+      $this->invokeHook('delete', $entity);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(EntityInterface $entity) {
+    $id = $entity->id();
+    if ($id === NULL || $id === '') {
+      throw new EntityMalformedException('The entity does not have an ID.');
+    }
+
+    // Track the original ID.
+    if ($entity->getOriginalId() !== NULL) {
+      $id = $entity->getOriginalId();
+    }
+
+    // Track if this entity is new.
+    $is_new = $entity->isNew();
+    // Track if this entity exists already.
+    $id_exists = $this->keyValueStore->has($id);
+
+    // A new entity should not already exist.
+    if ($id_exists && $is_new) {
+      throw new EntityStorageException(String::format('@type entity with ID @id already exists.', array('@type' => $this->entityTypeId, '@id' => $id)));
+    }
+
+    // Load the original entity, if any.
+    if ($id_exists && !isset($entity->original)) {
+      $entity->original = $this->loadUnchanged($id);
+    }
+
+    // Allow code to run before saving.
+    $entity->preSave($this);
+    $this->invokeHook('presave', $entity);
+
+    // If this is a rename, delete the original entity.
+    if ($id_exists && $id !== $entity->id()) {
+      $this->keyValueStore->delete($entity->original->id());
+    }
+
+    // Save the entity data in the key value store.
+    $this->keyValueStore->set($entity->id(), $entity->toArray());
+
+    // The entity is no longer new.
+    $entity->enforceIsNew(FALSE);
+
+    // Allow code to run after saving.
+    $entity->postSave($this, !$is_new);
+    $this->invokeHook($is_new ? 'insert' : 'update', $entity);
+
+    // If this entity has a concept of 'original ID', update it.
+    if ($id_exists) {
+      $entity->setOriginalId($entity->id());
+    }
+
+    unset($entity->original);
+
+    return $is_new ? SAVED_NEW : SAVED_UPDATED;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQueryServicename() {
+    return 'entity.query.keyvalue';
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php
new file mode 100644
index 0000000..024511b
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\KeyValueStore\Query\Condition.
+ */
+
+namespace Drupal\Core\Entity\KeyValueStore\Query;
+
+use Drupal\Core\Config\Entity\Query\Condition as ConditionParent;
+
+/**
+ * Defines the condition class for the key value entity query.
+ */
+class Condition extends ConditionParent {
+
+}
diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Query.php b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Query.php
new file mode 100644
index 0000000..d35466f
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Query.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\KeyValueStore\Query\Query.
+ */
+
+namespace Drupal\Core\Entity\KeyValueStore\Query;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Query\QueryBase;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+
+/**
+ * Defines the entity query for entities stored in a key value backend.
+ */
+class Query extends QueryBase {
+
+  /**
+   * The key value factory.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
+   */
+  protected $keyValueFactory;
+
+  /**
+   * Constructs a new Query.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   * @param string $conjunction
+   *   - AND: all of the conditions on the query need to match.
+   *   - OR: at least one of the conditions on the query need to match.
+   * @param array $namespaces
+   *   List of potential namespaces of the classes belonging to this query.
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
+   *   The key value factory.
+   */
+  public function __construct(EntityTypeInterface $entity_type, $conjunction, array $namespaces, KeyValueFactoryInterface $key_value_factory) {
+    parent::__construct($entity_type, $conjunction, $namespaces);
+    $this->keyValueFactory = $key_value_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    // Load the relevant records.
+    $records = $this->keyValueFactory->get('entity_storage__' . $this->entityTypeId)->getAll();
+
+    // Apply conditions.
+    $result = $this->condition->compile($records);
+
+    // Apply sort settings.
+    foreach ($this->sort as $sort) {
+      $direction = $sort['direction'] == 'ASC' ? -1 : 1;
+      $field = $sort['field'];
+      uasort($result, function($a, $b) use ($field, $direction) {
+        return ($a[$field] <= $b[$field]) ? $direction : -$direction;
+      });
+    }
+
+    // Let the pager do its work.
+    $this->initializePager();
+
+    if ($this->range) {
+      $result = array_slice($result, $this->range['start'], $this->range['length'], TRUE);
+    }
+    if ($this->count) {
+      return count($result);
+    }
+
+    // Create the expected structure of entity_id => entity_id.
+    $entity_ids = array_keys($result);
+    return array_combine($entity_ids, $entity_ids);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/Query/QueryFactory.php b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/QueryFactory.php
new file mode 100644
index 0000000..47de2c5
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/QueryFactory.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\KeyValueStore\Query\QueryFactory.
+ */
+
+namespace Drupal\Core\Entity\KeyValueStore\Query;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Query\QueryException;
+use Drupal\Core\Entity\Query\QueryFactoryInterface;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+
+/**
+ * Provides a factory for creating the key value entity query.
+ */
+class QueryFactory implements QueryFactoryInterface {
+
+  /**
+   * The key value factory.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
+   */
+  protected $keyValueFactory;
+
+  /**
+   * The namespace of this class, the parent class etc.
+   *
+   * @var array
+   */
+  protected $namespaces;
+
+  /**
+   * Constructs a QueryFactory object.
+   *
+   */
+  public function __construct(KeyValueFactoryInterface $key_value_factory) {
+    $this->keyValueFactory = $key_value_factory;
+    $this->namespaces = Query::getNamespaces($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get(EntityTypeInterface $entity_type, $conjunction) {
+    return new Query($entity_type, $conjunction, $this->namespaces, $this->keyValueFactory);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
+    throw new QueryException('Aggregation over key-value entity storage is not supported');
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueConfigEntityStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueConfigEntityStorageTest.php
new file mode 100644
index 0000000..a814c50
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueConfigEntityStorageTest.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\KeyValueStore\KeyValueConfigEntityStorageTest.
+ */
+
+namespace Drupal\system\Tests\KeyValueStore;
+
+use Drupal\config\Tests\ConfigEntityTest;
+
+/**
+ * Tests config entity CRUD with key value entity storage.
+ */
+class KeyValueConfigEntityStorageTest extends ConfigEntityTest {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('keyvalue_test');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'KeyValueEntityStorage config entity test',
+      'description' => 'Tests KeyValueEntityStorage for config entities.',
+      'group' => 'Entity API',
+    );
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueContentEntityStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueContentEntityStorageTest.php
new file mode 100644
index 0000000..726deac
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueContentEntityStorageTest.php
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\KeyValueStore\KeyValueContentEntityStorageTest.
+ */
+
+namespace Drupal\system\Tests\KeyValueStore;
+
+use Drupal\Core\Entity\EntityMalformedException;
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests content entity CRUD with key value entity storage.
+ */
+class KeyValueContentEntityStorageTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('keyvalue_test');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'KeyValueEntityStorage content entity test',
+      'description' => 'Tests KeyValueEntityStorage for content entities.',
+      'group' => 'Entity API',
+    );
+  }
+
+  /**
+   * Tests CRUD operations.
+   */
+  function testCRUD() {
+    $default_langcode = language_default()->id;
+    // Verify default properties on a newly created empty entity.
+    $empty = entity_create('entity_test_label');
+    $this->assertIdentical($empty->id->value, NULL);
+    $this->assertIdentical($empty->name->value, NULL);
+    $this->assertTrue($empty->uuid->value);
+    $this->assertIdentical($empty->langcode->value, $default_langcode);
+
+    // Verify ConfigEntity properties/methods on the newly created empty entity.
+    $this->assertIdentical($empty->isNew(), TRUE);
+    $this->assertIdentical($empty->bundle(), 'entity_test_label');
+    $this->assertIdentical($empty->id(), NULL);
+    $this->assertTrue($empty->uuid());
+    $this->assertIdentical($empty->label(), NULL);
+
+    // Verify Entity properties/methods on the newly created empty entity.
+    $this->assertIdentical($empty->getEntityTypeId(), 'entity_test_label');
+    // The URI can only be checked after saving.
+    try {
+      $empty->urlInfo();
+      $this->fail('EntityMalformedException was thrown.');
+    }
+    catch (EntityMalformedException $e) {
+      $this->pass('EntityMalformedException was thrown.');
+    }
+
+    // Verify that an empty entity cannot be saved.
+    try {
+      $empty->save();
+      $this->fail('EntityMalformedException was thrown.');
+    }
+    catch (EntityMalformedException $e) {
+      $this->pass('EntityMalformedException was thrown.');
+    }
+
+    // Verify that an entity with an empty ID string is considered empty, too.
+    $empty_id = entity_create('entity_test_label', array(
+      'id' => '',
+    ));
+    $this->assertIdentical($empty_id->isNew(), TRUE);
+    try {
+      $empty_id->save();
+      $this->fail('EntityMalformedException was thrown.');
+    }
+    catch (EntityMalformedException $e) {
+      $this->pass('EntityMalformedException was thrown.');
+    }
+
+    // Verify properties on a newly created entity.
+    $entity_test = entity_create('entity_test_label', $expected = array(
+      'id' => $this->randomName(),
+      'name' => $this->randomString(),
+    ));
+    $this->assertIdentical($entity_test->id->value, $expected['id']);
+    $this->assertTrue($entity_test->uuid->value);
+    $this->assertNotEqual($entity_test->uuid->value, $empty->uuid->value);
+    $this->assertIdentical($entity_test->name->value, $expected['name']);
+    $this->assertIdentical($entity_test->langcode->value, $default_langcode);
+
+    // Verify methods on the newly created entity.
+    $this->assertIdentical($entity_test->isNew(), TRUE);
+    $this->assertIdentical($entity_test->id(), $expected['id']);
+    $this->assertTrue($entity_test->uuid());
+    $expected['uuid'] = $entity_test->uuid();
+    $this->assertIdentical($entity_test->label(), $expected['name']);
+
+    // Verify that the entity can be saved.
+    try {
+      $status = $entity_test->save();
+      $this->pass('EntityMalformedException was not thrown.');
+    }
+    catch (EntityMalformedException $e) {
+      $this->fail('EntityMalformedException was not thrown.');
+    }
+
+    // Verify that the correct status is returned and properties did not change.
+    $this->assertIdentical($status, SAVED_NEW);
+    $this->assertIdentical($entity_test->id(), $expected['id']);
+    $this->assertIdentical($entity_test->uuid(), $expected['uuid']);
+    $this->assertIdentical($entity_test->label(), $expected['name']);
+    $this->assertIdentical($entity_test->isNew(), FALSE);
+
+    // Save again, and verify correct status and properties again.
+    $status = $entity_test->save();
+    $this->assertIdentical($status, SAVED_UPDATED);
+    $this->assertIdentical($entity_test->id(), $expected['id']);
+    $this->assertIdentical($entity_test->uuid(), $expected['uuid']);
+    $this->assertIdentical($entity_test->label(), $expected['name']);
+    $this->assertIdentical($entity_test->isNew(), FALSE);
+
+    // Ensure that creating an entity with the same id as an existing one is not
+    // possible.
+    $same_id = entity_create('entity_test_label', array(
+      'id' => $entity_test->id(),
+    ));
+    $this->assertIdentical($same_id->isNew(), TRUE);
+    try {
+      $same_id->save();
+      $this->fail('Not possible to overwrite an entity entity.');
+    } catch (EntityStorageException $e) {
+      $this->pass('Not possible to overwrite an entity entity.');
+    }
+
+    // Verify that renaming the ID returns correct status and properties.
+    $ids = array($expected['id'], 'second_' . $this->randomName(4), 'third_' . $this->randomName(4));
+    for ($i = 1; $i < 3; $i++) {
+      $old_id = $ids[$i - 1];
+      $new_id = $ids[$i];
+      // Before renaming, everything should point to the current ID.
+      $this->assertIdentical($entity_test->id(), $old_id);
+
+      // Rename.
+      $entity_test->id = $new_id;
+      $this->assertIdentical($entity_test->id(), $new_id);
+      $status = $entity_test->save();
+      $this->assertIdentical($status, SAVED_UPDATED);
+      $this->assertIdentical($entity_test->isNew(), FALSE);
+
+      // Verify that originalID points to new ID directly after renaming.
+      $this->assertIdentical($entity_test->id(), $new_id);
+    }
+  }
+
+}
diff --git a/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml
new file mode 100644
index 0000000..41cfb33
--- /dev/null
+++ b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml
@@ -0,0 +1,10 @@
+name: 'KeyValue tests'
+type: module
+description: 'A support module to test key value storage.'
+core: 8.x
+package: Testing
+version: VERSION
+hidden: true
+dependencies:
+  - config_test
+  - entity_test
diff --git a/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module
new file mode 100644
index 0000000..96404a1
--- /dev/null
+++ b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Sets up the key value entity storage.
+ */
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function keyvalue_test_entity_type_alter(array &$entity_types) {
+  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
+  if (isset($entity_types['config_test'])) {
+    $entity_types['config_test']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage');
+  }
+  if (isset($entity_types['entity_test_label'])) {
+    $entity_types['entity_test_label']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage');
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
new file mode 100644
index 0000000..16cd546
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
@@ -0,0 +1,617 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Entity\KeyValueStore\KeyValueEntityStorageTest.
+ */
+
+namespace Drupal\Tests\Core\Entity\KeyValueStore {
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Language\Language;
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage
+ *
+ * @group Drupal
+ * @group KeyValueEntityStorage
+ */
+class KeyValueEntityStorageTest extends UnitTestCase {
+
+  /**
+   * The entity type.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityType;
+
+  /**
+   * The key value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $keyValueStore;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The UUID service.
+   *
+   * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $uuidService;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $languageManager;
+
+  /**
+   * @var \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage
+   */
+  protected $entityStorage;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'KeyValueEntityStorage',
+      'description' => 'Tests \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage',
+      'group' => 'Entity',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->setUpKeyValueEntityStorage();
+  }
+
+  /**
+   * Prepares the key value entity storage.
+   *
+   * @param string $uuid_key
+   *   (optional) The entity key used for the UUID. Defaults to 'uuid'.
+   */
+  protected function setUpKeyValueEntityStorage($uuid_key = 'uuid') {
+    $this->entityType = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $this->entityType->expects($this->atLeastOnce())
+      ->method('getKey')
+      ->with('uuid')
+      ->will($this->returnValue($uuid_key));
+    $this->entityType->expects($this->atLeastOnce())
+      ->method('id')
+      ->will($this->returnValue('test_entity_type'));
+
+    $this->keyValueStore = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->uuidService = $this->getMock('Drupal\Component\Uuid\UuidInterface');
+    $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
+    $this->languageManager->expects($this->any())
+      ->method('getDefaultLanguage')
+      ->will($this->returnValue(new Language(array('langcode' => 'en'))));
+
+    $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager);
+    $this->entityStorage->setModuleHandler($this->moduleHandler);
+  }
+
+  /**
+   * @covers ::create()
+   */
+  public function testCreateWithPredefinedUuid() {
+    $this->entityType->expects($this->once())
+      ->method('getClass')
+      ->will($this->returnValue(get_class($this->getMockEntity())));
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_create');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_create');
+    $this->uuidService->expects($this->never())
+      ->method('generate');
+
+    $entity = $this->entityStorage->create(array('id' => 'foo', 'uuid' => 'baz'));
+    $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+    $this->assertSame('foo', $entity->id());
+    $this->assertSame('baz', $entity->uuid());
+  }
+
+  /**
+   * @covers ::create()
+   */
+  public function testCreateWithoutUuidKey() {
+    // Set up the entity storage to expect no UUID key.
+    $this->setUpKeyValueEntityStorage(NULL);
+    $this->entityType->expects($this->once())
+      ->method('getClass')
+      ->will($this->returnValue(get_class($this->getMockEntity())));
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_create');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_create');
+    $this->uuidService->expects($this->never())
+      ->method('generate');
+
+    $entity = $this->entityStorage->create(array('id' => 'foo', 'uuid' => 'baz'));
+    $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+    $this->assertSame('foo', $entity->id());
+    $this->assertSame('baz', $entity->uuid());
+  }
+
+  /**
+   * @covers ::create()
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   */
+  public function testCreate() {
+    $entity = $this->getMockEntity('Drupal\Core\Entity\Entity', array(), array('toArray'));
+    $this->entityType->expects($this->once())
+      ->method('getClass')
+      ->will($this->returnValue(get_class($entity)));
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_create');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_create');
+    $this->uuidService->expects($this->once())
+      ->method('generate')
+      ->will($this->returnValue('bar'));
+
+    $entity = $this->entityStorage->create(array('id' => 'foo'));
+    $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+    $this->assertSame('foo', $entity->id());
+    $this->assertSame('bar', $entity->uuid());
+    return $entity;
+  }
+
+  /**
+   * @covers ::save()
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *
+   * @depends testCreate
+   */
+  public function testSaveInsert(EntityInterface $entity) {
+    $expected = array('id' => 'foo');
+    $this->keyValueStore->expects($this->once())
+      ->method('has')
+      ->with('foo')
+      ->will($this->returnValue(FALSE));
+    $this->keyValueStore->expects($this->never())
+      ->method('getMultiple');
+    $this->keyValueStore->expects($this->never())
+      ->method('delete');
+
+    $entity->expects($this->atLeastOnce())
+      ->method('toArray')
+      ->will($this->returnValue($expected));
+
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_presave');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_presave');
+    $this->moduleHandler->expects($this->at(2))
+      ->method('invokeAll')
+      ->with('test_entity_type_insert');
+    $this->moduleHandler->expects($this->at(3))
+      ->method('invokeAll')
+      ->with('entity_insert');
+    $this->keyValueStore->expects($this->once())
+      ->method('set')
+      ->with('foo', $expected);
+    $return = $this->entityStorage->save($entity);
+    $this->assertSame(SAVED_NEW, $return);
+    return $entity;
+  }
+
+  /**
+   * @covers ::save()
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *
+   * @depends testSaveInsert
+   */
+  public function testSaveUpdate(EntityInterface $entity) {
+    $this->entityType->expects($this->exactly(2))
+      ->method('getClass')
+      ->will($this->returnValue(get_class($entity)));
+    $expected = array('id' => 'foo');
+    $this->keyValueStore->expects($this->once())
+      ->method('has')
+      ->with('foo')
+      ->will($this->returnValue(TRUE));
+    $this->keyValueStore->expects($this->once())
+      ->method('get')
+      ->with('foo')
+      ->will($this->returnValue(array('id' => 'foo')));
+    $this->keyValueStore->expects($this->never())
+      ->method('delete');
+
+    $this->moduleHandler->expects($this->at(0))
+      ->method('getImplementations')
+      ->with('entity_load')
+      ->will($this->returnValue(array()));
+    $this->moduleHandler->expects($this->at(1))
+      ->method('getImplementations')
+      ->with('test_entity_type_load')
+      ->will($this->returnValue(array()));
+    $this->moduleHandler->expects($this->at(2))
+      ->method('invokeAll')
+      ->with('test_entity_type_presave');
+    $this->moduleHandler->expects($this->at(3))
+      ->method('invokeAll')
+      ->with('entity_presave');
+    $this->moduleHandler->expects($this->at(4))
+      ->method('invokeAll')
+      ->with('test_entity_type_update');
+    $this->moduleHandler->expects($this->at(5))
+      ->method('invokeAll')
+      ->with('entity_update');
+    $this->keyValueStore->expects($this->once())
+      ->method('set')
+      ->with('foo', $expected);
+    $return = $this->entityStorage->save($entity);
+    $this->assertSame(SAVED_UPDATED, $return);
+    return $entity;
+  }
+
+  /**
+   * @covers ::save()
+   */
+  public function testSaveConfigEntity() {
+    $entity = $this->getMockEntity('Drupal\Core\Config\Entity\ConfigEntityBase', array(array('id' => 'foo')), array(
+      'toArray',
+      'preSave',
+    ));
+    $entity->enforceIsNew();
+    // When creating a new entity, the ID is tracked as the original ID.
+    $this->assertSame('foo', $entity->getOriginalId());
+
+    $expected = array('id' => 'foo');
+    $entity->expects($this->once())
+      ->method('toArray')
+      ->will($this->returnValue($expected));
+
+    $this->keyValueStore->expects($this->once())
+      ->method('has')
+      ->with('foo')
+      ->will($this->returnValue(FALSE));
+    $this->keyValueStore->expects($this->once())
+      ->method('set')
+      ->with('foo', $expected);
+    $this->keyValueStore->expects($this->never())
+      ->method('delete');
+
+    $return = $this->entityStorage->save($entity);
+    $this->assertSame(SAVED_NEW, $return);
+    return $entity;
+  }
+
+  /**
+   * @covers ::save()
+   *
+   * @depends testSaveConfigEntity
+   */
+  public function testSaveRenameConfigEntity(ConfigEntityInterface $entity) {
+    $this->entityType->expects($this->exactly(2))
+      ->method('getClass')
+      ->will($this->returnValue(get_class($entity)));
+
+    $this->moduleHandler->expects($this->at(0))
+      ->method('getImplementations')
+      ->with('entity_load')
+      ->will($this->returnValue(array()));
+    $this->moduleHandler->expects($this->at(1))
+      ->method('getImplementations')
+      ->with('test_entity_type_load')
+      ->will($this->returnValue(array()));
+    $expected = array('id' => 'foo');
+    $entity->expects($this->once())
+      ->method('toArray')
+      ->will($this->returnValue($expected));
+    $this->keyValueStore->expects($this->once())
+      ->method('has')
+      ->with('foo')
+      ->will($this->returnValue(TRUE));
+    $this->keyValueStore->expects($this->once())
+      ->method('get')
+      ->with('foo')
+      ->will($this->returnValue(array('id' => 'foo')));
+    $this->keyValueStore->expects($this->once())
+      ->method('delete')
+      ->with('foo');
+    $this->keyValueStore->expects($this->once())
+      ->method('set')
+      ->with('bar', $expected);
+
+    // Performing a rename does not change the original ID until saving.
+    $this->assertSame('foo', $entity->getOriginalId());
+    $entity->set('id', 'bar');
+    $this->assertSame('foo', $entity->getOriginalId());
+
+    $return = $this->entityStorage->save($entity);
+    $this->assertSame(SAVED_UPDATED, $return);
+    $this->assertSame('bar', $entity->getOriginalId());
+  }
+
+  /**
+   * @covers ::save()
+   */
+  public function testSaveContentEntity() {
+    $expected = array('id' => 'foo');
+    $this->keyValueStore->expects($this->once())
+      ->method('has')
+      ->with('foo')
+      ->will($this->returnValue(FALSE));
+    $this->keyValueStore->expects($this->once())
+      ->method('set')
+      ->with('foo', $expected);
+    $this->keyValueStore->expects($this->never())
+      ->method('delete');
+    $entity = $this->getMockForAbstractClass('Drupal\Core\Entity\ContentEntityBase', array(), '', FALSE, TRUE, TRUE, array(
+      'onSaveOrDelete',
+      'onUpdateBundleEntity',
+      'getFieldDefinitions',
+      'toArray',
+      'id',
+    ));
+    $entity->expects($this->atLeastOnce())
+      ->method('id')
+      ->will($this->returnValue('foo'));
+    $entity->expects($this->once())
+      ->method('toArray')
+      ->will($this->returnValue($expected));
+    $this->entityStorage->save($entity);
+  }
+
+  /**
+   * @covers ::save()
+   *
+   * @expectedException \Drupal\Core\Entity\EntityMalformedException
+   * @expectedExceptionMessage The entity does not have an ID.
+   */
+  public function testSaveInvalid() {
+    $entity = $this->getMockEntity('Drupal\Core\Config\Entity\ConfigEntityBase');
+    $this->entityStorage->save($entity);
+    $this->keyValueStore->expects($this->never())
+      ->method('has');
+    $this->keyValueStore->expects($this->never())
+      ->method('set');
+    $this->keyValueStore->expects($this->never())
+      ->method('delete');
+  }
+
+  /**
+   * @covers ::save()
+   *
+   * @expectedException \Drupal\Core\Entity\EntityStorageException
+   * @expectedExceptionMessage test_entity_type entity with ID foo already exists
+   */
+  public function testSaveDuplicate() {
+    $entity = $this->getMockEntity('Drupal\Core\Entity\Entity', array(array('id' => 'foo')));
+    $entity->enforceIsNew();
+    $this->keyValueStore->expects($this->once())
+      ->method('has')
+      ->will($this->returnValue(TRUE));
+    $this->keyValueStore->expects($this->never())
+      ->method('set');
+    $this->keyValueStore->expects($this->never())
+      ->method('delete');
+    $this->entityStorage->save($entity);
+  }
+
+  /**
+   * @covers ::load()
+   * @covers ::postLoad()
+   */
+  public function testLoad() {
+    $entity = $this->getMockEntity();
+    $this->entityType->expects($this->exactly(2))
+      ->method('getClass')
+      ->will($this->returnValue(get_class($entity)));
+    $this->keyValueStore->expects($this->once())
+      ->method('get')
+      ->with('foo')
+      ->will($this->returnValue(array('id' => 'foo')));
+    $this->moduleHandler->expects($this->at(0))
+      ->method('getImplementations')
+      ->with('entity_load')
+      ->will($this->returnValue(array()));
+    $this->moduleHandler->expects($this->at(1))
+      ->method('getImplementations')
+      ->with('test_entity_type_load')
+      ->will($this->returnValue(array()));
+    $entity = $this->entityStorage->load('foo');
+    $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+    $this->assertSame('foo', $entity->id());
+  }
+
+  /**
+   * @covers ::loadMultiple()
+   * @covers ::postLoad()
+   */
+  public function testLoadMultipleAll() {
+    $expected['foo'] = $this->getMockEntity('Drupal\Core\Entity\Entity', array(array('id' => 'foo')));
+    $expected['bar'] = $this->getMockEntity('Drupal\Core\Entity\Entity', array(array('id' => 'bar')));
+    $this->entityType->expects($this->exactly(2))
+      ->method('getClass')
+      ->will($this->returnValue(get_class(reset($expected))));
+    $this->keyValueStore->expects($this->once())
+      ->method('getAll')
+      ->will($this->returnValue(array(array('id' => 'foo'), array('id' => 'bar'))));
+    $this->moduleHandler->expects($this->at(0))
+      ->method('getImplementations')
+      ->with('entity_load')
+      ->will($this->returnValue(array()));
+    $this->moduleHandler->expects($this->at(1))
+      ->method('getImplementations')
+      ->with('test_entity_type_load')
+      ->will($this->returnValue(array()));
+    $entities = $this->entityStorage->loadMultiple();
+    foreach ($entities as $id => $entity) {
+      $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+      $this->assertSame($id, $entity->id());
+      $this->assertSame($id, $expected[$id]->id());
+    }
+  }
+
+  /**
+   * @covers ::loadMultiple()
+   * @covers ::postLoad()
+   */
+  public function testLoadMultipleIds() {
+    $entity = $this->getMockEntity('Drupal\Core\Entity\Entity', array(array('id' => 'foo')));
+    $this->entityType->expects($this->exactly(2))
+      ->method('getClass')
+      ->will($this->returnValue(get_class($entity)));
+    $expected[] = $entity;
+    $this->keyValueStore->expects($this->once())
+      ->method('getMultiple')
+      ->with(array('foo'))
+      ->will($this->returnValue(array(array('id' => 'foo'))));
+    $this->moduleHandler->expects($this->at(0))
+      ->method('getImplementations')
+      ->with('entity_load')
+      ->will($this->returnValue(array()));
+    $this->moduleHandler->expects($this->at(1))
+      ->method('getImplementations')
+      ->with('test_entity_type_load')
+      ->will($this->returnValue(array()));
+    $entities = $this->entityStorage->loadMultiple(array('foo'));
+    foreach ($entities as $id => $entity) {
+      $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+      $this->assertSame($id, $entity->id());
+    }
+  }
+
+  /**
+   * @covers ::loadRevision()
+   */
+  public function testLoadRevision() {
+    $this->assertSame(FALSE, $this->entityStorage->loadRevision(1));
+  }
+
+  /**
+   * @covers ::deleteRevision()
+   */
+  public function testDeleteRevision() {
+    $this->assertSame(NULL, $this->entityStorage->deleteRevision(1));
+  }
+
+  /**
+   * @covers ::delete()
+   */
+  public function testDelete() {
+    $entities['foo'] = $this->getMockEntity('Drupal\Core\Entity\Entity', array(array('id' => 'foo')));
+    $entities['bar'] = $this->getMockEntity('Drupal\Core\Entity\Entity', array(array('id' => 'bar')));
+    $this->entityType->expects($this->once())
+      ->method('getClass')
+      ->will($this->returnValue(get_class(reset($entities))));
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_predelete');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_predelete');
+    $this->moduleHandler->expects($this->at(2))
+      ->method('invokeAll')
+      ->with('test_entity_type_predelete');
+    $this->moduleHandler->expects($this->at(3))
+      ->method('invokeAll')
+      ->with('entity_predelete');
+    $this->moduleHandler->expects($this->at(4))
+      ->method('invokeAll')
+      ->with('test_entity_type_delete');
+    $this->moduleHandler->expects($this->at(5))
+      ->method('invokeAll')
+      ->with('entity_delete');
+    $this->moduleHandler->expects($this->at(6))
+      ->method('invokeAll')
+      ->with('test_entity_type_delete');
+    $this->moduleHandler->expects($this->at(7))
+      ->method('invokeAll')
+      ->with('entity_delete');
+
+    $this->keyValueStore->expects($this->once())
+      ->method('deleteMultiple')
+      ->with(array('foo', 'bar'));
+    $this->entityStorage->delete($entities);
+  }
+
+  /**
+   * @covers ::delete()
+   */
+  public function testDeleteNothing() {
+    $this->moduleHandler->expects($this->never())
+      ->method($this->anything());
+    $this->keyValueStore->expects($this->never())
+      ->method('delete');
+    $this->keyValueStore->expects($this->never())
+      ->method('deleteMultiple');
+
+    $this->entityStorage->delete(array());
+  }
+
+  /**
+   * Creates an entity with specific methods mocked.
+   *
+   * @param string $class
+   *   (optional) The concrete entity class to mock. Defaults to
+   *   'Drupal\Core\Entity\Entity'.
+   * @param array $arguments
+   *   (optional) Arguments to pass to the constructor. An empty set of values
+   *   and an entity type ID will be provided.
+   * @param array $methods
+   *   (optional) The methods to mock.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  public function getMockEntity($class = 'Drupal\Core\Entity\Entity', array $arguments = array(), $methods = array()) {
+    // Ensure the entity is passed at least an array of values and an entity
+    // type ID
+    if (!isset($arguments[0])) {
+      $arguments[0] = array();
+    }
+    if (!isset($arguments[1])) {
+      $arguments[1] = 'test_entity_type';
+    }
+    $methods[] = 'onSaveOrDelete';
+    $methods[] = 'onUpdateBundleEntity';
+    return $this->getMockForAbstractClass($class, $arguments, '', TRUE, TRUE, TRUE, $methods);
+  }
+
+}
+
+}
+
+namespace {
+  if (!defined('SAVED_NEW')) {
+    define('SAVED_NEW', 1);
+  }
+  if (!defined('SAVED_UPDATED')) {
+    define('SAVED_UPDATED', 2);
+  }
+}
