← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jtv/maas/provisioning-fake-factory into lp:maas

 

Jeroen T. Vermeulen has proposed merging lp:~jtv/maas/provisioning-fake-factory into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~jtv/maas/provisioning-fake-factory/+merge/100791

The provisioning server test suite has a bunch of very nice factory helpers that create objects in the provisioning server, and cleans them up afterwards in case you're testing against a real pserv.

We need that in more places.  And so this extracts that, and converts another test that duplicated the effort.
-- 
https://code.launchpad.net/~jtv/maas/provisioning-fake-factory/+merge/100791
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jtv/maas/provisioning-fake-factory into lp:maas.
=== modified file 'src/maasserver/tests/test_provisioning.py'
--- src/maasserver/tests/test_provisioning.py	2012-04-04 09:49:49 +0000
+++ src/maasserver/tests/test_provisioning.py	2012-04-04 13:45:25 +0000
@@ -11,6 +11,7 @@
 __metaclass__ = type
 __all__ = []
 
+from abc import ABCMeta
 from urlparse import parse_qs
 from xmlrpclib import Fault
 
@@ -41,13 +42,18 @@
     POWER_TYPE,
     PSERV_FAULT,
     )
+from provisioningserver.testing.factory import ProvisioningFakeFactory
+from testtools.deferredruntest import AsynchronousDeferredRunTest
 from testtools.testcase import ExpectedException
+from twisted.internet.defer import inlineCallbacks
 
 
 class ProvisioningTests:
     """Tests for the Provisioning API as maasserver sees it."""
 
-    # Must be defined in concrete subclasses.
+    __metaclass__ = ABCMeta
+
+    # Must be set to the provisioning server API proxy to test against.
     papi = None
 
     def make_node_without_saving(self, arch=ARCHITECTURE.i386):
@@ -59,20 +65,6 @@
                 NODE_AFTER_COMMISSIONING_ACTION.DEFAULT),
             architecture=arch)
 
