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