plugin/ryu: make live-migration work with Ryu plugin

Fixes bug 1085861

Live-migration with Ryu plugin doesn't work because the unique constraint of
PortBinding table is violated as follows.
Now Ryu can handle those informations itself, so remove the table itself and
simplify Ryu plugin.

> ERROR [quantum.api.v2.resource] update failed
> Traceback (most recent call last):
>   File "/quantum/api/v2/resource.py", line 95, in resource
>     result = method(request=request, **args)
>   File "/quantum/api/v2/base.py", line 397, in update
>     obj = obj_updater(request.context, id, **kwargs)
>   File "/quantum/plugins/ryu/ryu_quantum_plugin.py", line 226, in update_port
>     port_binding.dpid, port_binding.port_no))
> InvalidInput: Invalid input for operation: invalid (datapath_id, port_no) requested (00002eab88ec5140, 4), but (0000c2f19014c74a, 1).

Change-Id: I7d993fd01125a27f6bf8e1b3fcac79ddcb8cb361
This commit is contained in:
Isaku Yamahata 2012-11-20 14:41:04 +09:00
parent 779093f3c2
commit 06b2b2b22a
4 changed files with 14 additions and 142 deletions

View File

@ -36,10 +36,13 @@ from quantum.common import config as logging_config
from quantum.openstack.common import cfg
from quantum.openstack.common.cfg import NoSuchGroupError
from quantum.openstack.common.cfg import NoSuchOptError
from quantum.openstack.common import log as LOG
from quantum.openstack.common import log
from quantum.plugins.ryu.common import config
LOG = log.getLogger(__name__)
# This is copied of nova.flags._get_my_ip()
# Agent shouldn't depend on nova module
def _get_my_ip():

View File

@ -187,46 +187,6 @@ class TunnelKey(object):
return session.query(ryu_models_v2.TunnelKey).all()
def port_binding_create(port_id, net_id, dpid, port_no):
session = db.get_session()
session.query(models_v2.Port).filter(
models_v2.Port.network_id == net_id).filter(
models_v2.Port.id == port_id).one() # confirm port exists
with session.begin():
port_binding = ryu_models_v2.PortBinding(net_id, port_id,
dpid, port_no)
session.add(port_binding)
session.flush()
return port_binding
def port_binding_get(port_id, net_id):
session = db.get_session()
session.query(models_v2.Port).filter(
models_v2.Port.network_id == net_id).filter(
models_v2.Port.id == port_id).one() # confirm port exists
return session.query(ryu_models_v2.PortBinding).filter_by(
network_id=net_id).filter_by(port_id=port_id).one()
def port_binding_destroy(session, port_id, net_id):
try:
session.query(models_v2.Port).filter(
models_v2.Port.network_id == net_id).filter(
models_v2.Port.id == port_id).one() # confirm port exists
port_binding = session.query(ryu_models_v2.PortBinding).filter_by(
network_id=net_id).filter_by(port_id=port_id).one()
session.delete(port_binding)
session.flush()
return port_binding
except orm_exc.NoResultFound:
raise q_exc.PortNotFound(port_id=port_id, net_id=net_id)
def port_binding_all_list(session):
return session.query(ryu_models_v2.PortBinding).all()
def set_port_status(session, port_id, status):
try:
port = session.query(models_v2.Port).filter_by(id=port_id).one()

View File

@ -52,28 +52,3 @@ class TunnelKey(model_base.BASEV2):
def __repr__(self):
return "<TunnelKey(%s,%x)>" % (self.network_id, self.tunnel_key)
class PortBinding(model_base.BASEV2):
"""Represents Port binding to ovs ports."""
__tablename__ = 'port_binding'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
network_id = sa.Column(sa.String(255), sa.ForeignKey("networks.id"),
nullable=False)
port_id = sa.Column(sa.String(255), sa.ForeignKey("ports.id"), unique=True,
nullable=False)
dpid = sa.Column(sa.String(255), nullable=False)
port_no = sa.Column(sa.Integer, nullable=False)
def __init__(self, network_id, port_id, dpid, port_no):
self.network_id = network_id
self.port_id = port_id
self.dpid = dpid
self.port_no = port_no
def __repr__(self):
return "<PortBinding(%s,%s,%s,%s,%s)>" % (self.network_id,
self.port_id,
self.dpid,
self.port_no)

View File

