← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/maas/test-papi-with-real-cobbler into lp:maas

 

Gavin Panella has proposed merging lp:~allenap/maas/test-papi-with-real-cobbler into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~allenap/maas/test-papi-with-real-cobbler/+merge/96604

This changes the provisioning API tests to also run against a real, running, Cobbler instance, by passing a URL in the TEST_COBBLER_URL environment variable. We don't want to do this automatically because the Cobbler database is modified. It's not feasible to have a Cobbler fixture (i.e. bring up a separate instance of Cobbler) because it hard-codes so many paths. In the future we could consider using a schroot here, then we could run the tests every time, but this works fine for now (and can be used to run against a remote Cobbler, e.g. within odev).

-- 
https://code.launchpad.net/~allenap/maas/test-papi-with-real-cobbler/+merge/96604
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/maas/test-papi-with-real-cobbler into lp:maas.
=== modified file 'src/provisioningserver/tests/test_api.py'
--- src/provisioningserver/tests/test_api.py	2012-02-29 11:40:46 +0000
+++ src/provisioningserver/tests/test_api.py	2012-03-08 16:37:18 +0000
@@ -18,6 +18,15 @@
     ABCMeta,
     abstractmethod,
     )
+from itertools import (
+    count,
+    islice,
+    )
+from os import environ
+from random import randint
+from time import time
+from unittest import skipIf
+from urlparse import urlparse
 
 from provisioningserver.api import (
     cobbler_to_papi_distro,
@@ -27,16 +36,48 @@
     postprocess_mapping,
     ProvisioningAPI,
     )
-from provisioningserver.cobblerclient import CobblerSystem
+from provisioningserver.cobblerclient import (
+    CobblerSession,
+    CobblerSystem,
+    )
 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
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import (
+    inlineCallbacks,
+    returnValue,
+    )
+from twisted.web.xmlrpc import Fault
 from zope.interface.verify import verifyObject
 
 
+names = ("test%d" % num for num in count(int(time())))
+
+random_octet = lambda: randint(0, 255)
+random_octets = iter(random_octet, None)
+
+
+def fake_mac_address():
+    """Return a random MAC address."""
+    octets = islice(random_octets, 6)
+    return ":".join(format(octet, "02x") for octet in octets)
+
+
+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:8000/metadata/' % fake_name(),
+        'maas-metadata-credentials': 'fake/%s' % fake_name(),
+        }
+
+
 class TestFunctions(TestCase):
     """Tests for the free functions in `provisioningserver.api`."""
 
@@ -191,13 +232,13 @@
         self.assertEqual(expected, observed)
 
 
-class ProvisioningAPITestScenario:
+class ProvisioningAPITests:
     """Tests for `provisioningserver.api.ProvisioningAPI`.
 
     Abstract base class.  To exercise these tests, derive a test case from
     this class as well as from TestCase.  Provide it with a
-    get_provisioning_api method that returns a ProvisioningAPI implementation
-    that you want to test against.
+    ``get_provisioning_api`` method that returns an :class:`IProvisioningAPI`
+    implementation that you want to test against.
     """
 
     __metaclass__ = ABCMeta
@@ -211,12 +252,69 @@
         Override this in the test case that exercises this scenario.
         """
 
-    def fake_metadata(self):
-        """Produce fake metadata parameters for adding a node."""
-        return {
-            'maas-metadata-url': 'http://localhost:8000/metadata/',
-            'maas-metadata-credentials': 'Fake metadata credentials',
-        }
+    @inlineCallbacks
+    def add_distro(self, papi):
+        """Creates a new distro object via `papi`.
+
+        Arranges for it to be deleted during test clean-up.
+        """
+        # 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(fake_name(), initrd, kernel)
+
+        def cleanup():
+            d = papi.delete_distros_by_name([distro_name])
+            d.addErrback(lambda failure: failure.trap(Fault))
+            return d
+
+        self.addCleanup(cleanup)
+        returnValue(distro_name)
+
+    @inlineCallbacks
+    def add_profile(self, papi, distro_name=None):
+        """Creates a new profile object via `papi`.
+
+        Arranges for it to be deleted during test clean-up. If `distro_name`
+        is not specified, one will be obtained by calling `add_distro`.
+        """
+        if distro_name is None:
+            distro_name = yield self.add_distro(papi)
+        profile_name = yield papi.add_profile(fake_name(), distro_name)
+
+        def cleanup():
+            d = papi.delete_profiles_by_name([profile_name])
+            d.addErrback(lambda failure: failure.trap(Fault))
+            return d
+
+        self.addCleanup(cleanup)
+        returnValue(profile_name)
+
+    @inlineCallbacks
+    def add_node(self, papi, profile_name=None, metadata=None):
+        """Creates a new node object via `papi`.
+
+        Arranges for it to be deleted during test clean-up. 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 profile_name is None:
+            profile_name = yield self.add_profile(papi)
+        if metadata is None:
+            metadata = fake_node_metadata()
+        node_name = yield papi.add_node(
+            fake_name(), profile_name, metadata)
+
+        def cleanup():
+            d = papi.delete_nodes_by_name([node_name])
+            d.addErrback(lambda failure: failure.trap(Fault))
+            return d
+
+        self.addCleanup(cleanup)
+        returnValue(node_name)
 
     def test_ProvisioningAPI_interfaces(self):
         papi = self.get_provisioning_api()
