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