Clean up ovsdb-native's use of verify()

When updating an ovsdb set-type column, the existing code does
the following:

1. Read the existing column value
2. Call verify() to cause a write failure if something else
   modifies the column before we commit
3. Append the value to our local copy of the column
4. Assign the local value to the object.column to trigger
   __setattr__ to write the value to the database

If verify() fails, which it *very* often does in a test
environment or a busy system, ovsdb-server will respond with a
TRY_AGAIN response which will cause the whole process to start
over.

In the 2.6 cycle, Row.addvalue()/delvalue() methods were added
which use OVDB's native 'mutate' methods to handle adding/deleting
from a set/map-type column. Using these means calling verify() is
no longer required and things will proceed *much* more efficiently.

Bug #1627106 where we get frequent TimeoutExceptions appears to be
related. Eliminating the frequent TRY_AGAIN responses, in my local
testing, also eliminates the TimeoutExceptions. This doesn't mean
that the underlying issue is resolved, but it may be avoided.
Related-Bug: #1627106

Change-Id: I26c7731f5dbd3bd2955dbfa18a7c41517da63e6e
This commit is contained in:
Terry Wilson 2017-02-03 16:39:24 -06:00
parent cea029f6d3
commit 26766963c6
2 changed files with 78 additions and 41 deletions

View File

