[OVN] Import OVN Client, L3 and QoS related code

This patch moves the OVN Client, L3 and QoS related code:
Previous paths in networking-ovn tree:
./networking_ovn/ml2/qos_driver.py -> neutron/services/qos/drivers/ovn/qos_driver.py
./networking_ovn/common/ovn_client.py -> neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py
./networking_ovn/l3/l3_ovn_scheduler.py -> neutron/scheduler/l3_ovn_scheduler.py
./networking_ovn/l3/l3_ovn.py -> neutron/services/ovn_l3/plugin.py

Co-Authored-By: Amitabha Biswas <abiswas@us.ibm.com>
Co-Authored-By: Andrew Austin <aaustin@redhat.com>
Co-Authored-By: Anh Tran <anhtt@vn.fujitsu.com>
Co-Authored-By: Armando Migliaccio <armamig@gmail.com>
Co-Authored-By: Arslan Qadeer <arslanq@xgrid.co>
Co-Authored-By: Boden R <bodenvmw@gmail.com>
Co-Authored-By: Brian Haley <bhaley@redhat.com>
Co-Authored-By: Cao Xuan Hoang <hoangcx@vn.fujitsu.com>
Co-Authored-By: Chandra S Vejendla <csvejend@us.ibm.com>
Co-Authored-By: Changxun Zhou <zhoucx@dtdream.com>
Co-Authored-By: Daniel Alvarez <dalvarez@redhat.com>
Co-Authored-By: Dong Jun <dongj@dtdream.com>
Co-Authored-By: Doug Hellmann <doug@doughellmann.com>
Co-Authored-By: Gary Kotton <gkotton@vmware.com>
Co-Authored-By: Guoshuai Li <ligs@dtdream.com>
Co-Authored-By: Han Zhou <zhouhan@gmail.com>
Co-Authored-By: Ihar Hrachyshka <ihrachys@redhat.com>
Co-Authored-By: Jakub Libosvar <libosvar@redhat.com>
Co-Authored-By: John Kasperski <jckasper@us.ibm.com>
Co-Authored-By: Kamil Sambor <ksambor@redhat.com>
Co-Authored-By: Kevin Benton <kevin@benton.pub>
Co-Authored-By: Kyle Mestery <mestery@mestery.com>
Co-Authored-By: LIU Yulong <liuyulong@le.com>
Co-Authored-By: Lucas Alvares Gomes <lucasagomes@gmail.com>
Co-Authored-By: Maciej Józefczyk <mjozefcz@redhat.com>
Co-Authored-By: Miguel Angel Ajo <majopela@redhat.com>
Co-Authored-By: Na <nazhu@cn.ibm.com>
Co-Authored-By: Numan Siddique <nusiddiq@redhat.com>
Co-Authored-By: Richard Theis <rtheis@us.ibm.com>
Co-Authored-By: Russell Bryant <rbryant@redhat.com>
Co-Authored-By: Sławek Kapłoński <slawek@kaplonski.pl>
Co-Authored-By: Terry Wilson <twilson@redhat.com>
Co-Authored-By: Yunxiang Tao <taoyunxiang@cmss.chinamobile.com>
Co-Authored-By: lzklibj <lzklibj@cn.ibm.com>
Co-Authored-By: melissaml <ma.lei@99cloud.net>
Co-Authored-By: reedip <rbanerje@redhat.com>
Co-Authored-By: venkata anil <anilvenkata@redhat.com>
Co-Authored-By: xurong00037997 <xu.rong@zte.com.cn>
Co-Authored-By: zhufl <zhu.fanglei@zte.com.cn>

