← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/maas/node-tld-send-to-cobbler into lp:maas

 

Gavin Panella has proposed merging lp:~allenap/maas/node-tld-send-to-cobbler into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~allenap/maas/node-tld-send-to-cobbler/+merge/99997

Send the node hostname to Cobbler, and set dns_name on each node interface. This is so that Cobbler can manage DNS and such.
-- 
https://code.launchpad.net/~allenap/maas/node-tld-send-to-cobbler/+merge/99997
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/maas/node-tld-send-to-cobbler into lp:maas.
=== modified file 'src/maasserver/provisioning.py'
--- src/maasserver/provisioning.py	2012-03-27 03:50:43 +0000
+++ src/maasserver/provisioning.py	2012-03-29 18:34:25 +0000
@@ -40,7 +40,7 @@
         The provisioning server was unable to reach the Cobbler service:
         %(fault_string)s
 
-        Check pserv.log, and restart MaaS if needed.
+        Check pserv.log, and restart MAAS if needed.
         """,
     PSERV_FAULT.COBBLER_AUTH_FAILED: """
         The provisioning server failed to authenticate with the Cobbler
@@ -71,7 +71,7 @@
     8002: """
         Unable to reach provisioning server (%(fault_string)s).
 
-        Check pserv.log and your PSERV_URL setting, and restart MaaS if
+        Check pserv.log and your PSERV_URL setting, and restart MAAS if
         needed.
         """,
 }
@@ -242,7 +242,9 @@
     profile = select_profile_for_node(instance, papi)
     power_type = instance.get_effective_power_type()
     metadata = compose_metadata(instance)
-    papi.add_node(instance.system_id, profile, power_type, metadata)
+    papi.add_node(
+        instance.system_id, instance.hostname,
+        profile, power_type, metadata)
 
 
 def set_node_mac_addresses(node):

=== modified file 'src/maasserver/tests/test_provisioning.py'
--- src/maasserver/tests/test_provisioning.py	2012-03-27 03:50:43 +0000
+++ src/maasserver/tests/test_provisioning.py	2012-03-29 18:34:25 +0000
@@ -153,8 +153,10 @@
         effective_power_types = {
             power_type: node.get_effective_power_type()
             for power_type, node in nodes.items()}
+        pserv_nodes = self.papi.get_nodes_by_name(
+            node.system_id for node in nodes.values())
         pserv_power_types = {
-            power_type: self.papi.power_types[node.system_id]
+            power_type: pserv_nodes[node.system_id]["power_type"]
             for power_type, node in nodes.items()}
         self.assertEqual(effective_power_types, pserv_power_types)
 

=== modified file 'src/provisioningserver/api.py'
--- src/provisioningserver/api.py	2012-03-15 13:58:32 +0000
+++ src/provisioningserver/api.py	2012-03-29 18:34:25 +0000
@@ -15,7 +15,12 @@
 
 from base64 import b64encode
 from functools import partial
