launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06296
[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()