Change-Id: I52bc2785d815b3f04efbda3b78a28861ca9e8fe1
Related-Blueprint: neutron-ovn-merge
This commit is contained in:
Maciej Józefczyk
2019-12-14 19:17:40 +00:00
committed by Rodolfo Alonso Hernandez
co-authored by Amitabha Biswas Andrew Austin Anh Tran Armando Migliaccio Arslan Qadeer Boden R Brian Haley Cao Xuan Hoang Chandra S Vejendla Changxun Zhou Daniel Alvarez Dong Jun Doug Hellmann Gary Kotton Guoshuai Li Han Zhou Ihar Hrachyshka Jakub Libosvar John Kasperski Kamil Sambor Kevin Benton Kyle Mestery LIU Yulong Lucas Alvares Gomes Miguel Angel Ajo Na Numan Siddique Richard Theis Russell Bryant Sławek Kapłoński Terry Wilson Yunxiang Tao lzklibj melissaml reedip venkata anil xurong00037997 zhufl
parent 36727e3463
commit be1bdd4342
13 changed files with 4918 additions and 16 deletions
+9 -16
View File
@@ -23,6 +23,7 @@ from oslo_utils import timeutils
import sqlalchemy as sa
from sqlalchemy.orm import exc
from neutron.common.ovn import utils as ovn_utils
from neutron.db.models import l3 # noqa
from neutron.db.models import ovn as ovn_models
from neutron.db.models import securitygroup # noqa
@@ -87,13 +88,6 @@ class UnknownResourceType(n_exc.NeutronException):
message = 'Uknown resource type: %(resource_type)s'
def get_revision_number(resource, resource_type):
"""Get the resource's revision number based on its type."""
if resource_type in _TYPES_PRIORITY_ORDER:
return resource['revision_number']
raise UnknownResourceType(resource_type=resource_type)
def _get_standard_attr_id(context, resource_uuid, resource_type):
try:
row = context.session.query(STD_ATTR_MAP[resource_type]).filter_by(
@@ -110,20 +104,19 @@ def create_initial_revision(context, resource_uuid, resource_type,
LOG.debug('create_initial_revision uuid=%s, type=%s, rev=%s',
resource_uuid, resource_type, revision_number)
db_func = context.session.merge if may_exist else context.session.add
with db_api.CONTEXT_WRITER.using(context):
with context.session.begin(subtransactions=True):
std_attr_id = _get_standard_attr_id(
context, resource_uuid, resource_type)
row = ovn_models.OVNRevisionNumbers(
resource_uuid=resource_uuid, resource_type=resource_type,
standard_attr_id=std_attr_id, revision_number=revision_number)
db_func(row)
context.session.flush()
@db_api.retry_if_session_inactive()
def delete_revision(context, resource_uuid, resource_type):
LOG.debug('delete_revision(%s)', resource_uuid)
with db_api.CONTEXT_WRITER.using(context):
with context.session.begin(subtransactions=True):
row = context.session.query(ovn_models.OVNRevisionNumbers).filter_by(
resource_uuid=resource_uuid,
resource_type=resource_type).one_or_none()
@@ -143,7 +136,7 @@ def _ensure_revision_row_exist(context, resource, resource_type):
# deal with objects that already existed before the sync work. I believe
# that we can remove this method after few development cycles. Or,
# if we decide to make a migration script as well.
with db_api.CONTEXT_WRITER.using(context):
with context.session.begin(subtransactions=True):
if not context.session.query(ovn_models.OVNRevisionNumbers).filter_by(
resource_uuid=resource['id'],
resource_type=resource_type).one_or_none():
@@ -158,7 +151,7 @@ def _ensure_revision_row_exist(context, resource, resource_type):
@db_api.retry_if_session_inactive()
def get_revision_row(context, resource_uuid):
try:
with db_api.CONTEXT_READER.using(context):
with context.session.begin(subtransactions=True):
return context.session.query(
ovn_models.OVNRevisionNumbers).filter_by(
resource_uuid=resource_uuid).one()
@@ -168,8 +161,8 @@ def get_revision_row(context, resource_uuid):
@db_api.retry_if_session_inactive()
def bump_revision(context, resource, resource_type):
revision_number = get_revision_number(resource, resource_type)
with db_api.CONTEXT_WRITER.using(context):
revision_number = ovn_utils.get_revision_number(resource, resource_type)
with context.session.begin(subtransactions=True):
_ensure_revision_row_exist(context, resource, resource_type)
std_attr_id = _get_standard_attr_id(
context, resource['id'], resource_type)
@@ -202,7 +195,7 @@ def get_inconsistent_resources(context):
whens=MAINTENANCE_CREATE_UPDATE_TYPE_ORDER)
time_ = (timeutils.utcnow() -
datetime.timedelta(seconds=INCONSISTENCIES_OLDER_THAN))
with db_api.CONTEXT_READER.using(context):
with context.session.begin(subtransactions=True):
query = context.session.query(ovn_models.OVNRevisionNumbers).join(
standard_attr.StandardAttribute,
ovn_models.OVNRevisionNumbers.standard_attr_id ==
@@ -231,6 +224,6 @@ def get_deleted_resources(context):
"""
sort_order = sa.case(value=ovn_models.OVNRevisionNumbers.resource_type,
whens=MAINTENANCE_DELETE_TYPE_ORDER)
with db_api.CONTEXT_READER.using(context):
with context.session.begin(subtransactions=True):
return context.session.query(ovn_models.OVNRevisionNumbers).filter_by(
standard_attr_id=None).order_by(sort_order).all()
File diff suppressed because it is too large Load Diff
+154
View File
@@ -0,0 +1,154 @@
#
# 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 copy
import random
from oslo_log import log
import six
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
LOG = log.getLogger(__name__)
OVN_SCHEDULER_CHANCE = 'chance'
OVN_SCHEDULER_LEAST_LOADED = 'leastloaded'
@six.add_metaclass(abc.ABCMeta)
class OVNGatewayScheduler(object):
def __init__(self):
pass
@abc.abstractmethod
def select(self, nb_idl, sb_idl, gateway_name, candidates=None):
"""Schedule the gateway port of a router to an OVN chassis.
Schedule the gateway router port only if it is not already
scheduled.
"""
def filter_existing_chassis(self, nb_idl, gw_chassis,
physnet, chassis_physnets,
existing_chassis):
chassis_list = copy.copy(existing_chassis)
for chassis_name in existing_chassis:
if utils.is_gateway_chassis_invalid(chassis_name, gw_chassis,
physnet, chassis_physnets):
LOG.debug("Chassis %(chassis)s is invalid for scheduling "
"router in physnet: %(physnet)s.",
{'chassis': chassis_name,
'physnet': physnet})
chassis_list.remove(chassis_name)
return chassis_list
def _schedule_gateway(self, nb_idl, sb_idl, gateway_name, candidates,
existing_chassis):
existing_chassis = existing_chassis or []
candidates = candidates or self._get_chassis_candidates(sb_idl)
candidates = list(set(candidates) - set(existing_chassis))
# If no candidates, or gateway scheduled on MAX_GATEWAY_CHASSIS nodes
# or all candidates in existing_chassis, return existing_chassis.
# Otherwise, if more candidates present, then schedule them.
if existing_chassis:
if not candidates or (
len(existing_chassis) == ovn_const.MAX_GW_CHASSIS):
return existing_chassis
if not candidates:
return [ovn_const.OVN_GATEWAY_INVALID_CHASSIS]
chassis_count = ovn_const.MAX_GW_CHASSIS - len(existing_chassis)
# The actual binding of the gateway to a chassis via the options
# column or gateway_chassis column in the OVN_Northbound is done
# by the caller
chassis = self._select_gateway_chassis(
nb_idl, candidates)[:chassis_count]
# priority of existing chassis is higher than candidates
chassis = existing_chassis + chassis
LOG.debug("Gateway %s scheduled on chassis %s",
gateway_name, chassis)
return chassis
@abc.abstractmethod
def _select_gateway_chassis(self, nb_idl, candidates):
"""Choose a chassis from candidates based on a specific policy."""
def _get_chassis_candidates(self, sb_idl):
# TODO(azbiswas): Allow selection of a specific type of chassis when
# the upstream code merges.
# return (sb_idl.get_all_chassis('gateway_router') or
# sb_idl.get_all_chassis())
return sb_idl.get_all_chassis()
class OVNGatewayChanceScheduler(OVNGatewayScheduler):
"""Randomly select an chassis for a gateway port of a router"""
def select(self, nb_idl, sb_idl, gateway_name, candidates=None,
existing_chassis=None):
return self._schedule_gateway(nb_idl, sb_idl, gateway_name,
candidates, existing_chassis)
def _select_gateway_chassis(self, nb_idl, candidates):
candidates = copy.deepcopy(candidates)
random.shuffle(candidates)
return candidates
class OVNGatewayLeastLoadedScheduler(OVNGatewayScheduler):
"""Select the least loaded chassis for a gateway port of a router"""
def select(self, nb_idl, sb_idl, gateway_name, candidates=None,
existing_chassis=None):
return self._schedule_gateway(nb_idl, sb_idl, gateway_name,
candidates, existing_chassis)
@staticmethod
def _get_chassis_load_by_prios(chassis_info):
"""Retrieve the amount of ports by priorities hosted in the chassis.
@param chassis_info: list of (port, prio) hosted by this chassis
@type chassis_info: []
@return: A list of (prio, number_of_ports) tuples.
"""
chassis_load = {}
for lrp, prio in chassis_info:
chassis_load[prio] = chassis_load.get(prio, 0) + 1
return chassis_load.items()
@staticmethod
def _get_chassis_load(chassis):
chassis_ports_prios = chassis[1]
return sorted(
OVNGatewayLeastLoadedScheduler._get_chassis_load_by_prios(
chassis_ports_prios), reverse=True)
def _select_gateway_chassis(self, nb_idl, candidates):
chassis_bindings = nb_idl.get_all_chassis_gateway_bindings(candidates)
return [chassis for chassis, load in sorted(chassis_bindings.items(),
key=OVNGatewayLeastLoadedScheduler._get_chassis_load)]
OVN_SCHEDULER_STR_TO_CLASS = {
OVN_SCHEDULER_CHANCE: OVNGatewayChanceScheduler,
OVN_SCHEDULER_LEAST_LOADED: OVNGatewayLeastLoadedScheduler}
def get_scheduler():
return OVN_SCHEDULER_STR_TO_CLASS[ovn_conf.get_ovn_l3_scheduler()]()
View File
+403
View File
@@ -0,0 +1,403 @@
#
# 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 neutron.db import dns_db
from neutron.db import extraroute_db
from neutron.db import l3_gwmode_db
from neutron.db.models import l3 as l3_models
from neutron.quota import resource_registry
from neutron_lib.api.definitions import external_net
from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants as n_const
from neutron_lib import context as n_context
from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from neutron_lib.services import base as service_base
from oslo_log import log
from oslo_utils import excutils
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import extensions
from neutron.common.ovn import utils
from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
from neutron.scheduler import l3_ovn_scheduler
LOG = log.getLogger(__name__)
@registry.has_registry_receivers
class OVNL3RouterPlugin(service_base.ServicePluginBase,
extraroute_db.ExtraRoute_dbonly_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
dns_db.DNSDbMixin):
"""Implementation of the OVN L3 Router Service Plugin.
This class implements a L3 service plugin that provides
router and floatingip resources and manages associated
request/response.
"""
# TODO(mjozefcz): Start consuming it from neutron-lib
# once available.
supported_extension_aliases = (
extensions.ML2_SUPPORTED_API_EXTENSIONS_OVN_L3)
@resource_registry.tracked_resources(router=l3_models.Router,
floatingip=l3_models.FloatingIP)
def __init__(self):
LOG.info("Starting OVNL3RouterPlugin")
super(OVNL3RouterPlugin, self).__init__()
self._nb_ovn_idl = None
self._sb_ovn_idl = None
self._plugin_property = None
self._ovn_client_inst = None
self.scheduler = l3_ovn_scheduler.get_scheduler()
self._register_precommit_callbacks()
def _register_precommit_callbacks(self):
registry.subscribe(
self.create_router_precommit, resources.ROUTER,
events.PRECOMMIT_CREATE)
registry.subscribe(
self.create_floatingip_precommit, resources.FLOATING_IP,
events.PRECOMMIT_CREATE)
@property
def _ovn_client(self):
if self._ovn_client_inst is None:
self._ovn_client_inst = ovn_client.OVNClient(self._ovn,
self._sb_ovn)
return self._ovn_client_inst
@property
def _ovn(self):
if self._nb_ovn_idl is None:
LOG.info("Getting OvsdbNbOvnIdl")
conn = impl_idl_ovn.get_connection(impl_idl_ovn.OvsdbNbOvnIdl)
self._nb_ovn_idl = impl_idl_ovn.OvsdbNbOvnIdl(conn)
return self._nb_ovn_idl
@property
def _sb_ovn(self):
if self._sb_ovn_idl is None:
LOG.info("Getting OvsdbSbOvnIdl")
conn = impl_idl_ovn.get_connection(impl_idl_ovn.OvsdbSbOvnIdl)
self._sb_ovn_idl = impl_idl_ovn.OvsdbSbOvnIdl(conn)
return self._sb_ovn_idl
@property
def _plugin(self):
if self._plugin_property is None:
self._plugin_property = directory.get_plugin()
return self._plugin_property
def get_plugin_type(self):
return plugin_constants.L3
def get_plugin_description(self):
"""returns string description of the plugin."""
return ("L3 Router Service Plugin for basic L3 forwarding"
" using OVN")
def create_router_precommit(self, resource, event, trigger, context,
router, router_id, router_db):
db_rev.create_initial_revision(
context, router_id, ovn_const.TYPE_ROUTERS)
def create_router(self, context, router):
router = super(OVNL3RouterPlugin, self).create_router(context, router)
try:
self._ovn_client.create_router(router)
except Exception:
with excutils.save_and_reraise_exception():
# Delete the logical router
LOG.error('Unable to create lrouter for %s', router['id'])
super(OVNL3RouterPlugin, self).delete_router(context,
router['id'])
return router
def update_router(self, context, id, router):
original_router = self.get_router(context, id)
result = super(OVNL3RouterPlugin, self).update_router(context, id,
router)
try:
self._ovn_client.update_router(result, original_router)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to update lrouter for %s', id)
revert_router = {'router': original_router}
super(OVNL3RouterPlugin, self).update_router(context, id,
revert_router)
return result
def delete_router(self, context, id):
original_router = self.get_router(context, id)
super(OVNL3RouterPlugin, self).delete_router(context, id)
try:
self._ovn_client.delete_router(context, id)
except Exception:
with excutils.save_and_reraise_exception():
super(OVNL3RouterPlugin, self).create_router(
context, {'router': original_router})
def _add_neutron_router_interface(self, context, router_id,
interface_info, may_exist=False):
try:
router_interface_info = (
super(OVNL3RouterPlugin, self).add_router_interface(
context, router_id, interface_info))
except n_exc.PortInUse:
if not may_exist:
raise
# NOTE(lucasagomes): If the port is already being used it means
# the interface has been created already, let's just fetch it from
# the database. Perhaps the code below should live in Neutron
# itself, a get_router_interface() method in the main class
# would be handy
port = self._plugin.get_port(context, interface_info['port_id'])
subnets = [self._plugin.get_subnet(context, s)
for s in utils.get_port_subnet_ids(port)]
router_interface_info = (
self._make_router_interface_info(
router_id, port['tenant_id'], port['id'],
port['network_id'], subnets[0]['id'],
[subnet['id'] for subnet in subnets]))
return router_interface_info
def add_router_interface(self, context, router_id, interface_info,
may_exist=False):
router_interface_info = self._add_neutron_router_interface(
context, router_id, interface_info, may_exist=may_exist)
try:
self._ovn_client.create_router_port(
router_id, router_interface_info)
except Exception:
with excutils.save_and_reraise_exception():
super(OVNL3RouterPlugin, self).remove_router_interface(
context, router_id, router_interface_info)
return router_interface_info
def remove_router_interface(self, context, router_id, interface_info):
router_interface_info = (
super(OVNL3RouterPlugin, self).remove_router_interface(
context, router_id, interface_info))
try:
port_id = router_interface_info['port_id']
subnet_ids = router_interface_info.get('subnet_ids')
self._ovn_client.delete_router_port(
context, port_id, router_id=router_id, subnet_ids=subnet_ids)
except Exception:
with excutils.save_and_reraise_exception():
super(OVNL3RouterPlugin, self).add_router_interface(
context, router_id, interface_info)
return router_interface_info
def create_floatingip_precommit(self, resource, event, trigger, context,
floatingip, floatingip_id, floatingip_db):
db_rev.create_initial_revision(
context, floatingip_id, ovn_const.TYPE_FLOATINGIPS)
def create_floatingip(self, context, floatingip,
initial_status=n_const.FLOATINGIP_STATUS_DOWN):
fip = super(OVNL3RouterPlugin, self).create_floatingip(
context, floatingip, initial_status)
self._ovn_client.create_floatingip(context, fip)
return fip
def delete_floatingip(self, context, id):
# TODO(lucasagomes): Passing ``original_fip`` object as a
# parameter to the OVNClient's delete_floatingip() method is done
# for backward-compatible reasons. Remove it in the Rocky release
# of OpenStack.
original_fip = self.get_floatingip(context, id)
super(OVNL3RouterPlugin, self).delete_floatingip(context, id)
self._ovn_client.delete_floatingip(context, id,
fip_object=original_fip)
def update_floatingip(self, context, id, floatingip):
# TODO(lucasagomes): Passing ``original_fip`` object as a
# parameter to the OVNClient's update_floatingip() method is done
# for backward-compatible reasons. Remove it in the Rocky release
# of OpenStack.
original_fip = self.get_floatingip(context, id)
fip = super(OVNL3RouterPlugin, self).update_floatingip(context, id,
floatingip)
self._ovn_client.update_floatingip(context, fip,
fip_object=original_fip)
return fip
def update_floatingip_status(self, context, floatingip_id, status):
fip = super(OVNL3RouterPlugin, self).update_floatingip_status(
context, floatingip_id, status)
self._ovn_client.update_floatingip_status(context, fip)
return fip
def disassociate_floatingips(self, context, port_id, do_notify=True):
fips = self.get_floatingips(context.elevated(),
filters={'port_id': [port_id]})
router_ids = super(OVNL3RouterPlugin, self).disassociate_floatingips(
context, port_id, do_notify)
for fip in fips:
router_id = fip.get('router_id')
fixed_ip_address = fip.get('fixed_ip_address')
if router_id and fixed_ip_address:
update_fip = {'logical_ip': fixed_ip_address,
'external_ip': fip['floating_ip_address']}
try:
self._ovn_client.disassociate_floatingip(update_fip,
router_id)
self.update_floatingip_status(
context, fip['id'], n_const.FLOATINGIP_STATUS_DOWN)
except Exception as e:
LOG.error('Error in disassociating floatingip %(id)s: '
'%(error)s', {'id': fip['id'], 'error': e})
return router_ids
def _get_gateway_port_physnet_mapping(self):
# This function returns all gateway ports with corresponding
# external network's physnet
net_physnet_dict = {}
port_physnet_dict = {}
l3plugin = directory.get_plugin(plugin_constants.L3)
if not l3plugin:
return port_physnet_dict
context = n_context.get_admin_context()
for net in l3plugin._plugin.get_networks(
context, {external_net.EXTERNAL: [True]}):
if net.get(pnet.NETWORK_TYPE) in [n_const.TYPE_FLAT,
n_const.TYPE_VLAN]:
net_physnet_dict[net['id']] = net.get(pnet.PHYSICAL_NETWORK)
for port in l3plugin._plugin.get_ports(context, filters={
'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW]}):
port_physnet_dict[port['id']] = net_physnet_dict.get(
port['network_id'])
return port_physnet_dict
def update_router_gateway_port_bindings(self, router, host):
status = (n_const.PORT_STATUS_ACTIVE if host
else n_const.PORT_STATUS_DOWN)
context = n_context.get_admin_context()
filters = {'device_id': [router],
'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW]}
for port in self._plugin.get_ports(context, filters=filters):
# FIXME(lucasagomes): Ideally here we would use only
# one database transaction for the status and binding the
# host but, even tho update_port_status() receives a "host"
# parameter apparently it doesn't work for ports which the
# device owner is router_gateway. We need to look into it and
# fix the problem in Neutron before updating it here.
if host:
self._plugin.update_port(
context, port['id'],
{'port': {portbindings.HOST_ID: host}})
if port['status'] != status:
self._plugin.update_port_status(context, port['id'], status)
def schedule_unhosted_gateways(self):
port_physnet_dict = self._get_gateway_port_physnet_mapping()
chassis_physnets = self._sb_ovn.get_chassis_and_physnets()
cms = self._sb_ovn.get_gateway_chassis_from_cms_options()
unhosted_gateways = self._ovn.get_unhosted_gateways(
port_physnet_dict, chassis_physnets, cms)
for g_name in unhosted_gateways:
physnet = port_physnet_dict.get(g_name[len('lrp-'):])
# Remove any invalid gateway chassis from the list, otherwise
# we can have a situation where all existing_chassis are invalid
existing_chassis = self._ovn.get_gateway_chassis_binding(g_name)
master = existing_chassis[0] if existing_chassis else None
existing_chassis = self.scheduler.filter_existing_chassis(
nb_idl=self._ovn, gw_chassis=cms,
physnet=physnet, chassis_physnets=chassis_physnets,
existing_chassis=existing_chassis)
candidates = self._ovn_client.get_candidates_for_scheduling(
physnet, cms=cms, chassis_physnets=chassis_physnets)
chassis = self.scheduler.select(
self._ovn, self._sb_ovn, g_name, candidates=candidates,
existing_chassis=existing_chassis)
if master and master != chassis[0]:
if master not in chassis:
LOG.debug("Master gateway chassis %(old)s "
"has been removed from the system. Moving "
"gateway %(gw)s to other chassis %(new)s.",
{'gw': g_name,
'old': master,
'new': chassis[0]})
else:
LOG.debug("Gateway %s is hosted at %s.", g_name, master)
# NOTE(mjozefcz): It means scheduler moved master chassis
# to other gw based on scheduling method. But we don't
# want network flap - so moving actual master to be on
# the top.
index = chassis.index(master)
chassis[0], chassis[index] = chassis[index], chassis[0]
# NOTE(dalvarez): Let's commit the changes in separate transactions
# as we will rely on those for scheduling subsequent gateways.
with self._ovn.transaction(check_error=True) as txn:
txn.add(self._ovn.update_lrouter_port(
g_name, gateway_chassis=chassis))
@staticmethod
@registry.receives(resources.SUBNET, [events.AFTER_UPDATE])
def _subnet_update(resource, event, trigger, **kwargs):
l3plugin = directory.get_plugin(plugin_constants.L3)
if not l3plugin:
return
context = kwargs['context']
orig = kwargs['original_subnet']
current = kwargs['subnet']
orig_gw_ip = orig['gateway_ip']
current_gw_ip = current['gateway_ip']
if orig_gw_ip == current_gw_ip:
return
gw_ports = l3plugin._plugin.get_ports(context, filters={
'network_id': [orig['network_id']],
'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW],
'fixed_ips': {'subnet_id': [orig['id']]},
})
router_ids = {port['device_id'] for port in gw_ports}
remove = [{'destination': '0.0.0.0/0', 'nexthop': orig_gw_ip}
] if orig_gw_ip else []
add = [{'destination': '0.0.0.0/0', 'nexthop': current_gw_ip}
] if current_gw_ip else []
with l3plugin._ovn.transaction(check_error=True) as txn:
for router_id in router_ids:
l3plugin._ovn_client.update_router_routes(
context, router_id, add, remove, txn=txn)
@staticmethod
@registry.receives(resources.PORT, [events.AFTER_UPDATE])
def _port_update(resource, event, trigger, **kwargs):
l3plugin = directory.get_plugin(plugin_constants.L3)
if not l3plugin:
return
current = kwargs['port']
if utils.is_lsp_router_port(current):
# We call the update_router port with if_exists, because neutron,
# internally creates the port, and then calls update, which will
# trigger this callback even before we had the chance to create
# the OVN NB DB side
l3plugin._ovn_client.update_router_port(current, if_exists=True)
+168
View File
@@ -0,0 +1,168 @@
# 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 neutron.objects.qos import policy as qos_policy
from neutron.objects.qos import rule as qos_rule
from neutron_lib.api.definitions import portbindings
from neutron_lib import constants
from neutron_lib import context as n_context
from neutron_lib.db import constants as db_consts
from neutron_lib.plugins import directory
from neutron_lib.services.qos import base
from neutron_lib.services.qos import constants as qos_consts
from oslo_config import cfg
from oslo_log import log as logging
from neutron.common.ovn import utils
LOG = logging.getLogger(__name__)
OVN_QOS = 'qos'
SUPPORTED_RULES = {
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: {
qos_consts.MAX_KBPS: {
'type:range': [0, db_consts.DB_INTEGER_MAX_VALUE]},
qos_consts.MAX_BURST: {
'type:range': [0, db_consts.DB_INTEGER_MAX_VALUE]},
qos_consts.DIRECTION: {
'type:values': [constants.EGRESS_DIRECTION]}
},
}
VIF_TYPES = [portbindings.VIF_TYPE_OVS, portbindings.VIF_TYPE_VHOST_USER]
VNIC_TYPES = [portbindings.VNIC_NORMAL]
class OVNQosNotificationDriver(base.DriverBase):
"""OVN notification driver for QoS."""
def __init__(self, name='OVNQosDriver',
vif_types=VIF_TYPES,
vnic_types=VNIC_TYPES,
supported_rules=SUPPORTED_RULES,
requires_rpc_notifications=False):
super(OVNQosNotificationDriver, self).__init__(
name, vif_types, vnic_types, supported_rules,
requires_rpc_notifications)
@classmethod
def create(cls, plugin_driver):
cls._driver = plugin_driver
return cls()
@property
def is_loaded(self):
return OVN_QOS in cfg.CONF.ml2.extension_drivers
def create_policy(self, context, policy):
# No need to update OVN on create
pass
def update_policy(self, context, policy):
# Call into OVN client to update the policy
self._driver._ovn_client._qos_driver.update_policy(context, policy)
def delete_policy(self, context, policy):
# No need to update OVN on delete
pass
class OVNQosDriver(object):
"""Qos driver for OVN"""
def __init__(self, driver):
LOG.info("Starting OVNQosDriver")
super(OVNQosDriver, self).__init__()
self._driver = driver
self._plugin_property = None
@property
def _plugin(self):
if self._plugin_property is None:
self._plugin_property = directory.get_plugin()
return self._plugin_property
def _generate_port_options(self, context, policy_id):
if policy_id is None:
return {}
options = {}
# The policy might not have any rules
all_rules = qos_rule.get_rules(qos_policy.QosPolicy,
context, policy_id)
for rule in all_rules:
if isinstance(rule, qos_rule.QosBandwidthLimitRule):
if rule.max_kbps:
options['qos_max_rate'] = str(rule.max_kbps * 1000)
if rule.max_burst_kbps:
options['qos_burst'] = str(rule.max_burst_kbps * 1000)
return options
def get_qos_options(self, port):
# Is qos service enabled
if 'qos_policy_id' not in port:
return {}
# Don't apply qos rules to network devices
if utils.is_network_device_port(port):
return {}
# Determine if port or network policy should be used
context = n_context.get_admin_context()
port_policy_id = port.get('qos_policy_id')
network_policy_id = None
if not port_policy_id:
network_policy = qos_policy.QosPolicy.get_network_policy(
context, port['network_id'])
network_policy_id = network_policy.id if network_policy else None
# Generate qos options for the selected policy
policy_id = port_policy_id or network_policy_id
return self._generate_port_options(context, policy_id)
def _update_network_ports(self, context, network_id, options):
# Retrieve all ports for this network
ports = self._plugin.get_ports(context,
filters={'network_id': [network_id]})
for port in ports:
# Don't apply qos rules if port has a policy
port_policy_id = port.get('qos_policy_id')
if port_policy_id:
continue
# Don't apply qos rules to network devices
if utils.is_network_device_port(port):
continue
# Call into OVN client to update port
self._driver.update_port(port, qos_options=options)
def update_network(self, network):
# Is qos service enabled
if 'qos_policy_id' not in network:
return
# Update the qos options on each network port
context = n_context.get_admin_context()
options = self._generate_port_options(
context, network['qos_policy_id'])
self._update_network_ports(context, network.get('id'), options)
def update_policy(self, context, policy):
options = self._generate_port_options(context, policy.id)
# Update each network bound to this policy
network_bindings = policy.get_bound_networks()
for network_id in network_bindings:
self._update_network_ports(context, network_id, options)
# Update each port bound to this policy
port_bindings = policy.get_bound_ports()
for port_id in port_bindings:
port = self._plugin.get_port(context, port_id)
self._driver.update_port(port, qos_options=options)
@@ -0,0 +1,228 @@
#
# 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 random
import mock
from neutron.tests import base
from neutron.common.ovn import constants as ovn_const
from neutron.scheduler import l3_ovn_scheduler
class FakeOVNGatewaySchedulerNbOvnIdl(object):
def __init__(self, chassis_gateway_mapping, gateway):
self.get_all_chassis_gateway_bindings = mock.Mock(
return_value=chassis_gateway_mapping['Chassis_Bindings'])
self.get_gateway_chassis_binding = mock.Mock(
return_value=chassis_gateway_mapping['Gateways'].get(gateway,
None))
class FakeOVNGatewaySchedulerSbOvnIdl(object):
def __init__(self, chassis_gateway_mapping):
self.get_all_chassis = mock.Mock(
return_value=chassis_gateway_mapping['Chassis'])
class TestOVNGatewayScheduler(base.BaseTestCase):
def setUp(self):
super(TestOVNGatewayScheduler, self).setUp()
# Overwritten by derived classes
self.l3_scheduler = None
# Used for unit tests
self.new_gateway_name = 'lrp_new'
self.fake_chassis_gateway_mappings = {
'None': {'Chassis': [],
'Gateways': {
'g1': [ovn_const.OVN_GATEWAY_INVALID_CHASSIS]}},
'Multiple1': {'Chassis': ['hv1', 'hv2', 'hv3', 'hv4', 'hv5'],
'Gateways': {'g1': ['hv1', 'hv2', 'hv3', 'hv4'],
'g2': ['hv1', 'hv2', 'hv3'],
'g3': ['hv1', 'hv2'],
'g4': ['hv1']}},
'Multiple2': {'Chassis': ['hv1', 'hv2', 'hv3'],
'Gateways': {'g1': ['hv1'],
'g2': ['hv1'],
'g3': ['hv1']}},
'Multiple3': {'Chassis': ['hv1', 'hv2', 'hv3'],
'Gateways': {'g1': ['hv3'],
'g2': ['hv2'],
'g3': ['hv2']}},
'Multiple4': {'Chassis': ['hv1', 'hv2'],
'Gateways': {'g1': ['hv1'],
'g2': ['hv1'],
'g3': ['hv1'],
'g4': ['hv1'],
'g5': ['hv1'],
'g6': ['hv1']}}}
# Determine the chassis to gateway list bindings
for details in self.fake_chassis_gateway_mappings.values():
self.assertNotIn(self.new_gateway_name, details['Gateways'])
details.setdefault('Chassis_Bindings', {})
for chassis in details['Chassis']:
details['Chassis_Bindings'].setdefault(chassis, [])
for gw, chassis_list in details['Gateways'].items():
for chassis in chassis_list:
if chassis in details['Chassis_Bindings']:
details['Chassis_Bindings'][chassis].append((gw, 0))
def select(self, chassis_gateway_mapping, gateway_name):
nb_idl = FakeOVNGatewaySchedulerNbOvnIdl(chassis_gateway_mapping,
gateway_name)
sb_idl = FakeOVNGatewaySchedulerSbOvnIdl(chassis_gateway_mapping)
return self.l3_scheduler.select(nb_idl, sb_idl,
gateway_name)
def filter_existing_chassis(self, *args, **kwargs):
return self.l3_scheduler.filter_existing_chassis(
nb_idl=kwargs.pop('nb_idl'), gw_chassis=kwargs.pop('gw_chassis'),
physnet=kwargs.pop('physnet'),
chassis_physnets=kwargs.pop('chassis_physnets'),
existing_chassis=kwargs.pop('existing_chassis'))
class OVNGatewayChanceScheduler(TestOVNGatewayScheduler):
def setUp(self):
super(OVNGatewayChanceScheduler, self).setUp()
self.l3_scheduler = l3_ovn_scheduler.OVNGatewayChanceScheduler()
def test_no_chassis_available_for_existing_gateway(self):
mapping = self.fake_chassis_gateway_mappings['None']
gateway_name = random.choice(list(mapping['Gateways'].keys()))
chassis = self.select(mapping, gateway_name)
self.assertEqual([ovn_const.OVN_GATEWAY_INVALID_CHASSIS], chassis)
def test_no_chassis_available_for_new_gateway(self):
mapping = self.fake_chassis_gateway_mappings['None']
gateway_name = self.new_gateway_name
chassis = self.select(mapping, gateway_name)
self.assertEqual([ovn_const.OVN_GATEWAY_INVALID_CHASSIS], chassis)
def test_random_chassis_available_for_new_gateway(self):
mapping = self.fake_chassis_gateway_mappings['Multiple1']
gateway_name = self.new_gateway_name
chassis = self.select(mapping, gateway_name)
self.assertItemsEqual(chassis, mapping.get('Chassis'))
def test_filter_existing_chassis(self):
# filter_existing_chassis is scheduler independent, but calling
# it from Base class didnt seem right. Also, there is no need to have
# another test in LeastLoadedScheduler.
chassis_physnets = {'temp': ['phys-network-0', 'phys-network-1']}
nb_idl = FakeOVNGatewaySchedulerNbOvnIdl(
self.fake_chassis_gateway_mappings['None'], 'g1')
# Check if invalid chassis is removed
self.assertEqual(
['temp'], self.filter_existing_chassis(
nb_idl=nb_idl, gw_chassis=["temp"],
physnet='phys-network-1',
chassis_physnets=chassis_physnets,
existing_chassis=['temp',
ovn_const.OVN_GATEWAY_INVALID_CHASSIS]))
# Check if invalid is removed -II
self.assertFalse(
self.filter_existing_chassis(
nb_idl=nb_idl, gw_chassis=["temp"],
physnet='phys-network-1',
chassis_physnets=chassis_physnets,
existing_chassis=[ovn_const.OVN_GATEWAY_INVALID_CHASSIS]))
# Check if chassis removed when physnet doesnt exist
self.assertFalse(
self.filter_existing_chassis(
nb_idl=nb_idl, gw_chassis=["temp"],
physnet='phys-network-2',
chassis_physnets=chassis_physnets,
existing_chassis=['temp']))
# Check if chassis removed when it doesnt exist in gw_chassis
# or in chassis_physnets
self.assertFalse(
self.filter_existing_chassis(
nb_idl=nb_idl, gw_chassis=["temp1"],
physnet='phys-network-2',
chassis_physnets=chassis_physnets,
existing_chassis=['temp']))
class OVNGatewayLeastLoadedScheduler(TestOVNGatewayScheduler):
def setUp(self):
super(OVNGatewayLeastLoadedScheduler, self).setUp()
self.l3_scheduler = l3_ovn_scheduler.OVNGatewayLeastLoadedScheduler()
def test_no_chassis_available_for_existing_gateway(self):
mapping = self.fake_chassis_gateway_mappings['None']
gateway_name = random.choice(list(mapping['Gateways'].keys()))
chassis = self.select(mapping, gateway_name)
self.assertEqual([ovn_const.OVN_GATEWAY_INVALID_CHASSIS], chassis)
def test_no_chassis_available_for_new_gateway(self):
mapping = self.fake_chassis_gateway_mappings['None']
gateway_name = self.new_gateway_name
chassis = self.select(mapping, gateway_name)
self.assertEqual([ovn_const.OVN_GATEWAY_INVALID_CHASSIS], chassis)
def test_least_loaded_chassis_available_for_new_gateway1(self):
mapping = self.fake_chassis_gateway_mappings['Multiple1']
gateway_name = self.new_gateway_name
chassis = self.select(mapping, gateway_name)
self.assertItemsEqual(chassis, mapping.get('Chassis'))
# least loaded will be the first one in the list,
# networking-ovn will assign highest priority to this first element
self.assertEqual(['hv5', 'hv4', 'hv3', 'hv2', 'hv1'], chassis)
def test_least_loaded_chassis_available_for_new_gateway2(self):
mapping = self.fake_chassis_gateway_mappings['Multiple2']
gateway_name = self.new_gateway_name
chassis = self.select(mapping, gateway_name)
# hv1 will have least priority
self.assertEqual(chassis[2], 'hv1')
def test_least_loaded_chassis_available_for_new_gateway3(self):
mapping = self.fake_chassis_gateway_mappings['Multiple3']
gateway_name = self.new_gateway_name
chassis = self.select(mapping, gateway_name)
# least loaded chassis will be in the front of the list
self.assertEqual(['hv1', 'hv3', 'hv2'], chassis)
def test_least_loaded_chassis_with_rebalance(self):
mapping = self.fake_chassis_gateway_mappings['Multiple4']
gateway_name = self.new_gateway_name
chassis = self.select(mapping, gateway_name)
# least loaded chassis will be in the front of the list
self.assertEqual(['hv2', 'hv1'], chassis)
def test_existing_chassis_available_for_existing_gateway(self):
mapping = self.fake_chassis_gateway_mappings['Multiple1']
gateway_name = random.choice(list(mapping['Gateways'].keys()))
chassis = self.select(mapping, gateway_name)
self.assertEqual(ovn_const.MAX_GW_CHASSIS, len(chassis))
def test__get_chassis_load_by_prios_several_ports(self):
# Adding 5 ports of prio 1 and 5 ports of prio 2
chassis_info = []
for i in range(1, 6):
chassis_info.append(('lrp', 1))
chassis_info.append(('lrp', 2))
actual = self.l3_scheduler._get_chassis_load_by_prios(chassis_info)
expected = {1: 5, 2: 5}
self.assertItemsEqual(expected.items(), actual)
def test__get_chassis_load_by_prios_no_ports(self):
self.assertFalse(self.l3_scheduler._get_chassis_load_by_prios([]))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,242 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutron.objects.qos import policy as qos_policy
from neutron.objects.qos import rule as qos_rule
from neutron.tests import base
from neutron_lib import constants
from oslo_utils import uuidutils
from neutron.common.ovn import utils
from neutron.services.qos.drivers.ovn import driver
context = 'context'
class TestOVNQosNotificationDriver(base.BaseTestCase):
def setUp(self):
super(TestOVNQosNotificationDriver, self).setUp()
self.mech_driver = mock.Mock()
self.mech_driver._ovn_client = mock.Mock()
self.mech_driver._ovn_client._qos_driver = mock.Mock()
self.driver = driver.OVNQosNotificationDriver.create(
self.mech_driver)
self.policy = "policy"
def test_create_policy(self):
self.driver.create_policy(context, self.policy)
self.driver._driver._ovn_client._qos_driver.create_policy.\
assert_not_called()
def test_update_policy(self):
self.driver.update_policy(context, self.policy)
self.driver._driver._ovn_client._qos_driver.update_policy.\
assert_called_once_with(context, self.policy)
def test_delete_policy(self):
self.driver.delete_policy(context, self.policy)
self.driver._driver._ovn_client._qos_driver.delete_policy.\
assert_not_called()
class TestOVNQosDriver(base.BaseTestCase):
def setUp(self):
super(TestOVNQosDriver, self).setUp()
self.plugin = mock.Mock()
self.ovn_client = mock.Mock()
self.driver = driver.OVNQosDriver(self.ovn_client)
self.driver._plugin_property = self.plugin
self.port_id = uuidutils.generate_uuid()
self.policy_id = uuidutils.generate_uuid()
self.network_id = uuidutils.generate_uuid()
self.network_policy_id = uuidutils.generate_uuid()
self.policy = self._create_fake_policy()
self.port = self._create_fake_port()
self.rule = self._create_bw_limit_rule()
self.expected = {'qos_max_rate': '1000', 'qos_burst': '100000'}
def _create_bw_limit_rule(self):
rule_obj = qos_rule.QosBandwidthLimitRule()
rule_obj.id = uuidutils.generate_uuid()
rule_obj.max_kbps = 1
rule_obj.max_burst_kbps = 100
rule_obj.obj_reset_changes()
return rule_obj
def _create_fake_policy(self):
policy_dict = {'id': self.network_policy_id}
policy_obj = qos_policy.QosPolicy(context, **policy_dict)
policy_obj.obj_reset_changes()
return policy_obj
def _create_fake_port(self):
return {'id': self.port_id,
'qos_policy_id': self.policy_id,
'network_id': self.network_id,
'device_owner': 'compute:fake'}
def _create_fake_network(self):
return {'id': self.network_id,
'qos_policy_id': self.network_policy_id}
def test__is_network_device_port(self):
self.assertFalse(utils.is_network_device_port(self.port))
port = self._create_fake_port()
port['device_owner'] = constants.DEVICE_OWNER_DHCP
self.assertTrue(utils.is_network_device_port(port))
port['device_owner'] = 'neutron:LOADBALANCERV2'
self.assertTrue(utils.is_network_device_port(port))
def _generate_port_options(self, policy_id, return_val, expected_result):
with mock.patch.object(qos_rule, 'get_rules',
return_value=return_val) as get_rules:
options = self.driver._generate_port_options(context, policy_id)
if policy_id:
get_rules.assert_called_once_with(qos_policy.QosPolicy,
context, policy_id)
else:
get_rules.assert_not_called()
self.assertEqual(expected_result, options)
def test__generate_port_options_no_policy_id(self):
self._generate_port_options(None, [], {})
def test__generate_port_options_no_rules(self):
self._generate_port_options(self.policy_id, [], {})
def test__generate_port_options_with_rule(self):
self._generate_port_options(self.policy_id, [self.rule], self.expected)
def _get_qos_options(self, port, port_policy, network_policy):
with mock.patch.object(qos_policy.QosPolicy, 'get_network_policy',
return_value=self.policy) as get_network_policy:
with mock.patch.object(self.driver, '_generate_port_options',
return_value={}) as generate_port_options:
options = self.driver.get_qos_options(port)
if network_policy:
get_network_policy.\
assert_called_once_with(context, self.network_id)
generate_port_options. \
assert_called_once_with(context,
self.network_policy_id)
elif port_policy:
get_network_policy.assert_not_called()
generate_port_options.\
assert_called_once_with(context, self.policy_id)
else:
get_network_policy.assert_not_called()
generate_port_options.assert_not_called()
self.assertEqual({}, options)
def test_get_qos_options_no_qos(self):
port = self._create_fake_port()
port.pop('qos_policy_id')
self._get_qos_options(port, False, False)
def test_get_qos_options_network_port(self):
port = self._create_fake_port()
port['device_owner'] = constants.DEVICE_OWNER_DHCP
self._get_qos_options(port, False, False)
@mock.patch('neutron_lib.context.get_admin_context', return_value=context)
def test_get_qos_options_port_policy(self, *mocks):
self._get_qos_options(self.port, True, False)
@mock.patch('neutron_lib.context.get_admin_context', return_value=context)
def test_get_qos_options_network_policy(self, *mocks):
port = self._create_fake_port()
port['qos_policy_id'] = None
self._get_qos_options(port, False, True)
def _update_network_ports(self, port, called):
with mock.patch.object(self.plugin, 'get_ports',
return_value=[port]) as get_ports:
with mock.patch.object(self.ovn_client,
'update_port') as update_port:
self.driver._update_network_ports(
context, self.network_id, {})
get_ports.assert_called_once_with(
context, filters={'network_id': [self.network_id]})
if called:
update_port.assert_called()
else:
update_port.assert_not_called()
def test__update_network_ports_port_policy(self):
self._update_network_ports(self.port, False)
def test__update_network_ports_network_device(self):
port = self._create_fake_port()
port['device_owner'] = constants.DEVICE_OWNER_DHCP
self._update_network_ports(port, False)
def test__update_network_ports(self):
port = self._create_fake_port()
port['qos_policy_id'] = None
self._update_network_ports(port, True)
def _update_network(self, network, called):
with mock.patch.object(self.driver, '_generate_port_options',
return_value={}) as generate_port_options:
with mock.patch.object(self.driver, '_update_network_ports'
) as update_network_ports:
self.driver.update_network(network)
if called:
generate_port_options.assert_called_once_with(
context, self.network_policy_id)
update_network_ports.assert_called_once_with(
context, self.network_id, {})
else:
generate_port_options.assert_not_called()
update_network_ports.assert_not_called()
@mock.patch('neutron_lib.context.get_admin_context', return_value=context)
def test_update_network_no_qos(self, *mocks):
network = self._create_fake_network()
network.pop('qos_policy_id')
self._update_network(network, False)
@mock.patch('neutron_lib.context.get_admin_context', return_value=context)
def test_update_network_policy_change(self, *mocks):
network = self._create_fake_network()
self._update_network(network, True)
def test_update_policy(self):
with mock.patch.object(self.driver, '_generate_port_options',
return_value={}) as generate_port_options, \
mock.patch.object(self.policy, 'get_bound_networks',
return_value=[self.network_id]
) as get_bound_networks, \
mock.patch.object(self.driver, '_update_network_ports'
) as update_network_ports, \
mock.patch.object(self.policy, 'get_bound_ports',
return_value=[self.port_id]
) as get_bound_ports, \
mock.patch.object(self.plugin, 'get_port',
return_value=self.port) as get_port, \
mock.patch.object(self.ovn_client, 'update_port',
) as update_port:
self.driver.update_policy(context, self.policy)
generate_port_options.assert_called_once_with(
context, self.network_policy_id)
get_bound_networks.assert_called_once_with()
update_network_ports.assert_called_once_with(
context, self.network_id, {})
get_bound_ports.assert_called_once_with()
get_port.assert_called_once_with(context, self.port_id)
update_port.assert_called_once_with(self.port, qos_options={})
+1
View File
@@ -78,6 +78,7 @@ neutron.service_plugins =
port_forwarding = neutron.services.portforwarding.pf_plugin:PortForwardingPlugin
placement = neutron.services.placement_report.plugin:PlacementReportPlugin
conntrack_helper = neutron.services.conntrack_helper.plugin:Plugin
ovn-router = neutron.services.ovn_l3.plugin:OVNL3RouterPlugin
neutron.ml2.type_drivers =
flat = neutron.plugins.ml2.drivers.type_flat:FlatTypeDriver
local = neutron.plugins.ml2.drivers.type_local:LocalTypeDriver