From 2d20b87aef5a7d00cb36826b8907032912fb17fc Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Fri, 9 Aug 2013 11:15:26 -0700 Subject: [PATCH] Handle port over-quota when allocating network for instance This patch adds a check in the neutron API for a port create failure due to over-quota in neutron and raises the new exception PortLimitExceeded. Also moves the port-create block of code into it's own method to try and clean up some of the large allocate_for_instance method. Closes-Bug: #1207914 Related-Bug: #1209446 Change-Id: I4000c8ab550e032363f138a86e1b87f6ab2f5ff2 --- nova/exception.py | 4 ++ nova/network/neutronv2/api.py | 71 ++++++++++++++++++++-------- nova/tests/network/test_neutronv2.py | 9 ++-- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index d3cc67f5be05..7ed5c964925f 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1092,6 +1092,10 @@ class SecurityGroupLimitExceeded(QuotaError): msg_fmt = _("Maximum number of security groups or rules exceeded") +class PortLimitExceeded(QuotaError): + msg_fmt = _("Maximum number of ports exceeded") + + class AggregateError(NovaException): msg_fmt = _("Aggregate %(aggregate_id)s: action '%(action)s' " "caused an error: %(reason)s.") diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index bd887eb138bb..3f67fa289b77 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -18,6 +18,7 @@ import time +from neutronclient.common import exceptions as neutron_client_exc from oslo.config import cfg from nova.compute import flavors @@ -143,6 +144,52 @@ class API(base.Base): return nets + def _create_port(self, port_client, instance, network_id, port_req_body, + fixed_ip=None, security_group_ids=None, + available_macs=None, dhcp_opts=None): + """Attempts to create a port for the instance on the given network. + + :param port_client: The client to use to create the port. + :param instance: Create the port for the given instance. + :param network_id: Create the port on the given network. + :param port_req_body: Pre-populated port request. Should have the + device_id, device_owner, and any required neutron extension values. + :param fixed_ip: Optional fixed IP to use from the given network. + :param security_group_ids: Optional list of security group IDs to + apply to the port. + :param available_macs: Optional set of available MAC addresses to use. + :param dhcp_opts: Optional DHCP options. + :returns: ID of the created port. + :raises PortLimitExceeded: If neutron fails with an OverQuota error. + """ + try: + if fixed_ip: + port_req_body['port']['fixed_ips'] = [{'ip_address': fixed_ip}] + port_req_body['port']['network_id'] = network_id + port_req_body['port']['admin_state_up'] = True + port_req_body['port']['tenant_id'] = instance['project_id'] + if security_group_ids: + port_req_body['port']['security_groups'] = security_group_ids + if available_macs is not None: + if not available_macs: + raise exception.PortNotFree( + instance=instance['display_name']) + mac_address = available_macs.pop() + port_req_body['port']['mac_address'] = mac_address + if dhcp_opts is not None: + port_req_body['port']['extra_dhcp_opts'] = dhcp_opts + port_id = port_client.create_port(port_req_body)['port']['id'] + LOG.debug(_('Successfully created port: %s') % port_id, + instance=instance) + return port_id + except neutron_client_exc.NeutronClientException as e: + LOG.exception(_('Neutron error creating port on network %s') % + network_id, instance=instance) + # NOTE(mriedem): OverQuota in neutron is a 409 + if e.status_code == 409: + raise exception.PortLimitExceeded() + raise + @refresh_cache def allocate_for_instance(self, context, instance, **kwargs): """Allocate network resources for the instance. @@ -279,26 +326,10 @@ class API(base.Base): port_client.update_port(port['id'], port_req_body) touched_port_ids.append(port['id']) else: - fixed_ip = fixed_ips.get(network_id) - if fixed_ip: - port_req_body['port']['fixed_ips'] = [{'ip_address': - fixed_ip}] - port_req_body['port']['network_id'] = network_id - port_req_body['port']['admin_state_up'] = True - port_req_body['port']['tenant_id'] = instance['project_id'] - if security_group_ids: - port_req_body['port']['security_groups'] = ( - security_group_ids) - if available_macs is not None: - if not available_macs: - raise exception.PortNotFree( - instance=instance['display_name']) - mac_address = available_macs.pop() - port_req_body['port']['mac_address'] = mac_address - if dhcp_opts is not None: - port_req_body['port']['extra_dhcp_opts'] = dhcp_opts - created_port_ids.append( - port_client.create_port(port_req_body)['port']['id']) + created_port_ids.append(self._create_port( + port_client, instance, network_id, + port_req_body, fixed_ips.get(network_id), + security_group_ids, available_macs, dhcp_opts)) except Exception: with excutils.save_and_reraise_exception(): for port_id in touched_port_ids: diff --git a/nova/tests/network/test_neutronv2.py b/nova/tests/network/test_neutronv2.py index f7d3ae1ab290..9c5c37d40518 100644 --- a/nova/tests/network/test_neutronv2.py +++ b/nova/tests/network/test_neutronv2.py @@ -722,13 +722,16 @@ class TestNeutronv2(TestNeutronv2Base): self.moxed_client.create_port( MyComparator(port_req_body)).AndReturn({'port': port}) else: + NeutronOverQuota = exceptions.NeutronClientException( + message="Quota exceeded for resources: ['port']", + status_code=409) self.moxed_client.create_port( - MyComparator(port_req_body)).AndRaise( - Exception("fail to create port")) + MyComparator(port_req_body)).AndRaise(NeutronOverQuota) index += 1 self.moxed_client.delete_port('portid_' + self.nets2[0]['id']) self.mox.ReplayAll() - self.assertRaises(NEUTRON_CLIENT_EXCEPTION, api.allocate_for_instance, + self.assertRaises(exception.PortLimitExceeded, + api.allocate_for_instance, self.context, self.instance) def test_allocate_for_instance_ex2(self):