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