Merge "Convert SB API to use ovsdbapp"

changes/98/514098/1
Zuul 2017-10-21 07:28:24 +00:00 committed by Gerrit Code Review
commit c7dc194a67
6 changed files with 196 additions and 67 deletions

View File

@ -29,7 +29,7 @@ class MetadataAgentOvnSbIdl(ovsdb_monitor.OvnIdl):
def __init__(self, events=None):
connection_string = config.get_ovn_sb_connection()
helper = idlutils.get_schema_helper(connection_string, self.SCHEMA)
tables = ('Chassis', 'Port_Binding', 'Datapath_Binding')
tables = ('Chassis', 'Encap', 'Port_Binding', 'Datapath_Binding')
for table in tables:
helper.register_table(table)
super(MetadataAgentOvnSbIdl, self).__init__(

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from neutron_lib import exceptions as n_exc
from oslo_log import log
import tenacity
@ -19,13 +21,14 @@ from ovsdbapp.backend.ovs_idl import connection
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp.backend.ovs_idl import transaction as idl_trans
from ovsdbapp.backend.ovs_idl import vlog
from ovsdbapp.schema.ovn_northbound import impl_idl as nb_impl_idl
from ovsdbapp.schema.ovn_southbound import impl_idl as sb_impl_idl
from networking_ovn._i18n import _
from networking_ovn.common import config as cfg
from networking_ovn.common import constants as ovn_const
from networking_ovn.common import utils
from networking_ovn.ovsdb import commands as cmd
from networking_ovn.ovsdb import ovn_api
from networking_ovn.ovsdb import ovsdb_monitor
@ -109,10 +112,7 @@ def get_connection(db_class, trigger=None, driver=None):
return connection.Connection(idl_, timeout=cfg.get_ovn_ovsdb_timeout())
class OvsdbNbOvnIdl(Backend, ovn_api.API):
schema = 'OVN_Northbound'
ovsdb_connection = None
class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
def __init__(self, connection):
super(OvsdbNbOvnIdl, self).__init__(connection)
self.idl._session.reconnect.set_probe_interval(
@ -569,12 +569,11 @@ class OvsdbNbOvnIdl(Backend, ovn_api.API):
raise RuntimeError(msg)
class OvsdbSbOvnIdl(Backend, ovn_api.SbAPI):
schema = 'OVN_Southbound'
ovsdb_connection = None
class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
def __init__(self, connection):
super(OvsdbSbOvnIdl, self).__init__(connection)
# TODO(twilson) This direct access of the idl should be removed in
# favor of a backend-agnostic method
self.idl._session.reconnect.set_probe_interval(
cfg.get_ovn_ovsdb_probe_interval())
@ -585,37 +584,31 @@ class OvsdbSbOvnIdl(Backend, ovn_api.SbAPI):
return list(mapping_dict.keys())
def chassis_exists(self, hostname):
try:
idlutils.row_by_value(self.idl, 'Chassis', 'hostname', hostname)
except idlutils.RowNotFound:
return False
return True
cmd = self.db_find('Chassis', ('hostname', '=', hostname))
return bool(cmd.execute(check_error=True))
def get_chassis_hostname_and_physnets(self):
chassis_info_dict = {}
for ch in self.idl.tables['Chassis'].rows.values():
for ch in self.chassis_list().execute(check_error=True):
chassis_info_dict[ch.hostname] = self._get_chassis_physnets(ch)
return chassis_info_dict
def get_chassis_and_physnets(self):
chassis_info_dict = {}
for ch in self.idl.tables['Chassis'].rows.values():
for ch in self.chassis_list().execute(check_error=True):
chassis_info_dict[ch.name] = self._get_chassis_physnets(ch)
return chassis_info_dict
def get_all_chassis(self, chassis_type=None):
# TODO(azbiswas): Use chassis_type as input once the compute type
# preference patch (as part of external ids) merges.
chassis_list = []
for ch in self.idl.tables['Chassis'].rows.values():
chassis_list.append(ch.name)
return chassis_list
return [c.name for c in self.chassis_list().execute(check_error=True)]
def get_chassis_data_for_ml2_bind_port(self, hostname):
try:
chassis = idlutils.row_by_value(self.idl, 'Chassis',
'hostname', hostname)
except idlutils.RowNotFound:
cmd = self.db_find_rows('Chassis', ('hostname', '=', hostname))
chassis = next(c for c in cmd.execute(check_error=True))
except StopIteration:
msg = _('Chassis with hostname %s does not exist') % hostname
raise RuntimeError(msg)
return (chassis.external_ids.get('datapath-type', ''),
@ -623,49 +616,51 @@ class OvsdbSbOvnIdl(Backend, ovn_api.SbAPI):
self._get_chassis_physnets(chassis))
def get_metadata_port_network(self, network):
for port in self.idl.tables['Port_Binding'].rows.values():
if str(port.datapath.uuid) == network and port.type == 'localport':
return port
# TODO(twilson) This function should really just take a Row/RowView
try:
dp = self.lookup('Datapath_Binding', uuid.UUID(network))
except idlutils.RowNotFound:
return None
cmd = self.db_find_rows('Port_Binding', ('datapath', '=', dp),
('type', '=', 'localport'))
return next(iter(cmd.execute(check_error=True)), None)
def get_chassis_metadata_networks(self, chassis_name):
"""Return a list with the metadata networks the chassis is hosting."""
try:
chassis = idlutils.row_by_value(self.idl, 'Chassis',
'name', chassis_name)
except idlutils.RowNotFound:
msg = _('Chassis %s does not exist') % chassis_name
raise RuntimeError(msg)
chassis = self.lookup('Chassis', chassis_name)
proxy_networks = chassis.external_ids.get(
'neutron-metadata-proxy-networks', None)
return proxy_networks.split(',') if proxy_networks else []
def set_chassis_metadata_networks(self, chassis, networks):
nets = ','.join(networks) if networks else ''
# TODO(twilson) This could just use DbSetCommand
return cmd.UpdateChassisExtIdsCommand(
self, chassis, {'neutron-metadata-proxy-networks': nets},
if_exists=True)
def get_network_port_bindings_by_ip(self, network, ip_address):
port_list = []
for port in self.idl.tables['Port_Binding'].rows.values():
if (port.mac and str(port.datapath.uuid) == network and
ip_address in port.mac[0].split(' ')):
port_list.append(port)
return port_list
rows = self.db_list_rows('Port_Binding').execute(check_error=True)
# TODO(twilson) It would be useful to have a db_find that takes a
# comparison function
return [r for r in rows
if (r.mac and str(r.datapath.uuid) == network) and
ip_address in r.mac[0].split(' ')]
def set_port_cidrs(self, name, cidrs):
return cmd.UpdatePortBindingExtIdsCommand(
self, name, {'neutron-port-cidrs': cidrs}, if_exists=True)
# TODO(twilson) add if_exists to db commands
return self.db_set('Port_Binding', name, 'external_ids',
{'neutron-port-cidrs': cidrs}, if_exists=True)
def get_ports_on_chassis(self, chassis):
ports = []
for port in self.idl.tables['Port_Binding'].rows.values():
if port.chassis and port.chassis[0].name == chassis:
ports.append(port)
return ports
# TODO(twilson) Some day it would be nice to stop passing names around
# and just start using chassis objects so db_find_rows could be used
rows = self.db_list_rows('Port_Binding').execute(check_error=True)
return [r for r in rows if r.chassis and r.chassis[0].name == chassis]
def get_logical_port_chassis_and_datapath(self, name):
for port in self.idl.tables['Port_Binding'].rows.values():
rows = self.db_list_rows('Port_Binding').execute(check_error=True)
for port in rows:
if port.logical_port == name:
datapath = str(port.datapath.uuid)
chassis = port.chassis[0].name if port.chassis else None

View File

@ -154,6 +154,7 @@ class BaseOvnSbIdl(connection.OvsdbIdl):
_check_and_set_ssl_files(schema_name)
helper = idlutils.get_schema_helper(connection_string, schema_name)
helper.register_table('Chassis')
helper.register_table('Encap')
if ovn_config.is_ovn_metadata_enabled():
helper.register_table('Port_Binding')
helper.register_table('Datapath_Binding')
@ -246,6 +247,7 @@ class OvnSbIdl(OvnIdl):
_check_and_set_ssl_files(schema_name)
helper = idlutils.get_schema_helper(connection_string, schema_name)
helper.register_table('Chassis')
helper.register_table('Encap')
if ovn_config.is_ovn_metadata_enabled():
helper.register_table('Port_Binding')
helper.register_table('Datapath_Binding')

View File

@ -218,9 +218,8 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase):
bridge_mapping = ",".join(["%s:br-provider%s" % (phys_net, i)
for i, phys_net in enumerate(physical_nets)])
name = uuidutils.generate_uuid()
with self.sb_api.transaction(check_error=True) as txn:
external_ids['ovn-bridge-mappings'] = bridge_mapping
txn.add(AddFakeChassisCommand(self.sb_api, name, "172.24.4.10",
external_ids=external_ids,
hostname=host))
external_ids['ovn-bridge-mappings'] = bridge_mapping
self.sb_api.chassis_add(
name, ['geneve'], '172.24.4.10', external_ids=external_ids,
hostname=host).execute(check_error=True)
return name

View File

@ -0,0 +1,148 @@
#
# 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 uuid
from ovsdbapp import event as ovsdb_event
from ovsdbapp.tests.functional import base
from ovsdbapp.tests.functional.schema.ovn_southbound import test_impl_idl as \
test_sb
from ovsdbapp.tests import utils
from networking_ovn.ovsdb import impl_idl_ovn as impl
class WaitForPortBindingEvent(test_sb.WaitForPortBindingEvent):
def run(self, event, row, old):
self.row = row
super(WaitForPortBindingEvent, self).run(event, row, old)
class TestSbApi(base.FunctionalTestCase):
schemas = ['OVN_Southbound', 'OVN_Northbound']
def setUp(self):
super(TestSbApi, self).setUp()
self.data = {
'chassis': [
{'external_ids': {'ovn-bridge-mappings':
'public:br-ex,private:br-0'}},
{'external_ids': {'ovn-bridge-mappings':
'public:br-ex,public2:br-ex'}},
{'external_ids': {'ovn-bridge-mappings':
'public:br-ex'}},
]
}
self.api = impl.OvsdbSbOvnIdl(self.connection['OVN_Southbound'])
self.nbapi = impl.OvsdbNbOvnIdl(self.connection['OVN_Northbound'])
self.load_test_data()
self.handler = ovsdb_event.RowEventHandler()
self.api.idl.notify = self.handler.notify
def load_test_data(self):
with self.api.transaction(check_error=True) as txn:
for i, chassis in enumerate(self.data['chassis']):
chassis['name'] = utils.get_rand_device_name('chassis')
chassis['hostname'] = '%s.localdomain.com' % chassis['name']
txn.add(self.api.chassis_add(
chassis['name'], ['geneve'], '192.0.2.%d' % (i + 1,),
hostname=chassis['hostname'],
external_ids=chassis['external_ids']))
def test_get_chassis_hostname_and_physnets(self):
mapping = self.api.get_chassis_hostname_and_physnets()
self.assertTrue(len(self.data['chassis']) <= len(mapping))
self.assertTrue(set(mapping.keys()) >=
{c['hostname'] for c in self.data['chassis']})
def test_get_all_chassis(self):
chassis_list = set(self.api.get_all_chassis())
our_chassis = {c['name'] for c in self.data['chassis']}
self.assertTrue(our_chassis <= chassis_list)
def test_get_chassis_data_for_ml2_bind_port(self):
host = self.data['chassis'][0]['hostname']
dp, iface, phys = self.api.get_chassis_data_for_ml2_bind_port(host)
self.assertEqual(dp, '')
self.assertEqual(iface, '')
self.assertItemsEqual(phys, ['private', 'public'])
def test_chassis_exists(self):
self.assertTrue(self.api.chassis_exists(
self.data['chassis'][0]['hostname']))
self.assertFalse(self.api.chassis_exists("nochassishere"))
def test_get_chassis_and_physnets(self):
mapping = self.api.get_chassis_and_physnets()
self.assertTrue(len(self.data['chassis']) <= len(mapping))
self.assertTrue(set(mapping.keys()) >=
{c['name'] for c in self.data['chassis']})
def _add_switch_port(self, chassis_name, type='localport'):
sname, pname = (utils.get_rand_device_name(prefix=p)
for p in ('switch', 'port'))
chassis = self.api.lookup('Chassis', chassis_name)
row_event = WaitForPortBindingEvent(pname)
self.handler.watch_event(row_event)
with self.nbapi.transaction(check_error=True) as txn:
switch = txn.add(self.nbapi.ls_add(sname))
port = txn.add(self.nbapi.lsp_add(sname, pname, type=type))
row_event.wait()
return chassis, switch.result, port.result, row_event.row
def test_get_metadata_port_network(self):
chassis, switch, port, binding = self._add_switch_port(
self.data['chassis'][0]['name'])
result = self.api.get_metadata_port_network(str(binding.datapath.uuid))
self.assertEqual(binding, result)
self.assertEqual(binding.datapath.external_ids['logical-switch'],
str(switch.uuid))
def test_get_metadata_port_network_missing(self):
val = str(uuid.uuid4())
self.assertIsNone(self.api.get_metadata_port_network(val))
def test_set_get_chassis_metadata_networks(self):
name = self.data['chassis'][0]['name']
nets = [str(uuid.uuid4()) for _ in range(3)]
self.api.set_chassis_metadata_networks(name, nets).execute(
check_error=True)
self.assertEqual(nets, self.api.get_chassis_metadata_networks(name))
def test_get_network_port_bindings_by_ip(self):
chassis, switch, port, binding = self._add_switch_port(
self.data['chassis'][0]['name'])
mac = 'de:ad:be:ef:4d:ad'
ipaddr = '192.0.2.1'
self.nbapi.lsp_set_addresses(
port.name, ['%s %s' % (mac, ipaddr)]).execute(check_error=True)
self.api.lsp_bind(port.name, chassis.name).execute(check_error=True)
result = self.api.get_network_port_bindings_by_ip(
str(binding.datapath.uuid), ipaddr)
self.assertIn(binding, result)
def test_get_ports_on_chassis(self):
chassis, switch, port, binding = self._add_switch_port(
self.data['chassis'][0]['name'])
self.api.lsp_bind(port.name, chassis.name).execute(check_error=True)
self.assertEqual([binding],
self.api.get_ports_on_chassis(chassis.name))
def test_get_logical_port_chassis_and_datapath(self):
chassis, switch, port, binding = self._add_switch_port(
self.data['chassis'][0]['name'])
self.api.lsp_bind(port.name, chassis.name).execute(check_error=True)
self.assertEqual(
(chassis.name, str(binding.datapath.uuid)),
self.api.get_logical_port_chassis_and_datapath(port.name))

View File

@ -736,18 +736,3 @@ class TestSBImplIdlOvn(TestDBImplIdlOvn):
mock_get_probe_interval.return_value = 5000
inst = impl_idl_ovn.OvsdbSbOvnIdl(mock.Mock())
inst.idl._session.reconnect.set_probe_interval.assert_called_with(5000)
def test_get_chassis_hostname_and_physnets(self):
self._load_sb_db()
mapping = self.sb_ovn_idl.get_chassis_hostname_and_physnets()
self.assertEqual(len(mapping), 3)
self.assertItemsEqual(mapping.keys(), ['host-1.localdomain.com',
'host-2.localdomain.com',
'host-3.localdomain.com'])
def test_get_all_chassis(self):
self._load_sb_db()
chassis_list = self.sb_ovn_idl.get_all_chassis()
self.assertItemsEqual(chassis_list, ['host-1', 'host-2', 'host-3'])
# TODO(azbiswas): Unit test get_all_chassis with specific chassis
# type