
Right now the DbListCommand retrieves the uuid's of all the elements passed in as argument. This is an O(n^2) operation so when the number of elements in a grows it's likely that we get Timeout Exceptions. Instead of doing this, whenever possible, we'll retrieve all the elements (from the in-memory replica) and only fetch those who were passed as arguments avoiding the O(n^2) operation. Change-Id: I71411e5dd68753eb3825f8b0e91009f87de1b260 Closes-Bug: #1769897
317 lines
11 KiB
Python
317 lines
11 KiB
Python
# 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 collections
|
|
import logging
|
|
|
|
import six
|
|
|
|
from ovsdbapp import api
|
|
from ovsdbapp.backend.ovs_idl import idlutils
|
|
from ovsdbapp.backend.ovs_idl import rowview
|
|
|
|
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:
|
|
if log_errors:
|
|
LOG.exception("Error executing command")
|
|
if check_error:
|
|
raise
|
|
|
|
@classmethod
|
|
def set_column(cls, row, col, val):
|
|
setattr(row, col, idlutils.db_replace_record(val))
|
|
|
|
@classmethod
|
|
def set_columns(cls, row, **columns):
|
|
for col, val in columns.items():
|
|
cls.set_column(row, col, val)
|
|
|
|
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']))
|
|
|
|
|
|
class AddCommand(BaseCommand):
|
|
table_name = [] # unhashable, won't be looked up
|
|
|
|
def post_commit(self, txn):
|
|
# If get_insert_uuid fails, self.result was not a result of a
|
|
# recent insert. Most likely we are post_commit after a lookup()
|
|
real_uuid = txn.get_insert_uuid(self.result) or self.result
|
|
row = self.api.tables[self.table_name].rows[real_uuid]
|
|
self.result = rowview.RowView(row)
|
|
|
|
|
|
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])
|
|
self.set_columns(row, **self.columns)
|
|
# 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
|
|
self.set_column(record, col, 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 six.iteritems(value):
|
|
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)
|
|
self.set_column(record, self.column, 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 DbListCommand(BaseCommand):
|
|
def __init__(self, api, table, records, columns, if_exists, row=False):
|
|
super(DbListCommand, self).__init__(api)
|
|
self.table = table
|
|
self.columns = columns
|
|
self.if_exists = if_exists
|
|
self.records = records
|
|
self.row = row
|
|
|
|
def run_idl(self, txn):
|
|
table_schema = self.api._tables[self.table]
|
|
idx = idlutils.get_index_column(table_schema)
|
|
columns = self.columns or list(table_schema.columns.keys()) + ['_uuid']
|
|
# If there's an index for this table, we'll fetch all columns and
|
|
# remove the unwanted ones based on self.records. Otherwise, let's try
|
|
# to get the uuid of the wanted ones which is an O(n^2) operation.
|
|
if not idx and self.records:
|
|
rows = []
|
|
for record in self.records:
|
|
try:
|
|
rows.append(idlutils.row_by_record(
|
|
self.api.idl, self.table, record))
|
|
|
|
except idlutils.RowNotFound:
|
|
if self.if_exists:
|
|
continue
|
|
raise
|
|
else:
|
|
rows = table_schema.rows.values()
|
|
|
|
def _match(row):
|
|
elem = getattr(row, idx)
|
|
found = elem in self.records
|
|
if found:
|
|
records_found.remove(elem)
|
|
return found
|
|
|
|
records_found = []
|
|
if idx and self.records:
|
|
if self.if_exists:
|
|
match = lambda row: getattr(row, idx) in self.records
|
|
else:
|
|
# If we're using the approach of removing the unwanted
|
|
# elements, we'll use a helper list to remove elements as we
|
|
# find them in the DB contents. This will help us identify
|
|
# quickly if there's some record missing to raise a RowNotFound
|
|
# exception later.
|
|
records_found = list(self.records)
|
|
match = _match
|
|
else:
|
|
match = lambda row: True
|
|
|
|
self.result = [
|
|
rowview.RowView(row) if self.row else {
|
|
c: idlutils.get_column_value(row, c)
|
|
for c in columns
|
|
}
|
|
for row in rows if match(row)
|
|
]
|
|
|
|
if records_found:
|
|
raise idlutils.RowNotFound(table=self.table, col=idx,
|
|
match=records_found[0])
|
|
|
|
|
|
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.row = kwargs.get('row', False)
|
|
self.columns = (kwargs.get('columns') or
|
|
list(self.table.columns.keys()) + ['_uuid'])
|
|
|
|
def run_idl(self, txn):
|
|
self.result = [
|
|
rowview.RowView(r) if self.row else {
|
|
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)
|
|
]
|
|
|
|
|
|
class BaseGetRowCommand(BaseCommand):
|
|
def __init__(self, api, record):
|
|
super(BaseGetRowCommand, self).__init__(api)
|
|
self.record = record
|
|
|
|
def run_idl(self, txn):
|
|
self.result = self.api.lookup(self.table, self.record)
|
|
|
|
|
|
class DbRemoveCommand(BaseCommand):
|
|
def __init__(self, api, table, record, column, *values, **keyvalues):
|
|
super(DbRemoveCommand, self).__init__(api)
|
|
self.table = table
|
|
self.record = record
|
|
self.column = column
|
|
self.values = values
|
|
self.keyvalues = keyvalues
|
|
self.if_exists = keyvalues.pop('if_exists', False)
|
|
|
|
def run_idl(self, txn):
|
|
try:
|
|
record = self.api.lookup(self.table, self.record)
|
|
if isinstance(getattr(record, self.column), dict):
|
|
for value in self.values:
|
|
record.delkey(self.column, value)
|
|
for key, value in self.keyvalues.items():
|
|
record.delkey(self.column, key, value)
|
|
elif isinstance(getattr(record, self.column), list):
|
|
for value in self.values:
|
|
record.delvalue(self.column, value)
|
|
else:
|
|
value = type(getattr(record, self.column))()
|
|
setattr(record, self.column, value)
|
|
except idlutils.RowNotFound:
|
|
if self.if_exists:
|
|
return
|
|
else:
|
|
raise
|