Revert "Rework venv to support arbitrary schemas"

Related-Bug: #2132018

This reverts commit 9c1790796d.

Revert "Improve Row stringification"

This reverts commit 7744650d02.

Change-Id: I903334f4566ac6dd28452a21cde5ae96fd9a26e4
Signed-off-by: Rodolfo Alonso Hernandez <ralonsoh@redhat.com>
This commit is contained in:
Rodolfo Alonso Hernandez
2025-11-20 14:49:41 +01:00
committed by Yatin Karel
parent b172fdc49f
commit 61cde4703c
6 changed files with 215 additions and 708 deletions

View File

@@ -14,7 +14,6 @@
import collections
from collections import abc
import functools
# json is not deprecated
# pylint: disable=deprecated-module
import json
@@ -25,7 +24,6 @@ import time
import uuid
from ovs.db import idl
from ovs.db import types
from ovs import jsonrpc
from ovs import poller
from ovs import stream
@@ -455,58 +453,15 @@ def db_replace_record(obj):
return obj
def process_value_for_str(row, col):
class StrUuid(uuid.UUID):
"""A UUID class that will return a repr of the UUID as the UUID string
This lets us use the default stringification of lists/dicts to display
in the format we want without having to generate them ourselves
"""
__repr__ = uuid.UUID.__str__
@classmethod
def from_col(cls, col_src, value):
if col_src.is_ref():
return cls(int=value.uuid.int)
return cls(int=value.int)
# If we are passed UUID as a column, just return the modified row.uuid
if col == 'uuid':
return StrUuid(int=row.uuid.int)
val = getattr(row, col)
col_type = row._table.columns[col].type
if col_type.is_optional():
try:
val = val[0]
except IndexError:
return [] # Unset optional
elif col_type.is_map():
# pylint: disable=unnecessary-lambda-assignment
_ = k = v = lambda x: x
if col_type.key.type == types.UuidType:
k = functools.partial(StrUuid.from_col, col_type.key)
if col_type.value.type == types.UuidType:
v = functools.partial(StrUuid.from_col, col_type.value)
if k == v == _: # No change needed
return val
return {k(x): v(y) for x, y in val.items()}
elif col_type.is_set():
if col_type.key.type == types.UuidType:
return [StrUuid.from_col(col_type.key, v) for v in val]
return getattr(row, col)
# optional and non-optional uuid-type columns
if col_type.key.type == types.UuidType:
return StrUuid.from_col(col_type.key, val)
return val
def row2str(row):
return "{table}({data})".format(
table=row._table.name,
data=", ".join("{col!s}={val!r}".format(
col=c, val=process_value_for_str(row, c))
for c in ['uuid'] + sorted(row._table.columns) if hasattr(row, c)))
"""Get a string representation of a Row"""
# This is not a repr, as the Row object takes a dict of Datum objects and
# we don't really want to deal with those, just what the Python values are.
# Row foreign keys are printed as their UUID
return "%s(%s)" % (row._table.name, ", ".join(
"%s=%s" % (col, idl._row_to_uuid(getattr(row, col)))
for col in row._table.columns if hasattr(row, col)))
def frozen_row(row):

View File

@@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from ovsdbapp.backend.ovs_idl import idlutils
class RowView(object):
def __init__(self, row):
@@ -29,6 +27,3 @@ class RowView(object):
def __hash__(self):
return self._row.__hash__()
def __str__(self):
return idlutils.row2str(self._row)

View File