@ -17,10 +17,7 @@
# @author: Isaku Yamahata
from ryu.app import client
from ryu.app.client import ignore_http_not_found
from ryu.app import rest_nw_id
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import exc as orm_exc
from quantum.common import constants as q_const
from quantum.common import exceptions as q_exc
@ -68,6 +65,7 @@ class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
self.client = client.OFPClient(ofp_api_host)
self.tun_client = client.TunnelClient(ofp_api_host)
self.iface_client = client.QuantumIfaceClient(ofp_api_host)
for nw_id in rest_nw_id.RESERVED_NETWORK_IDS:
if nw_id != rest_nw_id.NW_ID_UNKNOWN:
self.client.update_network(nw_id)
@ -89,20 +87,8 @@ class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
for tun in self.tunnel_key.all_list():
self.tun_client.update_tunnel_key(tun.network_id, tun.tunnel_key)
session = db.get_session()
for port_binding in db_api_v2.port_binding_all_list(session):
network_id = port_binding.network_id
dpid = port_binding.dpid
port_no = port_binding.port_no
try:
port = session.query(models_v2.Port).filter(
models_v2.Port.id == port_binding.port_id).one()
except orm_exc.NoResultFound:
continue
except orm_exc.MultipleResultsFound:
continue
self.client.update_port(network_id, dpid, port_no)
self.client.update_mac(network_id, dpid, port_no, port.mac_address)
for port in session.query(models_v2.Port).all():
self.iface_client.update_network_id(port.id, port.network_id)
def _client_create_network(self, net_id, tunnel_key):
self.client.create_network(net_id)
@ -161,21 +147,12 @@ class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
return [self._fields(net, fields) for net in nets]
def delete_port(self, context, id, l3_port_check=True):
with context.session.begin(subtransactions=True):
port = self._get_port(context, id)
net_id = port.network_id
try:
port_binding = db_api_v2.port_binding_destroy(context.session,
port.id, net_id)
datapath_id = port_binding.dpid
port_no = port_binding.port_no
ignore_http_not_found(
lambda: self.client.delete_port(net_id, datapath_id,
port_no))
except q_exc.PortNotFound:
pass
def create_port(self, context, port):
port = super(RyuQuantumPluginV2, self).create_port(context, port)
self.iface_client.create_network_id(port['id'], port['network_id'])
return port
def delete_port(self, context, id, l3_port_check=True):
# if needed, check to see if this is a port owned by
# and l3-router. If so, we should prevent deletion.
if l3_port_check:
@ -184,52 +161,9 @@ class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
return super(RyuQuantumPluginV2, self).delete_port(context, id)
def update_port(self, context, id, port):
p = super(RyuQuantumPluginV2, self).update_port(context, id, port)
net_id = p['network_id']
mac_address = p['mac_address']
deleted = port['port'].get('deleted', False)
port = super(RyuQuantumPluginV2, self).update_port(context, id, port)
if deleted:
session = context.session
try:
db_api_v2.port_binding_destroy(session, id, net_id)
except q_exc.PortNotFound:
pass
db_api_v2.set_port_status(session, id, q_const.PORT_STATUS_DOWN)
return p
datapath_id = port['port'].get('datapath_id', None)
port_no = port['port'].get('port_no', None)
if datapath_id is None or port_no is None:
LOG.debug('p %s', p)
return p
try:
port_binding = db_api_v2.port_binding_get(id, net_id)
except orm_exc.NoResultFound:
try:
db_api_v2.port_binding_create(id, net_id, datapath_id, port_no)
except IntegrityError:
# TODO:XXX should do transaction?
return p
else:
self.client.create_port(net_id, datapath_id, port_no)
self.client.create_mac(net_id, datapath_id, port_no,
mac_address)
else:
if (port_binding.dpid != datapath_id or
port_binding.port_no != port_no):
variables = {'datapath_id': datapath_id,
'port_no': port_no,
'port_binding_dpid': port_binding.dpid,
'port_binding_port_no': port_binding.port_no}
raise q_exc.InvalidInput(
error_message=_('invalid (datapath_id, port_no) '
'is requested'
'(%(datapath_id)s, %(port_no)s), acutal'
'(%(port_binding_dpid)s, '
'%(port_binding_port_no)s)') % variables)
self.client.update_network(net_id)
self.client.update_port(net_id, datapath_id, port_no)
self.client.update_mac(net_id, datapath_id, port_no, mac_address)
return p
return port