From 6617f8fccc8d99520a87cd84a598c4f9a1a43761 Mon Sep 17 00:00:00 2001 From: Eugene Nikanorov Date: Mon, 17 Nov 2014 11:00:49 +0400 Subject: [PATCH] Change transaction isolation so retry logic could work properly Lower isolation level from REPEATABLE READ to READ COMMITTED for transaction that is used to create a network. This allows retry logic to see changes done in other connections while doing the same query. Perform that only for mysql db backend. Change-Id: I6b9d9212c37fe028566e0df4a3dfa51f284ce6e9 Closes-Bug: #1382064 --- neutron/db/sqlalchemyutils.py | 26 +++++++++ neutron/plugins/ml2/plugin.py | 6 +- .../tests/functional/db/test_sqla_utils.py | 57 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 neutron/tests/functional/db/test_sqla_utils.py diff --git a/neutron/db/sqlalchemyutils.py b/neutron/db/sqlalchemyutils.py index b21edc83e..19cc61887 100644 --- a/neutron/db/sqlalchemyutils.py +++ b/neutron/db/sqlalchemyutils.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import contextlib + from six import moves import sqlalchemy from sqlalchemy.orm import properties @@ -105,3 +107,27 @@ def paginate_query(query, model, limit, sorts, marker_obj=None): query = query.limit(limit) return query + + +default_tx_isolation_level = None + + +def get_default_tx_level(engine): + global default_tx_isolation_level + if not default_tx_isolation_level: + default_tx_isolation_level = engine.dialect.get_isolation_level( + engine.raw_connection()) + return default_tx_isolation_level + + +@contextlib.contextmanager +def set_mysql_tx_isolation_level(session, level): + engine = session.connection().engine + if (engine.name == "mysql" and level != get_default_tx_level(engine)): + session.connection().execution_options( + isolation_level=level) + yield + engine = session.connection().engine + if engine.name == "mysql": + session.connection().execution_options( + isolation_level=get_default_tx_level(engine)) diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 3a68f2047..96868018e 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -48,6 +48,7 @@ from neutron.db import extradhcpopt_db from neutron.db import models_v2 from neutron.db import quota_db # noqa from neutron.db import securitygroups_rpc_base as sg_db_rpc +from neutron.db import sqlalchemyutils as sqla from neutron.extensions import allowedaddresspairs as addr_pair from neutron.extensions import extra_dhcp_opt as edo_ext from neutron.extensions import l3agentscheduler @@ -538,7 +539,10 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, net_data = network[attributes.NETWORK] tenant_id = self._get_tenant_id_for_create(context, net_data) session = context.session - with session.begin(subtransactions=True): + with contextlib.nested( + session.begin(subtransactions=True), + sqla.set_mysql_tx_isolation_level(session, "READ COMMITTED") + ): self._ensure_default_security_group(context, tenant_id) result = super(Ml2Plugin, self).create_network(context, network) self.extension_manager.process_create_network(session, net_data, diff --git a/neutron/tests/functional/db/test_sqla_utils.py b/neutron/tests/functional/db/test_sqla_utils.py new file mode 100644 index 000000000..96c0c9483 --- /dev/null +++ b/neutron/tests/functional/db/test_sqla_utils.py @@ -0,0 +1,57 @@ +# Copyright (c) 2014 OpenStack Foundation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import contextlib + +from oslo.config import cfg +from oslo.db.sqlalchemy import test_base + +from neutron import context +from neutron.db import sqlalchemyutils +from neutron.tests import base + + +class TestSettingTXIsolationLevel(base.BaseTestCase, + test_base.MySQLOpportunisticTestCase): + """Check that transaction isolation level indeed changes.""" + + def setUp(self): + super(TestSettingTXIsolationLevel, self).setUp() + cfg.CONF.set_override('connection', + self.engine.url, + group='database') + + def _get_session_tx_isolation(self, session): + sql = "SELECT @@tx_isolation;" + res = session.connection().execute(sql) + res = [r for r in res] + res = [r for r in res[0]] + return res[0] + + def test_set_tx_iso_level_changes_back_and_forth_mysql(self): + ctx = context.get_admin_context() + default_level = sqlalchemyutils.get_default_tx_level(self.engine) + other_level = ("READ COMMITTED" if default_level == "REPEATABLE READ" + else "REPEATABLE READ") + with contextlib.nested( + ctx.session.begin(subtransactions=True), + sqlalchemyutils.set_mysql_tx_isolation_level( + ctx.session, other_level) + ): + res = self._get_session_tx_isolation(ctx.session) + self.assertEqual(other_level.replace(' ', '-'), res) + #check that context manager changes tx isolation level back + res = self._get_session_tx_isolation(ctx.session) + self.assertEqual(default_level.replace(' ', '-'), res) -- 2.45.2