@@ -226,47 +324,46 @@
     def test_add_distro(self):
         # Create a distro via the Provisioning API.
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
-        self.assertEqual("distro", distro)
+        distro_name = yield self.add_distro(papi)
+        distros = yield papi.get_distros_by_name([distro_name])
+        self.assertEqual([distro_name], sorted(distros))
 
     @inlineCallbacks
     def test_add_profile(self):
         # Create a profile via the Provisioning API.
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
-        profile = yield papi.add_profile("profile", distro)
-        self.assertEqual("profile", profile)
+        profile_name = yield self.add_profile(papi)
+        profiles = yield papi.get_profiles_by_name([profile_name])
+        self.assertEqual([profile_name], sorted(profiles))
 
     @inlineCallbacks
     def test_add_node(self):
         # Create a system/node via the Provisioning API.
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro("distro", "an_initrd", "a_kernel")
-        profile = yield papi.add_profile("profile", distro)
-        node = yield papi.add_node("node", profile, self.fake_metadata())
-        self.assertEqual("node", node)
+        node_name = yield self.add_node(papi)
+        nodes = yield papi.get_nodes_by_name([node_name])
+        self.assertEqual([node_name], sorted(nodes))
 
     @inlineCallbacks
     def test_modify_distros(self):
         papi = self.get_provisioning_api()
-        distro_name = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
+        distro_name = yield self.add_distro(papi)
+        values = yield papi.get_distros_by_name([distro_name])
+        # Reverse the initrd and kernel settings.
+        initrd_new = values[distro_name]["kernel"]
+        kernel_new = values[distro_name]["initrd"]
         yield papi.modify_distros(
-            {distro_name: {"initrd": "zig", "kernel": "zag"}})
+            {distro_name: {"initrd": initrd_new, "kernel": kernel_new}})
         values = yield papi.get_distros_by_name([distro_name])
-        self.assertEqual("zig", values[distro_name]["initrd"])
-        self.assertEqual("zag", values[distro_name]["kernel"])
+        self.assertEqual(initrd_new, values[distro_name]["initrd"])
+        self.assertEqual(kernel_new, values[distro_name]["kernel"])
 
     @inlineCallbacks
     def test_modify_profiles(self):
         papi = self.get_provisioning_api()
-        distro1_name = yield papi.add_distro(
-            "distro1", "an_initrd", "a_kernel")
-        distro2_name = yield papi.add_distro(
-            "distro2", "an_initrd", "a_kernel")
-        profile_name = yield papi.add_profile("profile", distro1_name)
+        distro1_name = yield self.add_distro(papi)
+        distro2_name = yield self.add_distro(papi)
+        profile_name = yield self.add_profile(papi, distro1_name)
         yield papi.modify_profiles({profile_name: {"distro": distro2_name}})
         values = yield papi.get_profiles_by_name([profile_name])
         self.assertEqual(distro2_name, values[profile_name]["distro"])
@@ -274,12 +371,10 @@
     @inlineCallbacks
     def test_modify_nodes(self):
         papi = self.get_provisioning_api()
-        distro_name = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
-        profile1_name = yield papi.add_profile("profile1", distro_name)
-        profile2_name = yield papi.add_profile("profile2", distro_name)
-        node_name = yield papi.add_node(
-            "node", profile1_name, self.fake_metadata())
+        distro_name = yield self.add_distro(papi)
+        profile1_name = yield self.add_profile(papi, distro_name)
+        profile2_name = yield self.add_profile(papi, distro_name)
+        node_name = yield self.add_node(papi, profile1_name)
         yield papi.modify_nodes({node_name: {"profile": profile2_name}})
         values = yield papi.get_nodes_by_name([node_name])
         self.assertEqual(profile2_name, values[node_name]["profile"])
