Merge "Add datastore configuration parameter support"

This commit is contained in:
Zuul authored and Gerrit Code Review committed 2026-09-28 11:59:39 +00:00
commit 44ca384d8a
7 files changed
+253

No files matched your search

@@ -0,0 +1,7 @@
---
features:
- |
Added ``openstack datastore configuration set`` and
``openstack datastore configuration delete`` commands. These commands
allow operators to maintain datastore validation rules through the API
without direct access to the Trove control plane.
+2
View File
@@ -116,3 +116,5 @@ openstack.database.v1 =
database_backup_strategy_list = troveclient.osc.v1.database_backup_strategy:ListDatabaseBackupStrategies
database_backup_strategy_create = troveclient.osc.v1.database_backup_strategy:CreateDatabaseBackupStrategy
database_backup_strategy_delete = troveclient.osc.v1.database_backup_strategy:DeleteDatabaseBackupStrategy
datastore_configuration_set = troveclient.osc.v1.datastore_configurations:UpdateAllDatastoreConfiguration
datastore_configuration_delete = troveclient.osc.v1.datastore_configurations:DeleteAllDatastoreConfiguration
@@ -0,0 +1,82 @@
# 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.
"""Database v1 Datastores action implementations"""
import json
from osc_lib.command import command
from troveclient import exceptions
from troveclient.i18n import _
class UpdateAllDatastoreConfiguration(command.Command):
_description = _("Updates all configuration parameters for "
"a datastore version.")
def get_parser(self, prog_name):
parser = super(
UpdateAllDatastoreConfiguration, self).get_parser(prog_name)
parser.add_argument(
'datastore_version',
metavar='<datastore_version>',
help=_('ID of the datastore version.')
)
parser.add_argument(
'configuration_file',
metavar='<configuration_file>',
help=_('Path to a JSON with validation rules.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.database.mgmt_configs
try:
with open(parsed_args.configuration_file,
encoding='utf-8') as config_file:
body = json.load(config_file)
client.update_all(parsed_args.datastore_version,
body['configuration-parameters'])
except Exception as exc:
msg = _("Failed to update datastore configuration parameters: "
"%s") % exc
raise exceptions.CommandError(msg)
class DeleteAllDatastoreConfiguration(command.Command):
_description = _("Deletes all configuration parameters for "
"a datastore version.")
def get_parser(self, prog_name):
parser = super(
DeleteAllDatastoreConfiguration, self).get_parser(prog_name)
parser.add_argument(
'datastore_version',
metavar='<datastore_version>',
help=_('ID of the datastore version.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.database.mgmt_configs
try:
client.delete_all(parsed_args.datastore_version)
except Exception as exc:
msg = _("Failed to delete datastore configuration parameters: "
"%s") % exc
raise exceptions.CommandError(msg)
@@ -0,0 +1,116 @@
# 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 json
from unittest import mock
from troveclient import exceptions
from troveclient.osc.v1 import datastore_configurations
from troveclient.tests.osc.v1 import fakes
class TestDatastoreConfigurations(fakes.TestDatabasev1):
def setUp(self):
super(TestDatastoreConfigurations, self).setUp()
self.configuration_client = (
self.app.client_manager.database.mgmt_configs
)
class TestUpdateAllDatastoreConfiguration(TestDatastoreConfigurations):
def setUp(self):
super(TestUpdateAllDatastoreConfiguration, self).setUp()
self.cmd = datastore_configurations.UpdateAllDatastoreConfiguration(
self.app, None)
def test_update_all(self):
body = {
'configuration-parameters': [{
'name': 'config_name',
'restart_required': False,
'type': 'integer',
'max': 1000,
'min': 1,
}]
}
args = ['version-id', 'validation-rules.json']
verifylist = [
('datastore_version', 'version-id'),
('configuration_file', 'validation-rules.json'),
]
parsed_args = self.check_parser(self.cmd, args, verifylist)
mock_file = mock.mock_open(read_data=json.dumps(body))
with mock.patch('builtins.open', mock_file):
result = self.cmd.take_action(parsed_args)
mock_file.assert_called_once_with(
'validation-rules.json', encoding='utf-8')
self.configuration_client.update_all.assert_called_once_with(
'version-id', body['configuration-parameters'])
self.assertIsNone(result)
def test_update_all_invalid_json(self):
args = ['version-id', 'validation-rules.json']
parsed_args = self.check_parser(self.cmd, args, [])
mock_file = mock.mock_open(read_data='{invalid-json')
with mock.patch('builtins.open', mock_file):
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
self.configuration_client.update_all.assert_not_called()
def test_update_all_error(self):
body = {'configuration-parameters': []}
args = ['version-id', 'validation-rules.json']
parsed_args = self.check_parser(self.cmd, args, [])
self.configuration_client.update_all.side_effect = (
exceptions.CommandError)
mock_file = mock.mock_open(read_data=json.dumps(body))
with mock.patch('builtins.open', mock_file):
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
self.configuration_client.update_all.assert_called_once_with(
'version-id', [])
class TestDeleteAllDatastoreConfiguration(TestDatastoreConfigurations):
def setUp(self):
super(TestDeleteAllDatastoreConfiguration, self).setUp()
self.cmd = datastore_configurations.DeleteAllDatastoreConfiguration(
self.app, None)
def test_delete_all(self):
args = ['version-id']
verifylist = [
('datastore_version', 'version-id'),
]
parsed_args = self.check_parser(self.cmd, args, verifylist)
result = self.cmd.take_action(parsed_args)
self.configuration_client.delete_all.assert_called_once_with(
'version-id')
self.assertIsNone(result)
def test_delete_all_error(self):
args = ['version-id']
parsed_args = self.check_parser(self.cmd, args, [])
self.configuration_client.delete_all.side_effect = (
exceptions.CommandError)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
+28
View File
@@ -255,3 +255,31 @@ class MgmtConfigurationParametersTest(testtools.TestCase):
self.assertEqual('/mgmt/datastores/versions/id/parameters/param_id',
self._url)
self.assertIsNone(self._body)
def test_update_all(self):
self.config_params.api.client.patch = self._get_mock_method()
self._resp.status_code = 202
parameters = [{
'name': 'config_name',
'restart_required': False,
'type': 'integer',
'max': 1000,
'min': 1,
}]
self.config_params.update_all('version-id', parameters)
expected_url = (
'/mgmt/datastores/versions/version-id/parameters/update_all')
self.assertEqual(expected_url, self._url)
self.assertEqual({'configuration-parameters': parameters}, self._body)
def test_delete_all(self):
self.config_params.api.client.delete = self._get_mock_method()
self._resp.status_code = 204
self.config_params.delete_all('version-id')
expected_url = (
'/mgmt/datastores/versions/version-id/parameters/delete_all')
self.assertEqual(expected_url, self._url)
self.assertIsNone(self._body)
+1
View File
@@ -87,6 +87,7 @@ class Client(object):
self.quota = quota.Quotas(self)
self.mgmt_instances = management.Management(self)
self.mgmt_ds_versions = management.MgmtDatastoreVersions(self)
self.mgmt_configs = management.MgmtConfigurationParameters(self)
# Add in any extensions...
if extensions:
+17
View File
@@ -252,6 +252,23 @@ class MgmtConfigurationParameters(configurations.ConfigurationParameters):
resp, body = self.api.client.delete(url)
common.check_for_exceptions(resp, body, url)
def update_all(self, version, configuration_parameters):
"""Mgmt call to update all configuration parameters."""
body = {
'configuration-parameters': configuration_parameters
}
url = ("/mgmt/datastores/versions/%s/parameters/"
"update_all" % version)
resp, resp_body = self.api.client.patch(url, body=body)
common.check_for_exceptions(resp, resp_body, url)
def delete_all(self, version):
"""Mgmt call to delete all configuration parameters."""
url = ("/mgmt/datastores/versions/%s/parameters/"
"delete_all" % version)
resp, body = self.api.client.delete(url)
common.check_for_exceptions(resp, body, url)
class MgmtDatastoreVersions(base.ManagerWithFind):
"""Manage :class:`DatastoreVersion` resources."""