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