Add OVN Northbound API for LS, LSP, and DHCP
This patch implments the ovn-nbctl API for the Logical_Switch, Logical_Switch_Port, and DHCP_Options commands. Additional patches will implement logical router and load balancer functionality. As a convenience, the add/list/get commands return a special read-only version of an ovs.db.idl.Row object called a RowView. This object can be compared to a Row for equality and hashing. This saves having to return uuids and then look them up. This behavior differs from the Open_vSwitch schema implementation. This wrapper serves both to keep people from modifying returned values outside of a transaction and as an interface for any future backend to implement. In addition, an ovs virtual environment fixture based on ovs-sandbox is added to set up a sandboxed ovs/ovn install for running functional tests. Change-Id: I93689158467ff73a1b02588510d168b50ed6292a
This commit is contained in:
parent
0823a67e6e
commit
4bde2d5237
@ -4,3 +4,9 @@
|
||||
openvswitch [platform:rpm test]
|
||||
openvswitch-switch [platform:dpkg test]
|
||||
curl [test]
|
||||
autoconf [test]
|
||||
automake [test]
|
||||
libtool [test]
|
||||
gcc [test]
|
||||
make [test]
|
||||
patch [test]
|
||||
|
@ -10,7 +10,30 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
from ovsdbapp.backend.ovs_idl import command as cmd
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
|
||||
_NO_DEFAULT = object()
|
||||
|
||||
|
||||
class RowView(object):
|
||||
def __init__(self, row):
|
||||
self._row = row
|
||||
|
||||
def __getattr__(self, column_name):
|
||||
return getattr(self._row, column_name)
|
||||
|
||||
def __eq__(self, other):
|
||||
# use other's == since it is likely to be a Row object
|
||||
try:
|
||||
return other == self._row
|
||||
except NotImplemented:
|
||||
return other._row == self._row
|
||||
|
||||
def __hash__(self):
|
||||
return self._row.__hash__()
|
||||
|
||||
|
||||
class Backend(object):
|
||||
@ -37,3 +60,49 @@ class Backend(object):
|
||||
|
||||
def db_find(self, table, *conditions, **kwargs):
|
||||
return cmd.DbFindCommand(self, table, *conditions, **kwargs)
|
||||
|
||||
def lookup(self, table, record, default=_NO_DEFAULT):
|
||||
try:
|
||||
return self._lookup(table, record)
|
||||
except idlutils.RowNotFound:
|
||||
if default is not _NO_DEFAULT:
|
||||
return default
|
||||
raise
|
||||
|
||||
def _lookup(self, table, record):
|
||||
t = self.tables[table]
|
||||
try:
|
||||
if isinstance(record, uuid.UUID):
|
||||
return t.rows[record]
|
||||
try:
|
||||
uuid_ = uuid.UUID(record)
|
||||
return t.rows[uuid_]
|
||||
except ValueError:
|
||||
# Not a UUID string, continue lookup by other means
|
||||
pass
|
||||
except KeyError:
|
||||
# If record isn't found by UUID , go ahead and look up by the table
|
||||
pass
|
||||
|
||||
if not self.lookup_table:
|
||||
raise idlutils.RowNotFound(table=table, col='record',
|
||||
match=record)
|
||||
# NOTE (twilson) This is an approximation of the db-ctl implementation
|
||||
# that allows a partial table, assuming that if a table has a single
|
||||
# index, that we should be able to do a lookup by it.
|
||||
rl = self.lookup_table.get(
|
||||
table,
|
||||
idlutils.RowLookup(table, idlutils.get_index_column(t), None))
|
||||
# no table means uuid only, no column means lookup table has one row
|
||||
if rl.table is None:
|
||||
raise idlutils.RowNotFound(table=table, col='uuid', match=record)
|
||||
if rl.column is None:
|
||||
return next(iter(t.rows.values()))
|
||||
row = idlutils.row_by_value(self, rl.table, rl.column, record)
|
||||
if rl.uuid_column:
|
||||
rows = getattr(row, rl.uuid_column)
|
||||
if len(rows) != 1:
|
||||
raise idlutils.RowNotFound(table=table, col='record',
|
||||
match=record)
|
||||
row = rows[0]
|
||||
return row
|
||||
|
@ -13,5 +13,8 @@
|
||||
# under the License.
|
||||
|
||||
DEFAULT_OVSDB_CONNECTION = 'tcp:127.0.0.1:6640'
|
||||
DEFAULT_OVNNB_CONNECTION = 'tcp:127.0.0.1:6641'
|
||||
DEFAULT_TIMEOUT = 5
|
||||
DEVICE_NAME_MAX_LEN = 14
|
||||
|
||||
ACL_PRIORITY_MAX = 32767
|
||||
|
@ -15,7 +15,7 @@
|
||||
import six
|
||||
|
||||
|
||||
class OvsdbAppException(Exception):
|
||||
class OvsdbAppException(RuntimeError):
|
||||
"""Base OvsdbApp Exception.
|
||||
|
||||
To correctly use this class, inherit from it and define
|
||||
@ -52,3 +52,10 @@ class OvsdbAppException(Exception):
|
||||
|
||||
class TimeoutException(OvsdbAppException):
|
||||
message = "Commands %(commands)s exceeded timeout %(timeout)d seconds"
|
||||
|
||||
|
||||
class OvsdbConnectionUnavailable(OvsdbAppException):
|
||||
message = ("OVS database connection to %(db_schema)s failed with error: "
|
||||
"'%(error)s'. Verify that the OVS and OVN services are "
|
||||
"available and that the 'ovn_nb_connection' and "
|
||||
"'ovn_sb_connection' configuration options are correct.")
|
||||
|
0
ovsdbapp/schema/ovn_northbound/__init__.py
Normal file
0
ovsdbapp/schema/ovn_northbound/__init__.py
Normal file
339
ovsdbapp/schema/ovn_northbound/api.py
Normal file
339
ovsdbapp/schema/ovn_northbound/api.py
Normal file
@ -0,0 +1,339 @@
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
|
||||
from ovsdbapp import api
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class API(api.API):
|
||||
"""An API based off of the ovn-nbctl CLI interface
|
||||
|
||||
This API basically mirrors the ovn-nbctl operations with these changes:
|
||||
1. Methods that create objects will return a read-only view of the object
|
||||
2. Methods which list objects will return a list of read-only view objects
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def ls_add(self, switch=None, may_exist=False, **columns):
|
||||
"""Create a logical switch named 'switch'
|
||||
|
||||
:param switch: The name of the switch (optional)
|
||||
:type switch: string or uuid.UUID
|
||||
:param may_exist: If True, don't fail if the switch already exists
|
||||
:type may_exist: boolean
|
||||
:param columns: Additional columns to directly set on the switch
|
||||
:returns: :class:`Command` with RowView result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def ls_del(self, switch, if_exists=False):
|
||||
"""Delete logical switch 'switch' and all its ports
|
||||
|
||||
:param switch: The name or uuid of the switch
|
||||
:type switch: string or uuid.UUID
|
||||
:type if_exists: If True, don't fail if the switch doesn't exist
|
||||
:type if_exists: boolean
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def ls_list(self):
|
||||
"""Get all logical switches
|
||||
|
||||
:returns: :class:`Command` with RowView list result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def acl_add(self, switch, direction, priority, match, action, log=False):
|
||||
"""Add an ACL to 'switch'
|
||||
|
||||
:param switch: The name or uuid of the switch
|
||||
:type switch: string or uuid.UUID
|
||||
:param direction: The traffic direction to match
|
||||
:type direction: 'from-lport' or 'to-lport'
|
||||
:param priority: The priority field of the ACL
|
||||
:type priority: int
|
||||
:param match: The match rule
|
||||
:type match: string
|
||||
:param action: The action to take upon match
|
||||
:type action: 'allow', 'allow-related', 'drop', or 'reject'
|
||||
:param log: If True, enable packet logging for the ACL
|
||||
:type log: boolean
|
||||
:returns: :class:`Command` with RowView result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def acl_del(self, switch, direction=None, priority=None, match=None):
|
||||
"""Remove ACLs from 'switch'
|
||||
|
||||
If only switch is supplied, all the ACLs from the logical switch are
|
||||
deleted. If direction is also specified, then all the flows in that
|
||||
direction will be deleted from the logical switch. If all the fields
|
||||
are given, then only flows that match all fields will be deleted.
|
||||
|
||||
:param switch: The name or uuid of the switch
|
||||
:type switch: string or uuid.UUID
|
||||
:param direction: The traffic direction to match
|
||||
:type direction: 'from-lport' or 'to-lport'
|
||||
:param priority: The priority field of the ACL
|
||||
:type priority: int
|
||||
:param match: The match rule
|
||||
:type match: string
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def acl_list(self, switch):
|
||||
"""Get the ACLs for 'switch'
|
||||
|
||||
:param switch: The name or uuid of the switch
|
||||
:type switch: string or uuid.UUID
|
||||
:returns: :class:`Command` with RowView list result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_add(self, switch, port, parent=None, tag=None, may_exist=False,
|
||||
**columns):
|
||||
"""Add logical port 'port' on 'switch'
|
||||
|
||||
NOTE: for the purposes of testing the existence of the 'port',
|
||||
'port' is treated as either a name or a uuid, as in ovn-nbctl.
|
||||
|
||||
:param switch: The name or uuid of the switch
|
||||
:type switch: string or uuid.UUID
|
||||
:param port: The name of the port
|
||||
:type port: string or uuid.UUID
|
||||
:param parent: The name of the parent port (requires tag)
|
||||
:type parent: string
|
||||
:param tag: The tag_request field of the port. 0 causes
|
||||
ovn-northd to assign a unique tag
|
||||
:type tag: int [0, 4095]
|
||||
:param may_exist: If True, don't fail if the switch already exists
|
||||
:type may_exist: boolean
|
||||
:param columns: Additional columns to directly set on the switch
|
||||
:returns: :class:`Command` with RowView result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_del(self, port, if_exists=False):
|
||||
"""Delete 'port' from its attached switch
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:type if_exists: If True, don't fail if the switch doesn't exist
|
||||
:type if_exists: boolean
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_list(self, switch):
|
||||
"""Get the logical ports on switch
|
||||
|
||||
:param switch: The name or uuid of the switch
|
||||
:type switch: string or uuid.UUID
|
||||
:returns: :class:`Command` with RowView list result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_get_parent(self, port):
|
||||
"""Get the parent of 'port' if set
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:returns: :class:`Command` with port parent string result or
|
||||
"" if not set
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_set_addresses(self, port, addresses):
|
||||
"""Set addresses for 'port'
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:param addresses: One or more addresses in the format:
|
||||
'unknown', 'router', 'dynamic', or
|
||||
'ethaddr [ipaddr]...'
|
||||
:type addresses: string
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_get_addresses(self, port):
|
||||
"""Return the list of addresses assigned to port
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:returns: A list of string representations of addresses in the
|
||||
format referenced in lsp_set_addresses
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_set_port_security(self, port, addresses):
|
||||
"""Set port security addresses for 'port'
|
||||
|
||||
Sets the port security addresses associated with port to addrs.
|
||||
Multiple sets of addresses may be set by using multiple addrs
|
||||
arguments. If no addrs argument is given, port will not have
|
||||
port security enabled.
|
||||
|
||||
Port security limits the addresses from which a logical port may
|
||||
send packets and to which it may receive packets.
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:param addresses: The addresses in the format 'ethaddr [ipaddr...]'
|
||||
See `man ovn-nb` and port_security column for details
|
||||
:type addresses: string
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_get_port_security(self, port):
|
||||
"""Get port security addresses for 'port'
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:returns: :class:`Command` with list of strings described by
|
||||
lsp_set_port_security result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_get_up(self, port):
|
||||
"""Get state of port.
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:returns: :class:`Command` with boolean result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_set_enabled(self, port, is_enabled):
|
||||
"""Set administrative state of 'port'
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:param is_enabled: Whether the port should be enabled
|
||||
:type is_enabled: boolean
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_get_enabled(self, port):
|
||||
"""Get administrative state of 'port'
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:returns: :class:`Command` with boolean result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_set_type(self, port, port_type):
|
||||
"""Set the type for 'port
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:param port_type: The type of the port
|
||||
:type port_type: string
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_get_type(self, port):
|
||||
"""Get the type for 'port'
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:returns: :class:`Command` with string result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_set_options(self, port, **options):
|
||||
"""Set options related to the type of 'port'
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:param options: keys and values for the port 'options' dict
|
||||
:type options: key: string, value: string
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_get_options(self, port):
|
||||
"""Get the type-specific options for 'port'
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:returns: :class:`Command` with dict result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def lsp_set_dhcpv4_options(self, port, dhcp_options_uuid):
|
||||
"""Set the dhcp4 options for 'port'
|
||||
|
||||
:param port: The name or uuid of the port
|
||||
:type port: string or uuid.UUID
|
||||
:param dhcp_options_uuid: The uuid of the dhcp_options row
|
||||
:type dhcp_options_uuid: uuid.UUID
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def dhcp_options_add(self, cidr, **external_ids):
|
||||
"""Create a DHCP options row with CIDR
|
||||
|
||||
This is equivalent to ovn-nbctl's dhcp-options-create, but renamed
|
||||
to be consistent with other object creation methods
|
||||
|
||||
:param cidr: An IP network in CIDR format
|
||||
:type cidr: string
|
||||
:param external_ids: external_id field key/value mapping
|
||||
:type external_ids: key: string, value: string
|
||||
:returns: :class:`Command` with RowView result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def dhcp_options_del(self, uuid):
|
||||
"""Delete DHCP options row with 'uuid'
|
||||
|
||||
:param uuid: The uuid of the DHCP Options row to delete
|
||||
:type uuid: string or uuid.UUID
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def dhcp_options_list(self):
|
||||
"""Get all DHCP_Options
|
||||
|
||||
:returns: :class:`Command with RowView list result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def dhcp_options_set_options(self, uuid, **options):
|
||||
"""Set the DHCP options for 'uuid'
|
||||
|
||||
:param uuid: The uuid of the DHCP Options row
|
||||
:type uuid: string or uuid.UUID
|
||||
:returns: :class:`Command` with no result
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def dhcp_options_get_options(self, uuid):
|
||||
"""Get the DHCP options for 'uuid'
|
||||
|
||||
:param uuid: The uuid of the DHCP Options row
|
||||
:type uuid: string or uuid.UUID
|
||||
:returns: :class:`Command` with dict result
|
||||
"""
|
486
ovsdbapp/schema/ovn_northbound/commands.py
Normal file
486
ovsdbapp/schema/ovn_northbound/commands.py
Normal file
@ -0,0 +1,486 @@
|
||||
# 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 re
|
||||
|
||||
import netaddr
|
||||
|
||||
from ovsdbapp.backend import ovs_idl
|
||||
from ovsdbapp.backend.ovs_idl import command as cmd
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
from ovsdbapp import constants as const
|
||||
|
||||
|
||||
class AddCommand(cmd.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 = ovs_idl.RowView(row)
|
||||
|
||||
|
||||
class LsAddCommand(AddCommand):
|
||||
table_name = 'Logical_Switch'
|
||||
|
||||
def __init__(self, api, switch=None, may_exist=False, **columns):
|
||||
super(LsAddCommand, self).__init__(api)
|
||||
self.switch = switch
|
||||
self.columns = columns
|
||||
self.may_exist = may_exist
|
||||
|
||||
def run_idl(self, txn):
|
||||
# There is no requirement for name to be unique, so if a name is
|
||||
# specified, we always have to do a lookup since adding it won't
|
||||
# fail. If may_exist is set, we just don't do anything when dup'd
|
||||
if self.switch:
|
||||
sw = idlutils.row_by_value(self.api.idl, self.table_name, 'name',
|
||||
self.switch, None)
|
||||
if sw:
|
||||
if self.may_exist:
|
||||
self.result = ovs_idl.RowView(sw)
|
||||
return
|
||||
raise RuntimeError("Switch %s exists" % self.switch)
|
||||
elif self.may_exist:
|
||||
raise RuntimeError("may_exist requires name")
|
||||
sw = txn.insert(self.api.tables[self.table_name])
|
||||
if self.switch:
|
||||
sw.name = self.switch
|
||||
else:
|
||||
# because ovs.db.idl brokenly requires a changed column
|
||||
sw.name = ""
|
||||
for col, value in self.columns.items():
|
||||
setattr(sw, col, value)
|
||||
self.result = sw.uuid
|
||||
|
||||
|
||||
class LsDelCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, switch, if_exists=False):
|
||||
super(LsDelCommand, self).__init__(api)
|
||||
self.switch = switch
|
||||
self.if_exists = if_exists
|
||||
|
||||
def run_idl(self, txn):
|
||||
try:
|
||||
lswitch = self.api.lookup('Logical_Switch', self.switch)
|
||||
lswitch.delete()
|
||||
except idlutils.RowNotFound:
|
||||
if self.if_exists:
|
||||
return
|
||||
msg = "Logical Switch %s does not exist" % self.switch
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
class LsListCommand(cmd.BaseCommand):
|
||||
def run_idl(self, txn):
|
||||
table = self.api.tables['Logical_Switch']
|
||||
self.result = [ovs_idl.RowView(r) for r in table.rows.values()]
|
||||
|
||||
|
||||
class AclAddCommand(AddCommand):
|
||||
table_name = 'ACL'
|
||||
|
||||
def __init__(self, api, switch, direction, priority, match, action,
|
||||
log=False, may_exist=False, **external_ids):
|
||||
if direction not in ('from-lport', 'to-lport'):
|
||||
raise TypeError("direction must be either from-lport or to-lport")
|
||||
if not 0 <= priority <= const.ACL_PRIORITY_MAX:
|
||||
raise ValueError("priority must be beween 0 and %s, inclusive" % (
|
||||
const.ACL_PRIORITY_MAX))
|
||||
if action not in ('allow', 'allow-related', 'drop', 'reject'):
|
||||
raise TypeError("action must be allow/allow-related/drop/reject")
|
||||
super(AclAddCommand, self).__init__(api)
|
||||
self.switch = switch
|
||||
self.direction = direction
|
||||
self.priority = priority
|
||||
self.match = match
|
||||
self.action = action
|
||||
self.log = log
|
||||
self.may_exist = may_exist
|
||||
self.external_ids = external_ids
|
||||
|
||||
def acl_match(self, row):
|
||||
return (self.direction == row.direction and
|
||||
self.priority == row.priority and
|
||||
self.match == row.match)
|
||||
|
||||
def run_idl(self, txn):
|
||||
ls = self.api.lookup('Logical_Switch', self.switch)
|
||||
acls = [acl for acl in ls.acls if self.acl_match(acl)]
|
||||
if acls:
|
||||
if self.may_exist:
|
||||
self.result = ovs_idl.RowView(acls[0])
|
||||
return
|
||||
raise RuntimeError("ACL (%s, %s, %s) already exists" % (
|
||||
self.direction, self.priority, self.match))
|
||||
acl = txn.insert(self.api.tables[self.table_name])
|
||||
acl.direction = self.direction
|
||||
acl.priority = self.priority
|
||||
acl.match = self.match
|
||||
acl.action = self.action
|
||||
acl.log = self.log
|
||||
ls.addvalue('acls', acl)
|
||||
for col, value in self.external_ids.items():
|
||||
acl.setkey('external_ids', col, value)
|
||||
self.result = acl.uuid
|
||||
|
||||
|
||||
class AclDelCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, switch, direction=None,
|
||||
priority=None, match=None):
|
||||
if (priority is None) != (match is None):
|
||||
raise TypeError("Must specify priority and match together")
|
||||
if priority is not None and not direction:
|
||||
raise TypeError("Cannot specify priority/match without direction")
|
||||
super(AclDelCommand, self).__init__(api)
|
||||
self.switch = switch
|
||||
self.conditions = []
|
||||
if direction:
|
||||
self.conditions.append(('direction', '=', direction))
|
||||
# priority can be 0
|
||||
if match: # and therefor prioroity due to the above check
|
||||
self.conditions += [('priority', '=', priority),
|
||||
('match', '=', match)]
|
||||
|
||||
def run_idl(self, txn):
|
||||
ls = self.api.lookup('Logical_Switch', self.switch)
|
||||
for acl in [a for a in ls.acls
|
||||
if idlutils.row_match(a, self.conditions)]:
|
||||
ls.delvalue('acls', acl)
|
||||
acl.delete()
|
||||
|
||||
|
||||
class AclListCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, switch):
|
||||
super(AclListCommand, self).__init__(api)
|
||||
self.switch = switch
|
||||
|
||||
def run_idl(self, txn):
|
||||
ls = self.api.lookup('Logical_Switch', self.switch)
|
||||
self.result = [ovs_idl.RowView(acl) for acl in ls.acls]
|
||||
|
||||
|
||||
class LspAddCommand(AddCommand):
|
||||
table_name = 'Logical_Switch_Port'
|
||||
|
||||
def __init__(self, api, switch, port, parent=None, tag=None,
|
||||
may_exist=False, **columns):
|
||||
if tag and not 0 <= tag <= 4095:
|
||||
raise TypeError("tag must be 0 to 4095, inclusive")
|
||||
if (parent is None) != (tag is None):
|
||||
raise TypeError("parent and tag must be passed together")
|
||||
super(LspAddCommand, self).__init__(api)
|
||||
self.switch = switch
|
||||
self.port = port
|
||||
self.parent = parent
|
||||
self.tag = tag
|
||||
self.may_exist = may_exist
|
||||
self.columns = columns
|
||||
|
||||
def run_idl(self, txn):
|
||||
ls = self.api.lookup('Logical_Switch', self.switch)
|
||||
try:
|
||||
lsp = self.api.lookup(self.table_name, self.port)
|
||||
if self.may_exist:
|
||||
msg = None
|
||||
if lsp not in ls.ports:
|
||||
msg = "%s exists, but is not in %s" % (
|
||||
self.port, self.switch)
|
||||
if self.parent:
|
||||
if not lsp.parent_name:
|
||||
msg = "%s exists, but has no parent" % self.port
|
||||
# parent_name, being optional, is stored as list
|
||||
if self.parent not in lsp.parent_name:
|
||||
msg = "%s exists with different parent" % self.port
|
||||
if self.tag not in lsp.tag_request:
|
||||
msg = "%s exists with different tag request" % (
|
||||
self.port,)
|
||||
elif lsp.parent_name:
|
||||
msg = "%s exists, but with a parent" % self.port
|
||||
|
||||
if msg:
|
||||
raise RuntimeError(msg)
|
||||
self.result = ovs_idl.RowView(lsp)
|
||||
return
|
||||
except idlutils.RowNotFound:
|
||||
# This is what we want
|
||||
pass
|
||||
lsp = txn.insert(self.api.tables[self.table_name])
|
||||
lsp.name = self.port
|
||||
if self.tag is not None:
|
||||
lsp.parent_name = self.parent
|
||||
lsp.tag_request = self.tag
|
||||
ls.addvalue('ports', lsp)
|
||||
for col, value in self.columns.items():
|
||||
setattr(lsp, col, value)
|
||||
self.result = lsp.uuid
|
||||
|
||||
|
||||
class LspDelCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port, switch=None, if_exists=False):
|
||||
super(LspDelCommand, self).__init__(api)
|
||||
self.port = port
|
||||
self.switch = switch
|
||||
self.if_exists = if_exists
|
||||
|
||||
def run_idl(self, txn):
|
||||
try:
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
except idlutils.RowNotFound:
|
||||
if self.if_exists:
|
||||
return
|
||||
raise RuntimeError("%s does not exist" % self.port)
|
||||
|
||||
# We need to delete the port from its switch
|
||||
if self.switch:
|
||||
sw = self.api.lookup('Logical_Switch', self.switch)
|
||||
else:
|
||||
sw = next(iter(
|
||||
s for s in self.api.tables['Logical_Switch'].rows.values()
|
||||
if lsp in s.ports), None)
|
||||
if not (sw and lsp in sw.ports):
|
||||
raise RuntimeError("%s does not exist in %s" % (
|
||||
self.port, self.switch))
|
||||
sw.delvalue('ports', lsp)
|
||||
lsp.delete()
|
||||
|
||||
|
||||
class LspListCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, switch):
|
||||
super(LspListCommand, self).__init__(api)
|
||||
self.switch = switch
|
||||
|
||||
def run_idl(self, txn):
|
||||
sw = self.api.lookup('Logical_Switch', self.switch)
|
||||
self.result = [ovs_idl.RowView(r) for r in sw.ports]
|
||||
|
||||
|
||||
class LspGetParentCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port):
|
||||
super(LspGetParentCommand, self).__init__(api)
|
||||
self.port = port
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
self.result = next(iter(lsp.parent_name), "")
|
||||
|
||||
|
||||
class LspGetTagCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port):
|
||||
super(LspGetTagCommand, self).__init__(api)
|
||||
self.port = port
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
self.result = next(iter(lsp.tag), -1)
|
||||
|
||||
|
||||
class LspSetAddressesCommand(cmd.BaseCommand):
|
||||
addr_re = re.compile(
|
||||
r'^(router|unknown|dynamic|([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2} .+)$')
|
||||
|
||||
def __init__(self, api, port, addresses):
|
||||
for addr in addresses:
|
||||
if not self.addr_re.match(addr):
|
||||
raise TypeError(
|
||||
"address must be router/unknown/dynamic/ethaddr ipaddr...")
|
||||
super(LspSetAddressesCommand, self).__init__(api)
|
||||
self.port = port
|
||||
self.addresses = addresses
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
lsp.addresses = self.addresses
|
||||
|
||||
|
||||
class LspGetAddressesCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port):
|
||||
super(LspGetAddressesCommand, self).__init__(api)
|
||||
self.port = port
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
self.result = lsp.addresses
|
||||
|
||||
|
||||
class LspSetPortSecurityCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port, addresses):
|
||||
# NOTE(twilson) ovn-nbctl.c does not do any checking of addresses
|
||||
# so neither do we
|
||||
super(LspSetPortSecurityCommand, self).__init__(api)
|
||||
self.port = port
|
||||
self.addresses = addresses
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
lsp.port_security = self.addresses
|
||||
|
||||
|
||||
class LspGetPortSecurityCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port):
|
||||
super(LspGetPortSecurityCommand, self).__init__(api)
|
||||
self.port = port
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
self.result = lsp.port_security
|
||||
|
||||
|
||||
class LspGetUpCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port):
|
||||
super(LspGetUpCommand, self).__init__(api)
|
||||
self.port = port
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
# 'up' is optional, but if not up, it's not up :p
|
||||
self.result = next(iter(lsp.up), False)
|
||||
|
||||
|
||||
class LspSetEnabledCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port, is_enabled):
|
||||
super(LspSetEnabledCommand, self).__init__(api)
|
||||
self.port = port
|
||||
self.is_enabled = is_enabled
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
lsp.enabled = self.is_enabled
|
||||
|
||||
|
||||
class LspGetEnabledCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port):
|
||||
super(LspGetEnabledCommand, self).__init__(api)
|
||||
self.port = port
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
# enabled is optional, but if not disabled then enabled
|
||||
self.result = next(iter(lsp.enabled), True)
|
||||
|
||||
|
||||
class LspSetTypeCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port, port_type):
|
||||
super(LspSetTypeCommand, self).__init__(api)
|
||||
self.port = port
|
||||
self.port_type = port_type
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
lsp.type = self.port_type
|
||||
|
||||
|
||||
class LspGetTypeCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port):
|
||||
super(LspGetTypeCommand, self).__init__(api)
|
||||
self.port = port
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
self.result = lsp.type
|
||||
|
||||
|
||||
class LspSetOptionsCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port, **options):
|
||||
super(LspSetOptionsCommand, self).__init__(api)
|
||||
self.port = port
|
||||
self.options = options
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
lsp.options = self.options
|
||||
|
||||
|
||||
class LspGetOptionsCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port):
|
||||
super(LspGetOptionsCommand, self).__init__(api)
|
||||
self.port = port
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
self.result = lsp.options
|
||||
|
||||
|
||||
class LspSetDhcpV4OptionsCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port, dhcpopt_uuid):
|
||||
super(LspSetDhcpV4OptionsCommand, self).__init__(api)
|
||||
self.port = port
|
||||
self.dhcpopt_uuid = dhcpopt_uuid
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
lsp.dhcpv4_options = self.dhcpopt_uuid
|
||||
|
||||
|
||||
class LspGetDhcpV4OptionsCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, port):
|
||||
super(LspGetDhcpV4OptionsCommand, self).__init__(api)
|
||||
self.port = port
|
||||
|
||||
def run_idl(self, txn):
|
||||
lsp = self.api.lookup('Logical_Switch_Port', self.port)
|
||||
self.result = next((ovs_idl.RowView(d)
|
||||
for d in lsp.dhcpv4_options), [])
|
||||
|
||||
|
||||
class DhcpOptionsAddCommand(AddCommand):
|
||||
table_name = 'DHCP_Options'
|
||||
|
||||
def __init__(self, api, cidr, **external_ids):
|
||||
cidr = netaddr.IPNetwork(cidr)
|
||||
super(DhcpOptionsAddCommand, self).__init__(api)
|
||||
self.cidr = str(cidr)
|
||||
self.external_ids = external_ids
|
||||
|
||||
def run_idl(self, txn):
|
||||
dhcpopt = txn.insert(self.api.tables[self.table_name])
|
||||
dhcpopt.cidr = self.cidr
|
||||
dhcpopt.external_ids = self.external_ids
|
||||
self.result = dhcpopt.uuid
|
||||
|
||||
|
||||
class DhcpOptionsDelCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, dhcpopt_uuid):
|
||||
super(DhcpOptionsDelCommand, self).__init__(api)
|
||||
self.dhcpopt_uuid = dhcpopt_uuid
|
||||
|
||||
def run_idl(self, txn):
|
||||
dhcpopt = self.api.lookup('DHCP_Options', self.dhcpopt_uuid)
|
||||
dhcpopt.delete()
|
||||
|
||||
|
||||
class DhcpOptionsListCommand(cmd.BaseCommand):
|
||||
def run_idl(self, txn):
|
||||
self.result = [ovs_idl.RowView(r) for
|
||||
r in self.api.tables['DHCP_Options'].rows.values()]
|
||||
|
||||
|
||||
class DhcpOptionsSetOptionsCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, dhcpopt_uuid, **options):
|
||||
super(DhcpOptionsSetOptionsCommand, self).__init__(api)
|
||||
self.dhcpopt_uuid = dhcpopt_uuid
|
||||
self.options = options
|
||||
|
||||
def run_idl(self, txn):
|
||||
dhcpopt = self.api.lookup('DHCP_Options', self.dhcpopt_uuid)
|
||||
dhcpopt.options = self.options
|
||||
|
||||
|
||||
class DhcpOptionsGetOptionsCommand(cmd.BaseCommand):
|
||||
def __init__(self, api, dhcpopt_uuid):
|
||||
super(DhcpOptionsGetOptionsCommand, self).__init__(api)
|
||||
self.dhcpopt_uuid = dhcpopt_uuid
|
||||
|
||||
def run_idl(self, txn):
|
||||
dhcpopt = self.api.lookup('DHCP_Options', self.dhcpopt_uuid)
|
||||
self.result = dhcpopt.options
|
152
ovsdbapp/schema/ovn_northbound/impl_idl.py
Normal file
152
ovsdbapp/schema/ovn_northbound/impl_idl.py
Normal file
@ -0,0 +1,152 @@
|
||||
# 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 logging
|
||||
|
||||
from ovsdbapp.backend import ovs_idl
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
from ovsdbapp.backend.ovs_idl import transaction
|
||||
from ovsdbapp import exceptions
|
||||
from ovsdbapp.schema.ovn_northbound import api
|
||||
from ovsdbapp.schema.ovn_northbound import commands as cmd
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OvnNbApiIdlImpl(ovs_idl.Backend, api.API):
|
||||
schema = 'OVN_Northbound'
|
||||
ovsdb_connection = None
|
||||
lookup_table = {
|
||||
'Logical_Switch': idlutils.RowLookup('Logical_Switch', 'name', None),
|
||||
}
|
||||
|
||||
def __init__(self, connection):
|
||||
super(OvnNbApiIdlImpl, self).__init__()
|
||||
try:
|
||||
if OvnNbApiIdlImpl.ovsdb_connection is None:
|
||||
OvnNbApiIdlImpl.ovsdb_connection = connection
|
||||
OvnNbApiIdlImpl.ovsdb_connection.start()
|
||||
except Exception as e:
|
||||
connection_exception = exceptions.OvsdbConnectionUnavailable(
|
||||
db_schema=self.schema, error=e)
|
||||
LOG.exception(connection_exception)
|
||||
raise connection_exception
|
||||
|
||||
@property
|
||||
def idl(self):
|
||||
return OvnNbApiIdlImpl.ovsdb_connection.idl
|
||||
|
||||
@property
|
||||
def tables(self):
|
||||
return self.idl.tables
|
||||
|
||||
# NOTE(twilson) _tables is for legacy code, but it has always been used
|
||||
# outside the Idl API implementions
|
||||
_tables = tables
|
||||
|
||||
def create_transaction(self, check_error=False, log_errors=True, **kwargs):
|
||||
return transaction.Transaction(
|
||||
self, OvnNbApiIdlImpl.ovsdb_connection,
|
||||
OvnNbApiIdlImpl.ovsdb_connection.timeout,
|
||||
check_error, log_errors)
|
||||
|
||||
def ls_add(self, switch=None, may_exist=False, **columns):
|
||||
return cmd.LsAddCommand(self, switch, may_exist, **columns)
|
||||
|
||||
def ls_del(self, switch, if_exists=False):
|
||||
return cmd.LsDelCommand(self, switch, if_exists)
|
||||
|
||||
def ls_list(self):
|
||||
return cmd.LsListCommand(self)
|
||||
|
||||
def acl_add(self, switch, direction, priority, match, action, log=False,
|
||||
may_exist=False, **external_ids):
|
||||
return cmd.AclAddCommand(self, switch, direction, priority,
|
||||
match, action, log, may_exist, **external_ids)
|
||||
|
||||
def acl_del(self, switch, direction=None, priority=None, match=None):
|
||||
return cmd.AclDelCommand(self, switch, direction, priority, match)
|
||||
|
||||
def acl_list(self, switch):
|
||||
return cmd.AclListCommand(self, switch)
|
||||
|
||||
def lsp_add(self, switch, port, parent=None, tag=None, may_exist=False,
|
||||
**columns):
|
||||
return cmd.LspAddCommand(self, switch, port, parent, tag, may_exist,
|
||||
**columns)
|
||||
|
||||
def lsp_del(self, port, switch=None, if_exists=False):
|
||||
return cmd.LspDelCommand(self, port, switch, if_exists)
|
||||
|
||||
def lsp_list(self, switch):
|
||||
return cmd.LspListCommand(self, switch)
|
||||
|
||||
def lsp_get_parent(self, port):
|
||||
return cmd.LspGetParentCommand(self, port)
|
||||
|
||||
def lsp_get_tag(self, port):
|
||||
# NOTE (twilson) tag can be unassigned for a while after setting
|
||||
return cmd.LspGetTagCommand(self, port)
|
||||
|
||||
def lsp_set_addresses(self, port, addresses):
|
||||
return cmd.LspSetAddressesCommand(self, port, addresses)
|
||||
|
||||
def lsp_get_addresses(self, port):
|
||||
return cmd.LspGetAddressesCommand(self, port)
|
||||
|
||||
def lsp_set_port_security(self, port, addresses):
|
||||
return cmd.LspSetPortSecurityCommand(self, port, addresses)
|
||||
|
||||
def lsp_get_port_security(self, port):
|
||||
return cmd.LspGetPortSecurityCommand(self, port)
|
||||
|
||||
def lsp_get_up(self, port):
|
||||
return cmd.LspGetUpCommand(self, port)
|
||||
|
||||
def lsp_set_enabled(self, port, is_enabled):
|
||||
return cmd.LspSetEnabledCommand(self, port, is_enabled)
|
||||
|
||||
def lsp_get_enabled(self, port):
|
||||
return cmd.LspGetEnabledCommand(self, port)
|
||||
|
||||
def lsp_set_type(self, port, port_type):
|
||||
return cmd.LspSetTypeCommand(self, port, port_type)
|
||||
|
||||
def lsp_get_type(self, port):
|
||||
return cmd.LspGetTypeCommand(self, port)
|
||||
|
||||
def lsp_set_options(self, port, **options):
|
||||
return cmd.LspSetOptionsCommand(self, port, **options)
|
||||
|
||||
def lsp_get_options(self, port):
|
||||
return cmd.LspGetOptionsCommand(self, port)
|
||||
|
||||
def lsp_set_dhcpv4_options(self, port, dhcpopt_uuids):
|
||||
return cmd.LspSetDhcpV4OptionsCommand(self, port, dhcpopt_uuids)
|
||||
|
||||
def lsp_get_dhcpv4_options(self, port):
|
||||
return cmd.LspGetDhcpV4OptionsCommand(self, port)
|
||||
|
||||
def dhcp_options_add(self, cidr, **external_ids):
|
||||
return cmd.DhcpOptionsAddCommand(self, cidr, **external_ids)
|
||||
|
||||
def dhcp_options_del(self, dhcpopt_uuid):
|
||||
return cmd.DhcpOptionsDelCommand(self, dhcpopt_uuid)
|
||||
|
||||
def dhcp_options_list(self):
|
||||
return cmd.DhcpOptionsListCommand(self)
|
||||
|
||||
def dhcp_options_set_options(self, dhcpopt_uuid, **options):
|
||||
return cmd.DhcpOptionsSetOptionsCommand(self, dhcpopt_uuid, **options)
|
||||
|
||||
def dhcp_options_get_options(self, dhcpopt_uuid):
|
||||
return cmd.DhcpOptionsGetOptionsCommand(self, dhcpopt_uuid)
|
0
ovsdbapp/schema/ovn_southbound/__init__.py
Normal file
0
ovsdbapp/schema/ovn_southbound/__init__.py
Normal file
47
ovsdbapp/tests/functional/base.py
Normal file
47
ovsdbapp/tests/functional/base.py
Normal file
@ -0,0 +1,47 @@
|
||||
# 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 atexit
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from ovsdbapp.backend.ovs_idl import connection
|
||||
from ovsdbapp import constants
|
||||
from ovsdbapp.tests import base
|
||||
from ovsdbapp import venv
|
||||
|
||||
|
||||
class FunctionalTestCase(base.TestCase):
|
||||
connection = None
|
||||
ovsvenv = venv.OvsVenvFixture(tempfile.mkdtemp(),
|
||||
ovsdir=os.getenv('OVS_SRCDIR'), remove=True)
|
||||
atexit.register(ovsvenv.cleanUp)
|
||||
ovsvenv.setUp()
|
||||
|
||||
@classmethod
|
||||
def set_connection(cls):
|
||||
if cls.connection is not None:
|
||||
return
|
||||
if cls.schema == "Open_vSwitch":
|
||||
conn = cls.ovsvenv.ovs_connection
|
||||
elif cls.schema == "OVN_Northbound":
|
||||
conn = cls.ovsvenv.ovnnb_connection
|
||||
elif cls.schema == "OVN_Southbound":
|
||||
conn = cls.ovsvenv.ovnsb_connection
|
||||
else:
|
||||
raise TypeError("Unknown schema '%s'" % cls.schema)
|
||||
idl = connection.OvsdbIdl.from_server(conn, cls.schema)
|
||||
cls.connection = connection.Connection(idl, constants.DEFAULT_TIMEOUT)
|
||||
|
||||
def setUp(self):
|
||||
super(FunctionalTestCase, self).setUp()
|
||||
self.set_connection()
|
@ -13,23 +13,17 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from ovsdbapp.backend.ovs_idl import connection
|
||||
from ovsdbapp import constants
|
||||
from ovsdbapp.schema.open_vswitch import impl_idl
|
||||
from ovsdbapp.tests import base
|
||||
from ovsdbapp.tests.functional import base
|
||||
from ovsdbapp.tests import utils
|
||||
|
||||
ovsdb_connection = connection.Connection(
|
||||
idl=connection.OvsdbIdl.from_server(
|
||||
constants.DEFAULT_OVSDB_CONNECTION, 'Open_vSwitch'),
|
||||
timeout=constants.DEFAULT_TIMEOUT)
|
||||
|
||||
|
||||
class TestOvsdbIdl(base.TestCase):
|
||||
class TestOvsdbIdl(base.FunctionalTestCase):
|
||||
schema = "Open_vSwitch"
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvsdbIdl, self).setUp()
|
||||
self.api = impl_idl.OvsdbIdl(ovsdb_connection)
|
||||
self.api = impl_idl.OvsdbIdl(self.connection)
|
||||
self.brname = utils.get_rand_device_name()
|
||||
# Destroying the bridge cleans up most things created by tests
|
||||
cleanup_cmd = self.api.del_br(self.brname)
|
||||
|
55
ovsdbapp/tests/functional/schema/ovn_northbound/fixtures.py
Normal file
55
ovsdbapp/tests/functional/schema/ovn_northbound/fixtures.py
Normal file
@ -0,0 +1,55 @@
|
||||
# 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.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import fixtures
|
||||
|
||||
from ovsdbapp.schema.ovn_northbound import impl_idl
|
||||
|
||||
|
||||
class ImplIdlFixture(fixtures.Fixture):
|
||||
api, create, delete = (None, None, None)
|
||||
delete_args = {'if_exists': True}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ImplIdlFixture, self).__init__()
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def _setUp(self):
|
||||
api = self.api(None)
|
||||
create_fn = getattr(api, self.create)
|
||||
delete_fn = getattr(api, self.delete)
|
||||
self.obj = create_fn(*self.args, **self.kwargs).execute(
|
||||
check_error=True)
|
||||
self.addCleanup(delete_fn(self.obj.uuid,
|
||||
**self.delete_args).execute, check_error=True)
|
||||
|
||||
|
||||
class LogicalSwitchFixture(ImplIdlFixture):
|
||||
api = impl_idl.OvnNbApiIdlImpl
|
||||
create = 'ls_add'
|
||||
delete = 'ls_del'
|
||||
|
||||
|
||||
class DhcpOptionsFixture(ImplIdlFixture):
|
||||
api = impl_idl.OvnNbApiIdlImpl
|
||||
create = 'dhcp_options_add'
|
||||
delete = 'dhcp_options_del'
|
||||
delete_args = {}
|
||||
|
||||
|
||||
class LogicalRouterFixture(ImplIdlFixture):
|
||||
api = impl_idl.OvnNbApiIdlImpl
|
||||
create = 'lr_add'
|
||||
delete = 'lr_del'
|
417
ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py
Normal file
417
ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py
Normal file
@ -0,0 +1,417 @@
|
||||
# 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 netaddr
|
||||
|
||||
from ovsdbapp.schema.ovn_northbound import impl_idl
|
||||
from ovsdbapp.tests.functional import base
|
||||
from ovsdbapp.tests.functional.schema.ovn_northbound import fixtures
|
||||
from ovsdbapp.tests import utils
|
||||
|
||||
|
||||
class OvnNorthboundTest(base.FunctionalTestCase):
|
||||
schema = 'OVN_Northbound'
|
||||
|
||||
def setUp(self):
|
||||
super(OvnNorthboundTest, self).setUp()
|
||||
self.api = impl_idl.OvnNbApiIdlImpl(self.connection)
|
||||
|
||||
|
||||
class TestLogicalSwitchOps(OvnNorthboundTest):
|
||||
def setUp(self):
|
||||
super(TestLogicalSwitchOps, self).setUp()
|
||||
self.table = self.api.tables['Logical_Switch']
|
||||
|
||||
def _ls_add(self, *args, **kwargs):
|
||||
fix = self.useFixture(fixtures.LogicalSwitchFixture(*args, **kwargs))
|
||||
self.assertIn(fix.obj.uuid, self.table.rows)
|
||||
return fix.obj
|
||||
|
||||
def test_ls_add_no_name(self):
|
||||
self._ls_add()
|
||||
|
||||
def test_ls_add_name(self):
|
||||
name = utils.get_rand_device_name()
|
||||
sw = self._ls_add(name)
|
||||
self.assertEqual(name, sw.name)
|
||||
|
||||
def test_ls_add_exists(self):
|
||||
name = utils.get_rand_device_name()
|
||||
self._ls_add(name)
|
||||
cmd = self.api.ls_add(name)
|
||||
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
|
||||
|
||||
def test_ls_add_may_exist(self):
|
||||
name = utils.get_rand_device_name()
|
||||
sw = self._ls_add(name)
|
||||
sw2 = self.api.ls_add(name, may_exist=True).execute(check_error=True)
|
||||
self.assertEqual(sw, sw2)
|
||||
|
||||
def test_ls_add_columns(self):
|
||||
external_ids = {'mykey': 'myvalue', 'yourkey': 'yourvalue'}
|
||||
ls = self._ls_add(external_ids=external_ids)
|
||||
self.assertEqual(external_ids, ls.external_ids)
|
||||
|
||||
def test_ls_del(self):
|
||||
sw = self._ls_add()
|
||||
self.api.ls_del(sw.uuid).execute(check_error=True)
|
||||
self.assertNotIn(sw.uuid, self.table.rows)
|
||||
|
||||
def test_ls_del_by_name(self):
|
||||
name = utils.get_rand_device_name()
|
||||
self._ls_add(name)
|
||||
self.api.ls_del(name).execute(check_error=True)
|
||||
|
||||
def test_ls_del_no_exist(self):
|
||||
name = utils.get_rand_device_name()
|
||||
cmd = self.api.ls_del(name)
|
||||
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
|
||||
|
||||
def test_ls_del_if_exists(self):
|
||||
name = utils.get_rand_device_name()
|
||||
self.api.ls_del(name, if_exists=True).execute(check_error=True)
|
||||
|
||||
def test_ls_list(self):
|
||||
with self.api.transaction(check_error=True):
|
||||
switches = {self._ls_add() for _ in range(3)}
|
||||
switch_set = set(self.api.ls_list().execute(check_error=True))
|
||||
self.assertTrue(switches.issubset(switch_set))
|
||||
|
||||
|
||||
class TestAclOps(OvnNorthboundTest):
|
||||
def setUp(self):
|
||||
super(TestAclOps, self).setUp()
|
||||
self.switch = self.useFixture(fixtures.LogicalSwitchFixture()).obj
|
||||
|
||||
def _acl_add(self, *args, **kwargs):
|
||||
cmd = self.api.acl_add(self.switch.uuid, *args, **kwargs)
|
||||
aclrow = cmd.execute(check_error=True)
|
||||
self.assertIn(aclrow._row, self.switch.acls)
|
||||
self.assertEqual(cmd.direction, aclrow.direction)
|
||||
self.assertEqual(cmd.priority, aclrow.priority)
|
||||
self.assertEqual(cmd.match, aclrow.match)
|
||||
self.assertEqual(cmd.action, aclrow.action)
|
||||
return aclrow
|
||||
|
||||
def test_acl_add(self):
|
||||
self._acl_add('from-lport', 0, 'output == "fake_port" && ip',
|
||||
'drop')
|
||||
|
||||
def test_acl_add_exists(self):
|
||||
args = ('from-lport', 0, 'output == "fake_port" && ip', 'drop')
|
||||
self._acl_add(*args)
|
||||
self.assertRaises(RuntimeError, self._acl_add, *args)
|
||||
|
||||
def test_acl_add_may_exist(self):
|
||||
args = ('from-lport', 0, 'output == "fake_port" && ip', 'drop')
|
||||
row = self._acl_add(*args)
|
||||
row2 = self._acl_add(*args, may_exist=True)
|
||||
self.assertEqual(row, row2)
|
||||
|
||||
def test_acl_add_extids(self):
|
||||
external_ids = {'mykey': 'myvalue', 'yourkey': 'yourvalue'}
|
||||
acl = self._acl_add('from-lport', 0, 'output == "fake_port" && ip',
|
||||
'drop', **external_ids)
|
||||
self.assertEqual(external_ids, acl.external_ids)
|
||||
|
||||
def test_acl_del_all(self):
|
||||
r1 = self._acl_add('from-lport', 0, 'output == "fake_port"', 'drop')
|
||||
self.api.acl_del(self.switch.uuid).execute(check_error=True)
|
||||
self.assertNotIn(r1.uuid, self.api.tables['ACL'].rows)
|
||||
self.assertEqual([], self.switch.acls)
|
||||
|
||||
def test_acl_del_direction(self):
|
||||
r1 = self._acl_add('from-lport', 0, 'output == "fake_port"', 'drop')
|
||||
r2 = self._acl_add('to-lport', 0, 'output == "fake_port"', 'allow')
|
||||
self.api.acl_del(self.switch.uuid, 'from-lport').execute(
|
||||
check_error=True)
|
||||
self.assertNotIn(r1, self.switch.acls)
|
||||
self.assertIn(r2, self.switch.acls)
|
||||
|
||||
def test_acl_del_direction_priority_match(self):
|
||||
r1 = self._acl_add('from-lport', 0, 'output == "fake_port"', 'drop')
|
||||
r2 = self._acl_add('from-lport', 1, 'output == "fake_port"', 'allow')
|
||||
cmd = self.api.acl_del(self.switch.uuid,
|
||||
'from-lport', 0, 'output == "fake_port"')
|
||||
cmd.execute(check_error=True)
|
||||
self.assertNotIn(r1, self.switch.acls)
|
||||
self.assertIn(r2, self.switch.acls)
|
||||
|
||||
def test_acl_del_priority_without_match(self):
|
||||
self.assertRaises(TypeError, self.api.acl_del, self.switch.uuid,
|
||||
'from-lport', 0)
|
||||
|
||||
def test_acl_del_priority_without_direction(self):
|
||||
self.assertRaises(TypeError, self.api.acl_del, self.switch.uuid,
|
||||
priority=0)
|
||||
|
||||
def test_acl_list(self):
|
||||
r1 = self._acl_add('from-lport', 0, 'output == "fake_port"', 'drop')
|
||||
r2 = self._acl_add('from-lport', 1, 'output == "fake_port2"', 'allow')
|
||||
acls = self.api.acl_list(self.switch.uuid).execute(check_error=True)
|
||||
self.assertIn(r1, acls)
|
||||
self.assertIn(r2, acls)
|
||||
|
||||
|
||||
class TestLspOps(OvnNorthboundTest):
|
||||
def setUp(self):
|
||||
super(TestLspOps, self).setUp()
|
||||
name = utils.get_rand_device_name()
|
||||
self.switch = self.useFixture(
|
||||
fixtures.LogicalSwitchFixture(name)).obj
|
||||
|
||||
def _lsp_add(self, switch, name, *args, **kwargs):
|
||||
name = utils.get_rand_device_name() if name is None else name
|
||||
lsp = self.api.lsp_add(switch, name, *args, **kwargs).execute(
|
||||
check_error=True)
|
||||
self.assertIn(lsp, self.switch.ports)
|
||||
return lsp
|
||||
|
||||
def test_lsp_add(self):
|
||||
self._lsp_add(self.switch.uuid, None)
|
||||
|
||||
def test_lsp_add_exists(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.assertRaises(RuntimeError, self._lsp_add, self.switch.uuid,
|
||||
lsp.name)
|
||||
|
||||
def test_lsp_add_may_exist(self):
|
||||
lsp1 = self._lsp_add(self.switch.uuid, None)
|
||||
lsp2 = self._lsp_add(self.switch.uuid, lsp1.name, may_exist=True)
|
||||
self.assertEqual(lsp1, lsp2)
|
||||
|
||||
def test_lsp_add_may_exist_wrong_switch(self):
|
||||
sw = self.useFixture(fixtures.LogicalSwitchFixture()).obj
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.assertRaises(RuntimeError, self._lsp_add, sw, lsp.name,
|
||||
may_exist=True)
|
||||
|
||||
def test_lsp_add_parent(self):
|
||||
lsp1 = self._lsp_add(self.switch.uuid, None)
|
||||
lsp2 = self._lsp_add(self.switch.uuid, None, parent=lsp1.name, tag=0)
|
||||
# parent_name, being optional, is stored as a list
|
||||
self.assertIn(lsp1.name, lsp2.parent_name)
|
||||
|
||||
def test_lsp_add_parent_no_tag(self):
|
||||
self.assertRaises(TypeError, self._lsp_add, self.switch.uuid,
|
||||
None, parent="fake_parent")
|
||||
|
||||
def test_lsp_add_parent_may_exist(self):
|
||||
lsp1 = self._lsp_add(self.switch.uuid, None)
|
||||
lsp2 = self._lsp_add(self.switch.uuid, None, parent=lsp1.name, tag=0)
|
||||
lsp3 = self._lsp_add(self.switch.uuid, lsp2.name, parent=lsp1.name,
|
||||
tag=0, may_exist=True)
|
||||
self.assertEqual(lsp2, lsp3)
|
||||
|
||||
def test_lsp_add_parent_may_exist_no_parent(self):
|
||||
lsp1 = self._lsp_add(self.switch.uuid, None)
|
||||
self.assertRaises(RuntimeError, self._lsp_add, self.switch.uuid,
|
||||
lsp1.name, parent="fake_parent", tag=0,
|
||||
may_exist=True)
|
||||
|
||||
def test_lsp_add_parent_may_exist_different_parent(self):
|
||||
lsp1 = self._lsp_add(self.switch.uuid, None)
|
||||
lsp2 = self._lsp_add(self.switch.uuid, None, parent=lsp1.name, tag=0)
|
||||
self.assertRaises(RuntimeError, self._lsp_add, self.switch.uuid,
|
||||
lsp2.name, parent="fake_parent", tag=0,
|
||||
may_exist=True)
|
||||
|
||||
def test_lsp_add_parent_may_exist_different_tag(self):
|
||||
lsp1 = self._lsp_add(self.switch.uuid, None)
|
||||
lsp2 = self._lsp_add(self.switch.uuid, None, parent=lsp1.name, tag=0)
|
||||
self.assertRaises(RuntimeError, self._lsp_add, self.switch.uuid,
|
||||
lsp2.name, parent=lsp1.name, tag=1, may_exist=True)
|
||||
|
||||
def test_lsp_add_may_exist_existing_parent(self):
|
||||
lsp1 = self._lsp_add(self.switch.uuid, None)
|
||||
lsp2 = self._lsp_add(self.switch.uuid, None, parent=lsp1.name, tag=0)
|
||||
self.assertRaises(RuntimeError, self._lsp_add, self.switch.uuid,
|
||||
lsp2.name, may_exist=True)
|
||||
|
||||
def test_lsp_add_columns(self):
|
||||
options = {'myside': 'yourside'}
|
||||
external_ids = {'myside': 'yourside'}
|
||||
lsp = self._lsp_add(self.switch.uuid, None, options=options,
|
||||
external_ids=external_ids)
|
||||
self.assertEqual(options, lsp.options)
|
||||
self.assertEqual(external_ids, lsp.external_ids)
|
||||
|
||||
def test_lsp_del_uuid(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.api.lsp_del(lsp.uuid).execute(check_error=True)
|
||||
self.assertNotIn(lsp, self.switch.ports)
|
||||
|
||||
def test_lsp_del_name(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.api.lsp_del(lsp.name).execute(check_error=True)
|
||||
self.assertNotIn(lsp, self.switch.ports)
|
||||
|
||||
def test_lsp_del_switch(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.api.lsp_del(lsp.uuid, self.switch.uuid).execute(check_error=True)
|
||||
self.assertNotIn(lsp, self.switch.ports)
|
||||
|
||||
def test_lsp_del_switch_name(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.api.lsp_del(lsp.uuid,
|
||||
self.switch.name).execute(check_error=True)
|
||||
self.assertNotIn(lsp, self.switch.ports)
|
||||
|
||||
def test_lsp_del_wrong_switch(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
sw_id = self.useFixture(fixtures.LogicalSwitchFixture()).obj
|
||||
cmd = self.api.lsp_del(lsp.uuid, sw_id)
|
||||
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
|
||||
|
||||
def test_lsp_del_switch_no_exist(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
cmd = self.api.lsp_del(lsp.uuid, utils.get_rand_device_name())
|
||||
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
|
||||
|
||||
def test_lsp_del_no_exist(self):
|
||||
cmd = self.api.lsp_del("fake_port")
|
||||
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
|
||||
|
||||
def test_lsp_del_if_exist(self):
|
||||
self.api.lsp_del("fake_port", if_exists=True).execute(check_error=True)
|
||||
|
||||
def test_lsp_list(self):
|
||||
ports = {self._lsp_add(self.switch.uuid, None) for _ in range(3)}
|
||||
port_set = set(self.api.lsp_list(self.switch.uuid).execute(
|
||||
check_error=True))
|
||||
self.assertTrue(ports.issubset(port_set))
|
||||
|
||||
def test_lsp_get_parent(self):
|
||||
ls1 = self._lsp_add(self.switch.uuid, None)
|
||||
ls2 = self._lsp_add(self.switch.uuid, None, parent=ls1.name, tag=0)
|
||||
self.assertEqual(
|
||||
ls1.name, self.api.lsp_get_parent(ls2.name).execute(
|
||||
check_error=True))
|
||||
|
||||
def test_lsp_get_tag(self):
|
||||
ls1 = self._lsp_add(self.switch.uuid, None)
|
||||
ls2 = self._lsp_add(self.switch.uuid, None, parent=ls1.name, tag=0)
|
||||
self.assertIsInstance(self.api.lsp_get_tag(ls2.uuid).execute(
|
||||
check_error=True), int)
|
||||
|
||||
def test_lsp_set_addresses(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
for addr in ('dynamic', 'unknown', 'router',
|
||||
'de:ad:be:ef:4d:ad 192.0.2.1'):
|
||||
self.api.lsp_set_addresses(lsp.name, [addr]).execute(
|
||||
check_error=True)
|
||||
self.assertEqual([addr], lsp.addresses)
|
||||
|
||||
def test_lsp_set_addresses_invalid(self):
|
||||
self.assertRaises(
|
||||
TypeError,
|
||||
self.api.lsp_set_addresses, 'fake', '01:02:03:04:05:06')
|
||||
|
||||
def test_lsp_get_addresses(self):
|
||||
addresses = [
|
||||
'01:02:03:04:05:06 192.0.2.1',
|
||||
'de:ad:be:ef:4d:ad 192.0.2.2']
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.api.lsp_set_addresses(
|
||||
lsp.name, addresses).execute(check_error=True)
|
||||
self.assertEqual(set(addresses), set(self.api.lsp_get_addresses(
|
||||
lsp.name).execute(check_error=True)))
|
||||
|
||||
def test_lsp_get_set_port_security(self):
|
||||
port_security = [
|
||||
'01:02:03:04:05:06 192.0.2.1',
|
||||
'de:ad:be:ef:4d:ad 192.0.2.2']
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.api.lsp_set_port_security(lsp.name, port_security).execute(
|
||||
check_error=True)
|
||||
ps = self.api.lsp_get_port_security(lsp.name).execute(
|
||||
check_error=True)
|
||||
self.assertEqual(port_security, ps)
|
||||
|
||||
def test_lsp_get_up(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.assertFalse(self.api.lsp_get_up(lsp.name).execute(
|
||||
check_error=True))
|
||||
|
||||
def test_lsp_get_set_enabled(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
# default is True
|
||||
self.assertTrue(self.api.lsp_get_enabled(lsp.name).execute(
|
||||
check_error=True))
|
||||
self.api.lsp_set_enabled(lsp.name, False).execute(check_error=True)
|
||||
self.assertFalse(self.api.lsp_get_enabled(lsp.name).execute(
|
||||
check_error=True))
|
||||
self.api.lsp_set_enabled(lsp.name, True).execute(check_error=True)
|
||||
self.assertTrue(self.api.lsp_get_enabled(lsp.name).execute(
|
||||
check_error=True))
|
||||
|
||||
def test_lsp_get_set_type(self):
|
||||
type_ = 'router'
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.api.lsp_set_type(lsp.uuid, type_).execute(check_error=True)
|
||||
self.assertEqual(type_, self.api.lsp_get_type(lsp.uuid).execute(
|
||||
check_error=True))
|
||||
|
||||
def test_lsp_get_set_options(self):
|
||||
options = {'one': 'two', 'three': 'four'}
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
self.api.lsp_set_options(lsp.uuid, **options).execute(
|
||||
check_error=True)
|
||||
self.assertEqual(options, self.api.lsp_get_options(lsp.uuid).execute(
|
||||
check_error=True))
|
||||
|
||||
def test_lsp_set_get_dhcpv4_options(self):
|
||||
lsp = self._lsp_add(self.switch.uuid, None)
|
||||
dhcpopt = self.useFixture(
|
||||
fixtures.DhcpOptionsFixture('192.0.2.1/24')).obj
|
||||
self.api.lsp_set_dhcpv4_options(
|
||||
lsp.name, dhcpopt.uuid).execute(check_error=True)
|
||||
options = self.api.lsp_get_dhcpv4_options(
|
||||
lsp.uuid).execute(check_error=True)
|
||||
self.assertEqual(dhcpopt, options)
|
||||
|
||||
|
||||
class TestDhcpOptionsOps(OvnNorthboundTest):
|
||||
def _dhcpopt_add(self, cidr, *args, **kwargs):
|
||||
dhcpopt = self.useFixture(fixtures.DhcpOptionsFixture(
|
||||
cidr, *args, **kwargs)).obj
|
||||
self.assertEqual(cidr, dhcpopt.cidr)
|
||||
return dhcpopt
|
||||
|
||||
def test_dhcp_options_add(self):
|
||||
self._dhcpopt_add('192.0.2.1/24')
|
||||
|
||||
def test_dhcp_options_add_v6(self):
|
||||
self._dhcpopt_add('2001:db8::1/32')
|
||||
|
||||
def test_dhcp_options_invalid_cidr(self):
|
||||
self.assertRaises(netaddr.AddrFormatError, self.api.dhcp_options_add,
|
||||
'256.0.0.1/24')
|
||||
|
||||
def test_dhcp_options_add_ext_ids(self):
|
||||
ext_ids = {'subnet-id': '1', 'other-id': '2'}
|
||||
dhcpopt = self._dhcpopt_add('192.0.2.1/24', **ext_ids)
|
||||
self.assertEqual(ext_ids, dhcpopt.external_ids)
|
||||
|
||||
def test_dhcp_options_list(self):
|
||||
dhcpopts = {self._dhcpopt_add('192.0.2.1/24') for d in range(3)}
|
||||
dhcpopts_set = set(
|
||||
self.api.dhcp_options_list().execute(check_error=True))
|
||||
self.assertTrue(dhcpopts.issubset(dhcpopts_set))
|
||||
|
||||
def test_dhcp_options_get_set_options(self):
|
||||
dhcpopt = self._dhcpopt_add('192.0.2.1/24')
|
||||
options = {'a': 'one', 'b': 'two'}
|
||||
self.api.dhcp_options_set_options(
|
||||
dhcpopt.uuid, **options).execute(check_error=True)
|
||||
cmd = self.api.dhcp_options_get_options(dhcpopt.uuid)
|
||||
self.assertEqual(options, cmd.execute(check_error=True))
|
46
ovsdbapp/tests/unit/backend/test_ovs_idl.py
Normal file
46
ovsdbapp/tests/unit/backend/test_ovs_idl.py
Normal file
@ -0,0 +1,46 @@
|
||||
# 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.
|
||||
|
||||
from ovsdbapp.backend import ovs_idl
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
from ovsdbapp.tests import base
|
||||
|
||||
|
||||
class FakeRow(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
|
||||
class FakeTable(object):
|
||||
rows = {'fake-id-1': FakeRow(uuid='fake-id-1', name='Fake1')}
|
||||
indexes = []
|
||||
|
||||
|
||||
class TestBackendOvsIdl(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestBackendOvsIdl, self).setUp()
|
||||
self.backend = ovs_idl.Backend()
|
||||
self.backend.tables = {'Faketable': FakeTable()}
|
||||
self.backend.lookup_table = {
|
||||
'Faketable': idlutils.RowLookup('Faketable', 'name', None)}
|
||||
|
||||
def test_lookup_found(self):
|
||||
row = self.backend.lookup('Faketable', 'Fake1')
|
||||
self.assertEqual('Fake1', row.name)
|
||||
|
||||
def test_lookup_not_found(self):
|
||||
self.assertRaises(idlutils.RowNotFound, self.backend.lookup,
|
||||
'Faketable', 'notthere')
|
||||
|
||||
def test_lookup_not_found_default(self):
|
||||
row = self.backend.lookup('Faketable', 'notthere', "NOT_FOUND")
|
||||
self.assertEqual(row, "NOT_FOUND")
|
160
ovsdbapp/venv.py
Normal file
160
ovsdbapp/venv.py
Normal file
@ -0,0 +1,160 @@
|
||||
# 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 glob
|
||||
import os
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import fixtures
|
||||
|
||||
|
||||
class OvsVenvFixture(fixtures.Fixture):
|
||||
def __init__(self, venv, ovsdir, dummy=None, remove=False):
|
||||
if not os.path.isdir(ovsdir):
|
||||
raise Exception("%s is not a directory" % ovsdir)
|
||||
self.venv = venv
|
||||
self.ovsdir = ovsdir
|
||||
self._dummy = dummy
|
||||
self.remove = remove
|
||||
self.env = {'OVS_RUNDIR': self.venv, 'OVS_LOGDIR': self.venv,
|
||||
'OVS_DBDIR': self.venv, 'OVS_SYSCONFDIR': self.venv,
|
||||
'PATH': "{0}/ovsdb:{0}/vswitchd:{0}/utilities:{0}/vtep:"
|
||||
"{0}/ovn/controller:{0}/ovn/controller-vtep:"
|
||||
"{0}/ovn/northd:{0}/ovn/utilities:{1}:".format(
|
||||
self.ovsdir, os.getenv('PATH'))}
|
||||
|
||||
@property
|
||||
def ovs_schema(self):
|
||||
return os.path.join(self.ovsdir, 'vswitchd', 'vswitch.ovsschema')
|
||||
|
||||
@property
|
||||
def ovnsb_schema(self):
|
||||
return os.path.join(self.ovsdir, 'ovn', 'ovn-sb.ovsschema')
|
||||
|
||||
@property
|
||||
def ovnnb_schema(self):
|
||||
return os.path.join(self.ovsdir, 'ovn', 'ovn-nb.ovsschema')
|
||||
|
||||
@property
|
||||
def vtep_schema(self):
|
||||
return os.path.join(self.ovsdir, 'vtep', 'vtep.ovsschema')
|
||||
|
||||
@property
|
||||
def dummy_arg(self):
|
||||
if self._dummy == 'override':
|
||||
return "--enable-dummy=override"
|
||||
elif self._dummy == 'system':
|
||||
return "--enable-dummy=system"
|
||||
else:
|
||||
return "--enable-dummy="
|
||||
|
||||
@property
|
||||
def ovs_connection(self):
|
||||
return 'unix:' + os.path.join(self.venv, 'db.sock')
|
||||
|
||||
@property
|
||||
def ovnnb_connection(self):
|
||||
return 'unix:' + os.path.join(self.venv, 'ovnnb_db.sock')
|
||||
|
||||
@property
|
||||
def ovnsb_connection(self):
|
||||
return 'unix:' + os.path.join(self.venv, 'ovnsb_db.sock')
|
||||
|
||||
def _setUp(self):
|
||||
self.addCleanup(self.deactivate)
|
||||
if not os.path.isdir(self.venv):
|
||||
os.mkdir(self.venv)
|
||||
self.create_db('conf.db', self.ovs_schema)
|
||||
self.create_db('ovnsb.db', self.ovnsb_schema)
|
||||
self.create_db('ovnnb.db', self.ovnnb_schema)
|
||||
self.create_db('vtep.db', self.vtep_schema)
|
||||
self.call(['ovsdb-server',
|
||||
'--remote=p' + self.ovs_connection,
|
||||
'--detach', '--no-chdir', '--pidfile', '-vconsole:off',
|
||||
'--log-file', 'vtep.db', 'conf.db'])
|
||||
self.call(['ovsdb-server', '--detach', '--no-chdir', '-vconsole:off',
|
||||
'--pidfile=%s' % os.path.join(self.venv, 'ovnnb_db.pid'),
|
||||
'--log-file=%s' % os.path.join(self.venv, 'ovnnb_db.log'),
|
||||
'--remote=db:OVN_Northbound,NB_Global,connections',
|
||||
'--private-key=db:OVN_Northbound,SSL,private_key',
|
||||
'--certificate=db:OVN_Northbound,SSL,certificate',
|
||||
'--ca-cert=db:OVN_Northbound,SSL,ca_cert',
|
||||
'--ssl-protocols=db:OVN_Northbound,SSL,ssl_protocols',
|
||||
'--ssl-ciphers=db:OVN_Northbound,SSL,ssl_ciphers',
|
||||
'--remote=p' + self.ovnnb_connection, 'ovnnb.db'])
|
||||
self.call(['ovsdb-server', '--detach', '--no-chdir', '-vconsole:off',
|
||||
'--pidfile=%s' % os.path.join(self.venv, 'ovnsb_db.pid'),
|
||||
'--log-file=%s' % os.path.join(self.venv, 'ovnsb_db.log'),
|
||||
'--remote=db:OVN_Southbound,SB_Global,connections',
|
||||
'--private-key=db:OVN_Southbound,SSL,private_key',
|
||||
'--certificate=db:OVN_Southbound,SSL,certificate',
|
||||
'--ca-cert=db:OVN_Southbound,SSL,ca_cert',
|
||||
'--ssl-protocols=db:OVN_Southbound,SSL,ssl_protocols',
|
||||
'--ssl-ciphers=db:OVN_Southbound,SSL,ssl_ciphers',
|
||||
'--remote=p' + self.ovnsb_connection, 'ovnsb.db'])
|
||||
time.sleep(1) # wait_until_true(os.path.isfile(db_sock)
|
||||
self.call(['ovs-vsctl', '--no-wait', '--', 'init'])
|
||||
self.call(['ovs-vswitchd', '--detach', '--no-chdir', '--pidfile',
|
||||
'-vconsole:off', '-vvconn', '-vnetdev_dummy', '--log-file',
|
||||
self.dummy_arg, self.ovs_connection])
|
||||
self.call(['ovn-nbctl', 'init'])
|
||||
self.call(['ovn-sbctl', 'init'])
|
||||
self.call([
|
||||
'ovs-vsctl', 'set', 'open', '.',
|
||||
'external_ids:system-id=56b18105-5706-46ef-80c4-ff20979ab068',
|
||||
'external_ids:hostname=sandbox',
|
||||
'external_ids:ovn-encap-type=geneve',
|
||||
'external_ids:ovn-encap-ip=127.0.0.1'])
|
||||
# TODO(twilson) SSL stuff
|
||||
if False:
|
||||
pass
|
||||
else:
|
||||
self.call(['ovs-vsctl', 'set', 'open', '.',
|
||||
'external_ids:ovn-remote=' + self.ovnsb_connection])
|
||||
self.call(['ovn-northd', '--detach', '--no-chdir', '--pidfile',
|
||||
'-vconsole:off', '--log-file',
|
||||
'--ovnsb-db=' + self.ovnsb_connection,
|
||||
'--ovnnb-db=' + self.ovnnb_connection])
|
||||
self.call(['ovn-controller', '--detach', '--no-chdir', '--pidfile',
|
||||
'-vconsole:off', '--log-file'])
|
||||
self.call(['ovn-controller-vtep', '--detach', '--no-chdir',
|
||||
'--pidfile', '-vconsole:off', '--log-file',
|
||||
'--ovnsb-db=' + self.ovnsb_connection])
|
||||
|
||||
def deactivate(self):
|
||||
self.kill_processes()
|
||||
if self.remove:
|
||||
shutil.rmtree(self.venv, ignore_errors=True)
|
||||
|
||||
def create_db(self, name, schema):
|
||||
filename = os.path.join(self.venv, name)
|
||||
if not os.path.isfile(filename):
|
||||
return self.call(['ovsdb-tool', '-v', 'create', name, schema])
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
cwd = kwargs.pop('cwd', self.venv)
|
||||
return subprocess.check_call(*args, env=self.env, cwd=cwd, **kwargs)
|
||||
|
||||
def get_pids(self):
|
||||
files = glob.glob(os.path.join(self.venv, "*.pid"))
|
||||
result = []
|
||||
for fname in files:
|
||||
with open(fname, 'r') as f:
|
||||
result.append(int(f.read().strip()))
|
||||
return result
|
||||
|
||||
def kill_processes(self):
|
||||
for pid in self.get_pids():
|
||||
os.kill(pid, signal.SIGTERM)
|
@ -3,5 +3,7 @@
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
netaddr>=0.7.13,!=0.7.16 # BSD
|
||||
ovs>=2.7.0 # Apache-2.0
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
|
@ -48,5 +48,14 @@ fi
|
||||
# install will be constrained and we need to unconstrain it.
|
||||
edit-constraints $localfile -- $LIB_NAME "-e file://$PWD#egg=$LIB_NAME"
|
||||
|
||||
# We require at least OVS 2.7. Testing infrastructure doesn't support it yet,
|
||||
# so build it. Eventually, we should run some checks to see what is actually
|
||||
# installed and see if we can use it instead.
|
||||
if [ "$OVS_SRCDIR" -a ! -d "$OVS_SRCDIR" ]; then
|
||||
echo "Building OVS in $OVS_SRCDIR"
|
||||
mkdir -p $OVS_SRCDIR
|
||||
git clone git://github.com/openvswitch/ovs.git $OVS_SRCDIR
|
||||
(cd $OVS_SRCDIR && ./boot.sh && PYTHON=/usr/bin/python ./configure && make -j$(($(nproc) + 1)))
|
||||
fi
|
||||
$install_cmd -U $*
|
||||
exit $?
|
||||
|
Loading…
Reference in New Issue
Block a user