Implement /NetworkDriver.Leave

This patch implements /NetworkDriver.Leave, which unbinds the Neutron
port from the veth pair on the host and delete the veth pair.

Change-Id: If02f52594924811180acfaac0ec29d10c25c6869
Signed-off-by: Taku Fukushima <f.tac.mac@gmail.com>
This commit is contained in:
Taku Fukushima 2015-09-29 17:09:14 +09:00
parent fa7fed02b7
commit 6f680fb8bc
7 changed files with 180 additions and 0 deletions

View File

@ -31,6 +31,7 @@ IP_ADDRESS_KEY = 'ip_address'
KIND_VETH = 'veth' KIND_VETH = 'veth'
MAC_ADDRESS_KEY = 'mac_address' MAC_ADDRESS_KEY = 'mac_address'
SUBNET_ID_KEY = 'subnet_id' SUBNET_ID_KEY = 'subnet_id'
UNBINDING_SUBCOMMAND = 'unbind'
VETH_POSTFIX = '-veth' VETH_POSTFIX = '-veth'
VIF_TYPE_KEY = 'binding:vif_type' VIF_TYPE_KEY = 'binding:vif_type'
@ -122,3 +123,30 @@ def port_bind(endpoint_id, neutron_port, neutron_subnets):
cleanup_veth(ifname) cleanup_veth(ifname)
return (ifname, peer_name, (stdout, stderr)) return (ifname, peer_name, (stdout, stderr))
def port_unbind(endpoint_id, neutron_port):
"""Unbinds the Neutorn port from the network interface on the host.
:param endpoint_id: the ID of the Docker container as string
:param neutron_port: a port dictionary returned from python-neutronclient
:returns: the tuple of stdout and stderr returned by processutils.execute
invoked with the executable script for unbinding
:raises: processutils.ProcessExecutionError, pyroute2.netlink.NetlinkError
"""
# NOTE(tfukushima): pyroute2.netlink requires Linux to be imported. So I
# don't import it in the module scope but here.
import pyroute2.netlink
vif_type = neutron_port.get(VIF_TYPE_KEY, FALLBACK_VIF_TYPE)
unbinding_exec_path = os.path.join(config.CONF.bindir, vif_type)
port_id = neutron_port['id']
stdout, stderr = processutils.execute(
unbinding_exec_path, UNBINDING_SUBCOMMAND, port_id, run_as_root=True)
ifname = endpoint_id[:8] + VETH_POSTFIX
try:
cleanup_veth(ifname)
except pyroute2.netlink.NetlinkError:
raise exceptions.VethDeleteionFailure(
'Deleting the veth pair was failed.')
return (stdout, stderr)

View File

@ -45,3 +45,11 @@ class VethCreationFailure(KuryrException):
This exception is thrown when the veth pair is not created appropriately This exception is thrown when the veth pair is not created appropriately
and Kuryr can't proceed the binding further. and Kuryr can't proceed the binding further.
""" """
class VethDeletionFailure(KuryrException):
"""Exception represents the veth pair deletion is failed.
This exception is thrown when the veth pair is not deleted appropriately
and Kuryr can't proceed the unbinding further.
"""

View File

@ -637,5 +637,36 @@ def network_driver_leave():
app.logger.debug("Received JSON data {0} for /NetworkDriver.DeleteEndpoint" app.logger.debug("Received JSON data {0} for /NetworkDriver.DeleteEndpoint"
.format(json_data)) .format(json_data))
jsonschema.validate(json_data, schemata.LEAVE_SCHEMA) jsonschema.validate(json_data, schemata.LEAVE_SCHEMA)
neutron_network_name = json_data['NetworkID']
endpoint_id = json_data['EndpointID']
filtered_networks = _get_networks_by_attrs(name=neutron_network_name)
if not filtered_networks:
return flask.jsonify({
'Err': "Neutron network associated with ID {0} doesn't exit."
.format(neutron_network_name)
})
else:
neutron_port_name = '-'.join([endpoint_id, 'port'])
filtered_ports = _get_ports_by_attrs(name=neutron_port_name)
if not filtered_ports:
raise exceptions.NoResourceException(
"The port doesn't exist for the name {0}"
.format(neutron_port_name))
neutron_port = filtered_ports[0]
try:
stdout, stderr = binding.port_unbind(endpoint_id, neutron_port)
app.logger.debug(stdout)
if stderr:
app.logger.error(stderr)
except processutils.ProcessExecutionError:
with excutils.save_and_reraise_exception():
app.logger.error(
'Could not unbind the Neutron port from the veth '
'endpoint.')
except exceptions.VethDeletionFailure:
with excutils.save_and_reraise_exception():
app.logger.error('Cleaning the veth pair up was failed.')
return flask.jsonify(constants.SCHEMA['SUCCESS']) return flask.jsonify(constants.SCHEMA['SUCCESS'])

View File