-from itertools import count
+from itertools import (
+    chain,
+    count,
+    izip,
+    repeat,
+    )
 
 from provisioningserver.cobblerclient import (
     CobblerDistro,
@@ -48,6 +53,7 @@
         for interface in interfaces.itervalues())
     return {
         "name": data["name"],
+        "hostname": data["hostname"],
         "profile": data["profile"],
         "mac_addresses": [
             mac_address.strip()
@@ -84,13 +90,14 @@
     postprocess_mapping, function=cobbler_to_papi_distro)
 
 
-def mac_addresses_to_cobbler_deltas(interfaces, mac_addresses):
+def gen_cobbler_interface_deltas(interfaces, hostname, mac_addresses):
     """Generate `modify_system` dicts for use with `xapi_object_edit`.
 
     This takes `interfaces` - the current state of a system's interfaces - and
     generates the operations required to transform it into a list of
     interfaces containing exactly `mac_addresses`.
 
+    :param hostname: The hostname of the system.
     :param interfaces: A dict of interface-names -> interface-configurations.
     :param mac_addresses: A collection of desired MAC addresses.
     """
@@ -98,9 +105,9 @@
     # assigned. We may end up setting the MAC on these interfaces, but whether
     # or not that happens is undefined (for now).
     interfaces = {
-        name: configuration
-        for name, configuration in interfaces.items()
-        if configuration["mac_address"]
+        interface_name: interface
+        for interface_name, interface in interfaces.items()
+        if interface["mac_address"]
         }
 
     interface_names_by_mac_address = {
@@ -112,13 +119,11 @@
     mac_addresses_to_add = set(
         mac_addresses).difference(interface_names_by_mac_address)
 
-    # Keep track of the used interface names.
-    interface_names = set(interfaces)
     # The following generator will lazily return interface names that can be
     # used when adding MAC addresses.
     interface_names_unused = (
         "eth%d" % num for num in count(0)
-        if "eth%d" % num not in interface_names)
+        if "eth%d" % num not in interfaces)
 
     # Create a delta to remove an interface in Cobbler. We sort the MAC
     # addresses to provide stability in this function's output (which
@@ -127,7 +132,7 @@
         interface_name = interface_names_by_mac_address[mac_address]
         # Deallocate this interface name from our records; it can be used when
         # allocating interfaces later.
-        interface_names.remove(interface_name)
+        del interfaces[interface_name]
         yield {
             "interface": interface_name,
             "delete_interface": True,
@@ -142,11 +147,24 @@
         # necessary (interface_names_unused will never go backwards) but we do
         # it defensively in case of later additions to this function, and
         # because it has a satifying symmetry.
-        interface_names.add(interface_name)
-        yield {
+        interface = {
             "interface": interface_name,
             "mac_address": mac_address,
             }
+        interfaces[interface_name] = interface
+        yield interface
+
+    # Set dns_name to `hostname` for the first interface, and to the empty
+    # string for the rest.
+    interface_names = sorted(interfaces)  # Lowest-numbered first.
+    dns_names = chain([hostname], repeat(""))
+    for interface_name, dns_name in izip(interface_names, dns_names):
+        current_dns_name = interfaces[interface_name].get("dns_name", "")
+        if current_dns_name != dns_name:
+            yield {
+                "interface": interface_name,
+                "dns_name": dns_name,
+                }
 
 
 # Preseed data to send to cloud-init.  We set this as MAAS_PRESEED in
@@ -190,13 +208,15 @@
         returnValue(profile.name)
 
     @inlineCallbacks
-    def add_node(self, name, profile, power_type, metadata):
+    def add_node(self, name, hostname, profile, power_type, metadata):
         assert isinstance(name, basestring)
+        assert isinstance(hostname, basestring)
         assert isinstance(profile, basestring)
         assert power_type in (POWER_TYPE.VIRSH, POWER_TYPE.WAKE_ON_LAN)
         assert isinstance(metadata, dict)
         preseed = b64encode(metadata_preseed % metadata)
         attributes = {
+            "hostname": hostname,
             "profile": profile,
             "ks_meta": {"MAAS_PRESEED": preseed},
             "power_type": power_type,
@@ -222,9 +242,10 @@
                 # This needs to be handled carefully.
                 mac_addresses = delta.pop("mac_addresses")
                 system_state = yield system.get_values()
+                hostname = system_state.get("hostname", "")
                 interfaces = system_state.get("interfaces", {})
-                interface_modifications = mac_addresses_to_cobbler_deltas(
-                    interfaces, mac_addresses)
+                interface_modifications = gen_cobbler_interface_deltas(
+                    interfaces, hostname, mac_addresses)
                 for interface_modification in interface_modifications:
                     yield system.modify(interface_modification)
             yield system.modify(delta)

=== modified file 'src/provisioningserver/cobblerclient.py'
--- src/provisioningserver/cobblerclient.py	2012-03-28 20:13:23 +0000
+++ src/provisioningserver/cobblerclient.py	2012-03-29 18:34:25 +0000
@@ -720,10 +720,12 @@
         'profile',
         ]
     modification_attributes = [
+        'hostname',
         'delete_interface',
         'interface',  # Required for mac_address and delete_interface.
         'mac_address',
         'profile',
+        'dns_name',
         ]
 
     @classmethod

=== modified file 'src/provisioningserver/interfaces.py'
--- src/provisioningserver/interfaces.py	2012-03-13 09:34:02 +0000
+++ src/provisioningserver/interfaces.py	2012-03-29 18:34:25 +0000
@@ -50,9 +50,10 @@
         :return: The name of the new profile.
         """
 
-    def add_node(name, profile, power_type, metadata):
+    def add_node(name, hostname, profile, power_type, metadata):
         """Add a node with the given `name`.
 
+        :param hostname: The fully-qualified hostname for the node.
         :param profile: Name of profile to associate the node with.
         :param power_type: A choice of power-control method, as in
             :class:`POWER_TYPE`.

=== modified file 'src/provisioningserver/testing/fakeapi.py'
--- src/provisioningserver/testing/fakeapi.py	2012-03-13 15:52:17 +0000
+++ src/provisioningserver/testing/fakeapi.py	2012-03-29 18:34:25 +0000
@@ -73,11 +73,6 @@
         self.distros = FakeProvisioningDatabase()
         self.profiles = FakeProvisioningDatabase()
         self.nodes = FakeProvisioningDatabase()
-        # Record power_type settings for nodes (by node name).  This is
-        # not part of the provisioning-server node as returned to the
-        # maasserver, so it's not stored as a regular attribute even if
-        # it works like one internally.
-        self.power_types = {}
         # This records nodes that start/stop commands have been issued
         # for.  If a node has been started, its name maps to 'start'; if
         # it has been stopped, its name maps to 'stop' (whichever
@@ -93,11 +88,12 @@
         self.profiles[name]["distro"] = distro
         return name
 
-    def add_node(self, name, profile, power_type, metadata):
+    def add_node(self, name, hostname, profile, power_type, metadata):
+        self.nodes[name]["hostname"] = hostname
         self.nodes[name]["profile"] = profile
         self.nodes[name]["mac_addresses"] = []
         self.nodes[name]["metadata"] = metadata
-        self.power_types[name] = power_type
+        self.nodes[name]["power_type"] = power_type
         return name
 
     def modify_distros(self, deltas):

=== modified file 'src/provisioningserver/testing/fakecobbler.py'
--- src/provisioningserver/testing/fakecobbler.py	2012-03-19 04:47:43 +0000
+++ src/provisioningserver/testing/fakecobbler.py	2012-03-29 18:34:25 +0000
@@ -358,13 +358,16 @@
         if "mac_address" in attrs:
             interface = interfaces.setdefault(interface_name, {})
             interface["mac_address"] = attrs.pop("mac_address")
+        elif "dns_name" in attrs:
+            interface = interfaces.setdefault(interface_name, {})
+            interface["dns_name"] = attrs.pop("dns_name")
         elif "delete_interface" in attrs:
             if interface_name in interfaces:
                 del interfaces[interface_name]
         else:
             raise AssertionError(
-                "Edit operation defined interface but "
-                "not mac_address or delete_interface. "
+                "Edit operation defined interface but not "
+                "mac_address, dns_name, or delete_interface. "
                 "Got: %r" % (attrs,))
         self._api_modify_object(
             token, 'system', handle, "interfaces", interfaces)

=== modified file 'src/provisioningserver/tests/test_api.py'
--- src/provisioningserver/tests/test_api.py	2012-03-16 08:58:05 +0000
+++ src/provisioningserver/tests/test_api.py	2012-03-29 18:34:25 +0000
@@ -32,7 +32,7 @@
     cobbler_to_papi_distro,
     cobbler_to_papi_node,
     cobbler_to_papi_profile,
-    mac_addresses_to_cobbler_deltas,
+    gen_cobbler_interface_deltas,
     postprocess_mapping,
     ProvisioningAPI,
     )
@@ -96,6 +96,7 @@
         data = {
             "name": "iced",
             "profile": "earth",
+            "hostname": "dystopia",
             "interfaces": {
                 "eth0": {"mac_address": "12:34:56:78:9a:bc"},
                 },
@@ -105,6 +106,7 @@
         expected = {
             "name": "iced",
             "profile": "earth",
+            "hostname": "dystopia",
             "mac_addresses": ["12:34:56:78:9a:bc"],
             "power_type": "virsh",
             }
@@ -115,12 +117,14 @@
         data = {
             "name": "iced",
             "profile": "earth",
+            "hostname": "darksaga",
             "power_type": "ether_wake",
             "ju": "nk",
             }
         expected = {
             "name": "iced",
             "profile": "earth",
+            "hostname": "darksaga",
             "mac_addresses": [],
             "power_type": "ether_wake",
             }
@@ -158,28 +162,35 @@
 
 class TestInterfaceDeltas(TestCase):
 
-    def test_mac_addresses_to_cobbler_deltas_set_1(self):
+    def test_gen_cobbler_interface_deltas_set_1(self):
         current_interfaces = {
             "eth0": {
                 "mac_address": "",
+                "dns_name": "colony",
                 },
             }
+        dns_name_desired = "clayman"
         mac_addresses_desired = ["12:34:56:78:90:12"]
         expected = [
             {"interface": "eth0",
              "mac_address": "12:34:56:78:90:12"},
+            # The desired DNS name is set on the lowest-numbered interface.
+            {"interface": "eth0",
+             "dns_name": dns_name_desired},
             ]
         observed = list(
-            mac_addresses_to_cobbler_deltas(
-                current_interfaces, mac_addresses_desired))
+            gen_cobbler_interface_deltas(
+                current_interfaces, dns_name_desired,
+                mac_addresses_desired))
         self.assertEqual(expected, observed)
 
-    def test_mac_addresses_to_cobbler_deltas_set_2(self):
+    def test_gen_cobbler_interface_deltas_set_2(self):
         current_interfaces = {
             "eth0": {
                 "mac_address": "",
                 },
             }
+        dns_name_desired = "crowbar"
         mac_addresses_desired = [
             "11:11:11:11:11:11", "22:22:22:22:22:22"]
         expected = [
@@ -187,16 +198,22 @@
              "mac_address": "11:11:11:11:11:11"},
             {"interface": "eth1",
              "mac_address": "22:22:22:22:22:22"},
+            # The desired DNS name is set on the lowest-numbered interface.
+            {"interface": "eth0",
+             "dns_name": dns_name_desired},
             ]
         observed = list(
-            mac_addresses_to_cobbler_deltas(
-                current_interfaces, mac_addresses_desired))
+            gen_cobbler_interface_deltas(
+                current_interfaces, dns_name_desired,
+                mac_addresses_desired))
         self.assertEqual(expected, observed)
 
