From 4dc7caf89c1daa03ab11636fff6b4cc4e00ac13c Mon Sep 17 00:00:00 2001 From: Mohammad Banikazemi <mb@us.ibm.com> Date: Thu, 23 Jun 2016 14:20:34 -0400 Subject: [PATCH] Waiting for Neutron port to become ACTIVE This patch set adds two config options similar to those used in Nova, namely vif_plugging_is_fatal and vif_plugging_timeout to make it possible to ask Kuryr to wait until the port status becomes Active during libnetwork join operations. Closes-Bug: #1578016 Change-Id: I106d2c8c289c7fca2a465c72f1dbd53acc539f39 --- kuryr/common/config.py | 7 ++++++ kuryr/common/constants.py | 3 +++ kuryr/common/exceptions.py | 9 ++++++++ kuryr/controllers.py | 35 +++++++++++++++++++++++++++++ kuryr/tests/unit/base.py | 7 +++--- kuryr/tests/unit/test_join.py | 2 +- kuryr/tests/unit/test_kuryr.py | 25 +++++++++++++++++---- kuryr/tests/unit/test_kuryr_ipam.py | 4 ++-- kuryr/tests/unit/test_leave.py | 3 ++- 9 files changed, 84 insertions(+), 11 deletions(-) diff --git a/kuryr/common/config.py b/kuryr/common/config.py index e5dca7cd..b7ab51db 100644 --- a/kuryr/common/config.py +++ b/kuryr/common/config.py @@ -61,6 +61,13 @@ neutron_opts = [ cfg.StrOpt('default_subnetpool_v6', default='kuryr6', help=_('Name of default subnetpool version 6')), + cfg.BoolOpt('vif_plugging_is_fatal', + default=False, + help=_("Whether a plugging operation is failed if the port " + "to plug does not become active")), + cfg.IntOpt('vif_plugging_timeout', + default=0, + help=_("Seconds to wait for port to become active")), ] keystone_opts = [ cfg.StrOpt('auth_uri', diff --git a/kuryr/common/constants.py b/kuryr/common/constants.py index ec2c70fe..640e1a84 100644 --- a/kuryr/common/constants.py +++ b/kuryr/common/constants.py @@ -25,6 +25,9 @@ ROUTE_TYPE = { "CONNECTED": 1 } +PORT_STATUS_ACTIVE = 'ACTIVE' +PORT_STATUS_DOWN = 'DOWN' + DEVICE_OWNER = 'kuryr:container' NIC_NAME_LEN = 14 VETH_PREFIX = 'tap' diff --git a/kuryr/common/exceptions.py b/kuryr/common/exceptions.py index 9fd8b838..05ef61c0 100644 --- a/kuryr/common/exceptions.py +++ b/kuryr/common/exceptions.py @@ -63,6 +63,15 @@ class NoResourceException(KuryrException): """ +class InactiveResourceException(KuryrException): + """Exception represents the resource for the given query is not active. + + This exception is thrown when you query the Neutron resource associated + with the given query and you get the status of the resource as something + other than ACTIVE. + """ + + class VethCreationFailure(KuryrException): """Exception represents the veth pair creation is failed. diff --git a/kuryr/controllers.py b/kuryr/controllers.py index 9a52fe2a..f64de894 100755 --- a/kuryr/controllers.py +++ b/kuryr/controllers.py @@ -15,11 +15,13 @@ import os_client_config import flask import jsonschema import netaddr +import time from neutronclient.common import exceptions as n_exceptions from neutronclient.neutron import client from oslo_concurrency import processutils from oslo_config import cfg +from oslo_log import log from oslo_utils import excutils from kuryr import app @@ -31,6 +33,8 @@ from kuryr._i18n import _LE, _LI, _LW from kuryr import schemata from kuryr import utils +LOG = log.getLogger(__name__) + MANDATORY_NEUTRON_EXTENSION = "subnet_allocation" TAG_NEUTRON_EXTENSION = "tag" @@ -110,6 +114,8 @@ def neutron_client(): if not hasattr(app, 'neutron'): app.neutron = get_neutron_client() app.enable_dhcp = cfg.CONF.neutron_client.enable_dhcp + app.vif_plug_is_fatal = cfg.CONF.neutron_client.vif_plugging_is_fatal + app.vif_plug_timeout = cfg.CONF.neutron_client.vif_plugging_timeout app.neutron.format = 'json' @@ -367,6 +373,27 @@ def _get_networks_by_identifier(identifier): return _get_networks_by_attrs(name=identifier) +def _port_active(neutron_port_id, vif_plug_timeout): + port_active = False + tries = 0 + while True: + try: + port = app.neutron.show_port(neutron_port_id) + except n_exceptions.NeutronClientException as ex: + app.logger.error(_LE('Could not get the port %s to check ' + 'its status'), ex) + else: + if port['port']['status'] == const.PORT_STATUS_ACTIVE: + port_active = True + if port_active or (tries >= vif_plug_timeout): + break + LOG.debug('Waiting for port %s to become ACTIVE', neutron_port_id) + tries += 1 + time.sleep(1) + + return port_active + + @app.route('/Plugin.Activate', methods=['POST']) def plugin_activate(): """Returns the list of the implemented drivers. @@ -825,6 +852,14 @@ def network_driver_join(): app.logger.error(_LE( 'Could not bind the Neutron port to the veth endpoint.')) + if app.vif_plug_is_fatal: + port_active = _port_active(neutron_port['id'], + app.vif_plug_timeout) + if not port_active: + raise exceptions.InactiveResourceException( + "Neutron port {0} did not become active on time." + .format(neutron_port_name)) + join_response = { "InterfaceName": { "SrcName": peer_name, diff --git a/kuryr/tests/unit/base.py b/kuryr/tests/unit/base.py index 49c8e47c..138b165a 100644 --- a/kuryr/tests/unit/base.py +++ b/kuryr/tests/unit/base.py @@ -169,6 +169,7 @@ class TestKuryrBase(TestCase): @staticmethod def _get_fake_port(docker_endpoint_id, neutron_network_id, neutron_port_id, + neutron_port_status=const.PORT_STATUS_DOWN, neutron_subnet_v4_id=None, neutron_subnet_v6_id=None, neutron_subnet_v4_address="192.168.1.2", @@ -177,7 +178,7 @@ class TestKuryrBase(TestCase): # http://developer.openstack.org/api-ref-networking-v2.html#createPort # noqa fake_port = { 'port': { - "status": "DOWN", + "status": neutron_port_status, "name": utils.get_neutron_port_name(docker_endpoint_id), "allowed_address_pairs": [], "admin_state_up": True, @@ -206,11 +207,11 @@ class TestKuryrBase(TestCase): @classmethod def _get_fake_ports(cls, docker_endpoint_id, neutron_network_id, - fake_neutron_port_id, + fake_neutron_port_id, neutron_port_status, fake_neutron_subnet_v4_id, fake_neutron_subnet_v6_id): fake_port = cls._get_fake_port( docker_endpoint_id, neutron_network_id, - fake_neutron_port_id, + fake_neutron_port_id, neutron_port_status, fake_neutron_subnet_v4_id, fake_neutron_subnet_v6_id) fake_port = fake_port['port'] fake_ports = { diff --git a/kuryr/tests/unit/test_join.py b/kuryr/tests/unit/test_join.py index 011f70db..5c49492f 100644 --- a/kuryr/tests/unit/test_join.py +++ b/kuryr/tests/unit/test_join.py @@ -79,7 +79,7 @@ class TestKuryrJoinFailures(base.TestKuryrFailures): fake_neutron_v6_subnet_id = str(uuid.uuid4()) fake_neutron_ports_response = self._get_fake_ports( fake_docker_endpoint_id, fake_neutron_network_id, - fake_neutron_port_id, + fake_neutron_port_id, const.PORT_STATUS_ACTIVE, fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id) app.neutron.list_ports(name=neutron_port_name).AndReturn( fake_neutron_ports_response) diff --git a/kuryr/tests/unit/test_kuryr.py b/kuryr/tests/unit/test_kuryr.py index c083f97e..213dff16 100644 --- a/kuryr/tests/unit/test_kuryr.py +++ b/kuryr/tests/unit/test_kuryr.py @@ -420,7 +420,7 @@ class TestKuryr(base.TestKuryrBase): fake_port_id = str(uuid.uuid4()) fake_port = self._get_fake_port( docker_endpoint_id, fake_neutron_net_id, - fake_port_id, + fake_port_id, constants.PORT_STATUS_ACTIVE, subnet_v4_id, subnet_v6_id) fake_fixed_ips = ['subnet_id=%s' % subnet_v4_id, 'ip_address=192.168.1.2', @@ -477,7 +477,13 @@ class TestKuryr(base.TestKuryrBase): decoded_json = jsonutils.loads(response.data) self.assertEqual(constants.SCHEMA['SUCCESS'], decoded_json) - def test_network_driver_join(self): + @ddt.data( + (False), (True)) + def test_network_driver_join(self, vif_plug_is_fatal): + if vif_plug_is_fatal: + self.mox.StubOutWithMock(app, "vif_plug_is_fatal") + app.vif_plug_is_fatal = True + fake_docker_net_id = utils.get_hash() fake_docker_endpoint_id = utils.get_hash() fake_container_id = utils.get_hash() @@ -492,7 +498,7 @@ class TestKuryr(base.TestKuryrBase): fake_neutron_v6_subnet_id = str(uuid.uuid4()) fake_neutron_ports_response = self._get_fake_ports( fake_docker_endpoint_id, fake_neutron_net_id, - fake_neutron_port_id, + fake_neutron_port_id, constants.PORT_STATUS_DOWN, fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id) app.neutron.list_ports(name=neutron_port_name).AndReturn( fake_neutron_ports_response) @@ -507,6 +513,16 @@ class TestKuryr(base.TestKuryrBase): fake_neutron_subnets = fake_neutron_subnets_response['subnets'] _, fake_peer_name, _ = self._mock_out_binding( fake_docker_endpoint_id, fake_neutron_port, fake_neutron_subnets) + + if vif_plug_is_fatal: + self.mox.StubOutWithMock(app.neutron, 'show_port') + fake_neutron_ports_response_2 = self._get_fake_port( + fake_docker_endpoint_id, fake_neutron_net_id, + fake_neutron_port_id, constants.PORT_STATUS_ACTIVE, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id) + app.neutron.show_port(fake_neutron_port_id).AndReturn( + fake_neutron_ports_response_2) + self.mox.ReplayAll() fake_subnets_dict_by_id = {subnet['id']: subnet @@ -554,7 +570,7 @@ class TestKuryr(base.TestKuryrBase): fake_neutron_v6_subnet_id = str(uuid.uuid4()) fake_neutron_ports_response = self._get_fake_ports( fake_docker_endpoint_id, fake_neutron_net_id, - fake_neutron_port_id, + fake_neutron_port_id, constants.PORT_STATUS_ACTIVE, fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id) app.neutron.list_ports(name=neutron_port_name).AndReturn( fake_neutron_ports_response) @@ -570,6 +586,7 @@ class TestKuryr(base.TestKuryrBase): content_type='application/json', data=jsonutils.dumps(leave_request)) + self.mox.ReplayAll() self.assertEqual(200, response.status_code) decoded_json = jsonutils.loads(response.data) self.assertEqual(constants.SCHEMA['SUCCESS'], decoded_json) diff --git a/kuryr/tests/unit/test_kuryr_ipam.py b/kuryr/tests/unit/test_kuryr_ipam.py index 734e512e..544f289c 100644 --- a/kuryr/tests/unit/test_kuryr_ipam.py +++ b/kuryr/tests/unit/test_kuryr_ipam.py @@ -196,7 +196,7 @@ class TestKuryrIpam(base.TestKuryrBase): fake_neutron_port_id = str(uuid.uuid4()) fake_port = base.TestKuryrBase._get_fake_port( docker_endpoint_id, neutron_network_id, - fake_neutron_port_id, + fake_neutron_port_id, const.PORT_STATUS_ACTIVE, subnet_v4_id, neutron_subnet_v4_address="10.0.0.5") port_request = { @@ -361,7 +361,7 @@ class TestKuryrIpam(base.TestKuryrBase): fake_neutron_port_id = str(uuid.uuid4()) fake_port = base.TestKuryrBase._get_fake_port( docker_endpoint_id, neutron_network_id, - fake_neutron_port_id, + fake_neutron_port_id, const.PORT_STATUS_ACTIVE, subnet_v4_id, neutron_subnet_v4_address=fake_ip4) port_request = { diff --git a/kuryr/tests/unit/test_leave.py b/kuryr/tests/unit/test_leave.py index 5b5f7251..a2e1be5d 100644 --- a/kuryr/tests/unit/test_leave.py +++ b/kuryr/tests/unit/test_leave.py @@ -19,6 +19,7 @@ from werkzeug import exceptions as w_exceptions from kuryr import app from kuryr import binding +from kuryr.common import constants as const from kuryr.common import exceptions from kuryr.tests.unit import base from kuryr import utils @@ -68,7 +69,7 @@ class TestKuryrLeaveFailures(base.TestKuryrFailures): fake_neutron_v6_subnet_id = str(uuid.uuid4()) fake_neutron_ports_response = self._get_fake_ports( fake_docker_endpoint_id, fake_neutron_network_id, - fake_neutron_port_id, + fake_neutron_port_id, const.PORT_STATUS_ACTIVE, fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id) app.neutron.list_ports(name=neutron_port_name).AndReturn( fake_neutron_ports_response)