@ -63,8 +63,12 @@ class AddManagerCommand(BaseCommand):
def run_idl(self, txn):
row = txn.insert(self.api._tables['Manager'])
row.target = self.target
self.api._ovs.verify('manager_options')
self.api._ovs.manager_options = self.api._ovs.manager_options + [row]
try:
self.api._ovs.addvalue('manager_options', row)
except AttributeError: # OVS < 2.6
self.api._ovs.verify('manager_options')
self.api._ovs.manager_options = (
self.api._ovs.manager_options + [row])
class GetManagerCommand(BaseCommand):
@ -89,11 +93,14 @@ class RemoveManagerCommand(BaseCommand):
msg = _("Manager with target %s does not exist") % self.target
LOG.error(msg)
raise RuntimeError(msg)
self.api._ovs.verify('manager_options')
manager_list = self.api._ovs.manager_options
manager_list.remove(manager)
self.api._ovs.manager_options = manager_list
self.api._tables['Manager'].rows[manager.uuid].delete()
try:
self.api._ovs.delvalue('manager_options', manager)
except AttributeError: # OVS < 2.6
self.api._ovs.verify('manager_options')
manager_list = self.api._ovs.manager_options
manager_list.remove(manager)
self.api._ovs.manager_options = manager_list
manager.delete()
class AddBridgeCommand(BaseCommand):
@ -115,8 +122,11 @@ class AddBridgeCommand(BaseCommand):
row.name = self.name
if self.datapath_type:
row.datapath_type = self.datapath_type
self.api._ovs.verify('bridges')
self.api._ovs.bridges = self.api._ovs.bridges + [row]
try:
self.api._ovs.addvalue('bridges', row)
except AttributeError: # OVS < 2.6
self.api._ovs.verify('bridges')
self.api._ovs.bridges = self.api._ovs.bridges + [row]
# Add the internal bridge port
cmd = AddPortCommand(self.api, self.name, self.name, self.may_exist)
@ -144,15 +154,19 @@ class DelBridgeCommand(BaseCommand):
msg = _("Bridge %s does not exist") % self.name
LOG.error(msg)
raise RuntimeError(msg)
self.api._ovs.verify('bridges')
# Clean up cached ports/interfaces
for port in br.ports:
cmd = DelPortCommand(self.api, port.name, self.name,
if_exists=True)
cmd.run_idl(txn)
bridges = self.api._ovs.bridges
bridges.remove(br)
self.api._ovs.bridges = bridges
self.api._tables['Bridge'].rows[br.uuid].delete()
for interface in port.interfaces:
interface.delete()
port.delete()
try:
self.api._ovs.delvalue('bridges', br)
except AttributeError: # OVS < 2.6
self.api._ovs.verify('bridges')
bridges = self.api._ovs.bridges
bridges.remove(br)
self.api._ovs.bridges = bridges
br.delete()
class BridgeExistsCommand(BaseCommand):
@ -267,6 +281,8 @@ class DbAddCommand(BaseCommand):
if isinstance(value, collections.Mapping):
# We should be doing an add on a 'map' column. If the key is
# already set, do nothing, otherwise set the key to the value
# Since this operation depends on the previous value, verify()
# must be called.
field = getattr(record, self.column, {})
for k, v in six.iteritems(value):
if k in field:
@ -274,8 +290,13 @@ class DbAddCommand(BaseCommand):
field[k] = v
else:
# We should be appending to a 'set' column.
field = getattr(record, self.column, [])
field.append(value)
try:
record.addvalue(self.column,
idlutils.db_replace_record(value))
continue
except AttributeError: # OVS < 2.6
field = getattr(record, self.column, [])
field.append(value)
record.verify(self.column)
setattr(record, self.column, idlutils.db_replace_record(field))
@ -327,7 +348,7 @@ class SetControllerCommand(BaseCommand):
controller = txn.insert(self.api._tables['Controller'])
controller.target = target
controllers.append(controller)
br.verify('controller')
# Don't need to verify because we unconditionally overwrite
br.controller = controllers
@ -348,7 +369,6 @@ class GetControllerCommand(BaseCommand):
def run_idl(self, txn):
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge)
br.verify('controller')
self.result = [c.target for c in br.controller]
@ -360,7 +380,6 @@ class SetFailModeCommand(BaseCommand):
def run_idl(self, txn):
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge)
br.verify('fail_mode')
br.fail_mode = self.mode
@ -380,18 +399,19 @@ class AddPortCommand(BaseCommand):
return
port = txn.insert(self.api._tables['Port'])
port.name = self.port
br.verify('ports')
ports = getattr(br, 'ports', [])
ports.append(port)
br.ports = ports
try:
br.addvalue('ports', port)
except AttributeError: # OVS < 2.6
br.verify('ports')
ports = getattr(br, 'ports', [])
ports.append(port)
br.ports = ports
iface = txn.insert(self.api._tables['Interface'])
txn.expected_ifaces.add(iface.uuid)
iface.name = self.port
port.verify('interfaces')
ifaces = getattr(port, 'interfaces', [])
ifaces.append(iface)
port.interfaces = ifaces
# This is a new port, so it won't have any existing interfaces
port.interfaces = [iface]
class DelPortCommand(BaseCommand):
@ -417,24 +437,26 @@ class DelPortCommand(BaseCommand):
br = next(b for b in self.api._tables['Bridge'].rows.values()
if port in b.ports)
if port.uuid not in br.ports and not self.if_exists:
if port not in br.ports and not self.if_exists:
# TODO(twilson) Make real errors across both implementations
msg = _("Port %(port)s does not exist on %(bridge)s!") % {
'port': self.name, 'bridge': self.bridge
'port': self.port, 'bridge': self.bridge
}
LOG.error(msg)
raise RuntimeError(msg)
br.verify('ports')
ports = br.ports
ports.remove(port)
br.ports = ports
try:
br.delvalue('ports', port)
except AttributeError: # OVS < 2.6
br.verify('ports')
ports = br.ports
ports.remove(port)
br.ports = ports
# Also remove port/interface directly for indexing?
port.verify('interfaces')
for iface in port.interfaces:
self.api._tables['Interface'].rows[iface.uuid].delete()
self.api._tables['Port'].rows[port.uuid].delete()
# The interface on the port will be cleaned up by ovsdb-server
for interface in port.interfaces:
interface.delete()
port.delete()
class ListPortsCommand(BaseCommand):

View File

@ -21,6 +21,7 @@ from neutron_lib import constants as const
from neutron.agent.common import ovs_lib
from neutron.agent.linux import ip_lib
from neutron.agent.ovsdb.native import idlutils
from neutron.common import utils
from neutron.tests.common.exclusive_resources import port
from neutron.tests.common import net_helpers
@ -454,6 +455,20 @@ class OVSBridgeTestCase(OVSBridgeTestBase):
txn.add(ovsdb.add_br(brname))
txn.add(ovsdb.db_add('Bridge', brname, 'protocols', 'OpenFlow10'))
def test_cascading_del_in_txn(self):
ovsdb = self.ovs.ovsdb
port_name, _ = self.create_ovs_port()
def del_port_mod_iface():
with ovsdb.transaction(check_error=True) as txn:
txn.add(ovsdb.del_port(port_name, self.br.br_name,
if_exists=False))
txn.add(ovsdb.db_set('Interface', port_name,
('type', 'internal')))
# native gives a more specific exception than vsctl
self.assertRaises((RuntimeError, idlutils.RowNotFound),
del_port_mod_iface)
class OVSLibTestCase(base.BaseOVSLinuxTestCase):