@@ -287,140 +382,107 @@
     @inlineCallbacks
     def test_modify_nodes_set_mac_addresses(self):
         papi = self.get_provisioning_api()
-        distro_name = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
-        profile_name = yield papi.add_profile("profile1", distro_name)
-        node_name = yield papi.add_node(
-            "node", profile_name, self.fake_metadata())
+        node_name = yield self.add_node(papi)
+        mac_address = fake_mac_address()
         yield papi.modify_nodes(
-            {node_name: {"mac_addresses": ["55:55:55:55:55:55"]}})
+            {node_name: {"mac_addresses": [mac_address]}})
         values = yield papi.get_nodes_by_name([node_name])
         self.assertEqual(
-            ["55:55:55:55:55:55"], values[node_name]["mac_addresses"])
+            [mac_address], values[node_name]["mac_addresses"])
 
     @inlineCallbacks
     def test_modify_nodes_remove_mac_addresses(self):
         papi = self.get_provisioning_api()
-        distro_name = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
-        profile_name = yield papi.add_profile("profile1", distro_name)
-        node_name = yield papi.add_node(
-            "node", profile_name, self.fake_metadata())
-        mac_addresses_from = ["55:55:55:55:55:55", "66:66:66:66:66:66"]
-        mac_addresses_to = ["66:66:66:66:66:66"]
+        node_name = yield self.add_node(papi)
+        mac_address1 = fake_mac_address()
+        mac_address2 = fake_mac_address()
+        mac_addresses_from = [mac_address1, mac_address2]
+        mac_addresses_to = [mac_address2]
         yield papi.modify_nodes(
             {node_name: {"mac_addresses": mac_addresses_from}})
         yield papi.modify_nodes(
             {node_name: {"mac_addresses": mac_addresses_to}})
         values = yield papi.get_nodes_by_name([node_name])
         self.assertEqual(
-            ["66:66:66:66:66:66"], values[node_name]["mac_addresses"])
+            [mac_address2], values[node_name]["mac_addresses"])
 
     @inlineCallbacks
     def test_delete_distros_by_name(self):
         # Create a distro via the Provisioning API.
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
+        distro_name = yield self.add_distro(papi)
         # Delete it again via the Provisioning API.
-        yield papi.delete_distros_by_name([distro])
+        yield papi.delete_distros_by_name([distro_name])
         # It has gone, checked via the Cobbler session.
-        distros = yield papi.get_distros_by_name([distro])
+        distros = yield papi.get_distros_by_name([distro_name])
         self.assertEqual({}, distros)
 
     @inlineCallbacks
     def test_delete_profiles_by_name(self):
         # Create a profile via the Provisioning API.
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
-        profile = yield papi.add_profile("profile", distro)
+        profile_name = yield self.add_profile(papi)
         # Delete it again via the Provisioning API.
-        yield papi.delete_profiles_by_name([profile])
+        yield papi.delete_profiles_by_name([profile_name])
         # It has gone, checked via the Cobbler session.
-        profiles = yield papi.get_profiles_by_name([profile])
+        profiles = yield papi.get_profiles_by_name([profile_name])
         self.assertEqual({}, profiles)
 
     @inlineCallbacks
     def test_delete_nodes_by_name(self):
         # Create a node via the Provisioning API.
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
-        profile = yield papi.add_profile("profile", distro)
-        node = yield papi.add_node("node", profile, self.fake_metadata())
+        node_name = yield self.add_node(papi)
         # Delete it again via the Provisioning API.
-        yield papi.delete_nodes_by_name([node])
+        yield papi.delete_nodes_by_name([node_name])
         # It has gone, checked via the Cobbler session.
-        nodes = yield papi.get_nodes_by_name([node])
+        nodes = yield papi.get_nodes_by_name([node_name])
         self.assertEqual({}, nodes)
 
     @inlineCallbacks
     def test_get_distros(self):
         papi = self.get_provisioning_api()
-        distros = yield papi.get_distros()
-        self.assertEqual({}, distros)
+        distros_before = yield papi.get_distros()
         # Create some distros via the Provisioning API.
-        expected = {}
+        distros_expected = set()
         for num in range(3):