-    def test_mac_addresses_to_cobbler_deltas_remove_1(self):
+    def test_gen_cobbler_interface_deltas_remove_1(self):
+        dns_name = "lifesblood"
         current_interfaces = {
             "eth0": {
                 "mac_address": "11:11:11:11:11:11",
+                "dns_name": dns_name,
                 },
             "eth1": {
                 "mac_address": "22:22:22:22:22:22",
@@ -206,13 +223,17 @@
         expected = [
             {"interface": "eth0",
              "delete_interface": True},
+            # The existing DNS name is moved to the lowest-numbered interface.
+            {"interface": "eth1",
+             "dns_name": dns_name},
             ]
         observed = list(
-            mac_addresses_to_cobbler_deltas(
-                current_interfaces, mac_addresses_desired))
+            gen_cobbler_interface_deltas(
+                current_interfaces, dns_name,
+                mac_addresses_desired))
         self.assertEqual(expected, observed)
 
-    def test_mac_addresses_to_cobbler_deltas_set_1_remove_1(self):
+    def test_gen_cobbler_interface_deltas_set_1_remove_1(self):
         current_interfaces = {
             "eth0": {
                 "mac_address": "11:11:11:11:11:11",
@@ -221,6 +242,7 @@
                 "mac_address": "22:22:22:22:22:22",
                 },
             }
+        dns_name_desired = "necrophagist"
         mac_addresses_desired = [
             "22:22:22:22:22:22", "33:33:33:33:33:33"]
         expected = [
@@ -228,10 +250,14 @@
              "delete_interface": True},
             {"interface": "eth0",
              "mac_address": "33:33:33:33:33:33"},
+            # The existing DNS name is set on the lowest-numbered interface.
+            {"interface": "eth0",
+             "dns_name": dns_name_desired},
             ]
         observed = list(
-            mac_addresses_to_cobbler_deltas(
-                current_interfaces, mac_addresses_desired))
+            gen_cobbler_interface_deltas(
+                current_interfaces, dns_name_desired,
+                mac_addresses_desired))
         self.assertEqual(expected, observed)
 
 
