271 lines
12 KiB
Python
271 lines
12 KiB
Python
# 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_lib.api.definitions import portbindings
|
|
from neutron_lib.callbacks import events
|
|
from neutron_lib.callbacks import registry
|
|
from neutron_lib.callbacks import resources
|
|
from neutron_lib import context as n_context
|
|
from neutron_lib.db import api as db_api
|
|
from neutron_lib import exceptions as n_exc
|
|
from neutron_lib.services.trunk import constants as trunk_consts
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
|
|
from neutron.common.ovn import constants as ovn_const
|
|
from neutron.common import utils as n_utils
|
|
from neutron.db import db_base_plugin_common
|
|
from neutron.db import ovn_revision_numbers_db as db_rev
|
|
from neutron.objects import ports as port_obj
|
|
from neutron.services.trunk.drivers import base as trunk_base
|
|
from neutron.services.trunk import exceptions as trunk_exc
|
|
|
|
|
|
SUPPORTED_INTERFACES = (
|
|
portbindings.VIF_TYPE_OVS,
|
|
portbindings.VIF_TYPE_VHOST_USER,
|
|
)
|
|
|
|
SUPPORTED_SEGMENTATION_TYPES = (
|
|
trunk_consts.SEGMENTATION_TYPE_VLAN,
|
|
)
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class OVNTrunkHandler(object):
|
|
def __init__(self, plugin_driver):
|
|
self.plugin_driver = plugin_driver
|
|
|
|
def _set_sub_ports(self, parent_port, subports):
|
|
txn = self.plugin_driver.nb_ovn.transaction
|
|
context = n_context.get_admin_context()
|
|
db_parent_port = port_obj.Port.get_object(context, id=parent_port)
|
|
parent_port_status = db_parent_port.status
|
|
parent_port_bindings = db_parent_port.bindings[0]
|
|
for subport in subports:
|
|
with db_api.CONTEXT_WRITER.using(context), (
|
|
txn(check_error=True)) as ovn_txn:
|
|
port = self._set_binding_profile(context, subport, parent_port,
|
|
parent_port_status,
|
|
parent_port_bindings, ovn_txn)
|
|
db_rev.bump_revision(context, port, ovn_const.TYPE_PORTS)
|
|
|
|
def _unset_sub_ports(self, subports):
|
|
txn = self.plugin_driver.nb_ovn.transaction
|
|
context = n_context.get_admin_context()
|
|
for subport in subports:
|
|
with db_api.CONTEXT_WRITER.using(context), (
|
|
txn(check_error=True)) as ovn_txn:
|
|
port = self._unset_binding_profile(context, subport, ovn_txn)
|
|
db_rev.bump_revision(context, port, ovn_const.TYPE_PORTS)
|
|
|
|
@db_base_plugin_common.convert_result_to_dict
|
|
def _set_binding_profile(self, context, subport, parent_port,
|
|
parent_port_status,
|
|
parent_port_bindings, ovn_txn):
|
|
LOG.debug("Setting parent %s for subport %s",
|
|
parent_port, subport.port_id)
|
|
db_port = port_obj.Port.get_object(context, id=subport.port_id)
|
|
if not db_port:
|
|
LOG.debug("Port not found while trying to set "
|
|
"binding_profile: %s",
|
|
subport.port_id)
|
|
return
|
|
check_rev_cmd = self.plugin_driver.nb_ovn.check_revision_number(
|
|
db_port.id, db_port, ovn_const.TYPE_PORTS)
|
|
ovn_txn.add(check_rev_cmd)
|
|
parent_binding_host = ''
|
|
if parent_port_bindings.host:
|
|
parent_binding_host = parent_port_bindings.host
|
|
try:
|
|
# NOTE(flaviof): We expect binding's host to be set. Otherwise,
|
|
# sub-port will not transition from DOWN to ACTIVE.
|
|
db_port.device_owner = trunk_consts.TRUNK_SUBPORT_OWNER
|
|
# NOTE(ksambor): When sub-port was created and event was process
|
|
# without binding profile this port will end forever in DOWN
|
|
# status so we need to switch it here to the parent port status
|
|
db_port.status = parent_port_status
|
|
for binding in db_port.bindings:
|
|
binding.profile['parent_name'] = parent_port
|
|
binding.profile['tag'] = subport.segmentation_id
|
|
# host + port_id is primary key
|
|
port_obj.PortBinding.update_object(
|
|
context,
|
|
{'profile': binding.profile,
|
|
'host': parent_binding_host,
|
|
'vif_type': portbindings.VIF_TYPE_OVS},
|
|
port_id=subport.port_id,
|
|
host=binding.host)
|
|
db_port.update()
|
|
except n_exc.ObjectNotFound:
|
|
LOG.debug("Port not found while trying to set "
|
|
"binding_profile: %s", subport.port_id)
|
|
return
|
|
ext_ids = {ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: db_port.device_owner}
|
|
ovn_txn.add(self.plugin_driver.nb_ovn.set_lswitch_port(
|
|
lport_name=subport.port_id,
|
|
parent_name=parent_port,
|
|
tag=subport.segmentation_id,
|
|
external_ids_update=ext_ids,
|
|
))
|
|
LOG.debug("Done setting parent %s for subport %s",
|
|
parent_port, subport.port_id)
|
|
return db_port
|
|
|
|
@db_base_plugin_common.convert_result_to_dict
|
|
def _unset_binding_profile(self, context, subport, ovn_txn):
|
|
LOG.debug("Unsetting parent for subport %s", subport.port_id)
|
|
db_port = port_obj.Port.get_object(context, id=subport.port_id)
|
|
if not db_port:
|
|
LOG.debug("Port not found while trying to unset "
|
|
"binding_profile: %s",
|
|
subport.port_id)
|
|
return
|
|
check_rev_cmd = self.plugin_driver.nb_ovn.check_revision_number(
|
|
db_port.id, db_port, ovn_const.TYPE_PORTS)
|
|
ovn_txn.add(check_rev_cmd)
|
|
try:
|
|
db_port.device_owner = ''
|
|
for binding in db_port.bindings:
|
|
binding.profile.pop('tag', None)
|
|
binding.profile.pop('parent_name', None)
|
|
# host + port_id is primary key
|
|
port_obj.PortBinding.update_object(
|
|
context,
|
|
{'profile': binding.profile,
|
|
'vif_type': portbindings.VIF_TYPE_UNBOUND},
|
|
port_id=subport.port_id,
|
|
host=binding.host)
|
|
port_obj.PortBindingLevel.delete_objects(
|
|
context, port_id=subport.port_id, host=binding.host)
|
|
db_port.update()
|
|
except n_exc.ObjectNotFound:
|
|
LOG.debug("Port not found while trying to unset "
|
|
"binding_profile: %s", subport.port_id)
|
|
return
|
|
ext_ids = {ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: db_port.device_owner}
|
|
ovn_txn.add(self.plugin_driver.nb_ovn.set_lswitch_port(
|
|
lport_name=subport.port_id,
|
|
parent_name=[],
|
|
up=False,
|
|
tag=[],
|
|
external_ids_update=ext_ids,
|
|
))
|
|
LOG.debug("Done unsetting parent for subport %s", subport.port_id)
|
|
return db_port
|
|
|
|
@staticmethod
|
|
def _is_port_bound(port):
|
|
return n_utils.is_port_bound(port, log_message=False)
|
|
|
|
def trunk_updated(self, trunk):
|
|
# Check if parent port is handled by OVN.
|
|
if not self.plugin_driver.nb_ovn.lookup('Logical_Switch_Port',
|
|
trunk.port_id, default=None):
|
|
return
|
|
if trunk.sub_ports:
|
|
self._set_sub_ports(trunk.port_id, trunk.sub_ports)
|
|
|
|
def trunk_created(self, trunk):
|
|
# Check if parent port is handled by OVN.
|
|
if not self.plugin_driver.nb_ovn.lookup('Logical_Switch_Port',
|
|
trunk.port_id, default=None):
|
|
return
|
|
if trunk.sub_ports:
|
|
self._set_sub_ports(trunk.port_id, trunk.sub_ports)
|
|
trunk.update(status=trunk_consts.TRUNK_ACTIVE_STATUS)
|
|
|
|
def trunk_deleted(self, trunk):
|
|
if trunk.sub_ports:
|
|
self._unset_sub_ports(trunk.sub_ports)
|
|
|
|
def subports_added(self, trunk, subports):
|
|
# Check if parent port is handled by OVN.
|
|
if not self.plugin_driver.nb_ovn.lookup('Logical_Switch_Port',
|
|
trunk.port_id, default=None):
|
|
return
|
|
if subports:
|
|
self._set_sub_ports(trunk.port_id, subports)
|
|
trunk.update(status=trunk_consts.TRUNK_ACTIVE_STATUS)
|
|
|
|
def subports_deleted(self, trunk, subports):
|
|
# Check if parent port is handled by OVN.
|
|
if not self.plugin_driver.nb_ovn.lookup('Logical_Switch_Port',
|
|
trunk.port_id, default=None):
|
|
return
|
|
if subports:
|
|
self._unset_sub_ports(subports)
|
|
trunk.update(status=trunk_consts.TRUNK_ACTIVE_STATUS)
|
|
|
|
def trunk_event(self, resource, event, trunk_plugin, payload):
|
|
if event == events.AFTER_CREATE:
|
|
self.trunk_created(payload.states[0])
|
|
elif event == events.AFTER_UPDATE:
|
|
self.trunk_updated(payload.states[0])
|
|
elif event == events.AFTER_DELETE:
|
|
self.trunk_deleted(payload.states[0])
|
|
elif event == events.PRECOMMIT_CREATE:
|
|
trunk = payload.desired_state
|
|
parent_port = trunk.db_obj.port
|
|
if self._is_port_bound(parent_port):
|
|
raise trunk_exc.ParentPortInUse(port_id=parent_port.id)
|
|
elif event == events.PRECOMMIT_DELETE:
|
|
trunk = payload.states[0]
|
|
parent_port = payload.states[1]
|
|
if self._is_port_bound(parent_port):
|
|
raise trunk_exc.TrunkInUse(trunk_id=trunk.id)
|
|
|
|
def subport_event(self, resource, event, trunk_plugin, payload):
|
|
if event == events.AFTER_CREATE:
|
|
self.subports_added(payload.states[0],
|
|
payload.metadata['subports'])
|
|
elif event == events.AFTER_DELETE:
|
|
self.subports_deleted(payload.states[0],
|
|
payload.metadata['subports'])
|
|
|
|
|
|
class OVNTrunkDriver(trunk_base.DriverBase):
|
|
@property
|
|
def is_loaded(self):
|
|
try:
|
|
return (ovn_const.OVN_ML2_MECH_DRIVER_NAME in
|
|
cfg.CONF.ml2.mechanism_drivers)
|
|
except cfg.NoSuchOptError:
|
|
return False
|
|
|
|
@registry.receives(resources.TRUNK_PLUGIN, [events.AFTER_INIT])
|
|
def register(self, resource, event, trigger, payload=None):
|
|
super(OVNTrunkDriver, self).register(
|
|
resource, event, trigger, payload=payload)
|
|
self._handler = OVNTrunkHandler(self.plugin_driver)
|
|
for _event in (events.AFTER_CREATE, events.AFTER_UPDATE,
|
|
events.AFTER_DELETE, events.PRECOMMIT_CREATE,
|
|
events.PRECOMMIT_DELETE):
|
|
registry.subscribe(self._handler.trunk_event,
|
|
resources.TRUNK,
|
|
_event)
|
|
|
|
for _event in (events.AFTER_CREATE, events.AFTER_DELETE):
|
|
registry.subscribe(self._handler.subport_event,
|
|
resources.SUBPORTS,
|
|
_event)
|
|
|
|
@classmethod
|
|
def create(cls, plugin_driver):
|
|
cls.plugin_driver = plugin_driver
|
|
return cls(ovn_const.OVN_ML2_MECH_DRIVER_NAME,
|
|
SUPPORTED_INTERFACES,
|
|
SUPPORTED_SEGMENTATION_TYPES,
|
|
None,
|
|
can_trunk_bound_port=True)
|