-            initrd = self.getUniqueString()
-            kernel = self.getUniqueString()
-            name = self.getUniqueString()
-            yield papi.add_distro(name, initrd, kernel)
-            expected[name] = {
-                "initrd": initrd,
-                "kernel": kernel,
-                "name": name,
-                }
-        distros = yield papi.get_distros()
-        self.assertEqual(expected, distros)
+            distro_name = yield self.add_distro(papi)
+            distros_expected.add(distro_name)
+        distros_after = yield papi.get_distros()
+        distros_created = set(distros_after) - set(distros_before)
+        self.assertSetEqual(distros_expected, distros_created)
 
     @inlineCallbacks
     def test_get_profiles(self):
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
-        profiles = yield papi.get_profiles()
-        self.assertEqual({}, profiles)
+        distro_name = yield self.add_distro(papi)
+        profiles_before = yield papi.get_profiles()
         # Create some profiles via the Provisioning API.
-        expected = {}
+        profiles_expected = set()
         for num in range(3):
-            name = self.getUniqueString()
-            yield papi.add_profile(name, distro)
-            expected[name] = {'distro': 'distro', 'name': name}
-        profiles = yield papi.get_profiles()
-        self.assertEqual(expected, profiles)
-
-    @inlineCallbacks
-    def test_get_nodes_returns_empty_dict_when_no_nodes_exist(self):
-        papi = self.get_provisioning_api()
-        nodes = yield papi.get_nodes()
-        self.assertEqual({}, nodes)
+            profile_name = yield self.add_profile(papi, distro_name)
+            profiles_expected.add(profile_name)
+        profiles_after = yield papi.get_profiles()
+        profiles_created = set(profiles_after) - set(profiles_before)
+        self.assertSetEqual(profiles_expected, profiles_created)
 
     @inlineCallbacks
     def test_get_nodes_returns_all_nodes(self):
         papi = self.get_provisioning_api()
-        node_names = [self.getUniqueString() for counter in range(3)]
-        distro = yield papi.add_distro(
-            "distro", "an_initrd", "a_kernel")
-        profile = yield papi.add_profile("profile", distro)
-        for name in node_names:
-            yield papi.add_node(name, profile, self.fake_metadata())
+        profile_name = yield self.add_profile(papi)
+        node_names = set()
+        for num in range(3):
+            node_name = yield self.add_node(papi, profile_name)
+            node_names.add(node_name)
         nodes = yield papi.get_nodes()
-        self.assertItemsEqual(node_names, nodes)
+        self.assertSetEqual(node_names, node_names.intersection(nodes))
 
     @inlineCallbacks
     def test_get_nodes_includes_node_attributes(self):
         papi = self.get_provisioning_api()
-        distro = self.getUniqueString('distro')
-        initrd = self.getUniqueString('initrd')
-        kernel = self.getUniqueString('kernel')
-        distro = yield papi.add_distro(distro, initrd, kernel)
-        profile = yield papi.add_profile(self.getUniqueString(), distro)
-        node_name = self.getUniqueString()
-        yield papi.add_node(node_name, profile, self.fake_metadata())
+        node_name = yield self.add_node(papi)
         nodes = yield papi.get_nodes()
-        self.assertItemsEqual([node_name], nodes)
+        self.assertIn(node_name, nodes)
         self.assertIn('name', nodes[node_name])
         self.assertIn('profile', nodes[node_name])
         self.assertIn('mac_addresses', nodes[node_name])
@@ -431,12 +493,10 @@
         nodes = yield papi.get_nodes_by_name([])
         self.assertEqual({}, nodes)
         # Create a node via the Provisioning API.
-        distro = yield papi.add_distro("distro", "initrd", "kernel")
-        profile = yield papi.add_profile("profile", distro)
-        yield papi.add_node("alice", profile, self.fake_metadata())
-        nodes = yield papi.get_nodes_by_name(["alice", "bob"])
+        node_name = yield self.add_node(papi)
+        nodes = yield papi.get_nodes_by_name([node_name])
         # The response contains keys for all systems found.
-        self.assertSequenceEqual(["alice"], sorted(nodes))
+        self.assertSequenceEqual([node_name], sorted(nodes))
 
     @inlineCallbacks
     def test_get_distros_by_name(self):
@@ -444,10 +504,10 @@
         distros = yield papi.get_distros_by_name([])
         self.assertEqual({}, distros)
         # Create a distro via the Provisioning API.
-        yield papi.add_distro("alice", "initrd", "kernel")
-        distros = yield papi.get_distros_by_name(["alice", "bob"])
+        distro_name = yield self.add_distro(papi)
+        distros = yield papi.get_distros_by_name([distro_name])
         # The response contains keys for all distributions found.
