← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/maas/pserv-fakeapi into lp:maas

 

Gavin Panella has proposed merging lp:~allenap/maas/pserv-fakeapi into lp:maas with lp:~allenap/maas/pserv-xmlrpc-split as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~allenap/maas/pserv-fakeapi/+merge/92153
-- 
https://code.launchpad.net/~allenap/maas/pserv-fakeapi/+merge/92153
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/maas/pserv-fakeapi into lp:maas.
=== added file 'src/provisioningserver/testing/fakeapi.py'
--- src/provisioningserver/testing/fakeapi.py	1970-01-01 00:00:00 +0000
+++ src/provisioningserver/testing/fakeapi.py	2012-02-08 22:45:44 +0000
@@ -0,0 +1,128 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Fake Provisioning API.
+
+:class:`FakeSynchronousProvisioningAPI` is intended to be useful in a Django
+environment, or similar, where the Provisioning API is being used via
+xmlrpclib.ServerProxy for example.
+
+:class:`FakeAsynchronousProvisioningAPI` is intended to be used in a Twisted
+environment, where all functions return :class:`defer.Deferred`s.
+"""
+
+from __future__ import (
+    print_function,
+    unicode_literals,
+    )
+
+__metaclass__ = type
+__all__ = [
+    "FakeAsynchronousProvisioningAPI",
+    "FakeSynchronousProvisioningAPI",
+    ]
+
+from functools import wraps
+
+from provisioningserver.interfaces import IProvisioningAPI
+from twisted.internet import defer
+from zope.interface import implementer
+
+
+class FakeProvisioningDatabase(dict):
+
+    def __missing__(self, key):
+        self[key] = {"name": key}
+        return self[key]
+
+    def select(self, keys):
+        """Select a subset of this mapping."""
+        keys = frozenset(keys)
+        return {
+            key: value
+            for key, value in self.iteritems()
+            if key in keys
+            }
+
+    def delete(self, keys):
+        """Delete a subset of this mapping."""
+        for key in keys:
+            if key in self:
+                del self[key]
+
+    def duplicate(self):
+        """Duplicate this mapping.
+
+        Keys are assumed to be immutable, and values are assumed to have a
+        `copy` method, like a `dict` for example.
+        """
+        return {
+            key: value.copy()
+            for key, value in self.iteritems()
+            }
+
+
+@implementer(IProvisioningAPI)
+class FakeSynchronousProvisioningAPI:
+
+    def __init__(self):
+        super(FakeSynchronousProvisioningAPI, self).__init__()
+        self.distros = FakeProvisioningDatabase()
+        self.profiles = FakeProvisioningDatabase()
+        self.nodes = FakeProvisioningDatabase()
+
+    def add_distro(self, name, initrd, kernel):
+        self.distros[name]["initrd"] = initrd
+        self.distros[name]["kernel"] = kernel
+        return name
+
+    def add_profile(self, name, distro):
+        self.profiles[name]["distro"] = distro
+        return name
+
+    def add_node(self, name, profile):
+        self.nodes[name]["profile"] = profile
+        return name
+
+    def get_distros_by_name(self, names):
+        return self.distros.select(names)
+
+    def get_profiles_by_name(self, names):
+        return self.profiles.select(names)
+
+    def get_nodes_by_name(self, names):
+        return self.nodes.select(names)
+
+    def delete_distros_by_name(self, names):
+        return self.distros.delete(names)
+
+    def delete_profiles_by_name(self, names):
+        return self.profiles.delete(names)
+
+    def delete_nodes_by_name(self, names):
+        return self.nodes.delete(names)
+
+    def get_distros(self):
+        return self.distros.duplicate()
+
+    def get_profiles(self):
+        return self.profiles.duplicate()
+
+    def get_nodes(self):
+        return self.nodes.duplicate()
+
+
+def async(func):
+    """Decorate a function so that it always return a `defer.Deferred`."""
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        return defer.execute(func, *args, **kwargs)
+    return wrapper
+
+
+# Generate an asynchronous variant based on the synchronous one.
+FakeAsynchronousProvisioningAPI = type(
+    b"FakeAsynchronousProvisioningAPI", (FakeSynchronousProvisioningAPI,), {
+        name: async(getattr(FakeSynchronousProvisioningAPI, name))
+        for name in IProvisioningAPI.names(all=True)
+        })

=== modified file 'src/provisioningserver/tests/test_api.py'
--- src/provisioningserver/tests/test_api.py	2012-02-08 22:45:44 +0000
+++ src/provisioningserver/tests/test_api.py	2012-02-08 22:45:44 +0000
@@ -1,7 +1,10 @@
 # Copyright 2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-"""Tests for `provisioningserver.api`."""
+"""Tests for `provisioningserver.api`.
+
+Also tests `provisioningserver.testing.fakeapi`.
+"""
 
 from __future__ import (
     print_function,
@@ -13,6 +16,7 @@
 
 from provisioningserver.api import ProvisioningAPI
 from provisioningserver.interfaces import IProvisioningAPI
+from provisioningserver.testing.fakeapi import FakeAsynchronousProvisioningAPI
 from provisioningserver.testing.fakecobbler import make_fake_cobbler_session
 from testtools import TestCase
 from testtools.deferredruntest import AsynchronousDeferredRunTest
@@ -187,3 +191,10 @@
         profiles = yield papi.get_profiles_by_name(["alice", "bob"])
         # The response contains keys for all profiles found.
         self.assertSequenceEqual(["alice"], sorted(profiles))
+
+
+class TestFakeProvisioningAPI(TestProvisioningAPI):
+    """Test :class:`FakeAsynchronousProvisioningAPI`."""
+
+    def get_provisioning_api(self):
+        return FakeAsynchronousProvisioningAPI()