-    def make_papi_profile(self):
-        """Create a new profile on the provisioning API."""
-        # Kernel and initrd are irrelevant here, but must be real files
-        # for Cobbler's benefit.  Cobbler may not be running locally, so
-        # just feed it some filenames that it's sure to have (and won't
-        # be allowed accidentally to overwrite).
-        shared_name = factory.getRandomString()
-        distro_name = 'distro-%s' % shared_name
-        fake_initrd = '/etc/cobbler/settings'
-        fake_kernel = '/etc/cobbler/version'
-        distro = self.papi.add_distro(distro_name, fake_initrd, fake_kernel)
-        profile_name = 'profile-%s' % shared_name
-        return self.papi.add_profile(profile_name, distro)
-
     def test_name_arch_in_cobbler_style_converts_architecture_names(self):
         self.assertSequenceEqual(
             ['i386', 'i386', 'x86_64', 'x86_64'],
@@ -88,10 +80,11 @@
     def test_name_arch_in_cobbler_returns_unicode(self):
         self.assertIsInstance(name_arch_in_cobbler_style(b'amd64'), unicode)
 
+    @inlineCallbacks
     def test_select_profile_for_node_ignores_previously_chosen_profile(self):
         node = factory.make_node(architecture='i386')
-        self.papi.modify_nodes(
-            {node.system_id: {'profile': self.make_papi_profile()}})
+        profile = yield self.add_profile(self.papi)
+        yield self.papi.modify_nodes({node.system_id: {'profile': profile}})
         self.assertEqual(
             'maas-precise-i386', select_profile_for_node(node))
 
@@ -334,9 +327,12 @@
         self.assertIsNone(present_user_friendly_fault(Fault(9999, "!!!")))
 
 
-class TestProvisioningWithFake(ProvisioningTests, TestCase):
+class TestProvisioningWithFake(ProvisioningTests, ProvisioningFakeFactory,
+                               TestCase):
     """Tests for the Provisioning API using a fake."""
 
+    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
+
     def setUp(self):
         super(TestProvisioningWithFake, self).setUp()
         self.papi = provisioning.get_provisioning_api_proxy()

=== added file 'src/provisioningserver/testing/factory.py'
--- src/provisioningserver/testing/factory.py	1970-01-01 00:00:00 +0000
+++ src/provisioningserver/testing/factory.py	2012-04-04 13:45:25 +0000
@@ -0,0 +1,138 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Provisioning test-objects factory."""
+
+from __future__ import (
+    print_function,
+    unicode_literals,
+    )
+
+__metaclass__ = type
+__all__ = [
+    'fake_node_metadata',
+    'ProvisioningFakeFactory',
+    ]
+
+from abc import ABCMeta
+from itertools import count
+from time import time
+from xmlrpclib import Fault
+
+from provisioningserver.enum import POWER_TYPE
+from twisted.internet.defer import (
+    inlineCallbacks,
+    returnValue,
+    )
+
+
+names = ("test%d" % num for num in count(int(time())))
+
+
+def fake_name():
+    """Return a fake name. Each call returns a different name."""
+    return next(names)
+
+
+def fake_node_metadata():
+    """Produce fake metadata parameters for adding a node."""
+    return {
+        'maas-metadata-url': 'http://%s:5240/metadata/' % fake_name(),
+        'maas-metadata-credentials': 'fake/%s' % fake_name(),
+        }
+
+
+class ProvisioningFakeFactory:
+    """Mixin for test cases: factory of fake provisioning objects.
+
+    This can be used while testing against a real Cobbler, or a real
+    provisioning server with a fake Cobbler, or a fake provisioning server.
+
+    All objects you create using this factory will be cleaned up at the end of
+    each test.
+    """
+
+    __metaclass__ = ABCMeta
+
+    @staticmethod
+    def clean_up_objects(deleter, *object_names):
+        """Remove named objects from the PAPI.
+
+        `delete_func` is expected to be one of the ``delete_*_by_name``
+        methods of the Provisioning API. XML-RPC errors are ignored; this
+        function does its best to remove the object but a failure to do so is
+        not an error.
+        """
+        d = deleter(object_names)
+        if d is not None:
+            d.addErrback(lambda failure: failure.trap(Fault))
+        return d
+
+    @inlineCallbacks
+    def add_distro(self, papi, name=None):
+        """Creates a new distro object via `papi`.
+
+        Arranges for it to be deleted during test clean-up. If `name` is not
+        specified, `fake_name` will be called to obtain one.
+        """
+        if name is None:
+            name = fake_name()
+        # For the initrd and kernel, use a file that we know will exist for a
+        # running Cobbler instance (at least, on Ubuntu) so that we can test
+        # against remote instances, like one in odev.
+        initrd = "/etc/cobbler/settings"
+        kernel = "/etc/cobbler/version"
+        distro_name = yield papi.add_distro(name, initrd, kernel)
+        self.addCleanup(
+            self.clean_up_objects,
+            papi.delete_distros_by_name,
+            distro_name)
+        returnValue(distro_name)
+
+    @inlineCallbacks
+    def add_profile(self, papi, name=None, distro_name=None):
+        """Creates a new profile object via `papi`.
+
+        Arranges for it to be deleted during test clean-up. If `name` is not
+        specified, `fake_name` will be called to obtain one. If `distro_name`
+        is not specified, one will be obtained by calling `add_distro`.
+        """
+        if name is None:
+            name = fake_name()
+        if distro_name is None:
+            distro_name = yield self.add_distro(papi)
+        profile_name = yield papi.add_profile(name, distro_name)
+        self.addCleanup(
+            self.clean_up_objects,
+            papi.delete_profiles_by_name,
+            profile_name)
+        returnValue(profile_name)
+
+    @inlineCallbacks
+    def add_node(self, papi, name=None, hostname=None, profile_name=None,
+                 power_type=None, metadata=None):
+        """Creates a new node object via `papi`.
+
+        Arranges for it to be deleted during test clean-up. If `name` is not
+        specified, `fake_name` will be called to obtain one. If `profile_name`
+        is not specified, one will be obtained by calling `add_profile`. If
+        `metadata` is not specified, it will be obtained by calling
+        `fake_node_metadata`.
+        """
+        if name is None:
+            name = fake_name()
+        if hostname is None:
+            hostname = fake_name()
+        if profile_name is None:
+            profile_name = yield self.add_profile(papi)
+        if power_type is None:
+            power_type = POWER_TYPE.WAKE_ON_LAN
+        if metadata is None:
+            metadata = fake_node_metadata()
+        node_name = yield papi.add_node(
+            name, hostname, profile_name, power_type, metadata)
+        self.addCleanup(
+            self.clean_up_objects,
+            papi.delete_nodes_by_name,
+            node_name)
+        returnValue(node_name)

=== modified file 'src/provisioningserver/tests/test_api.py'
--- src/provisioningserver/tests/test_api.py	2012-03-30 14:24:31 +0000
+++ src/provisioningserver/tests/test_api.py	2012-04-04 13:45:25 +0000
@@ -19,8 +19,6 @@
     abstractmethod,
     )
 from base64 import b64decode
-from itertools import count
-from time import time
 from unittest import skipIf
 
 from maasserver.testing.enum import map_enum
@@ -36,35 +34,19 @@
 from provisioningserver.cobblerclient import CobblerSystem
 from provisioningserver.enum import POWER_TYPE
 from provisioningserver.interfaces import IProvisioningAPI
+from provisioningserver.testing.factory import (
+    fake_node_metadata,
+    ProvisioningFakeFactory,
+    )
 from provisioningserver.testing.fakeapi import FakeAsynchronousProvisioningAPI
 from provisioningserver.testing.fakecobbler import make_fake_cobbler_session
 from provisioningserver.testing.realcobbler import RealCobbler
 from testtools import TestCase
 from testtools.deferredruntest import AsynchronousDeferredRunTest
-from twisted.internet.defer import (
-    inlineCallbacks,
-    returnValue,
-    )
-from twisted.web.xmlrpc import Fault
+from twisted.internet.defer import inlineCallbacks
 from zope.interface.verify import verifyObject
 
 
-names = ("test%d" % num for num in count(int(time())))
-
-
-def fake_name():
-    """Return a fake name. Each call returns a different name."""
-    return next(names)
-
-
-def fake_node_metadata():
-    """Produce fake metadata parameters for adding a node."""
-    return {
-        'maas-metadata-url': 'http://%s:5240/metadata/' % fake_name(),
-        'maas-metadata-credentials': 'fake/%s' % fake_name(),
-        }
-
-
 class TestFunctions(TestCase):
     """Tests for the free functions in `provisioningserver.api`."""
 
@@ -276,7 +258,7 @@
         self.assertItemsEqual(expected, observed)
 
 
-class ProvisioningAPITests:
+class ProvisioningAPITests(ProvisioningFakeFactory):
     """Tests for `provisioningserver.api.ProvisioningAPI`.
 
     Abstract base class.  To exercise these tests, derive a test case from
@@ -296,88 +278,6 @@
         Override this in the test case that exercises this scenario.
         """
 
-    @staticmethod
-    def cleanup_objects(delete_func, *object_names):
-        """Remove named objects from the PAPI.
-
-        `delete_func` is expected to be one of the ``delete_*_by_name``
-        methods of the Provisioning API. XML-RPC errors are ignored; this
-        function does its best to remove the object but a failure to do so is
-        not an error.
-        """
-        d = delete_func(object_names)
-        d.addErrback(lambda failure: failure.trap(Fault))
-        return d
-
-    @inlineCallbacks
-    def add_distro(self, papi, name=None):
-        """Creates a new distro object via `papi`.
-
-        Arranges for it to be deleted during test clean-up. If `name` is not
-        specified, `fake_name` will be called to obtain one.
-        """
-        if name is None:
-            name = fake_name()
-        # For the initrd and kernel, use a file that we know will exist for a
-        # running Cobbler instance (at least, on Ubuntu) so that we can test
-        # against remote instances, like one in odev.
-        initrd = "/etc/cobbler/settings"
-        kernel = "/etc/cobbler/version"
-        distro_name = yield papi.add_distro(name, initrd, kernel)
-        self.addCleanup(
-            self.cleanup_objects,
-            papi.delete_distros_by_name,
-            distro_name)
-        returnValue(distro_name)
-
-    @inlineCallbacks
-    def add_profile(self, papi, name=None, distro_name=None):
-        """Creates a new profile object via `papi`.
-
-        Arranges for it to be deleted during test clean-up. If `name` is not
-        specified, `fake_name` will be called to obtain one. If `distro_name`
-        is not specified, one will be obtained by calling `add_distro`.
-        """
-        if name is None:
-            name = fake_name()
-        if distro_name is None:
-            distro_name = yield self.add_distro(papi)
-        profile_name = yield papi.add_profile(name, distro_name)
-        self.addCleanup(
-            self.cleanup_objects,
-            papi.delete_profiles_by_name,
-            profile_name)
-        returnValue(profile_name)
-
-    @inlineCallbacks
-    def add_node(self, papi, name=None, hostname=None, profile_name=None,
-                 power_type=None, metadata=None):
-        """Creates a new node object via `papi`.
-
-        Arranges for it to be deleted during test clean-up. If `name` is not
-        specified, `fake_name` will be called to obtain one. If `profile_name`
-        is not specified, one will be obtained by calling `add_profile`. If
-        `metadata` is not specified, it will be obtained by calling
-        `fake_node_metadata`.
-        """
-        if name is None:
-            name = fake_name()
-        if hostname is None:
-            hostname = fake_name()
-        if profile_name is None:
-            profile_name = yield self.add_profile(papi)
-        if power_type is None:
-            power_type = POWER_TYPE.WAKE_ON_LAN
-        if metadata is None:
-            metadata = fake_node_metadata()
-        node_name = yield papi.add_node(
-            name, hostname, profile_name, power_type, metadata)
-        self.addCleanup(
-            self.cleanup_objects,
-            papi.delete_nodes_by_name,
-            node_name)
-        returnValue(node_name)
-
     def test_ProvisioningAPI_interfaces(self):
         papi = self.get_provisioning_api()
         verifyObject(IProvisioningAPI, papi)