-        self.assertSequenceEqual(["alice"], sorted(distros))
+        self.assertSequenceEqual([distro_name], sorted(distros))
 
     @inlineCallbacks
     def test_get_profiles_by_name(self):
@@ -455,37 +515,32 @@
         profiles = yield papi.get_profiles_by_name([])
         self.assertEqual({}, profiles)
         # Create a profile via the Provisioning API.
-        distro = yield papi.add_distro("distro", "initrd", "kernel")
-        yield papi.add_profile("alice", distro)
-        profiles = yield papi.get_profiles_by_name(["alice", "bob"])
+        profile_name = yield self.add_profile(papi)
+        profiles = yield papi.get_profiles_by_name([profile_name])
         # The response contains keys for all profiles found.
-        self.assertSequenceEqual(["alice"], sorted(profiles))
+        self.assertSequenceEqual([profile_name], sorted(profiles))
 
     @inlineCallbacks
     def test_stop_nodes(self):
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro("distro", "initrd", "kernel")
-        profile = yield papi.add_profile("profile", distro)
-        yield papi.add_node("alice", profile, self.fake_metadata())
-        yield papi.stop_nodes(["alice"])
+        node_name = yield self.add_node(papi)
+        yield papi.stop_nodes([node_name])
         # The test is that we get here without error.
         pass
 
     @inlineCallbacks
     def test_start_nodes(self):
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro("distro", "initrd", "kernel")
-        profile = yield papi.add_profile("profile", distro)
-        yield papi.add_node("alice", profile, self.fake_metadata())
-        yield papi.start_nodes(["alice"])
+        node_name = yield self.add_node(papi)
+        yield papi.start_nodes([node_name])
         # The test is that we get here without error.
         pass
 
 
-class TestProvisioningAPI(ProvisioningAPITestScenario, TestCase):
+class TestProvisioningAPI(ProvisioningAPITests, TestCase):
     """Test :class:`ProvisioningAPI`.
 
-    Includes by inheritance all the tests in ProvisioningAPITestScenario.
+    Includes by inheritance all the tests in :class:`ProvisioningAPITests`.
     """
 
     def get_provisioning_api(self):
@@ -495,24 +550,45 @@
     @inlineCallbacks
     def test_add_node_preseeds_metadata(self):
         papi = self.get_provisioning_api()
-        distro = yield papi.add_distro("distro", "an_initrd", "a_kernel")
-        profile = yield papi.add_profile("profile", distro)
-        metadata = self.fake_metadata()
-        node_name = self.getUniqueString("node")
-        yield papi.add_node(node_name, profile, metadata)
-
+        metadata = fake_node_metadata()
+        node_name = yield self.add_node(papi, metadata=metadata)
         attrs = yield CobblerSystem(papi.session, node_name).get_values()
         preseed = attrs['ks_meta']['MAAS_PRESEED']
         self.assertIn(metadata['maas-metadata-url'], preseed)
         self.assertIn(metadata['maas-metadata-credentials'], preseed)
 
 
-class TestFakeProvisioningAPI(ProvisioningAPITestScenario, TestCase):
+class TestFakeProvisioningAPI(ProvisioningAPITests, TestCase):
     """Test :class:`FakeAsynchronousProvisioningAPI`.
 
-    Includes by inheritance all the tests in ProvisioningAPITestScenario.
+    Includes by inheritance all the tests in :class:`ProvisioningAPITests`.
     """
 
     def get_provisioning_api(self):
         """Return a fake ProvisioningAPI."""
         return FakeAsynchronousProvisioningAPI()
+
+
+class TestProvisioningAPIRealCobbler(ProvisioningAPITests, TestCase):
+    """Test :class:`ProvisioningAPI` with a real Cobbler instance.
+
+    The URL for the Cobbler instance must be provided in the
+    `TEST_COBBLER_URL` environment variable.
+
+    Includes by inheritance all the tests in :class:`ProvisioningAPITests`.
+    """
+
+    url = environ.get("TEST_COBBLER_URL")
+
+    @skipIf(
+        url is None,
+        "Set TEST_COBBLER_URL to the URL for a Cobbler "
+        "instance to test against, e.g. http://username";
+        ":password@localhost/cobbler_api. Warning: this "
+        "will modify your Cobbler database.")
+    def get_provisioning_api(self):
+        """Return a connected :class:`ProvisioningAPI`."""
+        urlparts = urlparse(self.url)
+        cobbler_session = CobblerSession(
+            self.url, urlparts.username, urlparts.password)
+        return ProvisioningAPI(cobbler_session)