@ -47,6 +47,14 @@ class TestKuryrBase(TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
return fake_binding_response return fake_binding_response
def _mock_out_unbinding(self, endpoint_id, neutron_port):
self.mox.StubOutWithMock(binding, 'port_unbind')
fake_unbinding_response = ('fake stdout', '')
binding.port_unbind(endpoint_id, neutron_port).AndReturn(
fake_unbinding_response)
self.mox.ReplayAll()
return fake_unbinding_response
def _mock_out_network(self, neutron_network_id, docker_network_id): def _mock_out_network(self, neutron_network_id, docker_network_id):
fake_list_response = { fake_list_response = {
"networks": [{ "networks": [{

View File

@ -539,3 +539,39 @@ class TestKuryr(base.TestKuryrBase):
'StaticRoutes': [] 'StaticRoutes': []
} }
self.assertEqual(expected_response, decoded_json) self.assertEqual(expected_response, decoded_json)
def test_network_driver_leave(self):
fake_docker_network_id = hashlib.sha256(
str(random.getrandbits(256))).hexdigest()
fake_docker_endpoint_id = hashlib.sha256(
str(random.getrandbits(256))).hexdigest()
fake_neutron_network_id = str(uuid.uuid4())
self._mock_out_network(fake_neutron_network_id, fake_docker_network_id)
fake_neutron_port_id = str(uuid.uuid4())
self.mox.StubOutWithMock(app.neutron, 'list_ports')
neutron_port_name = utils.get_neutron_port_name(
fake_docker_endpoint_id)
fake_neutron_v4_subnet_id = str(uuid.uuid4())
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_v4_subnet_id, fake_neutron_v6_subnet_id)
app.neutron.list_ports(name=neutron_port_name).AndReturn(
fake_neutron_ports_response)
fake_neutron_port = fake_neutron_ports_response['ports'][0]
self._mock_out_unbinding(fake_docker_endpoint_id, fake_neutron_port)
leave_request = {
'NetworkID': fake_docker_network_id,
'EndpointID': fake_docker_endpoint_id,
}
response = self.app.post('/NetworkDriver.Leave',
content_type='application/json',
data=jsonutils.dumps(leave_request))
self.assertEqual(200, response.status_code)
decoded_json = jsonutils.loads(response.data)
self.assertEqual(constants.SCHEMA['SUCCESS'], decoded_json)

View File

@ -12,13 +12,21 @@
import hashlib import hashlib
import random import random
import uuid
import ddt
from oslo_concurrency import processutils
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from werkzeug import exceptions as w_exceptions from werkzeug import exceptions as w_exceptions
from kuryr import app
from kuryr import binding
from kuryr.common import exceptions
from kuryr.tests import base from kuryr.tests import base
from kuryr import utils
@ddt.ddt
class TestKuryrLeaveFailures(base.TestKuryrFailures): class TestKuryrLeaveFailures(base.TestKuryrFailures):
"""Unit tests for the failures for unbinding a Neutron port. """Unit tests for the failures for unbinding a Neutron port.
""" """
@ -34,6 +42,57 @@ class TestKuryrLeaveFailures(base.TestKuryrFailures):
return response return response
def _port_unbind_with_exception(self, docker_endpoint_id,
neutron_port, ex):
fake_unbinding_response = ('fake stdout', '')
self.mox.StubOutWithMock(binding, 'port_unbind')
if ex:
binding.port_unbind(docker_endpoint_id, neutron_port).AndRaise(ex)
else:
binding.port_unbind(docker_endpoint_id, neutron_port).AndReturn(
fake_unbinding_response)
self.mox.ReplayAll()
return fake_unbinding_response
@ddt.data(exceptions.VethDeletionFailure,
processutils.ProcessExecutionError)
def test_leave_unbinding_failure(self, GivenException):
fake_docker_network_id = hashlib.sha256(
str(random.getrandbits(256))).hexdigest()
fake_docker_endpoint_id = hashlib.sha256(
str(random.getrandbits(256))).hexdigest()
fake_neutron_network_id = str(uuid.uuid4())
self._mock_out_network(fake_neutron_network_id, fake_docker_network_id)
fake_neutron_port_id = str(uuid.uuid4())
self.mox.StubOutWithMock(app.neutron, 'list_ports')
neutron_port_name = utils.get_neutron_port_name(
fake_docker_endpoint_id)
fake_neutron_v4_subnet_id = str(uuid.uuid4())
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_v4_subnet_id, fake_neutron_v6_subnet_id)
app.neutron.list_ports(name=neutron_port_name).AndReturn(
fake_neutron_ports_response)
fake_neutron_port = fake_neutron_ports_response['ports'][0]
fake_message = "fake message"
fake_exception = GivenException(fake_message)
self._port_unbind_with_exception(
fake_docker_endpoint_id, fake_neutron_port, fake_exception)
response = self._invoke_leave_request(
fake_docker_network_id, fake_docker_endpoint_id)
self.assertEqual(
w_exceptions.InternalServerError.code, response.status_code)
decoded_json = jsonutils.loads(response.data)
self.assertTrue('Err' in decoded_json)
self.assertTrue(fake_message in decoded_json['Err'])
def test_leave_bad_request(self): def test_leave_bad_request(self):
fake_docker_network_id = hashlib.sha256( fake_docker_network_id = hashlib.sha256(
str(random.getrandbits(256))).hexdigest() str(random.getrandbits(256))).hexdigest()

View File

@ -16,12 +16,22 @@ bind_port() {
mm-ctl --bind-port $1 $2 mm-ctl --bind-port $1 $2
} }
unbind_port() {
echo "Unbinding Neutron port $1..."
mm-ctl --unbind-port $1
}
case $1 in case $1 in
"bind") "bind")
shift shift
bind_port "$@" bind_port "$@"
exit 0 exit 0
;; ;;
"unbind")
shift
unbind_port "$@"
exit 0
;;
*) *)
echo >&2 "$0: Invalid command $1." echo >&2 "$0: Invalid command $1."
exit 1 exit 1