Use the ovsdbapp library
This patch uses the ovsdbapp Python library, which is the new project based on the Neutron OVSDB API. The CLI implementation of the OVSDB API remains in the Neutron tree. Neutron continues providing the (deprecated) ability to allow the OVSDB API to be imported from Neutron. The deleted tests exist in the ovsdbapp project. More will be moved later, but many of the tests in the Neutron tree use ovs_lib, which doesn't exist in ovsdbapp so those tests will probably stay in the Neutron tree. Closes-Bug: #1684277 Depends-On: I3d3535b1d6fe37c78a9399903b65bbd688b1c4b9 Change-Id: Ic8c7db0e80d0ad104242322d3f1f70cab8caab92
This commit is contained in:
parent
6ecdfbb82b
commit
e6333593ae
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user