At the moment quite vital arguments, such as region_name and valid_interfaces, are ignored by the plugin, which results in inconsistent behaviour with Octavia when trying to interact with Neutron. For instance, while Octavia connects to Neutron through the internal endpoint, the plugin still tries to reach it through the public one, ignoring options defined in [service_auth] and [neutron] sections. This patch is basically a copy-paste from Octavia [1]. [1] https://review.opendev.org/c/openstack/octavia/+/905794 Closes-Bug: #2110488 Related-Bug: #2049551 Change-Id: I3a98825e40143dfa9017ca512a27197c48c31ee9
184 lines
7.5 KiB
Python
184 lines
7.5 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 keystoneauth1 import exceptions as ks_exceptions
|
|
from keystoneauth1 import loading as ks_loading
|
|
|
|
from octavia_lib.api.drivers import exceptions as driver_exceptions
|
|
import openstack
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
|
|
from ovn_octavia_provider.common import constants
|
|
from ovn_octavia_provider.i18n import _
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
|
|
|
|
class KeystoneSession():
|
|
|
|
def __init__(self, section=constants.SERVICE_AUTH):
|
|
self._session = None
|
|
self._auth = None
|
|
|
|
self.section = section
|
|
|
|
@property
|
|
def session(self):
|
|
"""Initialize a Keystone session.
|
|
|
|
:return: a Keystone Session object
|
|
"""
|
|
if not self._session:
|
|
self._session = ks_loading.load_session_from_conf_options(
|
|
cfg.CONF, self.section, auth=self.auth)
|
|
return self._session
|
|
|
|
@property
|
|
def auth(self):
|
|
if not self._auth:
|
|
try:
|
|
self._auth = ks_loading.load_auth_from_conf_options(
|
|
cfg.CONF, self.section)
|
|
except ks_exceptions.auth_plugins.MissingRequiredOptions as e:
|
|
if self.section == constants.SERVICE_AUTH:
|
|
raise e
|
|
# NOTE(gthiemonge): MissingRequiredOptions is raised: there is
|
|
# one or more missing auth options in the config file. It may
|
|
# be due to the migration from python-neutronclient to
|
|
# openstacksdk.
|
|
# With neutronclient, most of the auth settings were in
|
|
# [service_auth] with a few overrides in [neutron],
|
|
# but with openstacksdk, we have all the auth settings in the
|
|
# [neutron] section. In order to support smooth upgrades, in
|
|
# case those options are missing, we override the undefined
|
|
# options with the existing settings from [service_auth].
|
|
|
|
# This code should be removed when all the deployment tools set
|
|
# the correct options in [neutron]
|
|
|
|
# The config options are lazily registered/loaded by keystone,
|
|
# it means that we cannot get/set them before invoking
|
|
# 'load_auth_from_conf_options' on 'service_auth'.
|
|
ks_loading.load_auth_from_conf_options(
|
|
cfg.CONF, constants.SERVICE_AUTH)
|
|
|
|
config = getattr(cfg.CONF, self.section)
|
|
for opt in config:
|
|
# For each option in the [neutron] section, get its setting
|
|
# location, if the location is 'opt_default' or
|
|
# 'set_default', it means that the option is not configured
|
|
# in the config file, it should be replaced with the one
|
|
# from [service_auth]
|
|
loc = cfg.CONF.get_location(opt, self.section)
|
|
if not loc or loc.location in (cfg.Locations.opt_default,
|
|
cfg.Locations.set_default):
|
|
if hasattr(cfg.CONF.service_auth, opt):
|
|
cur_value = getattr(config, opt)
|
|
value = getattr(cfg.CONF.service_auth, opt)
|
|
if value != cur_value:
|
|
log_value = (value if opt != "password"
|
|
else "<hidden>")
|
|
LOG.debug("Overriding [%s].%s with '%s'",
|
|
self.section, opt, log_value)
|
|
cfg.CONF.set_override(opt, value, self.section)
|
|
|
|
# Now we can call load_auth_from_conf_options for this specific
|
|
# service with the newly defined options.
|
|
self._auth = ks_loading.load_auth_from_conf_options(
|
|
cfg.CONF, self.section)
|
|
|
|
return self._auth
|
|
|
|
|
|
class Singleton(type):
|
|
_instances = {}
|
|
|
|
def __call__(cls, *args, **kwargs):
|
|
if cls not in cls._instances:
|
|
cls._instances[cls] = super(Singleton, cls).__call__(*args,
|
|
**kwargs)
|
|
return cls._instances[cls]
|
|
|
|
|
|
class NeutronAuth(metaclass=Singleton):
|
|
def __init__(self):
|
|
"""Create neutron client object."""
|
|
try:
|
|
ksession = KeystoneSession('neutron')
|
|
|
|
kwargs = {'region_name': CONF.neutron.region_name}
|
|
try:
|
|
interface = CONF.neutron.valid_interfaces[0]
|
|
except (TypeError, LookupError):
|
|
interface = CONF.neutron.valid_interfaces
|
|
if interface:
|
|
kwargs['interface'] = interface
|
|
if CONF.neutron.endpoint_override:
|
|
kwargs['network_endpoint_override'] = (
|
|
CONF.neutron.endpoint_override)
|
|
if CONF.neutron.endpoint_override.startswith("https"):
|
|
kwargs['insecure'] = CONF.neutron.insecure
|
|
kwargs['cacert'] = CONF.neutron.cafile
|
|
self.network_proxy = openstack.connection.Connection(
|
|
session=ksession.session, **kwargs).network
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.exception("Error creating Neutron client.")
|
|
|
|
|
|
def get_neutron_client():
|
|
try:
|
|
return NeutronAuth().network_proxy
|
|
except Exception as e:
|
|
msg = _('Cannot initialize OpenStackSDK. Exception: %s. '
|
|
'Please verify Neutron service configuration '
|
|
'in Octavia API configuration.') % e
|
|
raise driver_exceptions.DriverError(
|
|
operator_fault_string=msg)
|
|
|
|
|
|
class OctaviaAuth(metaclass=Singleton):
|
|
def __init__(self):
|
|
"""Create Octavia client object."""
|
|
try:
|
|
ksession = KeystoneSession()
|
|
kwargs = {'region_name': CONF.service_auth.region_name}
|
|
# TODO(ricolin) `interface` option don't take list as option yet.
|
|
# We can move away from this when openstacksdk no longer depends
|
|
# on `interface`.
|
|
try:
|
|
interface = CONF.service_auth.valid_interfaces[0]
|
|
except (TypeError, LookupError):
|
|
interface = CONF.service_auth.valid_interfaces
|
|
if interface:
|
|
kwargs['interface'] = interface
|
|
|
|
self.loadbalancer_proxy = openstack.connection.Connection(
|
|
session=ksession.session, **kwargs).load_balancer
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.exception("Error creating Octavia client.")
|
|
|
|
|
|
def get_octavia_client():
|
|
try:
|
|
return OctaviaAuth().loadbalancer_proxy
|
|
except Exception as e:
|
|
msg = _('Cannot initialize OpenStackSDK. Exception: %s. '
|
|
'Please verify service_auth configuration '
|
|
'in Octavia API configuration.') % e
|
|
raise driver_exceptions.DriverError(
|
|
operator_fault_string=msg)
|