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:
parent
fa7fed02b7
commit
6f680fb8bc
@ -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)
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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'])
|
||||
|
@ -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": [{
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user