@@ -1,84 +0,0 @@
{
"name": "idltest",
"version": "1.0.0",
"cksum": "123456789 1234",
"tables": {
"SimpleTypes": {
"columns": {
"integer_col": {"type": "integer"},
"real_col": {"type": "real"},
"boolean_col": {"type": "boolean"},
"string_col": {"type": "string"},
"uuid_col": {"type": "uuid"}
},
"isRoot": true
},
"OptionalTypes": {
"columns": {
"opt_integer": {"type": {"key": "integer", "min": 0, "max": 1}},
"opt_real": {"type": {"key": "real", "min": 0, "max": 1}},
"opt_boolean": {"type": {"key": "boolean", "min": 0, "max": 1}},
"opt_string": {"type": {"key": "string", "min": 0, "max": 1}},
"opt_uuid": {"type": {"key": "uuid", "min": 0, "max": 1}}
},
"isRoot": true
},
"SetTypes": {
"columns": {
"integer_set": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
"real_set": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
"boolean_set": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
"string_set": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
"uuid_set": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}
},
"isRoot": true
},
"MapTypes": {
"columns": {
"int_int_map": {"type": {"key": "integer", "value": "integer", "min": 0, "max": "unlimited"}},
"int_real_map": {"type": {"key": "integer", "value": "real", "min": 0, "max": "unlimited"}},
"int_bool_map": {"type": {"key": "integer", "value": "boolean", "min": 0, "max": "unlimited"}},
"int_string_map": {"type": {"key": "integer", "value": "string", "min": 0, "max": "unlimited"}},
"int_uuid_map": {"type": {"key": "integer", "value": "uuid", "min": 0, "max": "unlimited"}},
"real_int_map": {"type": {"key": "real", "value": "integer", "min": 0, "max": "unlimited"}},
"real_real_map": {"type": {"key": "real", "value": "real", "min": 0, "max": "unlimited"}},
"real_bool_map": {"type": {"key": "real", "value": "boolean", "min": 0, "max": "unlimited"}},
"real_string_map": {"type": {"key": "real", "value": "string", "min": 0, "max": "unlimited"}},
"real_uuid_map": {"type": {"key": "real", "value": "uuid", "min": 0, "max": "unlimited"}},
"bool_int_map": {"type": {"key": "boolean", "value": "integer", "min": 0, "max": "unlimited"}},
"bool_real_map": {"type": {"key": "boolean", "value": "real", "min": 0, "max": "unlimited"}},
"bool_bool_map": {"type": {"key": "boolean", "value": "boolean", "min": 0, "max": "unlimited"}},
"bool_string_map": {"type": {"key": "boolean", "value": "string", "min": 0, "max": "unlimited"}},
"bool_uuid_map": {"type": {"key": "boolean", "value": "uuid", "min": 0, "max": "unlimited"}},
"string_int_map": {"type": {"key": "string", "value": "integer", "min": 0, "max": "unlimited"}},
"string_real_map": {"type": {"key": "string", "value": "real", "min": 0, "max": "unlimited"}},
"string_bool_map": {"type": {"key": "string", "value": "boolean", "min": 0, "max": "unlimited"}},
"string_string_map": {"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
"string_uuid_map": {"type": {"key": "string", "value": "uuid", "min": 0, "max": "unlimited"}},
"uuid_int_map": {"type": {"key": "uuid", "value": "integer", "min": 0, "max": "unlimited"}},
"uuid_real_map": {"type": {"key": "uuid", "value": "real", "min": 0, "max": "unlimited"}},
"uuid_bool_map": {"type": {"key": "uuid", "value": "boolean", "min": 0, "max": "unlimited"}},
"uuid_string_map": {"type": {"key": "uuid", "value": "string", "min": 0, "max": "unlimited"}},
"uuid_uuid_map": {"type": {"key": "uuid", "value": "uuid", "min": 0, "max": "unlimited"}}
},
"isRoot": true
},
"RefTarget": {
"columns": {
"value": {"type": "integer"}
},
"isRoot": true
},
"RefTypes": {
"columns": {
"single_ref": {"type": {"key": {"type": "uuid", "refTable": "RefTarget"}}},
"opt_ref": {"type": {"key": {"type": "uuid", "refTable": "RefTarget"}, "min": 0, "max": 1}},
"ref_set": {"type": {"key": {"type": "uuid", "refTable": "RefTarget"}, "min": 0, "max": "unlimited"}},
"ref_map_key": {"type": {"key": {"type": "uuid", "refTable": "RefTarget"}, "value": "string", "min": 0, "max": "unlimited"}},
"ref_map_value": {"type": {"key": "string", "value": {"type": "uuid", "refTable": "RefTarget"}, "min": 0, "max": "unlimited"}},
"ref_map_both": {"type": {"key": {"type": "uuid", "refTable": "RefTarget"}, "value": {"type": "uuid", "refTable": "RefTarget"}, "min": 0, "max": "unlimited"}}
},
"isRoot": true
}
}
}

View File

