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'
MAC_ADDRESS_KEY = 'mac_address'
SUBNET_ID_KEY = 'subnet_id'
UNBINDING_SUBCOMMAND = 'unbind'
VETH_POSTFIX = '-veth'
VIF_TYPE_KEY = 'binding:vif_type'
@ -122,3 +123,30 @@ def port_bind(endpoint_id, neutron_port, neutron_subnets):
cleanup_veth(ifname)
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
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"
.format(json_data))
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'])

View File

@ -47,6 +47,14 @@ class TestKuryrBase(TestCase):
self.mox.ReplayAll()
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):
fake_list_response = {
"networks": [{

View File

@ -539,3 +539,39 @@ class TestKuryr(base.TestKuryrBase):
'StaticRoutes': []
}
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 random
import uuid
import ddt
from oslo_concurrency import processutils
from oslo_serialization import jsonutils
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 import utils
@ddt.ddt
class TestKuryrLeaveFailures(base.TestKuryrFailures):
"""Unit tests for the failures for unbinding a Neutron port.
"""
@ -34,6 +42,57 @@ class TestKuryrLeaveFailures(base.TestKuryrFailures):
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):
fake_docker_network_id = hashlib.sha256(
str(random.getrandbits(256))).hexdigest()

View File

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