Use instance hostname for Neutron DNS

When booting an instance, its hostname is used to set the value of the
'dns_name' attribute of the ports attached to the instance. 'dns_name' is used
for the port's name in the Neutron's internal DNS service. It can also be
published to an external DNS service, like Designate, if integration with such
a service is enabled in Neutron.

Change-Id: I465ac35914186be0b645a6694b7d49d5dc396ba6
Implements: blueprint neutron-hostname-dns
This commit is contained in:
Miguel Lavalle 2016-01-23 00:13:11 +00:00
parent d574aaf7be
commit 30ba0c5eb1
4 changed files with 73 additions and 6 deletions

View File

@ -843,6 +843,12 @@ class PortNotUsable(Invalid):
msg_fmt = _("Port %(port_id)s not usable for instance %(instance)s.")
class PortNotUsableDNS(Invalid):
msg_fmt = _("Port %(port_id)s not usable for instance %(instance)s. "
"Value %(value)s assigned to dns_name attribute does not "
"match instance's hostname %(hostname)s")
class PortNotFree(Invalid):
msg_fmt = _("No free port available for instance %(instance)s.")

View File

@ -394,6 +394,8 @@ class API(base_api.NetworkAPI):
port's MAC address is not in that set.
:raises nova.exception.PortInUse: If a requested port is already
attached to another instance.
:raises nova.exception.PortNotUsableDNS: If a requested port has a
value assigned to its dns_name attribute.
"""
available_macs = None
@ -424,6 +426,15 @@ class API(base_api.NetworkAPI):
if port.get('device_id'):
raise exception.PortInUse(port_id=request.port_id)
# Make sure the user didn't assign a value to the port's
# dns_name attribute.
if port.get('dns_name'):
if port['dns_name'] != instance.hostname:
raise exception.PortNotUsableDNS(
port_id=request.port_id,
instance=instance.uuid, value=port['dns_name'],
hostname=instance.hostname)
# Make sure the port is usable
if (port.get('binding:vif_type') ==
network_model.VIF_TYPE_BINDING_FAILED):
@ -642,7 +653,8 @@ class API(base_api.NetworkAPI):
try:
self._populate_neutron_extension_values(
context, instance, request.pci_request_id, port_req_body,
neutron=neutron, bind_host_id=bind_host_id)
network=network, neutron=neutron,
bind_host_id=bind_host_id)
if request.port_id:
port = ports[request.port_id]
port_client.update_port(port['id'], port_req_body)
@ -655,6 +667,9 @@ class API(base_api.NetworkAPI):
security_group_ids, available_macs, dhcp_opts)
created_port_ids.append(created_port)
ports_in_requested_order.append(created_port)
self._update_port_dns_name(context, instance, network,
ports_in_requested_order[-1],
neutron)
except Exception:
with excutils.save_and_reraise_exception():
self._unbind_ports(context,
@ -715,7 +730,8 @@ class API(base_api.NetworkAPI):
def _populate_neutron_extension_values(self, context, instance,
pci_request_id, port_req_body,
neutron=None, bind_host_id=None):
network=None, neutron=None,
bind_host_id=None):
"""Populate neutron extension values for the instance.
If the extensions loaded contain QOS_QUEUE then pass the rxtx_factor.
@ -725,11 +741,53 @@ class API(base_api.NetworkAPI):
flavor = instance.get_flavor()
rxtx_factor = flavor.get('rxtx_factor')
port_req_body['port']['rxtx_factor'] = rxtx_factor
if self._has_port_binding_extension(context, neutron=neutron):
has_port_binding_extension = (
self._has_port_binding_extension(context, neutron=neutron))
if has_port_binding_extension:
port_req_body['port']['binding:host_id'] = bind_host_id
self._populate_neutron_binding_profile(instance,
pci_request_id,
port_req_body)
if constants.DNS_INTEGRATION in self.extensions:
# If the DNS integration extension is enabled in Neutron, most
# ports will get their dns_name attribute set in the port create or
# update requests in allocate_for_instance. So we just add the
# dns_name attribute to the payload of those requests. The
# exception is when the port binding extension is enabled in
# Neutron and the port is on a network that has a non-blank
# dns_domain attribute. This case requires to be processed by
# method _update_port_dns_name
if (not has_port_binding_extension
or not network.get('dns_domain')):
port_req_body['port']['dns_name'] = instance.hostname
def _update_port_dns_name(self, context, instance, network, port_id,
neutron):
"""Update an instance port dns_name attribute with instance.hostname.
The dns_name attribute of a port on a network with a non-blank
dns_domain attribute will be sent to the external DNS service
(Designate) if DNS integration is enabled in Neutron. This requires the
assignment of the dns_name to the port to be done with a Neutron client
using the user's context. allocate_for_instance uses a port with admin
context if the port binding extensions is enabled in Neutron. In this
case, we assign in this method the dns_name attribute to the port with
an additional update request. Only a very small fraction of ports will
require this additional update request.
"""
if (constants.DNS_INTEGRATION in self.extensions and
self._has_port_binding_extension(context) and
network.get('dns_domain')):
try:
port_req_body = {'port': {'dns_name': instance.hostname}}
neutron.update_port(port_id, port_req_body)
except neutron_client_exc.BadRequest:
LOG.warning(_LW('Neutron error: Instance hostname '
'%(hostname)s is not a valid DNS name'),
{'hostname': instance.hostname}, instance=instance)
msg = (_('Instance hostname %(hostname)s is not a valid DNS '
'name') % {'hostname': instance.hostname})
raise exception.InvalidInput(reason=msg)
def _delete_ports(self, neutron, instance, ports, raise_if_fail=False):
exceptions = []

View File

@ -17,3 +17,4 @@ QOS_QUEUE = 'QoS Queue'
NET_EXTERNAL = 'router:external'
PORTBINDING_EXT = 'Port Binding'
VNIC_INDEX_EXT = 'VNIC Index'
DNS_INTEGRATION = 'DNS Integration'

View File

@ -511,7 +511,8 @@ class TestNeutronv2Base(test.TestCase):
if not has_portbinding:
api._populate_neutron_extension_values(mox.IgnoreArg(),
self.instance, mox.IgnoreArg(),
mox.IgnoreArg(), neutron=self.moxed_client,
mox.IgnoreArg(), network=network,
neutron=self.moxed_client,
bind_host_id=None).AndReturn(None)
else:
# since _populate_neutron_extension_values() will call
@ -1140,7 +1141,7 @@ class TestNeutronv2(TestNeutronv2Base):
port = {'id': 'portid_' + network['id']}
api._populate_neutron_extension_values(self.context,
self.instance, None, binding_port_req_body,
self.instance, None, binding_port_req_body, network=network,
neutron=self.moxed_client, bind_host_id=None).AndReturn(None)
if index == 0:
self.moxed_client.create_port(
@ -1195,7 +1196,8 @@ class TestNeutronv2(TestNeutronv2Base):
}
api._populate_neutron_extension_values(self.context,
self.instance, None, binding_port_req_body,
neutron=self.moxed_client, bind_host_id=None).AndReturn(None)
network=self.nets2[0], neutron=self.moxed_client,
bind_host_id=None).AndReturn(None)
self.moxed_client.create_port(
MyComparator(port_req_body)).AndRaise(
Exception("fail to create port"))