Add support for backend_defaults group
With the introduction of multi store support, we can configure multiple stores of same or different type. Along with multiple upsides, there is a downside that the config options need to be repeated multiple times even though we want to pass the same value. Take the following example: [DEFAULT] enabled_backends=lvmdriver-1:cinder, lvmdriver-2:cinder [lvmdriver-1] cinder_use_multipath=True cinder_volume_type=lvmdriver-1 [lvmdriver-2] cinder_use_multipath=True cinder_volume_type=lvmdriver-2 Since we generally want multiple to be enabled for cinder backends that support it, if we configure 10 iSCSI/FC cinder backends, we need to specify the config option for all of them. To mitigate the issue, we introduce a new [backend_defaults] section that would allow us to configure the common config options at a single place. Rewriting the above example with [backend_defaults]: [DEFAULT] enabled_backends=lvmdriver-1:cinder, lvmdriver-2:cinder [backend_defaults] cinder_use_multipath=True [lvmdriver-1] cinder_volume_type=lvmdriver-1 [lvmdriver-2] cinder_volume_type=lvmdriver-2 Change-Id: I88859cdb979c66c807fef09b7e3e36b70877163b
This commit is contained in:
@@ -493,7 +493,8 @@ class Store(glance_store.driver.Store):
|
||||
self.mount = importlib.import_module('glance_store.common.fs_mount')
|
||||
self._set_url_prefix()
|
||||
if self.backend_group:
|
||||
self.store_conf = getattr(self.conf, self.backend_group)
|
||||
self.store_conf = glance_store.driver.BackendGroupConfiguration(
|
||||
self.OPTIONS, self.backend_group, conf=self.conf)
|
||||
else:
|
||||
self.store_conf = self.conf.glance_store
|
||||
self.volume_api = cinder_utils.API()
|
||||
|
||||
@@ -288,7 +288,8 @@ class Store(glance_store.driver.Store):
|
||||
def __init__(self, *args, **kargs):
|
||||
super(Store, self).__init__(*args, **kargs)
|
||||
if self.backend_group:
|
||||
self.store_conf = getattr(self.conf, self.backend_group)
|
||||
self.store_conf = glance_store.driver.BackendGroupConfiguration(
|
||||
self.OPTIONS, self.backend_group, conf=self.conf)
|
||||
else:
|
||||
self.store_conf = self.conf.glance_store
|
||||
|
||||
|
||||
@@ -339,7 +339,8 @@ class Store(glance_store.driver.Store):
|
||||
self.session = requests.Session()
|
||||
|
||||
if self.backend_group:
|
||||
store_conf = getattr(self.conf, self.backend_group)
|
||||
store_conf = glance_store.driver.BackendGroupConfiguration(
|
||||
self.OPTIONS, self.backend_group, conf=self.conf)
|
||||
else:
|
||||
store_conf = self.conf.glance_store
|
||||
|
||||
|
||||
@@ -285,7 +285,8 @@ class Store(driver.Store):
|
||||
def __init__(self, *args, **kargs):
|
||||
super(Store, self).__init__(*args, **kargs)
|
||||
if self.backend_group:
|
||||
self.store_conf = getattr(self.conf, self.backend_group)
|
||||
self.store_conf = driver.BackendGroupConfiguration(
|
||||
self.OPTIONS, self.backend_group, conf=self.conf)
|
||||
else:
|
||||
self.store_conf = self.conf.glance_store
|
||||
|
||||
|
||||
@@ -465,7 +465,8 @@ class Store(glance_store.driver.Store):
|
||||
|
||||
def _option_get(self, param):
|
||||
if self.backend_group:
|
||||
store_conf = getattr(self.conf, self.backend_group)
|
||||
store_conf = glance_store.driver.BackendGroupConfiguration(
|
||||
self.OPTIONS, self.backend_group, conf=self.conf)
|
||||
else:
|
||||
store_conf = self.conf.glance_store
|
||||
|
||||
|
||||
@@ -364,7 +364,8 @@ class Store(glance_store.Store):
|
||||
"the vmwareapi driver in nova was marked experimental and "
|
||||
"may be removed in a future release.")
|
||||
if self.backend_group:
|
||||
self.store_conf = getattr(self.conf, self.backend_group)
|
||||
self.store_conf = glance_store.driver.BackendGroupConfiguration(
|
||||
self.OPTIONS, self.backend_group, conf=self.conf)
|
||||
else:
|
||||
self.store_conf = self.conf.glance_store
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ from glance_store.i18n import _
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
SHARED_CONF_GROUP = 'backend_defaults'
|
||||
|
||||
_MULTI_BACKEND_OPTS = [
|
||||
cfg.StrOpt('store_description',
|
||||
@@ -80,6 +82,7 @@ class Store(capabilities.StoreCapability):
|
||||
self.MULTI_BACKEND_OPTIONS, group=group)
|
||||
|
||||
self.conf.register_opts(self.OPTIONS, group=group)
|
||||
self.conf.register_opts(self.OPTIONS, group=SHARED_CONF_GROUP)
|
||||
except cfg.DuplicateOptError:
|
||||
pass
|
||||
|
||||
@@ -300,3 +303,62 @@ def back_compat_add(store_add_fun):
|
||||
metadata_dict)
|
||||
|
||||
return add_adapter
|
||||
|
||||
|
||||
class BackendGroupConfiguration(object):
|
||||
|
||||
def __init__(self, store_opts, config_group=None, conf=None):
|
||||
"""Initialize configuration.
|
||||
|
||||
This takes care of grafting the implementation's config
|
||||
values into the config group and shared defaults. We will try to
|
||||
pull values from the specified 'config_group', but fall back to
|
||||
defaults from the SHARED_CONF_GROUP.
|
||||
"""
|
||||
self.config_group = config_group
|
||||
self.conf = conf or CONF
|
||||
|
||||
# set the local conf so that __call__'s know what to use
|
||||
self._ensure_config_values(store_opts)
|
||||
self.backend_conf = self.conf._get(self.config_group)
|
||||
self.shared_backend_conf = self.conf._get(SHARED_CONF_GROUP)
|
||||
|
||||
def _safe_register(self, opt, group):
|
||||
try:
|
||||
CONF.register_opt(opt, group=group)
|
||||
except cfg.DuplicateOptError:
|
||||
pass # If it's already registered ignore it
|
||||
|
||||
def _ensure_config_values(self, store_opts):
|
||||
"""Register the options in the shared group.
|
||||
|
||||
When we go to get a config option we will try the backend specific
|
||||
group first and fall back to the shared group. We override the default
|
||||
from all the config options for the backend group so we can know if it
|
||||
was set or not.
|
||||
"""
|
||||
for opt in store_opts:
|
||||
self._safe_register(opt, SHARED_CONF_GROUP)
|
||||
# Assuming they aren't the same groups, graft on the options into
|
||||
# the backend group and override its default value.
|
||||
if self.config_group != SHARED_CONF_GROUP:
|
||||
self._safe_register(opt, self.config_group)
|
||||
self.conf.set_default(opt.name, None, group=self.config_group)
|
||||
|
||||
def append_config_values(self, store_opts):
|
||||
self._ensure_config_values(store_opts)
|
||||
|
||||
def set_default(self, opt_name, default):
|
||||
self.conf.set_default(opt_name, default, group=SHARED_CONF_GROUP)
|
||||
|
||||
def get(self, key, default=None):
|
||||
return getattr(self, key, default)
|
||||
|
||||
def __getattr__(self, opt_name):
|
||||
# Don't use self.X to avoid reentrant call to __getattr__()
|
||||
backend_conf = object.__getattribute__(self, 'backend_conf')
|
||||
opt_value = getattr(backend_conf, opt_name)
|
||||
if opt_value is None:
|
||||
shared_conf = object.__getattribute__(self, 'shared_backend_conf')
|
||||
opt_value = getattr(shared_conf, opt_name)
|
||||
return opt_value
|
||||
|
||||
77
glance_store/tests/unit/test_backend_group_configuration.py
Normal file
77
glance_store/tests/unit/test_backend_group_configuration.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# Copyright (c) 2025 RedHat Inc.
|
||||
# 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.
|
||||
|
||||
"""Tests for the configuration wrapper in Glance Store drivers."""
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslotest import base
|
||||
|
||||
import glance_store.driver as driver
|
||||
|
||||
store_opts = [
|
||||
cfg.StrOpt('str_opt', default='STR_OPT'),
|
||||
cfg.BoolOpt('bool_opt', default=False)
|
||||
]
|
||||
more_store_opts = [
|
||||
cfg.IntOpt('int_opt', default=1),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(store_opts)
|
||||
CONF.register_opts(more_store_opts)
|
||||
|
||||
|
||||
class BackendGroupConfigurationTest(base.BaseTestCase):
|
||||
|
||||
def override_config(self, name, override, group=None):
|
||||
"""Cleanly override CONF variables."""
|
||||
CONF.set_override(name, override, group)
|
||||
self.addCleanup(CONF.clear_override, name, group)
|
||||
|
||||
def test_group_grafts_opts(self):
|
||||
c = driver.BackendGroupConfiguration(store_opts, config_group='foo')
|
||||
self.assertEqual(c.str_opt, 'STR_OPT')
|
||||
self.assertEqual(c.bool_opt, False)
|
||||
self.assertEqual(c.str_opt, CONF.backend_defaults.str_opt)
|
||||
self.assertEqual(c.bool_opt, CONF.backend_defaults.bool_opt)
|
||||
self.assertIsNone(CONF.foo.str_opt)
|
||||
self.assertIsNone(CONF.foo.bool_opt)
|
||||
|
||||
def test_grafting_multiple_opts(self):
|
||||
c = driver.BackendGroupConfiguration(store_opts, config_group='foo')
|
||||
c.append_config_values(more_store_opts)
|
||||
self.assertEqual(c.str_opt, 'STR_OPT')
|
||||
self.assertEqual(c.bool_opt, False)
|
||||
self.assertEqual(c.int_opt, 1)
|
||||
|
||||
# We get the right values, but they are coming from the
|
||||
# backend_defaults group of CONF and not the 'foo' one.
|
||||
self.assertEqual(c.str_opt, CONF.backend_defaults.str_opt)
|
||||
self.assertEqual(c.bool_opt, CONF.backend_defaults.bool_opt)
|
||||
self.assertEqual(c.int_opt, CONF.backend_defaults.int_opt)
|
||||
self.assertIsNone(CONF.foo.str_opt)
|
||||
self.assertIsNone(CONF.foo.bool_opt)
|
||||
self.assertIsNone(CONF.foo.int_opt)
|
||||
|
||||
def test_backend_specific_value(self):
|
||||
c = driver.BackendGroupConfiguration(store_opts, config_group='foo')
|
||||
|
||||
self.override_config('str_opt', 'bar', group='backend_defaults')
|
||||
actual_value = c.str_opt
|
||||
self.assertEqual('bar', actual_value)
|
||||
|
||||
self.override_config('str_opt', 'notbar', group='foo')
|
||||
actual_value = c.str_opt
|
||||
self.assertEqual('notbar', actual_value)
|
||||
@@ -450,6 +450,9 @@ class TestMultiStore(base.MultiStoreBaseTest,
|
||||
self.conf.set_override('filesystem_store_datadir',
|
||||
override=None,
|
||||
group='file1')
|
||||
self.conf.set_override('filesystem_store_datadir',
|
||||
override=None,
|
||||
group='backend_defaults')
|
||||
self.conf.set_override('filesystem_store_datadirs',
|
||||
[store_map[0] + ":100",
|
||||
store_map[1] + ":200"],
|
||||
@@ -568,6 +571,9 @@ class TestMultiStore(base.MultiStoreBaseTest,
|
||||
self.conf.set_override('filesystem_store_datadir',
|
||||
override=None,
|
||||
group='file1')
|
||||
self.conf.set_override('filesystem_store_datadir',
|
||||
override=None,
|
||||
group='backend_defaults')
|
||||
self.conf.set_override('filesystem_store_datadirs',
|
||||
[store_map[0] + ":100",
|
||||
store_map[1] + ":200",
|
||||
@@ -618,6 +624,9 @@ class TestMultiStore(base.MultiStoreBaseTest,
|
||||
self.conf.set_override('filesystem_store_datadir',
|
||||
override=None,
|
||||
group='file1')
|
||||
self.conf.set_override('filesystem_store_datadir',
|
||||
override=None,
|
||||
group='backend_defaults')
|
||||
|
||||
self.conf.set_override('filesystem_store_datadirs',
|
||||
[store_map[0] + ":100",
|
||||
@@ -670,6 +679,9 @@ class TestMultiStore(base.MultiStoreBaseTest,
|
||||
self.conf.set_override('filesystem_store_datadir',
|
||||
override=None,
|
||||
group='file1')
|
||||
self.conf.set_override('filesystem_store_datadir',
|
||||
override=None,
|
||||
group='backend_defaults')
|
||||
self.conf.set_override('filesystem_store_datadirs',
|
||||
[store_map[0] + ":100",
|
||||
store_map[1] + ":200"],
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added a new ``[backend_defaults]`` section in the glance
|
||||
configuration file.
|
||||
Now operators can add common configuration options in the
|
||||
``[backend_defaults]`` section which will act as a fallback
|
||||
mechanism for a configuration option not defined in the
|
||||
main backend group.
|
||||
Reference in New Issue
Block a user