Make ML2 ensure_dvr_port_binding more robust

There is a remote chance that this operation may
be prone to DB integrity errors, in case the binding
is attempted on the same port twice.

Ideally getter methods should not create, but this
is a common Neutron (anti)-pattern that would be
difficult to eradicate (at least in a single patch);
so for now let's make this code more defensive.

Related-bug: #1269131
Related-bug: #1335226

Change-Id: Ie6c57fd46f0752839814dbac5b14fae2364f973d
This commit is contained in:
armando-migliaccio 2014-07-03 17:00:44 -07:00
parent 4ab8740cd8
commit 0b30651678
2 changed files with 34 additions and 11 deletions

View File

@ -15,6 +15,8 @@
from sqlalchemy.orm import exc
from oslo.db import exception as db_exc
from neutron.common import constants as n_const
from neutron.db import api as db_api
from neutron.db import models_v2
@ -90,16 +92,13 @@ def get_locked_port_and_binding(session, port_id):
def ensure_dvr_port_binding(session, port_id, host, router_id=None):
# FIXME(armando-migliaccio): take care of LP #1335226
# DVR ports are slightly different from the others in
# that binding happens at a later stage via L3 agent
# therefore we need to keep this logic of creation on
# missing binding.
with session.begin(subtransactions=True):
try:
record = (session.query(models.DVRPortBinding).
filter_by(port_id=port_id, host=host).one())
except exc.NoResultFound:
record = (session.query(models.DVRPortBinding).
filter_by(port_id=port_id, host=host).first())
if record:
return record
try:
with session.begin(subtransactions=True):
record = models.DVRPortBinding(
port_id=port_id,
host=host,
@ -109,7 +108,11 @@ def ensure_dvr_port_binding(session, port_id, host, router_id=None):
cap_port_filter=False,
status=n_const.PORT_STATUS_DOWN)
session.add(record)
return record
return record
except db_exc.DBDuplicateEntry:
LOG.debug("DVR Port %s already bound", port_id)
return (session.query(models.DVRPortBinding).
filter_by(port_id=port_id, host=host).one())
def delete_dvr_port_binding(session, port_id, host):

View File

@ -13,6 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from sqlalchemy.orm import query
from neutron import context
from neutron.db import api as db_api
from neutron.db import l3_db
@ -66,6 +70,22 @@ class Ml2DBTestCase(base.BaseTestCase):
self.ctx.session.add(record)
return record
def test_ensure_dvr_port_binding_deals_with_db_duplicate(self):
network_id = 'foo_network_id'
port_id = 'foo_port_id'
router_id = 'foo_router_id'
host_id = 'foo_host_id'
self._setup_neutron_network(network_id, [port_id])
self._setup_dvr_binding(network_id, port_id, router_id, host_id)
with mock.patch.object(query.Query, 'first') as query_first:
query_first.return_value = []
with mock.patch.object(ml2_db.LOG, 'debug') as log_trace:
binding = ml2_db.ensure_dvr_port_binding(
self.ctx.session, port_id, host_id, router_id)
self.assertTrue(query_first.called)
self.assertTrue(log_trace.called)
self.assertEqual(port_id, binding.port_id)
def test_ensure_dvr_port_binding(self):
network_id = 'foo_network_id'
port_id = 'foo_port_id'