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
This commit is contained in:
Mohammad Banikazemi 2016-06-23 14:20:34 -04:00
parent 16bfca4ce0
commit 4dc7caf89c
9 changed files with 84 additions and 11 deletions

View File

@ -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',

View File

@ -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'

View File

@ -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.

View File

@ -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,

View File

@ -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 = {

View File

@ -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)

View File

@ -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)

View File

@ -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 = {

View File

@ -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)