@@ -1,162 +0,0 @@
# 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 os
import uuid
from ovsdbapp import api
from ovsdbapp.backend import ovs_idl
from ovsdbapp.backend.ovs_idl import connection
from ovsdbapp import constants
from ovsdbapp.tests.functional import base
from ovsdbapp import venv
# flake8 wrongly reports line 1 in the license has an f-string with a single }
# flake8: noqa E999
class IdlTestApi(ovs_idl.Backend, api.API):
pass
class IdlUtilsRow2StrTestCase(base.VenvPerClassFunctionalTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
schema_path = os.path.join(
os.path.dirname(__file__), 'idltest.ovsschema')
cls.ovsdb_server = venv.OvsdbServerFixture(
cls.virtualenv, "idltest", schema_path)
cls.ovsdb_server.setUp()
@classmethod
def tearDownClass(cls):
cls.ovsdb_server.cleanUp()
super().tearDownClass()
def setUp(self):
super().setUp()
self.idl = connection.OvsdbIdl.from_server(
self.ovsdb_server.connection, "idltest")
self.connection = connection.Connection(self.idl,
constants.DEFAULT_TIMEOUT)
self.api = IdlTestApi(self.connection)
def test_simple_row(self):
data = {
"integer_col": 42,
"real_col": 3.14159,
"boolean_col": True,
"string_col": "test_simple",
"uuid_col": uuid.uuid4()}
row = self.api.db_create_row("SimpleTypes",
**data).execute(check_error=True)
self.assertEqual(f"SimpleTypes(uuid={row.uuid}, "
f"boolean_col={data['boolean_col']}, "
f"integer_col={data['integer_col']}, "
f"real_col={data['real_col']}, "
f"string_col='{data['string_col']}', "
f"uuid_col={data['uuid_col']})",
str(row))
def test_optional_row_unset(self):
row = self.api.db_create_row("OptionalTypes").execute(check_error=True)
self.assertEqual(f"OptionalTypes(uuid={row.uuid}, opt_boolean=[], "
"opt_integer=[], opt_real=[], opt_string=[], "
"opt_uuid=[])", str(row))
def test_option_row_set(self):
data = {
"opt_integer": 42,
"opt_real": 3.14159,
"opt_boolean": False,
"opt_string": "foo",
"opt_uuid": uuid.uuid4()}
row = self.api.db_create_row("OptionalTypes", **data).execute(
check_error=True)
self.assertEqual(f"OptionalTypes(uuid={row.uuid}, "
f"opt_boolean={data['opt_boolean']}, "
f"opt_integer={data['opt_integer']}, "
f"opt_real={data['opt_real']}, "
f"opt_string='{data['opt_string']}', "
f"opt_uuid={data['opt_uuid']})", str(row))
def test_set_types(self):
data = {
"integer_set": [1, 2, 3],
"real_set": [1.1, 2.2, 3.3],
"boolean_set": [True, False],
"string_set": ["foo", "bar"],
"uuid_set": [uuid.uuid4(), uuid.uuid4()]}
string_set_str = ", ".join(repr(d) for d in sorted(data['string_set']))
uuid_set_str = ", ".join(str(d) for d in sorted(data['uuid_set']))
row = self.api.db_create_row("SetTypes", **data).execute()
self.assertEqual(f"SetTypes(uuid={row.uuid}, "
f"boolean_set={sorted(data['boolean_set'])}, "
f"integer_set={sorted(data['integer_set'])}, "
f"real_set={sorted(data['real_set'])}, "
f"string_set=[{string_set_str}], "
f"uuid_set=[{uuid_set_str}])", str(row))
def test_map_types(self):
import itertools
vals = {"int": 42, "real": 3.14, "bool": False,
"string": "foo", "uuid": uuid.uuid4()}
data = {f"{a}_{b}_map": {vals[a]: vals[b]}
for a, b in itertools.product(vals.keys(), vals.keys())}
row = self.api.db_create_row("MapTypes", **data).execute()
self.assertEqual(
f"MapTypes(uuid={row.uuid}, "
f"bool_bool_map={{{vals['bool']}: {vals['bool']}}}, "
f"bool_int_map={{{vals['bool']}: {vals['int']}}}, "
f"bool_real_map={{{vals['bool']}: {vals['real']}}}, "
f"bool_string_map={{{vals['bool']}: '{vals['string']}'}}, "
f"bool_uuid_map={{{vals['bool']}: {vals['uuid']}}}, "
f"int_bool_map={{{vals['int']}: {vals['bool']}}}, "
f"int_int_map={{{vals['int']}: {vals['int']}}}, "
f"int_real_map={{{vals['int']}: {vals['real']}}}, "
f"int_string_map={{{vals['int']}: '{vals['string']}'}}, "
f"int_uuid_map={{{vals['int']}: {vals['uuid']}}}, "
f"real_bool_map={{{vals['real']}: {vals['bool']}}}, "
f"real_int_map={{{vals['real']}: {vals['int']}}}, "
f"real_real_map={{{vals['real']}: {vals['real']}}}, "
f"real_string_map={{{vals['real']}: '{vals['string']}'}}, "
f"real_uuid_map={{{vals['real']}: {vals['uuid']}}}, "
f"string_bool_map={{'{vals['string']}': {vals['bool']}}}, "
f"string_int_map={{'{vals['string']}': {vals['int']}}}, "
f"string_real_map={{'{vals['string']}': {vals['real']}}}, "
f"string_string_map={{'{vals['string']}': '{vals['string']}'}}, "
f"string_uuid_map={{'{vals['string']}': {vals['uuid']}}}, "
f"uuid_bool_map={{{vals['uuid']}: {vals['bool']}}}, "
f"uuid_int_map={{{vals['uuid']}: {vals['int']}}}, "
f"uuid_real_map={{{vals['uuid']}: {vals['real']}}}, "
f"uuid_string_map={{{vals['uuid']}: '{vals['string']}'}}, "
f"uuid_uuid_map={{{vals['uuid']}: {vals['uuid']}}})", str(row))
def test_ref_types(self):
ref_target = self.api.db_create_row("RefTarget", value=42).execute()
ref_target = ref_target.uuid
data = {
"single_ref": ref_target,
"ref_set": [ref_target],
"ref_map_key": {ref_target: "foo"},
"ref_map_value": {"foo": ref_target},
"ref_map_both": {ref_target: ref_target}}
row = self.api.db_create_row("RefTypes", **data).execute()
self.assertEqual(
f"RefTypes(uuid={row.uuid}, "
"opt_ref=[], "
f"ref_map_both={{{ref_target}: {ref_target}}}, "
f"ref_map_key={{{ref_target}: 'foo'}}, "
f"ref_map_value={{'foo': {ref_target}}}, "
f"ref_set=[{ref_target}], "
f"single_ref={ref_target})", str(row))

View File

@@ -10,7 +10,9 @@
# 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
@@ -18,39 +20,7 @@ from ovsdbapp.tests import base
from ovsdbapp import venv
class VenvPerClassFunctionalTestCase(base.TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.virtualenv = venv.VenvFixture(
remove=not bool(os.getenv("KEEP_VENV")))
cls.virtualenv.setUp()
cls.ovsvenvlog = None
if os.getenv('KEEP_VENV') and os.getenv('VIRTUAL_ENV'):
cls.ovsvenvlog = open(
os.path.join(os.getenv('VIRTUAL_ENV'),
'ovsvenv.%s' % os.getpid()), 'a+')
cls.ovsvenvlog.write("%s\n" % cls.ovsvenv.venv)
@classmethod
def tearDownClass(cls):
if cls.ovsvenvlog:
cls.ovsvenvlog.close()
cls.virtualenv.cleanUp()
super().tearDownClass()
@classmethod
def venv_log(cls, val):
if cls.ovsvenvlog:
cls.ovsvenvlog.write("%s\n" % val)
def setUp(self):
super().setUp()
self.venv_log(self.id())
class FunctionalTestCase(VenvPerClassFunctionalTestCase):
class FunctionalTestCase(base.TestCase):
_connections = None
fixture_class = venv.OvsOvnVenvFixture
@@ -58,19 +28,28 @@ class FunctionalTestCase(VenvPerClassFunctionalTestCase):
def setUpClass(cls):
super().setUpClass()
cls.ovsvenv = cls.fixture_class(
cls.virtualenv,
tempfile.mkdtemp(),
ovsdir=os.getenv('OVS_SRCDIR'),
ovndir=os.getenv('OVN_SRCDIR'))
ovndir=os.getenv('OVN_SRCDIR'),
remove=not bool(os.getenv('KEEP_VENV')))
atexit.register(cls.ovsvenv.cleanUp)
cls.ovsvenv.setUp()
cls.schema_map = {'Open_vSwitch': cls.ovsvenv.ovs_connection,
'OVN_Northbound': cls.ovsvenv.ovnnb_connection,
'OVN_Southbound': cls.ovsvenv.ovnsb_connection,
}
cls.ovsvenvlog = None
if os.getenv('KEEP_VENV') and os.getenv('VIRTUAL_ENV'):
cls.ovsvenvlog = open(
os.path.join(os.getenv('VIRTUAL_ENV'),
'ovsvenv.%s' % os.getpid()), 'a+')
atexit.register(cls.ovsvenvlog.close)
cls.ovsvenvlog.write("%s\n" % cls.ovsvenv.venv)
@classmethod
def tearDownClass(cls):
cls.ovsvenv.cleanUp()
super().tearDownClass()
def venv_log(cls, val):
if cls.ovsvenvlog:
cls.ovsvenvlog.write("%s\n" % val)
@property
def connection(self):
@@ -92,5 +71,6 @@ class FunctionalTestCase(VenvPerClassFunctionalTestCase):
return connection.Connection(idl, constants.DEFAULT_TIMEOUT)
def setUp(self):
super().setUp()
super(FunctionalTestCase, self).setUp()
self.venv_log(self.id())
self.set_connection()

View File

@@ -10,13 +10,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import errno
import json
import glob
import os
import shutil
import signal
import subprocess
import tempfile
import time
import fixtures
@@ -27,324 +26,126 @@ DUMMY_OVERRIDE_SYSTEM = 'system'
DUMMY_OVERRIDE_NONE = ''
def get_pid(filename):
with open(filename) as f:
return int(f.read().strip())
def kill_pid(pidfile):
"""Kill process using PID from pidfile, handling common exceptions."""
try:
os.kill(get_pid(pidfile), signal.SIGTERM)
except (FileNotFoundError, ProcessLookupError):
pass # Process already stopped or pidfile doesn't exist
class VenvFixture(fixtures.Fixture):
def __init__(self, venvdir=None, remove=False):
super().__init__()
self.venvdir = venvdir or tempfile.mkdtemp()
self.env = {
"OVS_RUNDIR": self.venvdir,
"OVS_LOGDIR": self.venvdir,
"OVS_DBDIR": self.venvdir,
"OVS_SYSCONFDIR": self.venvdir,
"PATH": ""}
self.remove = remove
def deactivate(self):
if self.remove:
shutil.rmtree(self.venvdir, ignore_errors=True)
def _setUp(self):
super()._setUp()
self.addCleanup(self.deactivate)
def call(self, cmd, *args, **kwargs):
cwd = kwargs.pop("cwd", self.venvdir)
try:
return subprocess.check_output(
cmd, *args, env=self.env, stderr=subprocess.STDOUT,
cwd=cwd, **kwargs)
except subprocess.CalledProcessError as e:
raise Exception(
f"Command {cmd} failed with error: {e.returncode} {e.output}")
def set_path(self, path):
self.env["PATH"] = path
def prepend_paths(self, *dirs):
self.env["PATH"] = os.pathsep.join((os.pathsep.join(dirs),
self.env["PATH"]))
@classmethod
def from_fixtures(cls, fixtures, *args, **kwargs):
"""Create a VenvFixture with an environment from a list of fixtures."""
venv = cls(*args, **kwargs)
for fixture in fixtures:
# use dict.fromkeys to deduplicate paths while maintaining order
existing_path = dict.fromkeys(
fixture.env["PATH"].split(os.pathsep))
new_path = dict.fromkeys(fixture.env["PATH"].split(os.pathsep))
new_path.update(existing_path)
venv.env.update(fixture.env)
venv.env["PATH"] = os.pathsep.join(new_path)
return venv
class OvsdbServerFixture(fixtures.Fixture):
def __init__(self, venv, name, schema_filename, ovsdir=None, *args):
super().__init__()
self.venv = venv
self.name = name
self.schema_filename = schema_filename
self.ovsdir = ovsdir
self.args = args
self.additional_dbs = []
self.schema = self.get_schema_name()
if not self.ovsdir:
self.venv.set_path(os.getenv("PATH"))
elif not os.path.isdir(self.ovsdir):
raise FileNotFoundError(
errno.ENOENT, "OVS source directory not found", self.ovsdir)
else:
self.venv.prepend_paths(os.path.join(self.ovsdir, "ovsdb"),
os.path.join(self.ovsdir, "utilities"))
def get_schema_name(self):
with open(self.schema_filename) as f:
return json.load(f)["name"]
@property
def unix_socket(self):
return os.path.join(self.venv.venvdir, f"{self.name}.sock")
@property
def pidfile(self):
return os.path.join(self.venv.venvdir, f"{self.name}.pid")
@property
def logfile(self):
return os.path.join(self.venv.venvdir, f"{self.name}.log")
@property
def dbfile(self):
return os.path.join(self.venv.venvdir, f"{self.name}.db")
@property
def connection(self):
return "unix:" + self.unix_socket
def deactivate(self):
kill_pid(self.pidfile)
def create_db(self, dbfile=None, schema_filename=None):
dbfile = dbfile or self.dbfile
schema_filename = schema_filename or self.schema_filename
if os.path.isfile(dbfile):
return
self.venv.call(["ovsdb-tool", "-v", "create", dbfile, schema_filename])
def start(self):
base_args = (
"ovsdb-server",
"--detach",
"--no-chdir",
"-vconsole:off",
f"--pidfile={self.pidfile}",
f"--log-file={self.logfile}",
f"--remote=p{self.connection}")
# TODO(twilson) Make SSL configurable since not all schemas
# will support it the same way, e.g. OVN supports this but OVS doesn't
# f"--private-key=db:{self.schema},SSL,private_key",
# f"--certificate=db:{self.schema},SSL,certificate",
# f"--ca-cert=db:{self.schema},SSL,ca_cert")
dbs = (self.dbfile,) + tuple(self.additional_dbs)
self.venv.call(base_args + self.args + dbs)
def _setUp(self):
super()._setUp()
self.addCleanup(self.deactivate)
self.create_db()
self.start()
class VswitchdFixture(fixtures.Fixture):
def __init__(self, venv, ovsdb_server, dummy=DUMMY_OVERRIDE_ALL):
super().__init__()
self.venv = venv
self.ovsdb_server = ovsdb_server
self.dummy = dummy
@property
def pidfile(self):
return os.path.join(self.venv.venvdir, "ovs-vswitchd.pid")
@property
def logfile(self):
return os.path.join(self.venv.venvdir, "ovs-vswitchd.log")
@property
def dummy_arg(self):
return "--enable-dummy=%s" % self.dummy
def deactivate(self):
kill_pid(self.pidfile)
def start(self):
self.venv.call(
["ovs-vswitchd",
"--detach",
"--no-chdir",
f"--pidfile={self.pidfile}",
"-vconsole:off",
"-vvconn",
"-vnetdev_dummy",
f"--log-file={self.logfile}",
self.dummy_arg,
self.ovsdb_server.connection])
def _setUp(self):
super()._setUp()
self.addCleanup(self.deactivate)
self.start()
class NorthdFixture(fixtures.Fixture):
def __init__(self, venv, ovnnb_connection, ovnsb_connection):
super().__init__()
self.venv = venv
self.ovnnb_connection = ovnnb_connection
self.ovnsb_connection = ovnsb_connection
@property
def pidfile(self):
return os.path.join(self.venv.venvdir, "ovn-northd.pid")
@property
def logfile(self):
return os.path.join(self.venv.venvdir, "ovn-northd.log")
def deactivate(self):
kill_pid(self.pidfile)
def start(self):
self.venv.call([
"ovn-northd",
"--detach",
"--no-chdir",
f"--pidfile={self.pidfile}",
"-vconsole:off",
f"--log-file={self.logfile}",
f"--ovnsb-db={self.ovnsb_connection}",
f"--ovnnb-db={self.ovnnb_connection}"])
def _setUp(self):
super()._setUp()
self.addCleanup(self.deactivate)
self.start()
class OvnControllerFixture(fixtures.Fixture):
def __init__(self, venv):
super().__init__()
self.venv = venv
@property
def pidfile(self):
return os.path.join(self.venv.venvdir, "ovn-controller.pid")
@property
def logfile(self):
return os.path.join(self.venv.venvdir, "ovn-controller.log")
def deactivate(self):
kill_pid(self.pidfile)
def start(self):
self.venv.call([
"ovn-controller",
"--detach",
"--no-chdir",
f"--pidfile={self.pidfile}",
"-vconsole:off",
f"--log-file={self.logfile}"])
def _setUp(self):
super()._setUp()
self.addCleanup(self.deactivate)
self.start()
class OvsVenvFixture(fixtures.Fixture):
PATH_VAR_TEMPLATE = "{0}/ovsdb:{0}/vswitchd:{0}/utilities"
OVS_PATHS = (
os.path.join(os.path.sep, 'usr', 'local', 'share', 'openvswitch'),
os.path.join(os.path.sep, 'usr', 'share', 'openvswitch'))
OVS_SCHEMA = 'vswitch.ovsschema'
def __init__(self, venv=None, ovsdir=None, dummy=DUMMY_OVERRIDE_ALL):
def __init__(self, venv, ovsdir=None, dummy=DUMMY_OVERRIDE_ALL,
remove=False):
"""Initialize fixture
:param venv: A VenvFixture
:param venv: Path to venv directory.
:param ovsdir: Path to directory containing ovs source codes.
:param dummy: One of following: an empty string, 'override' or
'system'.
:param remove: Boolean value whether venv directory should be removed
at the fixture cleanup.
"""
super().__init__()
self.venv = venv or self.useFixture(VenvFixture())
self.ovsdir = ovsdir or ()
self.dummy = dummy
self.venv = venv
self.env = {'OVS_RUNDIR': self.venv, 'OVS_LOGDIR': self.venv,
'OVS_DBDIR': self.venv, 'OVS_SYSCONFDIR': self.venv}
if ovsdir and os.path.isdir(ovsdir):
# From source directory
self.venv.prepend_paths(os.path.join(ovsdir, "utilities"))
self.venv.prepend_paths(os.path.join(ovsdir, "vswitchd"))
# NOTE(twilson): We don't use useFixture here because we need to
# separate the setUp/start and the initialization of the fixture so
# that we can add additional DBs to the ovsdb-server fixture.
self.ovsdb_server = OvsdbServerFixture(
self.venv, "db", self.ovs_schema, self.ovsdir)
self.env['PATH'] = (self.PATH_VAR_TEMPLATE.format(ovsdir) +
":%s" % os.getenv('PATH'))
else:
# Use installed OVS
self.env['PATH'] = os.getenv('PATH')
self.ovsdir = self._share_path(self.OVS_PATHS, ovsdir)
self._dummy = dummy
self.remove = remove
self.ovsdb_server_dbs = []
@staticmethod
def schema_path(search_paths, filename):
paths = (os.path.join(p, filename) for p in search_paths)
try:
return next(p for p in paths if os.path.isfile(p))
except StopIteration:
raise FileNotFoundError(
errno.ENOENT,
f"Schema file {filename} not found in {search_paths}",
filename)
def _share_path(paths, override=None, files=tuple()):
if not override:
try:
return next(
p for p in paths if os.path.isdir(p) and
all(os.path.isfile(os.path.join(p, f)) for f in files))
except StopIteration:
pass
elif os.path.isdir(override):
return override
@property
def ovs_connection(self):
return self.ovsdb_server.connection
raise Exception("Invalid directories: %s" %
", ".join(paths + (str(override),)))
@property
def ovs_schema(self):
return self.schema_path((self.ovsdir,) + self.OVS_PATHS,
self.OVS_SCHEMA)
path = os.path.join(self.ovsdir, 'vswitchd', self.OVS_SCHEMA)
if os.path.isfile(path):
return path
return os.path.join(self.ovsdir, self.OVS_SCHEMA)
def call(self, *args, **kwargs):
# For backwards compatibility
return self.venv.call(*args, **kwargs)
@property
def dummy_arg(self):
return "--enable-dummy=%s" % self._dummy
@property
def ovs_connection(self):
return 'unix:' + os.path.join(self.venv, 'db.sock')
def _setUp(self):
super()._setUp()
self.useFixture(self.ovsdb_server)
self.vswitchd = self.useFixture(VswitchdFixture(
self.venv, self.ovsdb_server, self.dummy))
self.addCleanup(self.deactivate)
if not os.path.isdir(self.venv):
os.mkdir(self.venv)
self.setup_dbs()
self.start_ovsdb_processes()
time.sleep(1) # wait_until_true(os.path.isfile(db_sock)
self.init_processes()
def setup_dbs(self):
db_filename = 'conf.db'
self.create_db(db_filename, self.ovs_schema)
self.ovsdb_server_dbs.append(db_filename)
def start_ovsdb_processes(self):
self.call([
'ovsdb-server',
'--remote=p' + self.ovs_connection,
'--detach', '--no-chdir', '--pidfile', '-vconsole:off',
'--log-file'] + self.ovsdb_server_dbs)
def init_processes(self):
self.venv.call([
"ovs-vsctl",
"--no-wait",
f"--db={self.ovsdb_server.connection}",
"--",
"init"])
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])
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, cmd, *args, **kwargs):
cwd = kwargs.pop('cwd', self.venv)
return subprocess.check_call(
cmd, *args, env=self.env, stderr=subprocess.STDOUT,
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)
class OvsOvnVenvFixture(OvsVenvFixture):
@@ -358,117 +159,136 @@ class OvsOvnVenvFixture(OvsVenvFixture):
IC_NBSCHEMA = 'ovn-ic-nb.ovsschema'
def __init__(self, venv, ovndir=None, add_chassis=False, **kwargs):
super().__init__(venv, **kwargs)
self.add_chassis = add_chassis
self.ovndir = ovndir or ()
if ovndir and os.path.isdir(ovndir):
# Use OVN source dir
self.PATH_VAR_TEMPLATE += (
":{0}/controller:{0}/northd:{0}/utilities".format(ovndir))
super().__init__(venv, **kwargs)
self.ovndir = self._share_path(self.OVN_PATHS, ovndir,
[self.SBSCHEMA, self.NBSCHEMA])
self.env.update({'OVN_RUNDIR': self.venv})
@property
def ovnsb_schema(self):
search_paths = (self.ovndir,) + self.OVN_PATHS
return self.schema_path(search_paths, self.SBSCHEMA)
return os.path.join(self.ovndir, self.SBSCHEMA)
@property
def ovnnb_schema(self):
search_paths = (self.ovndir,) + self.OVN_PATHS
return self.schema_path(search_paths, self.NBSCHEMA)
return os.path.join(self.ovndir, self.NBSCHEMA)
@property
def ovnnb_connection(self):
return self.ovnnb_server.connection
return 'unix:' + os.path.join(self.venv, 'ovnnb_db.sock')
@property
def ovnsb_connection(self):
return self.ovnsb_server.connection
return 'unix:' + os.path.join(self.venv, 'ovnsb_db.sock')
def _setUp(self):
if self.ovndir and os.path.isdir(self.ovndir):
# Use OVN source dir - add paths to venv
self.venv.prepend_paths(
os.path.join(self.ovndir, "controller"),
os.path.join(self.ovndir, "northd"),
os.path.join(self.ovndir, "utilities"))
self.venv.env.update({"OVN_RUNDIR": self.venv.venvdir})
def setup_dbs(self):
super().setup_dbs()
self.create_db('ovnsb.db', self.ovnsb_schema)
self.create_db('ovnnb.db', self.ovnnb_schema)
self.ovnnb_server = self.useFixture(OvsdbServerFixture(
self.venv, "ovnnb_db", self.ovnnb_schema, self.ovsdir,
"--remote=db:OVN_Northbound,NB_Global,connections",
"--ssl-protocols=db:OVN_Northbound,SSL,ssl_protocols",
"--ssl-ciphers=db:OVN_Northbound,SSL,ssl_ciphers"))
def start_ovsdb_processes(self):
super().start_ovsdb_processes()
data = [
(self.ovnnb_connection,
"OVN_Northbound", "ovnnb", "NB_Global"),
(self.ovnsb_connection,
"OVN_Southbound", "ovnsb", "SB_Global")]
self.ovnsb_server = self.useFixture(OvsdbServerFixture(
self.venv, "ovnsb_db", self.ovnsb_schema, self.ovsdir,
"--remote=db:OVN_Southbound,SB_Global,connections",
"--ssl-protocols=db:OVN_Southbound,SSL,ssl_protocols",
"--ssl-ciphers=db:OVN_Southbound,SSL,ssl_ciphers"))
self.northd = self.useFixture(NorthdFixture(
self.venv, self.ovnnb_connection, self.ovnsb_connection))
self.controller = self.useFixture(OvnControllerFixture(self.venv))
super()._setUp()
for connection, schema, db_name, table in data:
self.call(['ovsdb-server',
'--detach', '--no-chdir', '-vconsole:off',
'--pidfile=%s' % os.path.join(self.venv,
'%s_db.pid' % (db_name)),
'--log-file=%s' % os.path.join(self.venv,
'%s_db.log' % (db_name)),
'--remote=db:%s,%s,connections' % (schema, table),
'--private-key=db:%s,SSL,private_key' % (schema),
'--certificate=db:%s,SSL,certificate' % (schema),
'--ca-cert=db:%s,SSL,ca_cert' % (schema),
'--ssl-protocols=db:%s,SSL,ssl_protocols' % (schema),
'--ssl-ciphers=db:%s,SSL,ssl_ciphers' % (schema),
'--remote=p' + connection, '%s.db' % (db_name)])
def init_processes(self):
super().init_processes()
self.venv.call(["ovn-nbctl", "init"])
self.venv.call(["ovn-sbctl", "init"])
self.call(['ovn-nbctl', 'init'])
self.call(['ovn-sbctl', 'init'])
if self.add_chassis:
self.venv.call([
"ovs-vsctl", f"--db={self.ovsdb_server.connection}",
"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"])
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
self.venv.call([
"ovs-vsctl", f"--db={self.ovsdb_server.connection}",
"set", "open", ".",
"external_ids:ovn-remote=" + self.ovnsb_connection])
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'])
class OvsOvnIcVenvFixture(OvsOvnVenvFixture):
def _setUp(self):
if not self.has_icnb():
return
self.ovn_icnb_server = self.useFixture(OvsdbServerFixture(
self.venv, "ovn_ic_nb_db", self.ovn_icnb_schema, self.ovsdir,
"--remote=db:OVN_IC_Northbound,IC_NB_Global,connections",
"--ssl-protocols=db:OVN_IC_Northbound,SSL,ssl_protocols",
"--ssl-ciphers=db:OVN_IC_Northbound,SSL,ssl_ciphers"))
super()._setUp()
if self.has_icnb():
super()._setUp()
@property
def ovn_icnb_connection(self):
return self.ovn_icnb_server.connection
return 'unix:' + os.path.join(self.venv, 'ovn_ic_nb_db.sock')
@property
def ovn_icnb_schema(self):
search_paths = (self.ovndir,) + self.OVN_PATHS
return self.schema_path(search_paths, self.IC_NBSCHEMA)
return os.path.join(self.ovndir, self.IC_NBSCHEMA)
def has_icnb(self):
return os.path.isfile(self.ovn_icnb_schema)
def setup_dbs(self):
super().setup_dbs()
self.create_db('ovn_ic_nb.db', self.ovn_icnb_schema)
def start_ovsdb_processes(self):
super().start_ovsdb_processes()
connection, schema, db_name, table = (
self.ovn_icnb_connection,
"OVN_IC_Northbound", "ovn_ic_nb", "IC_NB_Global")
self.call(['ovsdb-server',
'--detach', '--no-chdir', '-vconsole:off',
'--pidfile=%s' % os.path.join(self.venv,
'%s_db.pid' % (db_name)),
'--log-file=%s' % os.path.join(self.venv,
'%s_db.log' % (db_name)),
'--remote=db:%s,%s,connections' % (schema, table),
'--private-key=db:%s,SSL,private_key' % (schema),
'--certificate=db:%s,SSL,certificate' % (schema),
'--ca-cert=db:%s,SSL,ca_cert' % (schema),
'--ssl-protocols=db:%s,SSL,ssl_protocols' % (schema),
'--ssl-ciphers=db:%s,SSL,ssl_ciphers' % (schema),
'--remote=p' + connection, '%s.db' % (db_name)])
def init_processes(self):
super().init_processes()
self.venv.call(["ovn-ic-nbctl", "init"])
self.call(['ovn-ic-nbctl', 'init'])
class OvsVtepVenvFixture(OvsOvnVenvFixture):
VTEP_SCHEMA = 'vtep.ovsschema'
VTEP_DB = 'vtep.db'
def __init__(self, venv, **kwargs):
super().__init__(venv, **kwargs)
vtepdir = os.getenv('VTEP_SRCDIR') or ()
vtepdir = os.getenv('VTEP_SRCDIR')
if vtepdir and os.path.isdir(vtepdir):
# Add VTEP source dir to venv paths
self.venv.prepend_paths(vtepdir)
self.vtepdir = vtepdir
# Uses the existing OVS ovsdb-server fixture and passes in a second db
self.ovsdb_server.create_db(self.VTEP_DB, self.vtep_schema)
self.ovsdb_server.additional_dbs.append(self.VTEP_DB)
self.PATH_VAR_TEMPLATE += ":{0}".format(vtepdir)
self.vtepdir = self._share_path(self.OVS_PATHS, vtepdir,
[self.VTEP_SCHEMA])
super().__init__(venv, **kwargs)
def _setUp(self):
if self.has_vtep:
@@ -476,17 +296,20 @@ class OvsVtepVenvFixture(OvsOvnVenvFixture):
@property
def vtep_schema(self):
search_paths = (self.vtepdir,) + self.OVS_PATHS
return self.schema_path(search_paths, self.VTEP_SCHEMA)
return os.path.join(self.vtepdir, self.VTEP_SCHEMA)
@property
def has_vtep(self):
return os.path.isfile(self.vtep_schema)
def setup_dbs(self):
db_filename = 'vtep.conf'
super().setup_dbs()
self.create_db(db_filename, self.vtep_schema)
self.ovsdb_server_dbs.append(db_filename)
def init_processes(self):
super().init_processes()
# there are no 'init' method in vtep-ctl,
# but record in 'Global' table is needed
self.venv.call(["vtep-ctl",
f"--db={self.ovsdb_server.connection}",
"show"])
self.call(['vtep-ctl', 'show'])