
In case when enforce_new_defaults is set to True and new policy rules are used, context.is_admin flag isn't really working as it was with old rules. But in case when elevated context is needed, it means that we need context which has full rights to the system. So we should also set "system_scope" parameter to "all" to be sure that system scope queries can be done with such elevated context always. It is needed e.g. when elevated context is used to get some data from db. In such case we need to have db query which will not be scoped to the single project_id and with new defaults to achieve that system_scope has to be set to "all". Proper fix for that should be done in neutron-lib and it is proposed in [1] already but as we are have frozen neutron-lib version for stable/wallaby already this patch for neutron is temporary fix for that issue. We can revert that patch as soon as we will be in Xena development cycle and [1] will be merged and released. [1] https://review.opendev.org/c/openstack/neutron-lib/+/781625 Related-Bug: #1920001 Change-Id: I0068c1de09f5c6fae5bb5cd0d6f26f451e701939
217 lines
8.9 KiB
Python
217 lines
8.9 KiB
Python
# Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
#
|
|
# 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 collections
|
|
|
|
from neutron_lib.api.definitions import portbindings
|
|
from neutron_lib.db import api as db_api
|
|
from neutron_lib.plugins import directory
|
|
from neutron_lib import rpc as n_rpc
|
|
from neutron_lib.services.trunk import constants as trunk_consts
|
|
from oslo_log import helpers as log_helpers
|
|
from oslo_log import log as logging
|
|
import oslo_messaging
|
|
from sqlalchemy.orm import exc
|
|
|
|
from neutron.api.rpc.callbacks import events
|
|
from neutron.api.rpc.callbacks.producer import registry
|
|
from neutron.api.rpc.callbacks import resources
|
|
from neutron.api.rpc.handlers import resources_rpc
|
|
from neutron.common import utils as common_utils
|
|
from neutron.objects import trunk as trunk_objects
|
|
from neutron.services.trunk import exceptions as trunk_exc
|
|
from neutron.services.trunk.rpc import constants
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
# This module contains stub (client-side) and skeleton (server-side)
|
|
# proxy code that executes in the Neutron server process space. This
|
|
# is needed if any of the trunk service plugin drivers has a remote
|
|
# component (e.g. agent), that needs to communicate with the Neutron
|
|
# Server.
|
|
|
|
# The Server side exposes the following remote methods:
|
|
#
|
|
# - lookup method to retrieve trunk details: used by the agent to learn
|
|
# about the trunk.
|
|
# - update methods for trunk and its subports: used by the agent to
|
|
# inform the server about local trunk status changes.
|
|
#
|
|
# For agent-side stub and skeleton proxy code, please look at agent.py
|
|
|
|
|
|
def trunk_by_port_provider(resource, port_id, context, **kwargs):
|
|
"""Provider callback to supply trunk information by parent port."""
|
|
return trunk_objects.Trunk.get_object(context, port_id=port_id)
|
|
|
|
|
|
class TrunkSkeleton(object):
|
|
"""Skeleton proxy code for agent->server communication."""
|
|
|
|
# API version history:
|
|
# 1.0 Initial version
|
|
target = oslo_messaging.Target(version='1.0',
|
|
namespace=constants.TRUNK_BASE_NAMESPACE)
|
|
|
|
_core_plugin = None
|
|
|
|
def __init__(self):
|
|
# Used to provide trunk lookups for the agent.
|
|
registry.provide(trunk_by_port_provider, resources.TRUNK)
|
|
self._connection = n_rpc.Connection()
|
|
self._connection.create_consumer(
|
|
constants.TRUNK_BASE_TOPIC, [self], fanout=False)
|
|
self._connection.consume_in_threads()
|
|
|
|
@property
|
|
def core_plugin(self):
|
|
if not self._core_plugin:
|
|
self._core_plugin = directory.get_plugin()
|
|
return self._core_plugin
|
|
|
|
@log_helpers.log_method_call
|
|
def update_subport_bindings(self, context, subports):
|
|
"""Update subport bindings to match trunk host binding."""
|
|
el = common_utils.get_elevated_context(context)
|
|
ports_by_trunk_id = collections.defaultdict(list)
|
|
updated_ports = collections.defaultdict(list)
|
|
|
|
for s in subports:
|
|
ports_by_trunk_id[s['trunk_id']].append(s['port_id'])
|
|
for trunk_id, subport_ids in ports_by_trunk_id.items():
|
|
trunk = trunk_objects.Trunk.get_object(el, id=trunk_id)
|
|
if not trunk:
|
|
LOG.debug("Trunk not found. id: %s", trunk_id)
|
|
continue
|
|
|
|
trunk_updated_ports = self._process_trunk_subport_bindings(
|
|
el,
|
|
trunk,
|
|
subport_ids)
|
|
updated_ports[trunk.id].extend(trunk_updated_ports)
|
|
|
|
return updated_ports
|
|
|
|
def _safe_update_trunk(self, trunk, **kwargs):
|
|
for try_cnt in range(db_api.MAX_RETRIES):
|
|
try:
|
|
trunk.update(**kwargs)
|
|
break
|
|
except exc.StaleDataError as e:
|
|
if try_cnt < db_api.MAX_RETRIES - 1:
|
|
LOG.debug("Got StaleDataError exception: %s", e)
|
|
continue
|
|
# re-raise when all tries failed
|
|
raise
|
|
|
|
def update_trunk_status(self, context, trunk_id, status):
|
|
"""Update the trunk status to reflect outcome of data plane wiring."""
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
trunk = trunk_objects.Trunk.get_object(context, id=trunk_id)
|
|
if trunk:
|
|
self._safe_update_trunk(trunk, status=status)
|
|
|
|
def _process_trunk_subport_bindings(self, context, trunk, port_ids):
|
|
"""Process port bindings for subports on the given trunk."""
|
|
updated_ports = []
|
|
trunk_port_id = trunk.port_id
|
|
trunk_port = self.core_plugin.get_port(context, trunk_port_id)
|
|
trunk_host = trunk_port.get(portbindings.HOST_ID)
|
|
migrating_to_host = trunk_port.get(
|
|
portbindings.PROFILE, {}).get('migrating_to')
|
|
if migrating_to_host and trunk_host != migrating_to_host:
|
|
# Trunk is migrating now, so lets update host of the subports
|
|
# to the new host already
|
|
trunk_host = migrating_to_host
|
|
|
|
# NOTE(status_police) Set the trunk in BUILD state before
|
|
# processing subport bindings. The trunk will stay in BUILD
|
|
# state until an attempt has been made to bind all subports
|
|
# passed here and the agent acknowledges the operation was
|
|
# successful.
|
|
self._safe_update_trunk(
|
|
trunk, status=trunk_consts.TRUNK_BUILD_STATUS)
|
|
|
|
for port_id in port_ids:
|
|
try:
|
|
updated_port = self._handle_port_binding(context, port_id,
|
|
trunk, trunk_host)
|
|
# NOTE(fitoduarte): consider trimming down the content
|
|
# of the port data structure.
|
|
updated_ports.append(updated_port)
|
|
except trunk_exc.SubPortBindingError as e:
|
|
LOG.error("Failed to bind subport: %s", e)
|
|
|
|
# NOTE(status_police) The subport binding has failed in a
|
|
# manner in which we cannot proceed and the user must take
|
|
# action to bring the trunk back to a sane state.
|
|
self._safe_update_trunk(
|
|
trunk, status=trunk_consts.TRUNK_ERROR_STATUS)
|
|
return []
|
|
except Exception as e:
|
|
msg = ("Failed to bind subport port %(port)s on trunk "
|
|
"%(trunk)s: %(exc)s")
|
|
LOG.error(msg, {'port': port_id, 'trunk': trunk.id, 'exc': e})
|
|
|
|
if len(port_ids) != len(updated_ports):
|
|
self._safe_update_trunk(
|
|
trunk, status=trunk_consts.TRUNK_DEGRADED_STATUS)
|
|
|
|
return updated_ports
|
|
|
|
def _handle_port_binding(self, context, port_id, trunk, trunk_host):
|
|
"""Bind the given port to the given host.
|
|
|
|
:param context: The context to use for the operation
|
|
:param port_id: The UUID of the port to be bound
|
|
:param trunk: The trunk that the given port belongs to
|
|
:param trunk_host: The host to bind the given port to
|
|
"""
|
|
port = self.core_plugin.update_port(
|
|
context, port_id,
|
|
{'port': {portbindings.HOST_ID: trunk_host,
|
|
'device_owner': trunk_consts.TRUNK_SUBPORT_OWNER}})
|
|
vif_type = port.get(portbindings.VIF_TYPE)
|
|
if vif_type == portbindings.VIF_TYPE_BINDING_FAILED:
|
|
raise trunk_exc.SubPortBindingError(port_id=port_id,
|
|
trunk_id=trunk.id)
|
|
return port
|
|
|
|
|
|
class TrunkStub(object):
|
|
"""Stub proxy code for server->agent communication."""
|
|
|
|
def __init__(self):
|
|
self._resource_rpc = resources_rpc.ResourcesPushRpcApi()
|
|
|
|
@log_helpers.log_method_call
|
|
def trunk_created(self, context, trunk):
|
|
"""Tell the agent about a trunk being created."""
|
|
self._resource_rpc.push(context, [trunk], events.CREATED)
|
|
|
|
@log_helpers.log_method_call
|
|
def trunk_deleted(self, context, trunk):
|
|
"""Tell the agent about a trunk being deleted."""
|
|
self._resource_rpc.push(context, [trunk], events.DELETED)
|
|
|
|
@log_helpers.log_method_call
|
|
def subports_added(self, context, subports):
|
|
"""Tell the agent about new subports to add."""
|
|
self._resource_rpc.push(context, subports, events.CREATED)
|
|
|
|
@log_helpers.log_method_call
|
|
def subports_deleted(self, context, subports):
|
|
"""Tell the agent about existing subports to remove."""
|
|
self._resource_rpc.push(context, subports, events.DELETED)
|