Merge "Use the ovsdbapp library"
This commit is contained in:
@@ -30,7 +30,7 @@ import tenacity
|
||||
from neutron._i18n import _, _LE, _LI, _LW
|
||||
from neutron.agent.common import ip_lib
|
||||
from neutron.agent.common import utils
|
||||
from neutron.agent.ovsdb import api as ovsdb
|
||||
from neutron.agent.ovsdb import api as ovsdb_api
|
||||
from neutron.conf.agent import ovs_conf
|
||||
from neutron.plugins.common import constants as p_const
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common \
|
||||
@@ -106,7 +106,7 @@ class BaseOVS(object):
|
||||
|
||||
def __init__(self):
|
||||
self.vsctl_timeout = cfg.CONF.ovs_vsctl_timeout
|
||||
self.ovsdb = ovsdb.API.get(self)
|
||||
self.ovsdb = ovsdb_api.from_config(self)
|
||||
|
||||
def add_manager(self, connection_uri, timeout=_SENTINEL):
|
||||
"""Have ovsdb-server listen for manager connections
|
||||
|
||||
@@ -12,20 +12,26 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
import collections
|
||||
import contextlib
|
||||
import uuid
|
||||
|
||||
from debtcollector import moves
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
from ovsdbapp import api
|
||||
from ovsdbapp import exceptions
|
||||
|
||||
from neutron._i18n import _
|
||||
|
||||
API = moves.moved_class(api.API, 'API', __name__)
|
||||
Command = moves.moved_class(api.Command, 'Command', __name__)
|
||||
Transaction = moves.moved_class(api.Transaction, 'Transaction', __name__)
|
||||
TimeoutException = moves.moved_class(exceptions.TimeoutException,
|
||||
'TimeoutException', __name__)
|
||||
|
||||
interface_map = {
|
||||
'vsctl': 'neutron.agent.ovsdb.impl_vsctl.OvsdbVsctl',
|
||||
'native': 'neutron.agent.ovsdb.impl_idl.NeutronOvsdbIdl',
|
||||
'vsctl': 'neutron.agent.ovsdb.impl_vsctl',
|
||||
'native': 'neutron.agent.ovsdb.impl_idl',
|
||||
}
|
||||
|
||||
OPTS = [
|
||||
@@ -44,400 +50,11 @@ OPTS = [
|
||||
cfg.CONF.register_opts(OPTS, 'OVS')
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Command(object):
|
||||
"""An OVSDB command that can be executed in a transaction
|
||||
|
||||
:attr result: The result of executing the command in a transaction
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute(self, **transaction_options):
|
||||
"""Immediately execute an OVSDB command
|
||||
|
||||
This implicitly creates a transaction with the passed options and then
|
||||
executes it, returning the value of the executed transaction
|
||||
|
||||
:param transaction_options: Options to pass to the transaction
|
||||
"""
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Transaction(object):
|
||||
@abc.abstractmethod
|
||||
def commit(self):
|
||||
"""Commit the transaction to OVSDB"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def add(self, command):
|
||||
"""Append an OVSDB operation to the transaction"""
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, tb):
|
||||
if exc_type is None:
|
||||
self.result = self.commit()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class API(object):
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
self._nested_txn = None
|
||||
|
||||
@staticmethod
|
||||
def get(context, iface_name=None):
|
||||
"""Return the configured OVSDB API implementation"""
|
||||
iface = importutils.import_class(
|
||||
interface_map[iface_name or cfg.CONF.OVS.ovsdb_interface])
|
||||
return iface(context)
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_transaction(self, check_error=False, log_errors=True, **kwargs):
|
||||
"""Create a transaction
|
||||
|
||||
:param check_error: Allow the transaction to raise an exception?
|
||||
:type check_error: bool
|
||||
:param log_errors: Log an error if the transaction fails?
|
||||
:type log_errors: bool
|
||||
:returns: A new transaction
|
||||
:rtype: :class:`Transaction`
|
||||
"""
|
||||
|
||||
@contextlib.contextmanager
|
||||
def transaction(self, check_error=False, log_errors=True, **kwargs):
|
||||
"""Create a transaction context.
|
||||
|
||||
:param check_error: Allow the transaction to raise an exception?
|
||||
:type check_error: bool
|
||||
:param log_errors: Log an error if the transaction fails?
|
||||
:type log_errors: bool
|
||||
:returns: Either a new transaction or an existing one.
|
||||
:rtype: :class:`Transaction`
|
||||
"""
|
||||
if self._nested_txn:
|
||||
yield self._nested_txn
|
||||
else:
|
||||
with self.create_transaction(
|
||||
check_error, log_errors, **kwargs) as txn:
|
||||
self._nested_txn = txn
|
||||
try:
|
||||
yield txn
|
||||
finally:
|
||||
self._nested_txn = None
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_manager(self, connection_uri):
|
||||
"""Create a command to add a Manager to the OVS switch
|
||||
|
||||
This API will add a new manager without overriding the existing ones.
|
||||
|
||||
:param connection_uri: target to which manager needs to be set
|
||||
:type connection_uri: string, see ovs-vsctl manpage for format
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_manager(self):
|
||||
"""Create a command to get Manager list from the OVS switch
|
||||
|
||||
:returns: :class:`Command` with list of Manager names result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def remove_manager(self, connection_uri):
|
||||
"""Create a command to remove a Manager from the OVS switch
|
||||
|
||||
This API will remove the manager configured on the OVS switch.
|
||||
|
||||
:param connection_uri: target identifying the manager uri that
|
||||
needs to be removed.
|
||||
:type connection_uri: string, see ovs-vsctl manpage for format
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_br(self, name, may_exist=True, datapath_type=None):
|
||||
"""Create a command to add an OVS bridge
|
||||
|
||||
:param name: The name of the bridge
|
||||
:type name: string
|
||||
:param may_exist: Do not fail if bridge already exists
|
||||
:type may_exist: bool
|
||||
:param datapath_type: The datapath_type of the bridge
|
||||
:type datapath_type: string
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def del_br(self, name, if_exists=True):
|
||||
"""Create a command to delete an OVS bridge
|
||||
|
||||
:param name: The name of the bridge
|
||||
:type name: string
|
||||
:param if_exists: Do not fail if the bridge does not exist
|
||||
:type if_exists: bool
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def br_exists(self, name):
|
||||
"""Create a command to check if an OVS bridge exists
|
||||
|
||||
:param name: The name of the bridge
|
||||
:type name: string
|
||||
:returns: :class:`Command` with bool result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def port_to_br(self, name):
|
||||
"""Create a command to return the name of the bridge with the port
|
||||
|
||||
:param name: The name of the OVS port
|
||||
:type name: string
|
||||
:returns: :class:`Command` with bridge name result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def iface_to_br(self, name):
|
||||
"""Create a command to return the name of the bridge with the interface
|
||||
|
||||
:param name: The name of the OVS interface
|
||||
:type name: string
|
||||
:returns: :class:`Command` with bridge name result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def list_br(self):
|
||||
"""Create a command to return the current list of OVS bridge names
|
||||
|
||||
:returns: :class:`Command` with list of bridge names result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def br_get_external_id(self, name, field):
|
||||
"""Create a command to return a field from the Bridge's external_ids
|
||||
|
||||
:param name: The name of the OVS Bridge
|
||||
:type name: string
|
||||
:param field: The external_ids field to return
|
||||
:type field: string
|
||||
:returns: :class:`Command` with field value result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def db_create(self, table, **col_values):
|
||||
"""Create a command to create new record
|
||||
|
||||
:param table: The OVS table containing the record to be created
|
||||
:type table: string
|
||||
:param col_values: The columns and their associated values
|
||||
to be set after create
|
||||
:type col_values: Dictionary of columns id's and values
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def db_destroy(self, table, record):
|
||||
"""Create a command to destroy a record
|
||||
|
||||
:param table: The OVS table containing the record to be destroyed
|
||||
:type table: string
|
||||
:param record: The record id (name/uuid) to be destroyed
|
||||
:type record: uuid/string
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def db_set(self, table, record, *col_values):
|
||||
"""Create a command to set fields in a record
|
||||
|
||||
:param table: The OVS table containing the record to be modified
|
||||
:type table: string
|
||||
:param record: The record id (name/uuid) to be modified
|
||||
:type table: string
|
||||
:param col_values: The columns and their associated values
|
||||
:type col_values: Tuples of (column, value). Values may be atomic
|
||||
values or unnested sequences/mappings
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
# TODO(twilson) Consider handling kwargs for arguments where order
|
||||
# doesn't matter. Though that would break the assert_called_once_with
|
||||
# unit tests
|
||||
|
||||
@abc.abstractmethod
|
||||
def db_add(self, table, record, column, *values):
|
||||
"""Create a command to add a value to a record
|
||||
|
||||
Adds each value or key-value pair to column in record in table. If
|
||||
column is a map, then each value will be a dict, otherwise a base type.
|
||||
If key already exists in a map column, then the current value is not
|
||||
replaced (use the set command to replace an existing value).
|
||||
|
||||
:param table: The OVS table containing the record to be modified
|
||||
:type table: string
|
||||
:param record: The record id (name/uuid) to modified
|
||||
:type record: string
|
||||
:param column: The column name to be modified
|
||||
:type column: string
|
||||
:param values: The values to be added to the column
|
||||
:type values: The base type of the column. If column is a map, then
|
||||
a dict containing the key name and the map's value type
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def db_clear(self, table, record, column):
|
||||
"""Create a command to clear a field's value in a record
|
||||
|
||||
:param table: The OVS table containing the record to be modified
|
||||
:type table: string
|
||||
:param record: The record id (name/uuid) to be modified
|
||||
:type record: string
|
||||
:param column: The column whose value should be cleared
|
||||
:type column: string
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def db_get(self, table, record, column):
|
||||
"""Create a command to return a field's value in a record
|
||||
|
||||
:param table: The OVS table containing the record to be queried
|
||||
:type table: string
|
||||
:param record: The record id (name/uuid) to be queried
|
||||
:type record: string
|
||||
:param column: The column whose value should be returned
|
||||
:type column: string
|
||||
:returns: :class:`Command` with the field's value result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def db_list(self, table, records=None, columns=None, if_exists=False):
|
||||
"""Create a command to return a list of OVSDB records
|
||||
|
||||
:param table: The OVS table to query
|
||||
:type table: string
|
||||
:param records: The records to return values from
|
||||
:type records: list of record ids (names/uuids)
|
||||
:param columns: Limit results to only columns, None means all columns
|
||||
:type columns: list of column names or None
|
||||
:param if_exists: Do not fail if the record does not exist
|
||||
:type if_exists: bool
|
||||
:returns: :class:`Command` with [{'column', value}, ...] result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def db_find(self, table, *conditions, **kwargs):
|
||||
"""Create a command to return find OVSDB records matching conditions
|
||||
|
||||
:param table: The OVS table to query
|
||||
:type table: string
|
||||
:param conditions:The conditions to satisfy the query
|
||||
:type conditions: 3-tuples containing (column, operation, match)
|
||||
Type of 'match' parameter MUST be identical to column
|
||||
type
|
||||
Examples:
|
||||
atomic: ('tag', '=', 7)
|
||||
map: ('external_ids' '=', {'iface-id': 'xxx'})
|
||||
field exists?
|
||||
('external_ids', '!=', {'iface-id', ''})
|
||||
set contains?:
|
||||
('protocols', '{>=}', 'OpenFlow13')
|
||||
See the ovs-vsctl man page for more operations
|
||||
:param columns: Limit results to only columns, None means all columns
|
||||
:type columns: list of column names or None
|
||||
:returns: :class:`Command` with [{'column', value}, ...] result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_controller(self, bridge, controllers):
|
||||
"""Create a command to set an OVS bridge's OpenFlow controllers
|
||||
|
||||
:param bridge: The name of the bridge
|
||||
:type bridge: string
|
||||
:param controllers: The controller strings
|
||||
:type controllers: list of strings, see ovs-vsctl manpage for format
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def del_controller(self, bridge):
|
||||
"""Create a command to clear an OVS bridge's OpenFlow controllers
|
||||
|
||||
:param bridge: The name of the bridge
|
||||
:type bridge: string
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_controller(self, bridge):
|
||||
"""Create a command to return an OVS bridge's OpenFlow controllers
|
||||
|
||||
:param bridge: The name of the bridge
|
||||
:type bridge: string
|
||||
:returns: :class:`Command` with list of controller strings result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_fail_mode(self, bridge, mode):
|
||||
"""Create a command to set an OVS bridge's failure mode
|
||||
|
||||
:param bridge: The name of the bridge
|
||||
:type bridge: string
|
||||
:param mode: The failure mode
|
||||
:type mode: "secure" or "standalone"
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_port(self, bridge, port, may_exist=True):
|
||||
"""Create a command to add a port to an OVS bridge
|
||||
|
||||
:param bridge: The name of the bridge
|
||||
:type bridge: string
|
||||
:param port: The name of the port
|
||||
:type port: string
|
||||
:param may_exist: Do not fail if the port already exists
|
||||
:type may_exist: bool
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def del_port(self, port, bridge=None, if_exists=True):
|
||||
"""Create a command to delete a port an OVS port
|
||||
|
||||
:param port: The name of the port
|
||||
:type port: string
|
||||
:param bridge: Only delete port if it is attached to this bridge
|
||||
:type bridge: string
|
||||
:param if_exists: Do not fail if the port does not exist
|
||||
:type if_exists: bool
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def list_ports(self, bridge):
|
||||
"""Create a command to list the names of ports on a bridge
|
||||
|
||||
:param bridge: The name of the bridge
|
||||
:type bridge: string
|
||||
:returns: :class:`Command` with list of port names result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def list_ifaces(self, bridge):
|
||||
"""Create a command to list the names of interfaces on a bridge
|
||||
|
||||
:param bridge: The name of the bridge
|
||||
:type bridge: string
|
||||
:returns: :class:`Command` with list of interfaces names result
|
||||
"""
|
||||
|
||||
|
||||
class TimeoutException(Exception):
|
||||
pass
|
||||
def from_config(context, iface_name=None):
|
||||
"""Return the configured OVSDB API implementation"""
|
||||
iface = importutils.import_module(
|
||||
interface_map[iface_name or cfg.CONF.OVS.ovsdb_interface])
|
||||
return iface.api_factory(context)
|
||||
|
||||
|
||||
def val_to_py(val):
|
||||
|
||||
@@ -12,289 +12,32 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
|
||||
from neutron_lib import exceptions
|
||||
from debtcollector import moves
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from ovs.db import idl
|
||||
from six.moves import queue as Queue
|
||||
from ovsdbapp.schema.open_vswitch import impl_idl
|
||||
|
||||
from neutron._i18n import _, _LE
|
||||
from neutron.agent.ovsdb import api
|
||||
from neutron.agent.ovsdb.native import commands as cmd
|
||||
from neutron.agent.ovsdb.native import connection
|
||||
from neutron.agent.ovsdb.native import idlutils
|
||||
from neutron.agent.ovsdb.native import vlog
|
||||
|
||||
NeutronOVSDBTransaction = moves.moved_class(
|
||||
impl_idl.OvsVsctlTransaction,
|
||||
'NeutronOVSDBTransaction',
|
||||
__name__)
|
||||
|
||||
cfg.CONF.import_opt('ovs_vsctl_timeout', 'neutron.agent.common.ovs_lib')
|
||||
VswitchdInterfaceAddException = moves.moved_class(
|
||||
impl_idl.VswitchdInterfaceAddException,
|
||||
'VswitchdInterfaceAddException',
|
||||
__name__)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
_connection = connection.Connection(idl_factory=connection.idl_factory,
|
||||
timeout=cfg.CONF.ovs_vsctl_timeout)
|
||||
|
||||
|
||||
class VswitchdInterfaceAddException(exceptions.NeutronException):
|
||||
message = _("Failed to add interfaces: %(ifaces)s")
|
||||
def api_factory(context):
|
||||
return NeutronOvsdbIdl(_connection)
|
||||
|
||||
|
||||
class Transaction(api.Transaction):
|
||||
def __init__(self, api, ovsdb_connection, timeout,
|
||||
check_error=False, log_errors=True):
|
||||
self.api = api
|
||||
self.check_error = check_error
|
||||
self.log_errors = log_errors
|
||||
self.commands = []
|
||||
self.results = Queue.Queue(1)
|
||||
self.ovsdb_connection = ovsdb_connection
|
||||
self.timeout = timeout
|
||||
self.expected_ifaces = set()
|
||||
|
||||
def __str__(self):
|
||||
return ", ".join(str(cmd) for cmd in self.commands)
|
||||
|
||||
def add(self, command):
|
||||
"""Add a command to the transaction
|
||||
|
||||
returns The command passed as a convenience
|
||||
"""
|
||||
|
||||
self.commands.append(command)
|
||||
return command
|
||||
|
||||
def commit(self):
|
||||
self.ovsdb_connection.queue_txn(self)
|
||||
try:
|
||||
result = self.results.get(timeout=self.timeout)
|
||||
except Queue.Empty:
|
||||
raise api.TimeoutException(
|
||||
_("Commands %(commands)s exceeded timeout %(timeout)d "
|
||||
"seconds") % {'commands': self.commands,
|
||||
'timeout': self.timeout})
|
||||
if isinstance(result, idlutils.ExceptionResult):
|
||||
if self.log_errors:
|
||||
LOG.error(result.tb)
|
||||
if self.check_error:
|
||||
raise result.ex
|
||||
return result
|
||||
|
||||
def pre_commit(self, txn):
|
||||
pass
|
||||
|
||||
def post_commit(self, txn):
|
||||
for command in self.commands:
|
||||
command.post_commit(txn)
|
||||
|
||||
def do_commit(self):
|
||||
self.start_time = time.time()
|
||||
attempts = 0
|
||||
while True:
|
||||
if attempts > 0 and self.timeout_exceeded():
|
||||
raise RuntimeError(_("OVS transaction timed out"))
|
||||
attempts += 1
|
||||
# TODO(twilson) Make sure we don't loop longer than vsctl_timeout
|
||||
txn = idl.Transaction(self.api.idl)
|
||||
self.pre_commit(txn)
|
||||
for i, command in enumerate(self.commands):
|
||||
LOG.debug("Running txn command(idx=%(idx)s): %(cmd)s",
|
||||
{'idx': i, 'cmd': command})
|
||||
try:
|
||||
command.run_idl(txn)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception() as ctx:
|
||||
txn.abort()
|
||||
if not self.check_error:
|
||||
ctx.reraise = False
|
||||
seqno = self.api.idl.change_seqno
|
||||
status = txn.commit_block()
|
||||
if status == txn.TRY_AGAIN:
|
||||
LOG.debug("OVSDB transaction returned TRY_AGAIN, retrying")
|
||||
idlutils.wait_for_change(self.api.idl, self.time_remaining(),
|
||||
seqno)
|
||||
continue
|
||||
elif status == txn.ERROR:
|
||||
msg = _("OVSDB Error: %s") % txn.get_error()
|
||||
if self.log_errors:
|
||||
LOG.error(msg)
|
||||
if self.check_error:
|
||||
# For now, raise similar error to vsctl/utils.execute()
|
||||
raise RuntimeError(msg)
|
||||
return
|
||||
elif status == txn.ABORTED:
|
||||
LOG.debug("Transaction aborted")
|
||||
return
|
||||
elif status == txn.UNCHANGED:
|
||||
LOG.debug("Transaction caused no change")
|
||||
elif status == txn.SUCCESS:
|
||||
self.post_commit(txn)
|
||||
|
||||
return [cmd.result for cmd in self.commands]
|
||||
|
||||
def elapsed_time(self):
|
||||
return time.time() - self.start_time
|
||||
|
||||
def time_remaining(self):
|
||||
return self.timeout - self.elapsed_time()
|
||||
|
||||
def timeout_exceeded(self):
|
||||
return self.elapsed_time() > self.timeout
|
||||
|
||||
|
||||
class NeutronOVSDBTransaction(Transaction):
|
||||
def pre_commit(self, txn):
|
||||
self.api._ovs.increment('next_cfg')
|
||||
txn.expected_ifaces = set()
|
||||
|
||||
def post_commit(self, txn):
|
||||
super(NeutronOVSDBTransaction, self).post_commit(txn)
|
||||
# ovs-vsctl only logs these failures and does not return nonzero
|
||||
try:
|
||||
self.do_post_commit(txn)
|
||||
except Exception:
|
||||
LOG.exception(_LE("Post-commit checks failed"))
|
||||
|
||||
def do_post_commit(self, txn):
|
||||
next_cfg = txn.get_increment_new_value()
|
||||
while not self.timeout_exceeded():
|
||||
self.api.idl.run()
|
||||
if self.vswitchd_has_completed(next_cfg):
|
||||
failed = self.post_commit_failed_interfaces(txn)
|
||||
if failed:
|
||||
raise VswitchdInterfaceAddException(
|
||||
ifaces=", ".join(failed))
|
||||
break
|
||||
self.ovsdb_connection.poller.timer_wait(
|
||||
self.time_remaining() * 1000)
|
||||
self.api.idl.wait(self.ovsdb_connection.poller)
|
||||
self.ovsdb_connection.poller.block()
|
||||
else:
|
||||
raise api.TimeoutException(
|
||||
_("Commands %(commands)s exceeded timeout %(timeout)d "
|
||||
"seconds post-commit") % {'commands': self.commands,
|
||||
'timeout': self.timeout})
|
||||
|
||||
def post_commit_failed_interfaces(self, txn):
|
||||
failed = []
|
||||
for iface_uuid in txn.expected_ifaces:
|
||||
uuid = txn.get_insert_uuid(iface_uuid)
|
||||
if uuid:
|
||||
ifaces = self.api.idl.tables['Interface']
|
||||
iface = ifaces.rows.get(uuid)
|
||||
if iface and (not iface.ofport or iface.ofport == -1):
|
||||
failed.append(iface.name)
|
||||
return failed
|
||||
|
||||
def vswitchd_has_completed(self, next_cfg):
|
||||
return self.api._ovs.cur_cfg >= next_cfg
|
||||
|
||||
|
||||
class OvsdbIdl(api.API):
|
||||
|
||||
ovsdb_connection = connection.Connection(cfg.CONF.OVS.ovsdb_connection,
|
||||
cfg.CONF.ovs_vsctl_timeout,
|
||||
'Open_vSwitch')
|
||||
|
||||
def __init__(self, context):
|
||||
super(OvsdbIdl, self).__init__(context)
|
||||
OvsdbIdl.ovsdb_connection.start()
|
||||
self.idl = OvsdbIdl.ovsdb_connection.idl
|
||||
|
||||
@property
|
||||
def _tables(self):
|
||||
return self.idl.tables
|
||||
|
||||
@property
|
||||
def _ovs(self):
|
||||
return list(self._tables['Open_vSwitch'].rows.values())[0]
|
||||
|
||||
def create_transaction(self, check_error=False, log_errors=True, **kwargs):
|
||||
return NeutronOVSDBTransaction(self, OvsdbIdl.ovsdb_connection,
|
||||
self.context.vsctl_timeout,
|
||||
check_error, log_errors)
|
||||
|
||||
def add_manager(self, connection_uri):
|
||||
return cmd.AddManagerCommand(self, connection_uri)
|
||||
|
||||
def get_manager(self):
|
||||
return cmd.GetManagerCommand(self)
|
||||
|
||||
def remove_manager(self, connection_uri):
|
||||
return cmd.RemoveManagerCommand(self, connection_uri)
|
||||
|
||||
def add_br(self, name, may_exist=True, datapath_type=None):
|
||||
return cmd.AddBridgeCommand(self, name, may_exist, datapath_type)
|
||||
|
||||
def del_br(self, name, if_exists=True):
|
||||
return cmd.DelBridgeCommand(self, name, if_exists)
|
||||
|
||||
def br_exists(self, name):
|
||||
return cmd.BridgeExistsCommand(self, name)
|
||||
|
||||
def port_to_br(self, name):
|
||||
return cmd.PortToBridgeCommand(self, name)
|
||||
|
||||
def iface_to_br(self, name):
|
||||
return cmd.InterfaceToBridgeCommand(self, name)
|
||||
|
||||
def list_br(self):
|
||||
return cmd.ListBridgesCommand(self)
|
||||
|
||||
def br_get_external_id(self, name, field):
|
||||
return cmd.BrGetExternalIdCommand(self, name, field)
|
||||
|
||||
def br_set_external_id(self, name, field, value):
|
||||
return cmd.BrSetExternalIdCommand(self, name, field, value)
|
||||
|
||||
def db_create(self, table, **col_values):
|
||||
return cmd.DbCreateCommand(self, table, **col_values)
|
||||
|
||||
def db_destroy(self, table, record):
|
||||
return cmd.DbDestroyCommand(self, table, record)
|
||||
|
||||
def db_set(self, table, record, *col_values):
|
||||
return cmd.DbSetCommand(self, table, record, *col_values)
|
||||
|
||||
def db_add(self, table, record, column, *values):
|
||||
return cmd.DbAddCommand(self, table, record, column, *values)
|
||||
|
||||
def db_clear(self, table, record, column):
|
||||
return cmd.DbClearCommand(self, table, record, column)
|
||||
|
||||
def db_get(self, table, record, column):
|
||||
return cmd.DbGetCommand(self, table, record, column)
|
||||
|
||||
def db_list(self, table, records=None, columns=None, if_exists=False):
|
||||
return cmd.DbListCommand(self, table, records, columns, if_exists)
|
||||
|
||||
def db_find(self, table, *conditions, **kwargs):
|
||||
return cmd.DbFindCommand(self, table, *conditions, **kwargs)
|
||||
|
||||
def set_controller(self, bridge, controllers):
|
||||
return cmd.SetControllerCommand(self, bridge, controllers)
|
||||
|
||||
def del_controller(self, bridge):
|
||||
return cmd.DelControllerCommand(self, bridge)
|
||||
|
||||
def get_controller(self, bridge):
|
||||
return cmd.GetControllerCommand(self, bridge)
|
||||
|
||||
def set_fail_mode(self, bridge, mode):
|
||||
return cmd.SetFailModeCommand(self, bridge, mode)
|
||||
|
||||
def add_port(self, bridge, port, may_exist=True):
|
||||
return cmd.AddPortCommand(self, bridge, port, may_exist)
|
||||
|
||||
def del_port(self, port, bridge=None, if_exists=True):
|
||||
return cmd.DelPortCommand(self, port, bridge, if_exists)
|
||||
|
||||
def list_ports(self, bridge):
|
||||
return cmd.ListPortsCommand(self, bridge)
|
||||
|
||||
def list_ifaces(self, bridge):
|
||||
return cmd.ListIfacesCommand(self, bridge)
|
||||
|
||||
|
||||
class NeutronOvsdbIdl(OvsdbIdl):
|
||||
def __init__(self, context):
|
||||
vlog.use_oslo_logger()
|
||||
super(NeutronOvsdbIdl, self).__init__(context)
|
||||
class NeutronOvsdbIdl(impl_idl.OvsdbIdl):
|
||||
def __init__(self, connection):
|
||||
vlog.use_python_logger()
|
||||
super(NeutronOvsdbIdl, self).__init__(connection)
|
||||
|
||||
@@ -29,6 +29,10 @@ from neutron.agent.ovsdb import api as ovsdb
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def api_factory(context):
|
||||
return OvsdbVsctl(context)
|
||||
|
||||
|
||||
class Transaction(ovsdb.Transaction):
|
||||
def __init__(self, context, check_error=False, log_errors=True, opts=None):
|
||||
self.context = context
|
||||
@@ -178,6 +182,10 @@ class BrExistsCommand(DbCommand):
|
||||
|
||||
|
||||
class OvsdbVsctl(ovsdb.API):
|
||||
def __init__(self, context):
|
||||
super(OvsdbVsctl, self).__init__()
|
||||
self.context = context
|
||||
|
||||
def create_transaction(self, check_error=False, log_errors=True, **kwargs):
|
||||
return Transaction(self.context, check_error, log_errors, **kwargs)
|
||||
|
||||
|
||||
@@ -12,561 +12,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
from ovsdbapp.schema.open_vswitch import commands
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from neutron.common import _deprecate
|
||||
|
||||
from neutron._i18n import _, _LE
|
||||
from neutron.agent.ovsdb import api
|
||||
from neutron.agent.ovsdb.native import idlutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseCommand(api.Command):
|
||||
def __init__(self, api):
|
||||
self.api = api
|
||||
self.result = None
|
||||
|
||||
def execute(self, check_error=False, log_errors=True):
|
||||
try:
|
||||
with self.api.transaction(check_error, log_errors) as txn:
|
||||
txn.add(self)
|
||||
return self.result
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception() as ctx:
|
||||
if log_errors:
|
||||
LOG.exception(_LE("Error executing command"))
|
||||
if not check_error:
|
||||
ctx.reraise = False
|
||||
|
||||
def post_commit(self, txn):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
command_info = self.__dict__
|
||||
return "%s(%s)" % (
|
||||
self.__class__.__name__,
|
||||
", ".join("%s=%s" % (k, v) for k, v in command_info.items()
|
||||
if k not in ['api', 'result']))
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class AddManagerCommand(BaseCommand):
|
||||
def __init__(self, api, target):
|
||||
super(AddManagerCommand, self).__init__(api)
|
||||
self.target = target
|
||||
|
||||
def run_idl(self, txn):
|
||||
row = txn.insert(self.api._tables['Manager'])
|
||||
row.target = self.target
|
||||
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):
|
||||
def __init__(self, api):
|
||||
super(GetManagerCommand, self).__init__(api)
|
||||
|
||||
def run_idl(self, txn):
|
||||
self.result = [m.target for m in
|
||||
self.api._tables['Manager'].rows.values()]
|
||||
|
||||
|
||||
class RemoveManagerCommand(BaseCommand):
|
||||
def __init__(self, api, target):
|
||||
super(RemoveManagerCommand, self).__init__(api)
|
||||
self.target = target
|
||||
|
||||
def run_idl(self, txn):
|
||||
try:
|
||||
manager = idlutils.row_by_value(self.api.idl, 'Manager', 'target',
|
||||
self.target)
|
||||
except idlutils.RowNotFound:
|
||||
msg = _("Manager with target %s does not exist") % self.target
|
||||
LOG.error(msg)
|
||||
raise RuntimeError(msg)
|
||||
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):
|
||||
def __init__(self, api, name, may_exist, datapath_type):
|
||||
super(AddBridgeCommand, self).__init__(api)
|
||||
self.name = name
|
||||
self.may_exist = may_exist
|
||||
self.datapath_type = datapath_type
|
||||
|
||||
def run_idl(self, txn):
|
||||
if self.may_exist:
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name',
|
||||
self.name, None)
|
||||
if br:
|
||||
if self.datapath_type:
|
||||
br.datapath_type = self.datapath_type
|
||||
return
|
||||
row = txn.insert(self.api._tables['Bridge'])
|
||||
row.name = self.name
|
||||
if self.datapath_type:
|
||||
row.datapath_type = self.datapath_type
|
||||
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)
|
||||
cmd.run_idl(txn)
|
||||
|
||||
cmd = DbSetCommand(self.api, 'Interface', self.name,
|
||||
('type', 'internal'))
|
||||
cmd.run_idl(txn)
|
||||
|
||||
|
||||
class DelBridgeCommand(BaseCommand):
|
||||
def __init__(self, api, name, if_exists):
|
||||
super(DelBridgeCommand, self).__init__(api)
|
||||
self.name = name
|
||||
self.if_exists = if_exists
|
||||
|
||||
def run_idl(self, txn):
|
||||
try:
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name',
|
||||
self.name)
|
||||
except idlutils.RowNotFound:
|
||||
if self.if_exists:
|
||||
return
|
||||
else:
|
||||
msg = _("Bridge %s does not exist") % self.name
|
||||
LOG.error(msg)
|
||||
raise RuntimeError(msg)
|
||||
# Clean up cached ports/interfaces
|
||||
for port in br.ports:
|
||||
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):
|
||||
def __init__(self, api, name):
|
||||
super(BridgeExistsCommand, self).__init__(api)
|
||||
self.name = name
|
||||
|
||||
def run_idl(self, txn):
|
||||
self.result = bool(idlutils.row_by_value(self.api.idl, 'Bridge',
|
||||
'name', self.name, None))
|
||||
|
||||
|
||||
class ListBridgesCommand(BaseCommand):
|
||||
def __init__(self, api):
|
||||
super(ListBridgesCommand, self).__init__(api)
|
||||
|
||||
def run_idl(self, txn):
|
||||
# NOTE (twilson) [x.name for x in rows.values()] if no index
|
||||
self.result = [x.name for x in
|
||||
self.api._tables['Bridge'].rows.values()]
|
||||
|
||||
|
||||
class BrGetExternalIdCommand(BaseCommand):
|
||||
def __init__(self, api, name, field):
|
||||
super(BrGetExternalIdCommand, self).__init__(api)
|
||||
self.name = name
|
||||
self.field = field
|
||||
|
||||
def run_idl(self, txn):
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.name)
|
||||
self.result = br.external_ids[self.field]
|
||||
|
||||
|
||||
class BrSetExternalIdCommand(BaseCommand):
|
||||
def __init__(self, api, name, field, value):
|
||||
super(BrSetExternalIdCommand, self).__init__(api)
|
||||
self.name = name
|
||||
self.field = field
|
||||
self.value = value
|
||||
|
||||
def run_idl(self, txn):
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.name)
|
||||
external_ids = getattr(br, 'external_ids', {})
|
||||
external_ids[self.field] = self.value
|
||||
br.external_ids = external_ids
|
||||
|
||||
|
||||
class DbCreateCommand(BaseCommand):
|
||||
def __init__(self, api, table, **columns):
|
||||
super(DbCreateCommand, self).__init__(api)
|
||||
self.table = table
|
||||
self.columns = columns
|
||||
|
||||
def run_idl(self, txn):
|
||||
row = txn.insert(self.api._tables[self.table])
|
||||
for col, val in self.columns.items():
|
||||
setattr(row, col, idlutils.db_replace_record(val))
|
||||
# This is a temporary row to be used within the transaction
|
||||
self.result = row
|
||||
|
||||
def post_commit(self, txn):
|
||||
# Replace the temporary row with the post-commit UUID to match vsctl
|
||||
self.result = txn.get_insert_uuid(self.result.uuid)
|
||||
|
||||
|
||||
class DbDestroyCommand(BaseCommand):
|
||||
def __init__(self, api, table, record):
|
||||
super(DbDestroyCommand, self).__init__(api)
|
||||
self.table = table
|
||||
self.record = record
|
||||
|
||||
def run_idl(self, txn):
|
||||
record = idlutils.row_by_record(self.api.idl, self.table, self.record)
|
||||
record.delete()
|
||||
|
||||
|
||||
class DbSetCommand(BaseCommand):
|
||||
def __init__(self, api, table, record, *col_values):
|
||||
super(DbSetCommand, self).__init__(api)
|
||||
self.table = table
|
||||
self.record = record
|
||||
self.col_values = col_values
|
||||
|
||||
def run_idl(self, txn):
|
||||
record = idlutils.row_by_record(self.api.idl, self.table, self.record)
|
||||
for col, val in self.col_values:
|
||||
# TODO(twilson) Ugh, the OVS library doesn't like OrderedDict
|
||||
# We're only using it to make a unit test work, so we should fix
|
||||
# this soon.
|
||||
if isinstance(val, collections.OrderedDict):
|
||||
val = dict(val)
|
||||
if isinstance(val, dict):
|
||||
# NOTE(twilson) OVS 2.6's Python IDL has mutate methods that
|
||||
# would make this cleaner, but it's too early to rely on them.
|
||||
existing = getattr(record, col, {})
|
||||
existing.update(val)
|
||||
val = existing
|
||||
setattr(record, col, idlutils.db_replace_record(val))
|
||||
|
||||
|
||||
class DbAddCommand(BaseCommand):
|
||||
def __init__(self, api, table, record, column, *values):
|
||||
super(DbAddCommand, self).__init__(api)
|
||||
self.table = table
|
||||
self.record = record
|
||||
self.column = column
|
||||
self.values = values
|
||||
|
||||
def run_idl(self, txn):
|
||||
record = idlutils.row_by_record(self.api.idl, self.table, self.record)
|
||||
for value in self.values:
|
||||
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 value.items():
|
||||
if k in field:
|
||||
continue
|
||||
field[k] = v
|
||||
else:
|
||||
# We should be appending to a 'set' column.
|
||||
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))
|
||||
|
||||
|
||||
class DbClearCommand(BaseCommand):
|
||||
def __init__(self, api, table, record, column):
|
||||
super(DbClearCommand, self).__init__(api)
|
||||
self.table = table
|
||||
self.record = record
|
||||
self.column = column
|
||||
|
||||
def run_idl(self, txn):
|
||||
record = idlutils.row_by_record(self.api.idl, self.table, self.record)
|
||||
# Create an empty value of the column type
|
||||
value = type(getattr(record, self.column))()
|
||||
setattr(record, self.column, value)
|
||||
|
||||
|
||||
class DbGetCommand(BaseCommand):
|
||||
def __init__(self, api, table, record, column):
|
||||
super(DbGetCommand, self).__init__(api)
|
||||
self.table = table
|
||||
self.record = record
|
||||
self.column = column
|
||||
|
||||
def run_idl(self, txn):
|
||||
record = idlutils.row_by_record(self.api.idl, self.table, self.record)
|
||||
# TODO(twilson) This feels wrong, but ovs-vsctl returns single results
|
||||
# on set types without the list. The IDL is returning them as lists,
|
||||
# even if the set has the maximum number of items set to 1. Might be
|
||||
# able to inspect the Schema and just do this conversion for that case.
|
||||
result = idlutils.get_column_value(record, self.column)
|
||||
if isinstance(result, list) and len(result) == 1:
|
||||
self.result = result[0]
|
||||
else:
|
||||
self.result = result
|
||||
|
||||
|
||||
class SetControllerCommand(BaseCommand):
|
||||
def __init__(self, api, bridge, targets):
|
||||
super(SetControllerCommand, self).__init__(api)
|
||||
self.bridge = bridge
|
||||
self.targets = targets
|
||||
|
||||
def run_idl(self, txn):
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge)
|
||||
controllers = []
|
||||
for target in self.targets:
|
||||
controller = txn.insert(self.api._tables['Controller'])
|
||||
controller.target = target
|
||||
controllers.append(controller)
|
||||
# Don't need to verify because we unconditionally overwrite
|
||||
br.controller = controllers
|
||||
|
||||
|
||||
class DelControllerCommand(BaseCommand):
|
||||
def __init__(self, api, bridge):
|
||||
super(DelControllerCommand, self).__init__(api)
|
||||
self.bridge = bridge
|
||||
|
||||
def run_idl(self, txn):
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge)
|
||||
br.controller = []
|
||||
|
||||
|
||||
class GetControllerCommand(BaseCommand):
|
||||
def __init__(self, api, bridge):
|
||||
super(GetControllerCommand, self).__init__(api)
|
||||
self.bridge = bridge
|
||||
|
||||
def run_idl(self, txn):
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge)
|
||||
self.result = [c.target for c in br.controller]
|
||||
|
||||
|
||||
class SetFailModeCommand(BaseCommand):
|
||||
def __init__(self, api, bridge, mode):
|
||||
super(SetFailModeCommand, self).__init__(api)
|
||||
self.bridge = bridge
|
||||
self.mode = mode
|
||||
|
||||
def run_idl(self, txn):
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge)
|
||||
br.fail_mode = self.mode
|
||||
|
||||
|
||||
class AddPortCommand(BaseCommand):
|
||||
def __init__(self, api, bridge, port, may_exist):
|
||||
super(AddPortCommand, self).__init__(api)
|
||||
self.bridge = bridge
|
||||
self.port = port
|
||||
self.may_exist = may_exist
|
||||
|
||||
def run_idl(self, txn):
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge)
|
||||
if self.may_exist:
|
||||
port = idlutils.row_by_value(self.api.idl, 'Port', 'name',
|
||||
self.port, None)
|
||||
if port:
|
||||
return
|
||||
port = txn.insert(self.api._tables['Port'])
|
||||
port.name = self.port
|
||||
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
|
||||
# This is a new port, so it won't have any existing interfaces
|
||||
port.interfaces = [iface]
|
||||
|
||||
|
||||
class DelPortCommand(BaseCommand):
|
||||
def __init__(self, api, port, bridge, if_exists):
|
||||
super(DelPortCommand, self).__init__(api)
|
||||
self.port = port
|
||||
self.bridge = bridge
|
||||
self.if_exists = if_exists
|
||||
|
||||
def run_idl(self, txn):
|
||||
try:
|
||||
port = idlutils.row_by_value(self.api.idl, 'Port', 'name',
|
||||
self.port)
|
||||
except idlutils.RowNotFound:
|
||||
if self.if_exists:
|
||||
return
|
||||
msg = _("Port %s does not exist") % self.port
|
||||
raise RuntimeError(msg)
|
||||
if self.bridge:
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name',
|
||||
self.bridge)
|
||||
else:
|
||||
br = next(b for b in self.api._tables['Bridge'].rows.values()
|
||||
if port in b.ports)
|
||||
|
||||
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.port, 'bridge': self.bridge
|
||||
}
|
||||
LOG.error(msg)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
try:
|
||||
br.delvalue('ports', port)
|
||||
except AttributeError: # OVS < 2.6
|
||||
br.verify('ports')
|
||||
ports = br.ports
|
||||
ports.remove(port)
|
||||
br.ports = ports
|
||||
|
||||
# The interface on the port will be cleaned up by ovsdb-server
|
||||
for interface in port.interfaces:
|
||||
interface.delete()
|
||||
port.delete()
|
||||
|
||||
|
||||
class ListPortsCommand(BaseCommand):
|
||||
def __init__(self, api, bridge):
|
||||
super(ListPortsCommand, self).__init__(api)
|
||||
self.bridge = bridge
|
||||
|
||||
def run_idl(self, txn):
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge)
|
||||
self.result = [p.name for p in br.ports if p.name != self.bridge]
|
||||
|
||||
|
||||
class ListIfacesCommand(BaseCommand):
|
||||
def __init__(self, api, bridge):
|
||||
super(ListIfacesCommand, self).__init__(api)
|
||||
self.bridge = bridge
|
||||
|
||||
def run_idl(self, txn):
|
||||
br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge)
|
||||
self.result = [i.name for p in br.ports if p.name != self.bridge
|
||||
for i in p.interfaces]
|
||||
|
||||
|
||||
class PortToBridgeCommand(BaseCommand):
|
||||
def __init__(self, api, name):
|
||||
super(PortToBridgeCommand, self).__init__(api)
|
||||
self.name = name
|
||||
|
||||
def run_idl(self, txn):
|
||||
# TODO(twilson) This is expensive!
|
||||
# This traversal of all ports could be eliminated by caching the bridge
|
||||
# name on the Port's external_id field
|
||||
# In fact, if we did that, the only place that uses to_br functions
|
||||
# could just add the external_id field to the conditions passed to find
|
||||
port = idlutils.row_by_value(self.api.idl, 'Port', 'name', self.name)
|
||||
bridges = self.api._tables['Bridge'].rows.values()
|
||||
self.result = next(br.name for br in bridges if port in br.ports)
|
||||
|
||||
|
||||
class InterfaceToBridgeCommand(BaseCommand):
|
||||
def __init__(self, api, name):
|
||||
super(InterfaceToBridgeCommand, self).__init__(api)
|
||||
self.name = name
|
||||
|
||||
def run_idl(self, txn):
|
||||
interface = idlutils.row_by_value(self.api.idl, 'Interface', 'name',
|
||||
self.name)
|
||||
ports = self.api._tables['Port'].rows.values()
|
||||
pname = next(
|
||||
port for port in ports if interface in port.interfaces)
|
||||
|
||||
bridges = self.api._tables['Bridge'].rows.values()
|
||||
self.result = next(br.name for br in bridges if pname in br.ports)
|
||||
|
||||
|
||||
class DbListCommand(BaseCommand):
|
||||
def __init__(self, api, table, records, columns, if_exists):
|
||||
super(DbListCommand, self).__init__(api)
|
||||
self.table = table
|
||||
self.columns = columns
|
||||
self.if_exists = if_exists
|
||||
self.records = records
|
||||
|
||||
def run_idl(self, txn):
|
||||
table_schema = self.api._tables[self.table]
|
||||
columns = self.columns or list(table_schema.columns.keys()) + ['_uuid']
|
||||
if self.records:
|
||||
row_uuids = []
|
||||
for record in self.records:
|
||||
try:
|
||||
row_uuids.append(idlutils.row_by_record(
|
||||
self.api.idl, self.table, record).uuid)
|
||||
except idlutils.RowNotFound:
|
||||
if self.if_exists:
|
||||
continue
|
||||
# NOTE(kevinbenton): this is converted to a RuntimeError
|
||||
# for compat with the vsctl version. It might make more
|
||||
# sense to change this to a RowNotFoundError in the future.
|
||||
raise RuntimeError(_(
|
||||
"Row doesn't exist in the DB. Request info: "
|
||||
"Table=%(table)s. Columns=%(columns)s. "
|
||||
"Records=%(records)s.") % {
|
||||
"table": self.table,
|
||||
"columns": self.columns,
|
||||
"records": self.records,
|
||||
})
|
||||
else:
|
||||
row_uuids = table_schema.rows.keys()
|
||||
self.result = [
|
||||
{
|
||||
c: idlutils.get_column_value(table_schema.rows[uuid], c)
|
||||
for c in columns
|
||||
}
|
||||
for uuid in row_uuids
|
||||
]
|
||||
|
||||
|
||||
class DbFindCommand(BaseCommand):
|
||||
def __init__(self, api, table, *conditions, **kwargs):
|
||||
super(DbFindCommand, self).__init__(api)
|
||||
self.table = self.api._tables[table]
|
||||
self.conditions = conditions
|
||||
self.columns = (kwargs.get('columns') or
|
||||
list(self.table.columns.keys()) + ['_uuid'])
|
||||
|
||||
def run_idl(self, txn):
|
||||
self.result = [
|
||||
{
|
||||
c: idlutils.get_column_value(r, c)
|
||||
for c in self.columns
|
||||
}
|
||||
for r in self.table.rows.values()
|
||||
if idlutils.row_match(r, self.conditions)
|
||||
]
|
||||
_deprecate._MovedGlobals(commands)
|
||||
|
||||
@@ -12,150 +12,36 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
from debtcollector import removals
|
||||
from debtcollector import moves
|
||||
from oslo_config import cfg
|
||||
from ovs.db import idl
|
||||
from ovs import poller
|
||||
import six
|
||||
from six.moves import queue as Queue
|
||||
from ovsdbapp.backend.ovs_idl import connection as _connection
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
import tenacity
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.agent.ovsdb.native import idlutils
|
||||
from neutron.agent.ovsdb.native import helpers
|
||||
|
||||
TransactionQueue = moves.moved_class(_connection.TransactionQueue,
|
||||
'TransactionQueue', __name__)
|
||||
Connection = moves.moved_class(_connection.Connection, 'Connection', __name__)
|
||||
|
||||
|
||||
class TransactionQueue(Queue.Queue, object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TransactionQueue, self).__init__(*args, **kwargs)
|
||||
alertpipe = os.pipe()
|
||||
# NOTE(ivasilevskaya) python 3 doesn't allow unbuffered I/O. Will get
|
||||
# around this constraint by using binary mode.
|
||||
self.alertin = os.fdopen(alertpipe[0], 'rb', 0)
|
||||
self.alertout = os.fdopen(alertpipe[1], 'wb', 0)
|
||||
def idl_factory():
|
||||
conn = cfg.CONF.OVS.ovsdb_connection
|
||||
schema_name = 'Open_vSwitch'
|
||||
try:
|
||||
helper = idlutils.get_schema_helper(conn, schema_name)
|
||||
except Exception:
|
||||
helpers.enable_connection_uri(conn)
|
||||
|
||||
def get_nowait(self, *args, **kwargs):
|
||||
try:
|
||||
result = super(TransactionQueue, self).get_nowait(*args, **kwargs)
|
||||
except Queue.Empty:
|
||||
return None
|
||||
self.alertin.read(1)
|
||||
return result
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=0.01),
|
||||
stop=tenacity.stop_after_delay(1),
|
||||
reraise=True)
|
||||
def do_get_schema_helper():
|
||||
return idlutils.get_schema_helper(conn, schema_name)
|
||||
|
||||
def put(self, *args, **kwargs):
|
||||
super(TransactionQueue, self).put(*args, **kwargs)
|
||||
self.alertout.write(six.b('X'))
|
||||
self.alertout.flush()
|
||||
helper = do_get_schema_helper()
|
||||
|
||||
@property
|
||||
def alert_fileno(self):
|
||||
return self.alertin.fileno()
|
||||
|
||||
|
||||
class Connection(object):
|
||||
__rm_args = {'version': 'Ocata', 'removal_version': 'Pike',
|
||||
'message': _('Use an idl_factory function instead')}
|
||||
|
||||
@removals.removed_kwarg('connection', **__rm_args)
|
||||
@removals.removed_kwarg('schema_name', **__rm_args)
|
||||
@removals.removed_kwarg('idl_class', **__rm_args)
|
||||
def __init__(self, connection=None, timeout=None, schema_name=None,
|
||||
idl_class=None, idl_factory=None):
|
||||
"""Create a connection to an OVSDB server using the OVS IDL
|
||||
|
||||
:param connection: (deprecated) An OVSDB connection string
|
||||
:param timeout: The timeout value for OVSDB operations (required)
|
||||
:param schema_name: (deprecated) The name ovs the OVSDB schema to use
|
||||
:param idl_class: (deprecated) An Idl subclass. Defaults to idl.Idl
|
||||
:param idl_factory: A factory function that produces an Idl instance
|
||||
|
||||
The signature of this class is changing. It is recommended to pass in
|
||||
a timeout and idl_factory
|
||||
"""
|
||||
assert timeout is not None
|
||||
self.idl = None
|
||||
self.timeout = timeout
|
||||
self.txns = TransactionQueue(1)
|
||||
self.lock = threading.Lock()
|
||||
if idl_factory:
|
||||
if connection or schema_name:
|
||||
raise TypeError(_('Connection: Takes either idl_factory, or '
|
||||
'connection and schema_name. Both given'))
|
||||
self.idl_factory = idl_factory
|
||||
else:
|
||||
if not connection or not schema_name:
|
||||
raise TypeError(_('Connection: Takes either idl_factory, or '
|
||||
'connection and schema_name. Neither given'))
|
||||
self.idl_factory = self._idl_factory
|
||||
self.connection = connection
|
||||
self.schema_name = schema_name
|
||||
self.idl_class = idl_class or idl.Idl
|
||||
self._schema_filter = None
|
||||
|
||||
@removals.remove(**__rm_args)
|
||||
def _idl_factory(self):
|
||||
helper = self.get_schema_helper()
|
||||
self.update_schema_helper(helper)
|
||||
return self.idl_class(self.connection, helper)
|
||||
|
||||
@removals.removed_kwarg('table_name_list', **__rm_args)
|
||||
def start(self, table_name_list=None):
|
||||
"""
|
||||
:param table_name_list: A list of table names for schema_helper to
|
||||
register. When this parameter is given, schema_helper will only
|
||||
register tables which name are in list. Otherwise,
|
||||
schema_helper will register all tables for given schema_name as
|
||||
default.
|
||||
"""
|
||||
self._schema_filter = table_name_list
|
||||
with self.lock:
|
||||
if self.idl is not None:
|
||||
return
|
||||
|
||||
self.idl = self.idl_factory()
|
||||
idlutils.wait_for_change(self.idl, self.timeout)
|
||||
self.poller = poller.Poller()
|
||||
self.thread = threading.Thread(target=self.run)
|
||||
self.thread.setDaemon(True)
|
||||
self.thread.start()
|
||||
|
||||
@removals.remove(
|
||||
version='Ocata', removal_version='Pike',
|
||||
message=_("Use idlutils.get_schema_helper(conn, schema, retry=True)"))
|
||||
def get_schema_helper(self):
|
||||
"""Retrieve the schema helper object from OVSDB"""
|
||||
return idlutils.get_schema_helper(self.connection, self.schema_name,
|
||||
retry=True)
|
||||
|
||||
@removals.remove(
|
||||
version='Ocata', removal_version='Pike',
|
||||
message=_("Use an idl_factory and ovs.db.SchemaHelper for filtering"))
|
||||
def update_schema_helper(self, helper):
|
||||
if self._schema_filter:
|
||||
for table_name in self._schema_filter:
|
||||
helper.register_table(table_name)
|
||||
else:
|
||||
helper.register_all()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
self.idl.wait(self.poller)
|
||||
self.poller.fd_wait(self.txns.alert_fileno, poller.POLLIN)
|
||||
#TODO(jlibosva): Remove next line once losing connection to ovsdb
|
||||
# is solved.
|
||||
self.poller.timer_wait(self.timeout * 1000)
|
||||
self.poller.block()
|
||||
self.idl.run()
|
||||
txn = self.txns.get_nowait()
|
||||
if txn is not None:
|
||||
try:
|
||||
txn.results.put(txn.do_commit())
|
||||
except Exception as ex:
|
||||
er = idlutils.ExceptionResult(ex=ex,
|
||||
tb=traceback.format_exc())
|
||||
txn.results.put(er)
|
||||
self.txns.task_done()
|
||||
|
||||
def queue_txn(self, txn):
|
||||
self.txns.put(txn)
|
||||
# TODO(twilson) We should still select only the tables/columns we use
|
||||
helper.register_all()
|
||||
return idl.Idl(conn, helper)
|
||||
|
||||
@@ -12,31 +12,17 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
import functools
|
||||
|
||||
from neutron.agent.ovsdb import api as ovsdb
|
||||
from debtcollector import moves
|
||||
from ovsdbapp.schema.open_vswitch import helpers
|
||||
|
||||
cfg.CONF.import_opt('ovs_vsctl_timeout', 'neutron.agent.common.ovs_lib')
|
||||
from neutron.agent.common import utils
|
||||
|
||||
_connection_to_manager_uri = moves.moved_function(
|
||||
helpers._connection_to_manager_uri,
|
||||
'_connection_to_manager_uri', __name__)
|
||||
|
||||
def _connection_to_manager_uri(conn_uri):
|
||||
proto, addr = conn_uri.split(':', 1)
|
||||
if ':' in addr:
|
||||
ip, port = addr.split(':', 1)
|
||||
return 'p%s:%s:%s' % (proto, port, ip)
|
||||
else:
|
||||
return 'p%s:%s' % (proto, addr)
|
||||
|
||||
|
||||
def enable_connection_uri(conn_uri, set_timeout=False):
|
||||
class OvsdbVsctlContext(object):
|
||||
vsctl_timeout = cfg.CONF.ovs_vsctl_timeout
|
||||
|
||||
manager_uri = _connection_to_manager_uri(conn_uri)
|
||||
api = ovsdb.API.get(OvsdbVsctlContext, 'vsctl')
|
||||
with api.transaction() as txn:
|
||||
txn.add(api.add_manager(manager_uri))
|
||||
if set_timeout:
|
||||
timeout = cfg.CONF.ovs_vsctl_timeout * 1000
|
||||
txn.add(api.db_set('Manager', manager_uri,
|
||||
('inactivity_probe', timeout)))
|
||||
enable_connection_uri = functools.partial(
|
||||
helpers.enable_connection_uri, execute=utils.execute, run_as_root=True,
|
||||
log_fail_as_error=False, check_exit_code=False)
|
||||
|
||||
@@ -12,275 +12,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
|
||||
from neutron_lib import exceptions
|
||||
from ovs.db import idl
|
||||
from ovs import jsonrpc
|
||||
from ovs import poller
|
||||
from ovs import stream
|
||||
import six
|
||||
import tenacity
|
||||
from neutron.common import _deprecate
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.agent.ovsdb import api
|
||||
from neutron.agent.ovsdb.native import helpers
|
||||
|
||||
|
||||
RowLookup = collections.namedtuple('RowLookup',
|
||||
['table', 'column', 'uuid_column'])
|
||||
|
||||
# Tables with no index in OVSDB and special record lookup rules
|
||||
_LOOKUP_TABLE = {
|
||||
'Controller': RowLookup('Bridge', 'name', 'controller'),
|
||||
'Flow_Table': RowLookup('Flow_Table', 'name', None),
|
||||
'IPFIX': RowLookup('Bridge', 'name', 'ipfix'),
|
||||
'Mirror': RowLookup('Mirror', 'name', None),
|
||||
'NetFlow': RowLookup('Bridge', 'name', 'netflow'),
|
||||
'Open_vSwitch': RowLookup('Open_vSwitch', None, None),
|
||||
'QoS': RowLookup('Port', 'name', 'qos'),
|
||||
'Queue': RowLookup(None, None, None),
|
||||
'sFlow': RowLookup('Bridge', 'name', 'sflow'),
|
||||
'SSL': RowLookup('Open_vSwitch', None, 'ssl'),
|
||||
}
|
||||
|
||||
_NO_DEFAULT = object()
|
||||
|
||||
|
||||
class RowNotFound(exceptions.NeutronException):
|
||||
message = _("Cannot find %(table)s with %(col)s=%(match)s")
|
||||
|
||||
|
||||
def row_by_value(idl_, table, column, match, default=_NO_DEFAULT):
|
||||
"""Lookup an IDL row in a table by column/value"""
|
||||
tab = idl_.tables[table]
|
||||
for r in tab.rows.values():
|
||||
if getattr(r, column) == match:
|
||||
return r
|
||||
if default is not _NO_DEFAULT:
|
||||
return default
|
||||
raise RowNotFound(table=table, col=column, match=match)
|
||||
|
||||
|
||||
def row_by_record(idl_, table, record):
|
||||
t = idl_.tables[table]
|
||||
try:
|
||||
if isinstance(record, uuid.UUID):
|
||||
return t.rows[record]
|
||||
uuid_ = uuid.UUID(record)
|
||||
return t.rows[uuid_]
|
||||
except ValueError:
|
||||
# Not a UUID string, continue lookup by other means
|
||||
pass
|
||||
except KeyError:
|
||||
raise RowNotFound(table=table, col='uuid', match=record)
|
||||
|
||||
rl = _LOOKUP_TABLE.get(table, RowLookup(table, get_index_column(t), None))
|
||||
# no table means uuid only, no column means lookup table only has one row
|
||||
if rl.table is None:
|
||||
raise ValueError(_("Table %s can only be queried by UUID") % table)
|
||||
if rl.column is None:
|
||||
return next(iter(t.rows.values()))
|
||||
row = row_by_value(idl_, rl.table, rl.column, record)
|
||||
if rl.uuid_column:
|
||||
rows = getattr(row, rl.uuid_column)
|
||||
if len(rows) != 1:
|
||||
raise RowNotFound(table=table, col=_('record'), match=record)
|
||||
row = rows[0]
|
||||
return row
|
||||
|
||||
|
||||
class ExceptionResult(object):
|
||||
def __init__(self, ex, tb):
|
||||
self.ex = ex
|
||||
self.tb = tb
|
||||
|
||||
|
||||
def _get_schema_helper(connection, schema_name):
|
||||
err, strm = stream.Stream.open_block(
|
||||
stream.Stream.open(connection))
|
||||
if err:
|
||||
raise Exception(_("Could not connect to %s") % connection)
|
||||
rpc = jsonrpc.Connection(strm)
|
||||
req = jsonrpc.Message.create_request('get_schema', [schema_name])
|
||||
err, resp = rpc.transact_block(req)
|
||||
rpc.close()
|
||||
if err:
|
||||
raise Exception(_("Could not retrieve schema from %(conn)s: "
|
||||
"%(err)s") % {'conn': connection,
|
||||
'err': os.strerror(err)})
|
||||
elif resp.error:
|
||||
raise Exception(resp.error)
|
||||
return idl.SchemaHelper(None, resp.result)
|
||||
|
||||
|
||||
def get_schema_helper(connection, schema_name, retry=True,
|
||||
try_add_manager=True):
|
||||
try:
|
||||
return _get_schema_helper(connection, schema_name)
|
||||
except Exception:
|
||||
if not retry:
|
||||
raise
|
||||
# We may have failed due to set-manager not being called
|
||||
if try_add_manager:
|
||||
helpers.enable_connection_uri(connection, set_timeout=True)
|
||||
|
||||
# There is a small window for a race, so retry up to a second
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=0.01),
|
||||
stop=tenacity.stop_after_delay(1),
|
||||
reraise=True)
|
||||
def do_get_schema_helper():
|
||||
return _get_schema_helper(connection, schema_name)
|
||||
|
||||
return do_get_schema_helper()
|
||||
|
||||
|
||||
def wait_for_change(_idl, timeout, seqno=None):
|
||||
if seqno is None:
|
||||
seqno = _idl.change_seqno
|
||||
stop = time.time() + timeout
|
||||
while _idl.change_seqno == seqno and not _idl.run():
|
||||
ovs_poller = poller.Poller()
|
||||
_idl.wait(ovs_poller)
|
||||
ovs_poller.timer_wait(timeout * 1000)
|
||||
ovs_poller.block()
|
||||
if time.time() > stop:
|
||||
raise Exception(_("Timeout"))
|
||||
|
||||
|
||||
def get_column_value(row, col):
|
||||
"""Retrieve column value from the given row.
|
||||
|
||||
If column's type is optional, the value will be returned as a single
|
||||
element instead of a list of length 1.
|
||||
"""
|
||||
if col == '_uuid':
|
||||
val = row.uuid
|
||||
else:
|
||||
val = getattr(row, col)
|
||||
|
||||
# Idl returns lists of Rows where ovs-vsctl returns lists of UUIDs
|
||||
if isinstance(val, list) and len(val):
|
||||
if isinstance(val[0], idl.Row):
|
||||
val = [v.uuid for v in val]
|
||||
col_type = row._table.columns[col].type
|
||||
# ovs-vsctl treats lists of 1 as single results
|
||||
if col_type.is_optional():
|
||||
val = val[0]
|
||||
return val
|
||||
|
||||
|
||||
def condition_match(row, condition):
|
||||
"""Return whether a condition matches a row
|
||||
|
||||
:param row: An OVSDB Row
|
||||
:param condition: A 3-tuple containing (column, operation, match)
|
||||
"""
|
||||
col, op, match = condition
|
||||
val = get_column_value(row, col)
|
||||
|
||||
# both match and val are primitive types, so type can be used for type
|
||||
# equality here.
|
||||
if type(match) is not type(val):
|
||||
# Types of 'val' and 'match' arguments MUST match in all cases with 2
|
||||
# exceptions:
|
||||
# - 'match' is an empty list and column's type is optional;
|
||||
# - 'value' is an empty and column's type is optional
|
||||
if (not all([match, val]) and
|
||||
row._table.columns[col].type.is_optional()):
|
||||
# utilize the single elements comparison logic
|
||||
if match == []:
|
||||
match = None
|
||||
elif val == []:
|
||||
val = None
|
||||
else:
|
||||
# no need to process any further
|
||||
raise ValueError(
|
||||
_("Column type and condition operand do not match"))
|
||||
|
||||
matched = True
|
||||
|
||||
# TODO(twilson) Implement other operators and type comparisons
|
||||
# ovs_lib only uses dict '=' and '!=' searches for now
|
||||
if isinstance(match, dict):
|
||||
for key in match:
|
||||
if op == '=':
|
||||
if (key not in val or match[key] != val[key]):
|
||||
matched = False
|
||||
break
|
||||
elif op == '!=':
|
||||
if key not in val or match[key] == val[key]:
|
||||
matched = False
|
||||
break
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
elif isinstance(match, list):
|
||||
# According to rfc7047, lists support '=' and '!='
|
||||
# (both strict and relaxed). Will follow twilson's dict comparison
|
||||
# and implement relaxed version (excludes/includes as per standard)
|
||||
if op == "=":
|
||||
if not all([val, match]):
|
||||
return val == match
|
||||
for elem in set(match):
|
||||
if elem not in val:
|
||||
matched = False
|
||||
break
|
||||
elif op == '!=':
|
||||
if not all([val, match]):
|
||||
return val != match
|
||||
for elem in set(match):
|
||||
if elem in val:
|
||||
matched = False
|
||||
break
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
else:
|
||||
if op == '=':
|
||||
if val != match:
|
||||
matched = False
|
||||
elif op == '!=':
|
||||
if val == match:
|
||||
matched = False
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
return matched
|
||||
|
||||
|
||||
def row_match(row, conditions):
|
||||
"""Return whether the row matches the list of conditions"""
|
||||
return all(condition_match(row, cond) for cond in conditions)
|
||||
|
||||
|
||||
def get_index_column(table):
|
||||
if len(table.indexes) == 1:
|
||||
idx = table.indexes[0]
|
||||
if len(idx) == 1:
|
||||
return idx[0].name
|
||||
|
||||
|
||||
def db_replace_record(obj):
|
||||
"""Replace any api.Command objects with their results
|
||||
|
||||
This method should leave obj untouched unless the object contains an
|
||||
api.Command object.
|
||||
"""
|
||||
if isinstance(obj, collections.Mapping):
|
||||
for k, v in obj.items():
|
||||
if isinstance(v, api.Command):
|
||||
obj[k] = v.result
|
||||
elif (isinstance(obj, collections.Sequence)
|
||||
and not isinstance(obj, six.string_types)):
|
||||
for i, v in enumerate(obj):
|
||||
if isinstance(v, api.Command):
|
||||
try:
|
||||
obj[i] = v.result
|
||||
except TypeError:
|
||||
# NOTE(twilson) If someone passes a tuple, then just return
|
||||
# a tuple with the Commands replaced with their results
|
||||
return type(obj)(getattr(v, "result", v) for v in obj)
|
||||
elif isinstance(obj, api.Command):
|
||||
obj = obj.result
|
||||
return obj
|
||||
_deprecate._MovedGlobals(idlutils)
|
||||
|
||||
@@ -12,19 +12,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from ovs import vlog
|
||||
from ovsdbapp.backend.ovs_idl import vlog
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
from neutron.common import _deprecate
|
||||
|
||||
|
||||
def use_oslo_logger():
|
||||
"""Replace the OVS IDL logger functions with our logger"""
|
||||
|
||||
# NOTE(twilson) Replace functions directly instead of subclassing so that
|
||||
# debug messages contain the correct function/filename/line information
|
||||
vlog.Vlog.emer = LOG.critical
|
||||
vlog.Vlog.err = LOG.error
|
||||
vlog.Vlog.warn = LOG.warning
|
||||
vlog.Vlog.info = LOG.info
|
||||
vlog.Vlog.dbg = LOG.debug
|
||||
_deprecate._MovedGlobals(vlog)
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
|
||||
import mock
|
||||
|
||||
from ovsdbapp import exceptions as exc
|
||||
from ovsdbapp.schema.open_vswitch import impl_idl
|
||||
|
||||
from neutron.agent.common import ovs_lib
|
||||
from neutron.agent.ovsdb import api
|
||||
from neutron.agent.ovsdb import impl_idl
|
||||
from neutron.common import utils
|
||||
from neutron.tests.common import net_helpers
|
||||
from neutron.tests.functional import base
|
||||
@@ -26,7 +27,7 @@ from neutron.tests.functional import base
|
||||
# NOTE(twilson) functools.partial does not work for this
|
||||
def trpatch(*args, **kwargs):
|
||||
def wrapped(fn):
|
||||
return mock.patch.object(impl_idl.NeutronOVSDBTransaction,
|
||||
return mock.patch.object(impl_idl.OvsVsctlTransaction,
|
||||
*args, **kwargs)(fn)
|
||||
return wrapped
|
||||
|
||||
@@ -39,8 +40,8 @@ class ImplIdlTestCase(base.BaseSudoTestCase):
|
||||
self.brname = utils.get_rand_device_name(net_helpers.BR_PREFIX)
|
||||
# Make sure exceptions pass through by calling do_post_commit directly
|
||||
mock.patch.object(
|
||||
impl_idl.NeutronOVSDBTransaction, "post_commit",
|
||||
side_effect=impl_idl.NeutronOVSDBTransaction.do_post_commit,
|
||||
impl_idl.OvsVsctlTransaction, "post_commit",
|
||||
side_effect=impl_idl.OvsVsctlTransaction.do_post_commit,
|
||||
autospec=True).start()
|
||||
|
||||
def _add_br(self):
|
||||
@@ -65,11 +66,12 @@ class ImplIdlTestCase(base.BaseSudoTestCase):
|
||||
@trpatch("post_commit_failed_interfaces", return_value=["failed_if1"])
|
||||
@trpatch("timeout_exceeded", return_value=False)
|
||||
def test_post_commit_vswitchd_completed_failures(self, *args):
|
||||
self.assertRaises(impl_idl.VswitchdInterfaceAddException, self._add_br)
|
||||
self.assertRaises(impl_idl.VswitchdInterfaceAddException,
|
||||
self._add_br)
|
||||
|
||||
@trpatch("vswitchd_has_completed", return_value=False)
|
||||
def test_post_commit_vswitchd_incomplete_timeout(self, *args):
|
||||
# Due to timing issues we may rarely hit the global timeout, which
|
||||
# raises RuntimeError to match the vsctl implementation
|
||||
self.ovs.vsctl_timeout = 3
|
||||
self.assertRaises((api.TimeoutException, RuntimeError), self._add_br)
|
||||
self.ovs.ovsdb.connection.timeout = 3
|
||||
self.assertRaises((exc.TimeoutException, RuntimeError), self._add_br)
|
||||
|
||||
@@ -12,79 +12,22 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import eventlet
|
||||
import mock
|
||||
from ovs.db import idl
|
||||
from ovs import poller
|
||||
|
||||
from neutron.agent.ovsdb.native import connection
|
||||
from neutron.agent.ovsdb.native import idlutils
|
||||
from ovsdbapp.backend.ovs_idl import connection
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
|
||||
from neutron.agent.ovsdb.native import connection as native_conn
|
||||
from neutron.agent.ovsdb.native import helpers
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class TestOVSNativeConnection(base.BaseTestCase):
|
||||
|
||||
@mock.patch.object(connection, 'TransactionQueue')
|
||||
@mock.patch.object(idlutils, 'get_schema_helper')
|
||||
@mock.patch.object(idl, 'Idl')
|
||||
@mock.patch.object(idlutils, 'wait_for_change')
|
||||
def _test_start(self, wfc, idl, gsh, tq, table_name_list=None):
|
||||
gsh.return_value = helper = mock.Mock()
|
||||
self.connection = connection.Connection(
|
||||
mock.Mock(), mock.Mock(), mock.Mock())
|
||||
with mock.patch.object(poller, 'Poller') as poller_mock,\
|
||||
mock.patch('threading.Thread'):
|
||||
poller_mock.return_value.block.side_effect = eventlet.sleep
|
||||
self.connection.start(table_name_list=table_name_list)
|
||||
reg_all_called = table_name_list is None
|
||||
reg_table_called = table_name_list is not None
|
||||
self.assertEqual(reg_all_called, helper.register_all.called)
|
||||
self.assertEqual(reg_table_called, helper.register_table.called)
|
||||
|
||||
def test_start_without_table_name_list(self):
|
||||
self._test_start()
|
||||
|
||||
def test_start_with_table_name_list(self):
|
||||
self._test_start(table_name_list=['fake-table1', 'fake-table2'])
|
||||
|
||||
@mock.patch.object(connection, 'TransactionQueue')
|
||||
@mock.patch.object(idl, 'Idl')
|
||||
@mock.patch.object(idlutils, 'wait_for_change')
|
||||
def test_start_call_graph(self, wait_for_change, idl, transaction_queue):
|
||||
self.connection = connection.Connection(
|
||||
mock.sentinel, mock.sentinel, mock.sentinel)
|
||||
self.connection.get_schema_helper = mock.Mock()
|
||||
helper = self.connection.get_schema_helper.return_value
|
||||
self.connection.update_schema_helper = mock.Mock()
|
||||
with mock.patch.object(poller, 'Poller') as poller_mock,\
|
||||
mock.patch('threading.Thread'):
|
||||
poller_mock.return_value.block.side_effect = eventlet.sleep
|
||||
self.connection.start()
|
||||
self.connection.get_schema_helper.assert_called_once_with()
|
||||
self.connection.update_schema_helper.assert_called_once_with(helper)
|
||||
|
||||
def test_transaction_queue_init(self):
|
||||
# a test to cover py34 failure during initialization (LP Bug #1580270)
|
||||
# make sure no ValueError: can't have unbuffered text I/O is raised
|
||||
connection.TransactionQueue()
|
||||
|
||||
@mock.patch.object(connection, 'TransactionQueue')
|
||||
@mock.patch.object(idlutils, 'get_schema_helper')
|
||||
@mock.patch.object(idlutils, 'wait_for_change')
|
||||
def test_start_with_idl_class(self, wait_for_change, get_schema_helper,
|
||||
transaction_queue):
|
||||
idl_class = mock.Mock()
|
||||
self.connection = connection.Connection(
|
||||
mock.sentinel, mock.sentinel, mock.sentinel, idl_class=idl_class)
|
||||
idl_instance = idl_class.return_value
|
||||
self.connection.start()
|
||||
self.assertEqual(idl_instance, self.connection.idl)
|
||||
|
||||
@mock.patch.object(connection, 'threading')
|
||||
@mock.patch.object(connection.idlutils, 'wait_for_change')
|
||||
@mock.patch.object(connection, 'idl')
|
||||
@mock.patch.object(idlutils.helpers, 'enable_connection_uri')
|
||||
@mock.patch.object(connection.idlutils, '_get_schema_helper')
|
||||
@mock.patch.object(idlutils, 'wait_for_change')
|
||||
@mock.patch.object(native_conn, 'idl')
|
||||
@mock.patch.object(helpers, 'enable_connection_uri')
|
||||
@mock.patch.object(idlutils, 'get_schema_helper')
|
||||
def test_do_get_schema_helper_retry(self, mock_get_schema_helper,
|
||||
mock_enable_conn,
|
||||
mock_idl,
|
||||
@@ -94,8 +37,8 @@ class TestOVSNativeConnection(base.BaseTestCase):
|
||||
# raise until 3rd retry attempt
|
||||
mock_get_schema_helper.side_effect = [Exception(), Exception(),
|
||||
mock_helper]
|
||||
conn = connection.Connection(
|
||||
mock.Mock(), mock.Mock(), mock.Mock())
|
||||
conn = connection.Connection(idl_factory=native_conn.idl_factory,
|
||||
timeout=mock.Mock())
|
||||
conn.start()
|
||||
self.assertEqual(3, len(mock_get_schema_helper.mock_calls))
|
||||
mock_helper.register_all.assert_called_once_with()
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
# Copyright 2016, Mirantis Inc.
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from neutron.agent.ovsdb import api
|
||||
from neutron.agent.ovsdb.native import idlutils
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class MockColumn(object):
|
||||
def __init__(self, name, type, is_optional=False, test_value=None):
|
||||
self.name = name
|
||||
self.type = mock.MagicMock(
|
||||
**{"key.type.name": type,
|
||||
"is_optional": mock.Mock(return_value=is_optional),
|
||||
})
|
||||
# for test purposes only to operate with some values in condition_match
|
||||
# testcase
|
||||
self.test_value = test_value
|
||||
|
||||
|
||||
class MockTable(object):
|
||||
def __init__(self, name, *columns):
|
||||
# columns is a list of tuples (col_name, col_type)
|
||||
self.name = name
|
||||
self.columns = {c.name: c for c in columns}
|
||||
|
||||
|
||||
class MockRow(object):
|
||||
def __init__(self, table):
|
||||
self._table = table
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self._table.columns:
|
||||
return self._table.columns[attr].test_value
|
||||
return super(MockRow, self).__getattr__(attr)
|
||||
|
||||
|
||||
class MockCommand(api.Command):
|
||||
def __init__(self, result):
|
||||
self.result = result
|
||||
|
||||
def execute(self, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class TestIdlUtils(base.BaseTestCase):
|
||||
def test_condition_match(self):
|
||||
"""
|
||||
Make sure that the function respects the following:
|
||||
* if column type is_optional and value is a single element, value is
|
||||
transformed to a length-1-list
|
||||
* any other value is returned as it is, no type convertions
|
||||
"""
|
||||
table = MockTable("SomeTable",
|
||||
MockColumn("tag", "integer", is_optional=True,
|
||||
test_value=[42]),
|
||||
MockColumn("num", "integer", is_optional=True,
|
||||
test_value=[]),
|
||||
MockColumn("ids", "integer", is_optional=False,
|
||||
test_value=42),
|
||||
MockColumn("comments", "string",
|
||||
test_value=["a", "b", "c"]),
|
||||
MockColumn("status", "string",
|
||||
test_value="sorry for inconvenience"))
|
||||
row = MockRow(table=table)
|
||||
self.assertTrue(idlutils.condition_match(row, ("tag", "=", 42)))
|
||||
# optional types can be compared only as single elements
|
||||
self.assertRaises(ValueError,
|
||||
idlutils.condition_match, row, ("tag", "!=", [42]))
|
||||
# empty list comparison is ok for optional types though
|
||||
self.assertTrue(idlutils.condition_match(row, ("tag", "!=", [])))
|
||||
self.assertTrue(idlutils.condition_match(row, ("num", "=", [])))
|
||||
# value = [] may be compared to a single elem if optional column type
|
||||
self.assertTrue(idlutils.condition_match(row, ("num", "!=", 42)))
|
||||
# no type conversion for non optional types
|
||||
self.assertTrue(idlutils.condition_match(row, ("ids", "=", 42)))
|
||||
self.assertTrue(idlutils.condition_match(
|
||||
row, ("status", "=", "sorry for inconvenience")))
|
||||
self.assertFalse(idlutils.condition_match(
|
||||
row, ("status", "=", "sorry")))
|
||||
# bad types
|
||||
self.assertRaises(ValueError,
|
||||
idlutils.condition_match, row, ("ids", "=", "42"))
|
||||
self.assertRaises(ValueError,
|
||||
idlutils.condition_match, row, ("ids", "!=", "42"))
|
||||
self.assertRaises(ValueError,
|
||||
idlutils.condition_match, row,
|
||||
("ids", "!=", {"a": "b"}))
|
||||
# non optional list types are kept as they are
|
||||
self.assertTrue(idlutils.condition_match(
|
||||
row, ("comments", "=", ["c", "b", "a"])))
|
||||
# also true because list comparison is relaxed
|
||||
self.assertTrue(idlutils.condition_match(
|
||||
row, ("comments", "=", ["c", "b"])))
|
||||
self.assertTrue(idlutils.condition_match(
|
||||
row, ("comments", "!=", ["d"])))
|
||||
|
||||
def test_db_replace_record_dict(self):
|
||||
obj = {'a': 1, 'b': 2}
|
||||
self.assertIs(obj, idlutils.db_replace_record(obj))
|
||||
|
||||
def test_db_replace_record_dict_cmd(self):
|
||||
obj = {'a': 1, 'b': MockCommand(2)}
|
||||
res = {'a': 1, 'b': 2}
|
||||
self.assertEqual(res, idlutils.db_replace_record(obj))
|
||||
|
||||
def test_db_replace_record_list(self):
|
||||
obj = [1, 2, 3]
|
||||
self.assertIs(obj, idlutils.db_replace_record(obj))
|
||||
|
||||
def test_db_replace_record_list_cmd(self):
|
||||
obj = [1, MockCommand(2), 3]
|
||||
res = [1, 2, 3]
|
||||
self.assertEqual(res, idlutils.db_replace_record(obj))
|
||||
|
||||
def test_db_replace_record_tuple(self):
|
||||
obj = (1, 2, 3)
|
||||
self.assertIs(obj, idlutils.db_replace_record(obj))
|
||||
|
||||
def test_db_replace_record_tuple_cmd(self):
|
||||
obj = (1, MockCommand(2), 3)
|
||||
res = (1, 2, 3)
|
||||
self.assertEqual(res, idlutils.db_replace_record(obj))
|
||||
|
||||
def test_db_replace_record(self):
|
||||
obj = "test"
|
||||
self.assertIs(obj, idlutils.db_replace_record(obj))
|
||||
|
||||
def test_db_replace_record_cmd(self):
|
||||
obj = MockCommand("test")
|
||||
self.assertEqual("test", idlutils.db_replace_record(obj))
|
||||
|
||||
def test_row_by_record(self):
|
||||
FAKE_RECORD = 'fake_record'
|
||||
mock_idl_ = mock.MagicMock()
|
||||
mock_table = mock.MagicMock(
|
||||
rows={mock.sentinel.row: mock.sentinel.row_value})
|
||||
mock_idl_.tables = {mock.sentinel.table_name: mock_table}
|
||||
|
||||
res = idlutils.row_by_record(mock_idl_,
|
||||
mock.sentinel.table_name,
|
||||
FAKE_RECORD)
|
||||
self.assertEqual(mock.sentinel.row_value, res)
|
||||
|
||||
@mock.patch.object(idlutils.helpers, 'enable_connection_uri')
|
||||
@mock.patch.object(idlutils, '_get_schema_helper')
|
||||
def test_get_schema_helper_succeed_once(self,
|
||||
mock_get_schema_helper,
|
||||
mock_enable_conn):
|
||||
mock_get_schema_helper.return_value = mock.Mock()
|
||||
|
||||
idlutils.get_schema_helper(mock.Mock(), mock.Mock())
|
||||
self.assertEqual(1, mock_get_schema_helper.call_count)
|
||||
self.assertEqual(0, mock_enable_conn.call_count)
|
||||
|
||||
@mock.patch.object(idlutils.helpers, 'enable_connection_uri')
|
||||
@mock.patch.object(idlutils, '_get_schema_helper')
|
||||
def test_get_schema_helper_fail_and_then_succeed(self,
|
||||
mock_get_schema_helper,
|
||||
mock_enable_conn):
|
||||
# raise until 3rd retry attempt
|
||||
mock_get_schema_helper.side_effect = [Exception(), Exception(),
|
||||
mock.Mock()]
|
||||
|
||||
idlutils.get_schema_helper(mock.Mock(), mock.Mock())
|
||||
self.assertEqual(3, mock_get_schema_helper.call_count)
|
||||
self.assertEqual(1, mock_enable_conn.call_count)
|
||||
|
||||
@mock.patch.object(idlutils.helpers, 'enable_connection_uri')
|
||||
@mock.patch.object(idlutils, '_get_schema_helper')
|
||||
def test_get_schema_helper_not_add_manager_and_timeout(
|
||||
self, mock_get_schema_helper, mock_enable_conn):
|
||||
# raise always
|
||||
mock_get_schema_helper.side_effect = RuntimeError()
|
||||
|
||||
self.assertRaises(RuntimeError, idlutils.get_schema_helper,
|
||||
mock.Mock(), mock.Mock(), retry=True,
|
||||
try_add_manager=False)
|
||||
self.assertEqual(8, mock_get_schema_helper.call_count)
|
||||
self.assertEqual(0, mock_enable_conn.call_count)
|
||||
|
||||
@mock.patch.object(idlutils.helpers, 'enable_connection_uri')
|
||||
@mock.patch.object(idlutils, '_get_schema_helper')
|
||||
def test_get_schema_helper_not_retry(
|
||||
self, mock_get_schema_helper, mock_enable_conn):
|
||||
# raise always
|
||||
mock_get_schema_helper.side_effect = RuntimeError()
|
||||
|
||||
self.assertRaises(RuntimeError, idlutils.get_schema_helper,
|
||||
mock.Mock(), mock.Mock(), retry=False)
|
||||
self.assertEqual(1, mock_get_schema_helper.call_count)
|
||||
@@ -1,137 +0,0 @@
|
||||
# Copyright (c) 2017 Red Hat, Inc.
|
||||
#
|
||||
# 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 mock
|
||||
import testtools
|
||||
|
||||
from neutron.agent.ovsdb import api
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class FakeTransaction(object):
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, tb):
|
||||
self.commit()
|
||||
|
||||
def commit(self):
|
||||
"""Serves just for mock."""
|
||||
|
||||
|
||||
class TestingAPI(api.API):
|
||||
def create_transaction(self, check_error=False, log_errors=True, **kwargs):
|
||||
return FakeTransaction()
|
||||
|
||||
def add_manager(self, connection_uri):
|
||||
pass
|
||||
|
||||
def get_manager(self):
|
||||
pass
|
||||
|
||||
def remove_manager(self, connection_uri):
|
||||
pass
|
||||
|
||||
def add_br(self, name, may_exist=True, datapath_type=None):
|
||||
pass
|
||||
|
||||
def del_br(self, name, if_exists=True):
|
||||
pass
|
||||
|
||||
def br_exists(self, name):
|
||||
pass
|
||||
|
||||
def port_to_br(self, name):
|
||||
pass
|
||||
|
||||
def iface_to_br(self, name):
|
||||
pass
|
||||
|
||||
def list_br(self):
|
||||
pass
|
||||
|
||||
def br_get_external_id(self, name, field):
|
||||
pass
|
||||
|
||||
def db_create(self, table, **col_values):
|
||||
pass
|
||||
|
||||
def db_destroy(self, table, record):
|
||||
pass
|
||||
|
||||
def db_set(self, table, record, *col_values):
|
||||
pass
|
||||
|
||||
def db_add(self, table, record, column, *values):
|
||||
pass
|
||||
|
||||
def db_clear(self, table, record, column):
|
||||
pass
|
||||
|
||||
def db_get(self, table, record, column):
|
||||
pass
|
||||
|
||||
def db_list(self, table, records=None, columns=None, if_exists=False):
|
||||
pass
|
||||
|
||||
def db_find(self, table, *conditions, **kwargs):
|
||||
pass
|
||||
|
||||
def set_controller(self, bridge, controllers):
|
||||
pass
|
||||
|
||||
def del_controller(self, bridge):
|
||||
pass
|
||||
|
||||
def get_controller(self, bridge):
|
||||
pass
|
||||
|
||||
def set_fail_mode(self, bridge, mode):
|
||||
pass
|
||||
|
||||
def add_port(self, bridge, port, may_exist=True):
|
||||
pass
|
||||
|
||||
def del_port(self, port, bridge=None, if_exists=True):
|
||||
pass
|
||||
|
||||
def list_ports(self, bridge):
|
||||
pass
|
||||
|
||||
def list_ifaces(self, bridge):
|
||||
pass
|
||||
|
||||
|
||||
class TransactionTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TransactionTestCase, self).setUp()
|
||||
self.api = TestingAPI(None)
|
||||
mock.patch.object(FakeTransaction, 'commit').start()
|
||||
|
||||
def test_transaction_nested(self):
|
||||
with self.api.transaction() as txn1:
|
||||
with self.api.transaction() as txn2:
|
||||
self.assertIs(txn1, txn2)
|
||||
txn1.commit.assert_called_once_with()
|
||||
|
||||
def test_transaction_no_nested_transaction_after_error(self):
|
||||
class TestException(Exception):
|
||||
pass
|
||||
|
||||
with testtools.ExpectedException(TestException):
|
||||
with self.api.transaction() as txn1:
|
||||
raise TestException()
|
||||
|
||||
with self.api.transaction() as txn2:
|
||||
self.assertIsNot(txn1, txn2)
|
||||
@@ -15,7 +15,8 @@
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from neutron.agent.ovsdb import api
|
||||
from ovsdbapp import exceptions
|
||||
|
||||
from neutron.agent.ovsdb import impl_idl
|
||||
from neutron.tests import base
|
||||
|
||||
@@ -25,7 +26,7 @@ class TransactionTestCase(base.BaseTestCase):
|
||||
transaction = impl_idl.NeutronOVSDBTransaction(mock.sentinel,
|
||||
mock.Mock(), 1)
|
||||
with self.assert_max_execution_time(10):
|
||||
with testtools.ExpectedException(api.TimeoutException):
|
||||
with testtools.ExpectedException(exceptions.TimeoutException):
|
||||
transaction.commit()
|
||||
|
||||
def test_post_commit_does_not_raise_exception(self):
|
||||
|
||||
@@ -31,10 +31,10 @@ class OVSPhysicalBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
|
||||
dvr_process_next_table_id = ovs_const.LOCAL_VLAN_TRANSLATION
|
||||
|
||||
def setUp(self):
|
||||
super(OVSPhysicalBridgeTest, self).setUp()
|
||||
conn_patcher = mock.patch(
|
||||
'neutron.agent.ovsdb.native.connection.Connection.start')
|
||||
conn_patcher.start()
|
||||
super(OVSPhysicalBridgeTest, self).setUp()
|
||||
self.addCleanup(conn_patcher.stop)
|
||||
self.setup_bridge_mock('br-phys', self.br_phys_cls)
|
||||
self.stamp = self.br.default_cookie
|
||||
|
||||
@@ -44,6 +44,7 @@ oslo.utils>=3.20.0 # Apache-2.0
|
||||
oslo.versionedobjects>=1.17.0 # Apache-2.0
|
||||
osprofiler>=1.4.0 # Apache-2.0
|
||||
ovs>=2.7.0 # Apache-2.0
|
||||
ovsdbapp>=0.3.0 # Apache-2.0
|
||||
psutil>=3.2.2 # BSD
|
||||
pyroute2>=0.4.12 # Apache-2.0 (+ dual licensed GPL2)
|
||||
weakrefmethod>=1.0.2;python_version=='2.7' # PSF
|
||||
|
||||
Reference in New Issue
Block a user