neutron/neutron/services/ovn_l3/service_providers/ovn.py

253 lines
12 KiB
Python

# All Rights Reserved.
#
# 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 copy
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants
from neutron_lib.db import api as db_api
from oslo_log import log as logging
from oslo_utils import excutils
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
from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.extensions import revisions
from neutron.objects import router as l3_obj
from neutron.services.l3_router.service_providers import base
from neutron.services.portforwarding import constants as pf_consts
LOG = logging.getLogger(__name__)
@registry.has_registry_receivers
class OvnDriver(base.L3ServiceProvider):
ha_support = base.MANDATORY
dvr_support = base.MANDATORY
@registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE])
def _process_router_create_precommit(self, resource, event, trigger,
payload):
context = payload.context
context.session.flush()
router_id = payload.resource_id
router_db = payload.metadata['router_db']
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
# NOTE(ralonsoh): the "distributed" flag is a static configuration
# parameter that needs to be defined only during the router creation.
extra_attr = router_db['extra_attributes']
extra_attr.distributed = ovn_conf.is_ovn_distributed_floating_ip()
db_rev.create_initial_revision(
context, router_id, ovn_const.TYPE_ROUTERS,
std_attr_id=router_db.standard_attr.id)
@registry.receives(resources.ROUTER, [events.AFTER_CREATE])
def _process_router_create(self, resource, event, trigger, payload):
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
context = payload.context
try:
self.l3plugin._ovn_client.create_router(context, router)
except Exception:
with excutils.save_and_reraise_exception():
# Delete the logical router
LOG.exception('Unable to create lrouter %s', router['id'])
self.l3plugin.delete_router(context, router['id'])
@registry.receives(resources.ROUTER, [events.AFTER_UPDATE])
def _process_router_update(self, resource, event, trigger, payload):
router_id = payload.resource_id
original = payload.states[0]
updated = payload.states[1]
if not utils.is_ovn_provider_router(original):
# flavor_id attribute is not allowed in router PUTs, so we only
# need to check the original router
return
context = payload.context
try:
self.l3plugin._ovn_client.update_router(context, updated, original)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to update lrouter %s', router_id)
revert_router = {'router': original}
self.l3plugin.update_router(context, router_id, revert_router)
@registry.receives(resources.ROUTER, [events.AFTER_DELETE])
def _process_router_delete(self, resource, event, trigger, payload):
router_id = payload.resource_id
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
context = payload.context
try:
self.l3plugin._ovn_client.delete_router(context, router_id)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to delete lrouter %s', router['id'])
self.l3plugin.create_router(context, {'router': router})
@registry.receives(resources.ROUTER_INTERFACE, [events.AFTER_CREATE])
def _process_add_router_interface(self, resource, event, trigger, payload):
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
context = payload.context
port = payload.metadata['port']
subnets = payload.metadata['subnets']
router_interface_info = self.l3plugin._make_router_interface_info(
router.id, port['tenant_id'], port['id'], port['network_id'],
subnets[-1]['id'], [subnet['id'] for subnet in subnets])
try:
self.l3plugin._ovn_client.create_router_port(context, router.id,
router_interface_info)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to add router interface to lrouter %s. '
'Interface info: %s', router['id'],
router_interface_info)
self.l3plugin.remove_router_interface(context, router.id,
router_interface_info)
@registry.receives(resources.ROUTER_INTERFACE, [events.AFTER_DELETE])
def _process_remove_router_interface(self, resource, event, trigger,
payload):
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
context = payload.context
port = payload.metadata['port']
subnet_ids = payload.metadata['subnet_ids']
router_interface_info = self.l3plugin._make_router_interface_info(
router.id, port['tenant_id'], port['id'], port['network_id'],
subnet_ids[0], subnet_ids)
try:
self.l3plugin._ovn_client.delete_router_port(context, port['id'],
router_id=router.id,
subnet_ids=subnet_ids)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to remove router interface from lrouter '
'%s. Interface info: %s', router['id'],
router_interface_info)
self.l3plugin.add_router_interface(
context, router.id, payload.metadata['interface_info'])
def _create_floatingip_initial_revision(self, context, floatingip_db):
if not floatingip_db.router_id:
return
# We get the router with elevated context because floating IPs may be
# created to be associated with a router created by a different
# project. Please see
# https://review.opendev.org/c/openstack/neutron/+/2727B09
router = self.l3plugin.get_router(context.elevated(),
floatingip_db.router_id)
if not utils.is_ovn_provider_router(router):
return
db_rev.create_initial_revision(
context, floatingip_db.id, ovn_const.TYPE_FLOATINGIPS,
may_exist=True, std_attr_id=floatingip_db.standard_attr.id)
@registry.receives(resources.FLOATING_IP,
[events.PRECOMMIT_CREATE, events.PRECOMMIT_UPDATE])
def _process_floatingip_create_update_precommit(self, resource, event,
trigger, payload):
context = payload.context
floatingip_db = payload.desired_state
self._create_floatingip_initial_revision(context, floatingip_db)
@registry.receives(pf_consts.PORT_FORWARDING, [events.AFTER_CREATE])
def _process_portforwarding_create(self, resource, event, trigger,
payload):
context = payload.context
pf_obj = payload.states[0]
with db_api.CONTEXT_WRITER.using(context):
fip_db = l3_obj.FloatingIP.get_object(
context, id=pf_obj.floatingip_id).db_obj
self._create_floatingip_initial_revision(context, fip_db)
@registry.receives(resources.FLOATING_IP, [events.AFTER_CREATE])
def _process_floatingip_create(self, resource, event, trigger, payload):
revision_row = db_rev.get_revision_row(payload.context,
payload.resource_id)
if not revision_row:
return
# The floating ip dictionary that is sent by the L3 DB plugin in the
# notification doesn't include the revision number yet. We add it here
# to the dictionary that is passed to the ovn client
floatingip = copy.deepcopy(payload.states[0])
floatingip[revisions.REVISION] = revision_row.revision_number
qos_policy_id = payload.request_body['floatingip'].get('qos_policy_id')
if qos_policy_id and 'qos_policy_id' not in floatingip:
floatingip['qos_policy_id'] = qos_policy_id
self.l3plugin._ovn_client.create_floatingip(payload.context,
floatingip)
@registry.receives(resources.FLOATING_IP, [events.AFTER_UPDATE])
def _process_floatingip_update(self, resource, event, trigger, payload):
if not db_rev.get_revision_row(payload.context, payload.resource_id):
return
fip = payload.states[1]
old_fip = payload.states[0]
fip_request = payload.request_body
if fip_request:
self.l3plugin._ovn_client.update_floatingip(payload.context, fip,
fip_request)
else:
router_id = old_fip.get('router_id')
fixed_ip_address = old_fip.get('fixed_ip_address')
if router_id and fixed_ip_address:
update_fip = {
'id': old_fip['id'],
'logical_ip': fixed_ip_address,
'external_ip': old_fip['floating_ip_address'],
'floating_network_id': old_fip['floating_network_id']
}
try:
self.l3plugin._ovn_client.disassociate_floatingip(
update_fip, router_id)
self.l3plugin.update_floatingip_status(
payload.context, old_fip['id'],
constants.FLOATINGIP_STATUS_DOWN)
except Exception as e:
LOG.error('Error in disassociating floatingip %(id)s: '
'%(error)s', {'id': old_fip['id'], 'error': e})
if not fip['router_id']:
db_rev.delete_revision(payload.context, payload.resource_id,
ovn_const.TYPE_FLOATINGIPS)
@registry.receives(resources.FLOATING_IP, [events.AFTER_DELETE])
def _process_floatingip_delete(self, resource, event, trigger, payload):
if not db_rev.get_revision_row(payload.context, payload.resource_id):
return
self.l3plugin._ovn_client.delete_floatingip(payload.context,
payload.resource_id)
@registry.receives(resources.FLOATING_IP, [events.AFTER_STATUS_UPDATE])
def _process_floatingip_status_update(self, resource, event, trigger,
payload):
if not db_rev.get_revision_row(payload.context, payload.resource_id):
return
self.l3plugin._ovn_client.update_floatingip_status(payload.context,
payload.states[0])