@@ -309,8 +335,8 @@
         returnValue(profile_name)
 
     @inlineCallbacks
-    def add_node(self, papi, name=None, profile_name=None, power_type=None,
-                 metadata=None):
+    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
@@ -321,6 +347,8 @@
         """
         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:
@@ -328,7 +356,7 @@
         if metadata is None:
             metadata = fake_node_metadata()
         node_name = yield papi.add_node(
-            name, profile_name, power_type, metadata)
+            name, hostname, profile_name, power_type, metadata)
         self.addCleanup(
             self.cleanup_objects,
             papi.delete_nodes_by_name,
@@ -359,9 +387,13 @@
     def test_add_node(self):
         # Create a system/node via the Provisioning API.
         papi = self.get_provisioning_api()
-        node_name = yield self.add_node(papi)
+        node_name = yield self.add_node(papi, hostname="enthroned")
         nodes = yield papi.get_nodes_by_name([node_name])
         self.assertItemsEqual([node_name], nodes)
+        node = nodes[node_name]
+        self.assertEqual("enthroned", node["hostname"])
+        self.assertEqual("ether_wake", node["power_type"])
+        self.assertEqual([], node["mac_addresses"])
 
     @inlineCallbacks
     def _test_add_object_twice(self, method):

=== modified file 'src/provisioningserver/tests/test_fakecobbler.py'
--- src/provisioningserver/tests/test_fakecobbler.py	2012-03-19 04:47:43 +0000
+++ src/provisioningserver/tests/test_fakecobbler.py	2012-03-29 18:34:25 +0000
@@ -453,6 +453,19 @@
         self.assertEqual("12:34:56:78:90:12", state_eth0["mac_address"])
 
     @inlineCallbacks
+    def test_interface_set_dns_name(self):
+        session = yield fake_cobbler_session()
+        name = self.make_name()
+        obj = yield fake_cobbler_object(session, self.cobbler_class, name)
+        yield obj.modify(
+            {"interface": "eth0", "dns_name": "epitaph"})
+        state = yield obj.get_values()
+        interfaces = state.get("interfaces", {})
+        self.assertEqual(["eth0"], sorted(interfaces))
+        state_eth0 = interfaces["eth0"]
+        self.assertEqual("epitaph", state_eth0["dns_name"])
+
+    @inlineCallbacks
     def test_interface_delete_interface(self):
         session = yield fake_cobbler_session()
         name = self.make_name()