Datastore Configuration Parameters stored in db

Changes:
Add datastore configuration paramters to configuration model
Add ability to CRUD configuration parameters
adding a manage cmd to import the config params

partially implements blueprint configuration-parameters-in-db
Change-Id: I51d5281ba8922dc19c74e7c6e972f40e45416d50
This commit is contained in:
Craig Vyvial
2014-02-06 23:28:11 -06:00
committed by Nikhil Manchanda
parent 74b436e1d2
commit 9a35fa8b4d
19 changed files with 990 additions and 152 deletions

View File

@@ -24,6 +24,7 @@ gettext.install('trove', unicode=1)
from trove.common import cfg
from trove.common import exception
from trove.common import utils
from trove.configuration import models as config_models
from trove.db import get_db_api
from trove.openstack.common import log as logging
from trove.datastore import models as datastore_models
@@ -80,6 +81,14 @@ class Commands(object):
self.db_api.drop_db(CONF)
self.db_sync(repo_path)
def db_load_datastore_config_parameters(self,
datastore_version_id,
config_file_location):
print("Loading config parameters for datastore version: %s"
% datastore_version_id)
config_models.load_datastore_configuration_parameters(
datastore_version_id, config_file_location)
def params_of(self, command_name):
if Commands.has(command_name):
return utils.MethodInspector(getattr(self, command_name))
@@ -142,6 +151,19 @@ def main():
parser = subparser.add_parser(
'db_recreate', description='Drop the database and recreate it.')
parser.add_argument('repo_path', help=repo_path_help)
parser = subparser.add_parser(
'db_load_datastore_config_parameters',
description='Loads configuration group parameter validation rules '
'for a datastore version into the database.')
parser.add_argument(
'datastore_version_id',
help='UUID of the datastore version.')
parser.add_argument(
'config_file_location',
help='Fully qualified file path to the configuration group '
'parameter validation rules.')
cfg.custom_parser('action', actions)
cfg.parse_args(sys.argv)

View File

@@ -24,6 +24,31 @@ url_ref = {
'|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
}
boolean_string = {
"type": "integer",
"minimum": 0,
"maximum": 1
}
configuration_data_types = {
"type": "string",
"minLength": 1,
"pattern": "integer|string"
}
configuration_integer_size = {
"type": "string",
"maxLength": 40,
"pattern": "[0-9]+"
}
configuration_non_empty_string = {
"type": "string",
"minLength": 1,
"maxLength": 128,
"pattern": "^.*[0-9a-zA-Z]+.*$"
}
flavorref = {
'oneOf': [
url_ref,
@@ -502,6 +527,45 @@ configuration = {
}
}
mgmt_configuration = {
"create": {
"name": "configuration_parameter:create",
"type": "object",
"required": ["configuration-parameter"],
"properties": {
"configuration-parameter": {
"type": "object",
"required": ["name", "restart_required", "data_type"],
"properties": {
"name": configuration_non_empty_string,
"data_type": configuration_data_types,
"restart_required": boolean_string,
"max_size": configuration_integer_size,
"min_size": configuration_integer_size,
}
}
}
},
"update": {
"name": "configuration_parameter:update",
"type": "object",
"required": ["configuration-parameter"],
"properties": {
"configuration-parameter": {
"type": "object",
"required": ["name", "restart_required", "data_type"],
"properties": {
"name": configuration_non_empty_string,
"data_type": configuration_data_types,
"restart_required": boolean_string,
"max_size": configuration_integer_size,
"min_size": configuration_integer_size,
}
}
}
},
}
account = {
'create': {
"type": "object",

View File

@@ -14,48 +14,9 @@
# under the License.
import io
import json
from trove.common import cfg
from trove.common import exception
from trove.common import utils
from trove.openstack.common import log as logging
from six.moves import configparser
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
ENV = utils.ENV
def _get_item(key, dictList):
for item in dictList:
if key == item.get('name'):
return item
def do_configs_require_restart(overrides, datastore_manager='mysql'):
rules = get_validation_rules(datastore_manager=datastore_manager)
LOG.debug("overrides: %s" % overrides)
LOG.debug("rules?: %s" % rules)
for key in overrides.keys():
rule = _get_item(key, rules['configuration-parameters'])
if rule.get('restart_required'):
LOG.debug("rule requires restart: %s" % rule)
return True
return False
def get_validation_rules(datastore_manager='mysql'):
try:
config_location = ("%s/validation-rules.json" % datastore_manager)
template = ENV.get_template(config_location)
return json.loads(template.render())
except Exception:
msg = "This operation is not supported by this datastore at this time."
LOG.exception(msg)
raise exception.UnprocessableEntity(message=msg)
class MySQLConfParser(object):
"""MySQLConfParser"""
def __init__(self, config):

View File

@@ -421,7 +421,12 @@ class ConfigurationDatastoreNotMatchInstance(TroveError):
class ConfigurationParameterDeleted(TroveError):
message = _("%(parameter_name)s parameter can no longer be "
" set as of %(parameter_deleted_at)s.")
"set as of %(parameter_deleted_at)s.")
class ConfigurationParameterAlreadyExists(TroveError):
message = _("%(parameter_name)s parameter already exists "
"for datastore version %(datastore_version)s.")
class ConfigurationAlreadyAttached(TroveError):

View File

@@ -13,12 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
from datetime import datetime
from trove.common import cfg
from trove.common import configurations
from trove.common import exception
from trove.common import utils
from trove.common.exception import ModelNotFoundError
from trove.datastore import models as dstore_models
from trove.db import get_db_api
from trove.db import models as dbmodels
from trove.openstack.common import log as logging
from trove.openstack.common.gettextutils import _
@@ -66,6 +69,10 @@ class Configurations(object):
class Configuration(object):
def __init__(self, context, configuration_id):
self.context = context
self.configuration_id = configuration_id
@property
def instances(self):
return self.instances
@@ -89,7 +96,7 @@ class Configuration(object):
"values: %s" % (cfg_id, values))
config_items = []
for key, val in values.iteritems():
config_item = ConfigurationParameter.create(
config_item = DBConfigurationParameter.create(
configuration_id=cfg_id,
configuration_key=key,
configuration_value=val)
@@ -106,8 +113,8 @@ class Configuration(object):
@staticmethod
def remove_all_items(context, id, deleted_at):
items = ConfigurationParameter.find_all(configuration_id=id,
deleted=False).all()
items = DBConfigurationParameter.find_all(configuration_id=id,
deleted=False).all()
LOG.debug("Removing all configuration values for %s" % id)
for item in items:
item.deleted = True
@@ -125,59 +132,84 @@ class Configuration(object):
def load(context, id):
try:
if context.is_admin:
config_info = DBConfiguration.find_by(id=id,
deleted=False)
return DBConfiguration.find_by(id=id, deleted=False)
else:
config_info = DBConfiguration.find_by(id=id,
tenant_id=context.tenant,
deleted=False)
return DBConfiguration.find_by(id=id,
tenant_id=context.tenant,
deleted=False)
except ModelNotFoundError:
msg = _("Configuration group with ID %s could not be found.") % id
raise ModelNotFoundError(msg)
return config_info
@staticmethod
def find_parameter_details(name, detail_list):
for item in detail_list:
if item.name == name:
return item
return None
@staticmethod
def load_items(context, id):
datastore = Configuration.load_configuration_datastore_version(context,
id)
config_items = ConfigurationParameter.find_all(configuration_id=id,
deleted=False).all()
rules = configurations.get_validation_rules(
datastore_manager=datastore.manager)
datastore_v = Configuration.load_configuration_datastore_version(
context,
id)
config_items = DBConfigurationParameter.find_all(
configuration_id=id, deleted=False).all()
def _get_rule(key):
for rule in rules['configuration-parameters']:
if str(rule.get('name')) == key:
return rule
detail_list = DatastoreConfigurationParameters.load_parameters(
datastore_v.id)
for item in config_items:
rule = _get_rule(str(item.configuration_key))
if rule.get('type') == 'boolean':
rule = Configuration.find_parameter_details(
str(item.configuration_key), detail_list)
if not rule:
continue
if rule.data_type == 'boolean':
item.configuration_value = bool(int(item.configuration_value))
elif rule.get('type') == 'integer':
elif rule.data_type == 'integer':
item.configuration_value = int(item.configuration_value)
else:
item.configuration_value = str(item.configuration_value)
return config_items
@staticmethod
def get_configuration_overrides(context, configuration_id):
def get_configuration_overrides(self):
"""Gets the overrides dictionary to apply to an instance."""
overrides = {}
if configuration_id:
config_items = Configuration.load_items(context,
id=configuration_id)
if self.configuration_id:
config_items = Configuration.load_items(self.context,
id=self.configuration_id)
for i in config_items:
overrides[i.configuration_key] = i.configuration_value
return overrides
def does_configuration_need_restart(self):
datastore_v = Configuration.load_configuration_datastore_version(
self.context,
self.configuration_id)
config_items = Configuration.load_items(self.context,
id=self.configuration_id)
LOG.debug("config_items: %s" % config_items)
detail_list = DatastoreConfigurationParameters.load_parameters(
datastore_v.id, show_deleted=True)
for i in config_items:
LOG.debug("config item: %s" % i)
details = Configuration.find_parameter_details(
i.configuration_key, detail_list)
LOG.debug("parameter details: %s" % details)
if not details:
raise exception.NotFound(uuid=i.configuration_key)
if bool(details.restart_required):
return True
return False
@staticmethod
def save(context, configuration, configuration_items, instances):
DBConfiguration.save(configuration)
for item in configuration_items:
item["deleted_at"] = None
ConfigurationParameter.save(item)
DBConfigurationParameter.save(item)
items = Configuration.load_items(context, configuration.id)
@@ -210,7 +242,7 @@ class DBConfiguration(dbmodels.DatabaseModelBase):
return datastore_version
class ConfigurationParameter(dbmodels.DatabaseModelBase):
class DBConfigurationParameter(dbmodels.DatabaseModelBase):
_data_fields = ['configuration_id', 'configuration_key',
'configuration_value', 'deleted',
'deleted_at']
@@ -219,8 +251,164 @@ class ConfigurationParameter(dbmodels.DatabaseModelBase):
return self.configuration_key.__hash__()
class DBDatastoreConfigurationParameters(dbmodels.DatabaseModelBase):
"""Model for storing the configuration parameters on a datastore."""
_auto_generated_attrs = ['id']
_data_fields = [
'name',
'datastore_version_id',
'restart_required',
'max_size',
'min_size',
'data_type',
'deleted',
'deleted_at',
]
_table_name = "datastore_configuration_parameters"
preserve_on_delete = True
class DatastoreConfigurationParameters(object):
def __init__(self, db_info):
self.db_info = db_info
@staticmethod
def create(**kwargs):
"""Create a configuration parameter for a datastore version."""
# Do we already have a parameter in the db?
# yes: and its deleted then modify the param
# yes: and its not deleted then error on create.
# no: then just create the new param
ds_v_id = kwargs.get('datastore_version_id')
config_param_name = kwargs.get('name')
try:
param = DatastoreConfigurationParameters.load_parameter_by_name(
ds_v_id,
config_param_name,
show_deleted=True)
if param.deleted == 1:
param.restart_required = kwargs.get('restart_required')
param.data_type = kwargs.get('data_type')
param.max_size = kwargs.get('max_size')
param.min_size = kwargs.get('min_size')
param.deleted = 0
param.save()
return param
else:
raise exception.ConfigurationParameterAlreadyExists(
parameter_name=config_param_name,
datastore_version=ds_v_id)
except exception.NotFound:
pass
config_param = DBDatastoreConfigurationParameters.create(
**kwargs)
return config_param
@staticmethod
def delete(version_id, config_param_name):
config_param = DatastoreConfigurationParameters.load_parameter_by_name(
version_id, config_param_name)
config_param.deleted = True
config_param.deleted_at = datetime.utcnow()
config_param.save()
@classmethod
def load_parameters(cls, datastore_version_id, show_deleted=False):
try:
if show_deleted:
return DBDatastoreConfigurationParameters.find_all(
datastore_version_id=datastore_version_id
)
else:
return DBDatastoreConfigurationParameters.find_all(
datastore_version_id=datastore_version_id,
deleted=False
)
except exception.NotFound:
raise exception.NotFound(uuid=datastore_version_id)
@classmethod
def load_parameter(cls, config_id, show_deleted=False):
try:
if show_deleted:
return DBDatastoreConfigurationParameters.find_by(
id=config_id
)
else:
return DBDatastoreConfigurationParameters.find_by(
id=config_id, deleted=False
)
except exception.NotFound:
raise exception.NotFound(uuid=config_id)
@classmethod
def load_parameter_by_name(cls, datastore_version_id, config_param_name,
show_deleted=False):
try:
if show_deleted:
return DBDatastoreConfigurationParameters.find_by(
datastore_version_id=datastore_version_id,
name=config_param_name
)
else:
return DBDatastoreConfigurationParameters.find_by(
datastore_version_id=datastore_version_id,
name=config_param_name,
deleted=False
)
except exception.NotFound:
raise exception.NotFound(uuid=config_param_name)
def create_or_update_datastore_configuration_parameter(name,
datastore_version_id,
restart_required,
data_type,
max_size,
min_size):
get_db_api().configure_db(CONF)
datastore_version = dstore_models.DatastoreVersion.load_by_uuid(
datastore_version_id)
try:
config = DatastoreConfigurationParameters.load_parameter_by_name(
datastore_version_id, name, show_deleted=True)
config.restart_required = restart_required
config.max_size = max_size
config.min_size = min_size
config.data_type = data_type
get_db_api().save(config)
except exception.NotFound:
config = DBDatastoreConfigurationParameters(
id=utils.generate_uuid(),
name=name,
datastore_version_id=datastore_version.id,
restart_required=restart_required,
data_type=data_type,
max_size=max_size,
min_size=min_size,
deleted=False,
)
get_db_api().save(config)
def load_datastore_configuration_parameters(datastore_version_id, config_file):
with open(config_file) as f:
config = json.load(f)
for param in config['configuration-parameters']:
create_or_update_datastore_configuration_parameter(
param['name'],
datastore_version_id,
param['restart_required'],
param['type'],
param.get('max'),
param.get('min'),
)
def persisted_models():
return {
'configurations': DBConfiguration,
'configuration_parameters': ConfigurationParameter
'configuration_parameters': DBConfigurationParameter,
'datastore_configuration_parameters': DBDatastoreConfigurationParameters, # noqa
}

View File

@@ -15,13 +15,13 @@
from datetime import datetime
from trove.common import cfg
from trove.common import configurations
from trove.common import exception
from trove.common import pagination
from trove.common import wsgi
from trove.configuration import models
from trove.configuration import views
from trove.configuration.models import ConfigurationParameter
from trove.configuration.models import DBConfigurationParameter
from trove.configuration.models import DatastoreConfigurationParameters
from trove.datastore import models as ds_models
from trove.openstack.common import log as logging
from trove.openstack.common.gettextutils import _
@@ -101,10 +101,10 @@ class ConfigurationsController(wsgi.Controller):
# validate that the values passed in are permitted by the operator.
ConfigurationsController._validate_configuration(
body['configuration']['values'],
datastore_manager=datastore_version.manager)
datastore_version=datastore_version)
for k, v in values.iteritems():
configItems.append(ConfigurationParameter(
configItems.append(DBConfigurationParameter(
configuration_key=k,
configuration_value=v))
@@ -180,47 +180,42 @@ class ConfigurationsController(wsgi.Controller):
if 'values' in configuration:
# validate that the values passed in are permitted by the operator.
ConfigurationsController._validate_configuration(
configuration['values'], datastore_manager=ds_version.manager)
configuration['values'], datastore_version=ds_version)
for k, v in configuration['values'].iteritems():
items.append(ConfigurationParameter(configuration_id=group.id,
configuration_key=k,
configuration_value=v,
deleted=False))
items.append(DBConfigurationParameter(
configuration_id=group.id,
configuration_key=k,
configuration_value=v,
deleted=False))
return items
@staticmethod
def _validate_configuration(values, datastore_manager=None):
rules = configurations.get_validation_rules(
datastore_manager=datastore_manager)
LOG.debug("Validating configuration values for group "
"with manager %s" % datastore_manager)
def _validate_configuration(values, datastore_version=None):
LOG.info(_("Validating configuration values"))
for k, v in values.iteritems():
# get the validation rule dictionary, which will ensure there is a
# rule for the given key name. An exception will be thrown if no
# valid rule is located.
rule = ConfigurationsController._get_item(
k, rules['configuration-parameters'])
rule = DatastoreConfigurationParameters.load_parameter_by_name(
datastore_version.id, k)
if rule.get('deleted_at'):
raise exception.ConfigurationParameterDeleted(
parameter_name=rule.get('name'),
parameter_deleted_at=rule.get('deleted_at'))
if not rule or rule.deleted:
output = {"key": k}
msg = _("The parameter provided for the configuration "
"%(key)s is not available.") % output
raise exception.UnprocessableEntity(message=msg)
# type checking
valueType = rule.get('type')
value_type = rule.data_type
if not isinstance(v, ConfigurationsController._find_type(
valueType)):
output = {"key": k, "type": valueType}
value_type)):
output = {"key": k, "type": value_type}
msg = _("The value provided for the configuration "
"parameter %(key)s is not of type %(type)s.") % output
raise exception.UnprocessableEntity(message=msg)
# integer min/max checking
if isinstance(v, int) and not isinstance(v, bool):
if isinstance(v, (int, long)) and not isinstance(v, bool):
try:
min_value = int(rule.get('min'))
min_value = int(rule.min_size)
except ValueError:
raise exception.TroveError(_(
"Invalid or unsupported min value defined in the "
@@ -234,7 +229,7 @@ class ConfigurationsController(wsgi.Controller):
raise exception.UnprocessableEntity(message=message)
try:
max_value = int(rule.get('max'))
max_value = int(rule.max_size)
except ValueError:
raise exception.TroveError(_(
"Invalid or unsupported max value defined in the "
@@ -248,13 +243,13 @@ class ConfigurationsController(wsgi.Controller):
raise exception.UnprocessableEntity(message=message)
@staticmethod
def _find_type(valueType):
if valueType == "boolean":
def _find_type(value_type):
if value_type == "boolean":
return bool
elif valueType == "string":
elif value_type == "string":
return basestring
elif valueType == "integer":
return int
elif value_type == "integer":
return (int, long)
else:
raise exception.TroveError(_(
"Invalid or unsupported type defined in the "
@@ -273,35 +268,27 @@ class ParametersController(wsgi.Controller):
def index(self, req, tenant_id, datastore, id):
ds, ds_version = ds_models.get_datastore_version(
type=datastore, version=id)
rules = configurations.get_validation_rules(
datastore_manager=ds_version.manager)
rules = models.DatastoreConfigurationParameters.load_parameters(
ds_version.id)
return wsgi.Result(views.ConfigurationParametersView(rules).data(),
200)
def show(self, req, tenant_id, datastore, id, name):
ds, ds_version = ds_models.get_datastore_version(
type=datastore, version=id)
rules = configurations.get_validation_rules(
datastore_manager=ds_version.manager)
for rule in rules['configuration-parameters']:
if rule['name'] == name:
return wsgi.Result(
views.ConfigurationParametersView(rule).data(), 200)
raise exception.ConfigKeyNotFound(key=name)
rule = models.DatastoreConfigurationParameters.load_parameter_by_name(
ds_version.id, name)
return wsgi.Result(views.ConfigurationParameterView(rule).data(), 200)
def index_by_version(self, req, tenant_id, version):
ds_version = ds_models.DatastoreVersion.load_by_uuid(version)
rules = configurations.get_validation_rules(
datastore_manager=ds_version.manager)
rules = models.DatastoreConfigurationParameters.load_parameters(
ds_version.id)
return wsgi.Result(views.ConfigurationParametersView(rules).data(),
200)
def show_by_version(self, req, tenant_id, version, name):
ds_version = ds_models.DatastoreVersion.load_by_uuid(version)
rules = configurations.get_validation_rules(
datastore_manager=ds_version.manager)
for rule in rules['configuration-parameters']:
if rule['name'] == name:
return wsgi.Result(
views.ConfigurationParametersView(rule).data(), 200)
raise exception.ConfigKeyNotFound(key=name)
ds_models.DatastoreVersion.load_by_uuid(version)
rule = models.DatastoreConfigurationParameters.load_parameter_by_name(
version, name)
return wsgi.Result(views.ConfigurationParameterView(rule).data(), 200)

View File

@@ -112,10 +112,33 @@ class DetailedConfigurationView(object):
return {"configuration": configuration_dict}
class ConfigurationParametersView(object):
class ConfigurationParameterView(object):
def __init__(self, configuration_parameters):
self.configuration_parameters = configuration_parameters
def __init__(self, config):
self.config = config
def data(self):
return self.configuration_parameters
ret = {
"name": self.config.name,
"datastore_version_id": self.config.datastore_version_id,
"restart_required": self.config.restart_required,
"type": self.config.data_type,
}
if self.config.max_size:
ret["max_size"] = self.config.max_size
if self.config.min_size:
ret["min_size"] = self.config.min_size
return ret
class ConfigurationParametersView(object):
def __init__(self, configs):
self.configs = configs
def data(self):
params = []
for p in self.configs:
param = ConfigurationParameterView(p)
params.append(param.data())
return {"configuration-parameters": params}

View File

@@ -65,6 +65,9 @@ def map(engine, models):
Table('conductor_lastseen', meta, autoload=True))
orm.mapper(models['clusters'],
Table('clusters', meta, autoload=True))
orm.mapper(models['datastore_configuration_parameters'],
Table('datastore_configuration_parameters', meta,
autoload=True))
def mapping_exists(model):

View File

@@ -0,0 +1,60 @@
# Copyright 2014 Rackspace
# 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.
from sqlalchemy import ForeignKey
from sqlalchemy.schema import Column
from sqlalchemy.schema import MetaData
from sqlalchemy.schema import UniqueConstraint
from trove.db.sqlalchemy.migrate_repo.schema import create_tables
from trove.db.sqlalchemy.migrate_repo.schema import drop_tables
from trove.db.sqlalchemy.migrate_repo.schema import DateTime
from trove.db.sqlalchemy.migrate_repo.schema import Boolean
from trove.db.sqlalchemy.migrate_repo.schema import String
from trove.db.sqlalchemy.migrate_repo.schema import Table
meta = MetaData()
datastore_configuration_parameters = Table(
'datastore_configuration_parameters',
meta,
Column('id', String(36), primary_key=True, nullable=False),
Column('name', String(128), primary_key=True, nullable=False),
Column('datastore_version_id', String(36),
ForeignKey("datastore_versions.id"),
primary_key=True, nullable=False),
Column('restart_required', Boolean(), nullable=False, default=False),
Column('max_size', String(40)),
Column('min_size', String(40)),
Column('data_type', String(128), nullable=False),
Column('deleted', Boolean()),
Column('deleted_at', DateTime()),
UniqueConstraint(
'datastore_version_id', 'name',
name='UQ_datastore_configuration_parameters_datastore_version_id_name')
)
def upgrade(migrate_engine):
meta.bind = migrate_engine
Table('datastore_versions', meta, autoload=True)
create_tables([datastore_configuration_parameters])
def downgrade(migrate_engine):
meta.bind = migrate_engine
drop_tables([datastore_configuration_parameters])

View File

@@ -0,0 +1,136 @@
# Copyright 2014 Rackspace
# 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.
from trove.common import exception
from trove.common import wsgi
from trove.common.auth import admin_context
from trove.configuration import models as config_models
from trove.datastore import models as ds_models
from trove.extensions.mgmt.configuration import views
from trove.openstack.common import log as logging
from trove.openstack.common.gettextutils import _
import trove.common.apischema as apischema
LOG = logging.getLogger(__name__)
class ConfigurationsParameterController(wsgi.Controller):
"""Controller for configuration parameters functionality."""
schemas = apischema.mgmt_configuration
@admin_context
def index(self, req, tenant_id, version_id):
"""List all configuration parameters."""
ds_version = ds_models.DatastoreVersion.load_by_uuid(version_id)
config_params = config_models.DatastoreConfigurationParameters
rules = config_params.load_parameters(
ds_version.id, show_deleted=True)
return wsgi.Result(views.MgmtConfigurationParametersView(rules).data(),
200)
@admin_context
def show(self, req, tenant_id, version_id, id):
"""Show a configuration parameter."""
ds_models.DatastoreVersion.load_by_uuid(version_id)
config_params = config_models.DatastoreConfigurationParameters
rule = config_params.load_parameter_by_name(
version_id, id, show_deleted=True)
return wsgi.Result(views.MgmtConfigurationParameterView(rule).data(),
200)
def _validate_data_type(self, parameter):
min_size = None
max_size = None
data_type = parameter['data_type']
if data_type == "integer":
if 'max_size' not in parameter:
raise exception.BadRequest(_("max_size is required for "
"integer data type."))
if 'min_size' not in parameter:
raise exception.BadRequest(_("min_size is required for "
"integer data type."))
max_size = int(parameter['max_size'])
min_size = int(parameter['min_size'])
if max_size < min_size:
raise exception.BadRequest(
_("max_size must be greater than or equal to min_size."))
return data_type, min_size, max_size
@admin_context
def create(self, req, body, tenant_id, version_id):
"""Create configuration parameter for datastore version."""
LOG.info(_("Creating configuration parameter for datastore"))
LOG.debug("req : '%s'\n\n" % req)
LOG.debug("body : '%s'\n\n" % body)
if not body:
raise exception.BadRequest(_("Invalid request body."))
parameter = body['configuration-parameter']
name = parameter['name']
restart_required = bool(parameter['restart_required'])
data_type, min_size, max_size = self._validate_data_type(parameter)
datastore_version = ds_models.DatastoreVersion.load_by_uuid(version_id)
rule = config_models.DatastoreConfigurationParameters.create(
name=name,
datastore_version_id=datastore_version.id,
restart_required=restart_required,
data_type=data_type,
max_size=max_size,
min_size=min_size
)
return wsgi.Result(
views.MgmtConfigurationParameterView(rule).data(),
200)
@admin_context
def update(self, req, body, tenant_id, version_id, id):
"""Updating configuration parameter for datastore version."""
LOG.info(_("Updating configuration parameter for datastore"))
LOG.debug("req : '%s'\n\n" % req)
LOG.debug("body : '%s'\n\n" % body)
if not body:
raise exception.BadRequest(_("Invalid request body."))
parameter = body['configuration-parameter']
restart_required = bool(parameter['restart_required'])
data_type, min_size, max_size = self._validate_data_type(parameter)
ds_models.DatastoreVersion.load_by_uuid(version_id)
ds_config_params = config_models.DatastoreConfigurationParameters
param = ds_config_params.load_parameter_by_name(
version_id, id)
param.restart_required = restart_required
param.data_type = data_type
param.max_size = max_size
param.min_size = min_size
param.save()
return wsgi.Result(
views.MgmtConfigurationParameterView(param).data(),
200)
@admin_context
def delete(self, req, tenant_id, version_id, id):
"""Delete configuration parameter for datastore version."""
LOG.info(_("Deleting configuration parameter for datastore"))
LOG.debug("req : '%s'\n\n" % req)
ds_config_params = config_models.DatastoreConfigurationParameters
try:
ds_config_params.delete(version_id, id)
except exception.NotFound:
raise exception.BadRequest(_("Parameter %s does not exist in the "
"database.") % id)
return wsgi.Result(None, 204)

View File

@@ -0,0 +1,53 @@
# Copyright 2014 Rackspace
# 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.
#
from trove.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class MgmtConfigurationParameterView(object):
def __init__(self, config):
self.config = config
def data(self):
ret = {
"name": self.config.name,
"datastore_version_id": self.config.datastore_version_id,
"restart_required": self.config.restart_required,
"type": self.config.data_type,
"deleted": self.config.deleted,
"deleted_at": self.config.deleted_at,
}
if self.config.max_size:
ret["max_size"] = int(self.config.max_size)
if self.config.min_size:
ret["min_size"] = int(self.config.min_size)
return ret
class MgmtConfigurationParametersView(object):
def __init__(self, configs):
self.configs = configs
def data(self):
params = []
LOG.debug(self.configs.__dict__)
for p in self.configs:
param = MgmtConfigurationParameterView(p)
params.append(param.data())
return {"configuration-parameters": params}

View File

@@ -17,6 +17,7 @@ from trove.openstack.common import log as logging
from trove.common import extensions
from trove.extensions.mgmt.clusters.service import ClusterController
from trove.extensions.mgmt.configuration import service as conf_service
from trove.extensions.mgmt.instances.service import MgmtInstanceController
from trove.extensions.mgmt.host.service import HostController
from trove.extensions.mgmt.quota.service import QuotaController
@@ -94,4 +95,10 @@ class Mgmt(extensions.ExtensionDescriptor):
member_actions={})
resources.append(upgrade)
datastore_configuration_parameters = extensions.ResourceExtension(
'{tenant_id}/mgmt/datastores/versions/{version_id}/parameters',
conf_service.ConfigurationsParameterController(),
member_actions={})
resources.append(datastore_configuration_parameters)
return resources

View File

@@ -23,7 +23,6 @@ from oslo.config.cfg import NoSuchOptError
from trove.common import cfg
from trove.common import exception
from trove.common import template
from trove.common.configurations import do_configs_require_restart
import trove.common.instance as tr_instance
from trove.common.remote import create_dns_client
from trove.common.remote import create_guest_client
@@ -734,9 +733,9 @@ class Instance(BuiltInstance):
# generate an overrides dict to pass into the instance creation
# method
overrides = Configuration.get_configuration_overrides(
context, configuration_id)
datastore_status = InstanceServiceStatus.create(
config = Configuration(context, configuration_id)
overrides = config.get_configuration_overrides()
service_status = InstanceServiceStatus.create(
instance_id=db_info.id,
status=tr_instance.ServiceStatuses.NEW)
@@ -761,7 +760,7 @@ class Instance(BuiltInstance):
overrides, slave_of_id,
cluster_config)
return SimpleInstance(context, db_info, datastore_status,
return SimpleInstance(context, db_info, service_status,
root_password)
return run_with_quotas(context.tenant,
@@ -963,19 +962,16 @@ class Instance(BuiltInstance):
config_datastore_version=config_ds_v,
instance_datastore_version=inst_ds_v)
overrides = Configuration.get_configuration_overrides(
self.context, configuration.id)
LOG.debug("Config overrides is %s." % overrides)
self.update_overrides(overrides)
config = Configuration(self.context, configuration.id)
LOG.debug("Config config is %s." % config)
self.update_db(configuration_id=configuration.id)
self.update_overrides(config)
def update_overrides(self, overrides):
def update_overrides(self, config):
LOG.debug("Updating or removing overrides for instance %s."
% self.id)
need_restart = do_configs_require_restart(
overrides, datastore_manager=self.ds_version.manager)
overrides = config.get_configuration_overrides()
need_restart = config.does_configuration_need_restart()
LOG.debug("Config overrides has non-dynamic settings, "
"requires a restart: %s." % need_restart)
if need_restart:

View File

@@ -31,7 +31,6 @@ from trove.common import cfg
from trove.common import template
from trove.common import utils
from trove.common.utils import try_recover
from trove.common.configurations import do_configs_require_restart
from trove.common.exception import BackupCreationError
from trove.common.exception import GuestError
from trove.common.exception import GuestTimeout
@@ -976,9 +975,10 @@ class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin):
"%s.") % self.id)
LOG.debug("overrides: %s" % overrides)
LOG.debug("self.ds_version: %s" % self.ds_version.__dict__)
# todo(cp16net) How do we know what datastore type we have?
need_restart = do_configs_require_restart(
overrides, datastore_manager=self.ds_version.manager)
LOG.debug("self.configuration.id: %s" % self.configuration.id)
# check if the configuration requires a restart of the instance
config = Configuration(self.context, self.configuration.id)
need_restart = config.does_configuration_need_restart()
LOG.debug("do we need a restart?: %s" % need_restart)
if need_restart:
status = inst_models.InstanceTasks.RESTART_REQUIRED

View File

@@ -196,8 +196,10 @@ class CreateConfigurations(ConfigurationsTestBase):
def test_expected_get_configuration_parameter(self):
# tests get on a single parameter to verify it has expected attributes
param = 'key_buffer_size'
expected_config_params = ['name', 'restart_required', 'max',
'min', 'type']
expected_config_params = ['name', 'restart_required',
'max_size', 'min_size', 'type',
'deleted', 'deleted_at',
'datastore_version_id']
instance_info.dbaas.configuration_parameters.get_parameter(
instance_info.dbaas_datastore,
instance_info.dbaas_datastore_version,
@@ -216,8 +218,14 @@ class CreateConfigurations(ConfigurationsTestBase):
def test_configurations_create_invalid_values(self):
"""Test create configurations with invalid values."""
values = '{"this_is_invalid": 123}'
assert_unprocessable(instance_info.dbaas.configurations.create,
CONFIG_NAME, values, CONFIG_DESC)
try:
instance_info.dbaas.configurations.create(
CONFIG_NAME,
values,
CONFIG_DESC)
except exceptions.NotFound:
resp, body = instance_info.dbaas.client.last_response
assert_equal(resp.status, 404)
@test
def test_configurations_create_invalid_value_type(self):
@@ -587,7 +595,25 @@ class DeleteConfigurations(ConfigurationsTestBase):
instance_info.dbaas.configurations.delete,
invalid_configuration_id)
@test
@test(depends_on=[test_delete_invalid_configuration_not_found])
def test_delete_configuration_parameter_with_mgmt_api(self):
# delete a parameter that is used by a test
# connect_timeout
ds = instance_info.dbaas_datastore
ds_v = instance_info.dbaas_datastore_version
version = instance_info.dbaas.datastore_versions.get(
ds, ds_v)
client = instance_info.dbaas_admin.mgmt_configs
config_param_name = sql_variables[1]
client.delete(version.id, config_param_name)
assert_raises(
exceptions.NotFound,
instance_info.dbaas.configuration_parameters.get_parameter,
ds,
ds_v,
config_param_name)
@test(depends_on=[test_delete_configuration_parameter_with_mgmt_api])
def test_unable_delete_instance_configurations(self):
# test deleting a configuration that is assigned to
# an instance is not allowed.

View File

@@ -0,0 +1,228 @@
# Copyright 2014 Rackspace Hosting
# 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.
from troveclient.compat import exceptions
from proboscis import before_class
from proboscis import test
from proboscis import asserts
from trove import tests
from trove.tests.util import test_config
from trove.tests.util import create_dbaas_client
from trove.tests.util.users import Requirements
GROUP = "dbaas.api.mgmt.configurations"
@test(groups=[GROUP, tests.DBAAS_API, tests.PRE_INSTANCES],
depends_on_groups=["services.initialize"])
class ConfigGroupsSetupBeforeInstanceCreation(object):
@before_class
def setUp(self):
self.user = test_config.users.find_user(Requirements(is_admin=True))
self.admin_client = create_dbaas_client(self.user)
self.datastore_version_id = self.admin_client.datastore_versions.get(
"mysql", "5.5").id
@test
def test_valid_config_create_type(self):
name = "testconfig-create"
restart_required = 1
data_type = "string"
max_size = None
min_size = None
client = self.admin_client.mgmt_configs
param_list = client.parameters_by_version(
self.datastore_version_id)
asserts.assert_true(name not in [p.name for p in param_list])
client.create(
self.datastore_version_id,
name,
restart_required,
data_type,
max_size,
min_size)
param_list = client.parameters_by_version(
self.datastore_version_id)
asserts.assert_true(name in [p.name for p in param_list])
param = client.get_parameter_by_version(
self.datastore_version_id, name)
asserts.assert_equal(name, param.name)
asserts.assert_equal(restart_required, param.restart_required)
asserts.assert_equal(data_type, param.type)
# test the modify
restart_required = 0
data_type = "integer"
max_size = "10"
min_size = "1"
client.modify(
self.datastore_version_id,
name,
restart_required,
data_type,
max_size,
min_size)
param = client.get_parameter_by_version(
self.datastore_version_id, name)
asserts.assert_equal(name, param.name)
asserts.assert_equal(restart_required, param.restart_required)
asserts.assert_equal(data_type, param.type)
asserts.assert_equal(max_size, param.max_size)
asserts.assert_equal(min_size, param.min_size)
client.delete(self.datastore_version_id, name)
# test show deleted params work
param_list = client.list_all_parameter_by_version(
self.datastore_version_id)
asserts.assert_true(name in [p.name for p in param_list])
param = client.get_any_parameter_by_version(
self.datastore_version_id, name)
asserts.assert_equal(name, param.name)
asserts.assert_equal(restart_required, param.restart_required)
asserts.assert_equal(data_type, param.type)
asserts.assert_equal(int(max_size), int(param.max_size))
asserts.assert_equal(int(min_size), int(param.min_size))
asserts.assert_equal(True, param.deleted)
asserts.assert_true(param.deleted_at)
def test_create_config_type_twice_fails(self):
name = "test-delete-config-types"
restart_required = 1
data_type = "string"
max_size = None
min_size = None
client = self.admin_client.mgmt_configs
client.create(
self.datastore_version_id,
name,
restart_required,
data_type,
max_size,
min_size)
asserts.assert_raises(exceptions.BadRequest,
client.create,
self.datastore_version_id,
name,
restart_required,
data_type,
max_size,
min_size)
client.delete(self.datastore_version_id, name)
config_list = client.parameters_by_version(self.datastore_version_id)
asserts.assert_true(name not in [conf.name for conf in config_list])
# testing that recreate of a deleted parameter works.
client.create(
self.datastore_version_id,
name,
restart_required,
data_type,
max_size,
min_size)
config_list = client.parameters_by_version(self.datastore_version_id)
asserts.assert_false(name not in [conf.name for conf in config_list])
@test
def test_delete_config_type(self):
name = "test-delete-config-types"
restart_required = 1
data_type = "string"
max_size = None
min_size = None
client = self.admin_client.mgmt_configs
client.create(
self.datastore_version_id,
name,
restart_required,
data_type,
max_size,
min_size)
client.delete(self.datastore_version_id, name)
config_list = client.parameters_by_version(self.datastore_version_id)
asserts.assert_true(name not in [conf.name for conf in config_list])
@test
def test_delete_config_type_fail(self):
asserts.assert_raises(
exceptions.BadRequest,
self.admin_client.mgmt_configs.delete,
self.datastore_version_id,
"test-delete-config-types")
@test
def test_invalid_config_create_type(self):
name = "testconfig_invalid_type"
restart_required = 1
data_type = "other"
max_size = None
min_size = None
asserts.assert_raises(
exceptions.BadRequest,
self.admin_client.mgmt_configs.create,
self.datastore_version_id,
name,
restart_required,
data_type,
max_size,
min_size)
@test
def test_invalid_config_create_restart_required(self):
name = "testconfig_invalid_restart_required"
restart_required = 5
data_type = "string"
max_size = None
min_size = None
asserts.assert_raises(
exceptions.BadRequest,
self.admin_client.mgmt_configs.create,
self.datastore_version_id,
name,
restart_required,
data_type,
max_size,
min_size)
@test
def test_config_parameter_was_deleted_then_recreate_updates_it(self):
name = "test-delete-and-recreate-param"
restart_required = 1
data_type = "string"
max_size = None
min_size = None
client = self.admin_client.mgmt_configs
client.create(
self.datastore_version_id,
name,
restart_required,
data_type,
max_size,
min_size)
client.delete(self.datastore_version_id, name)
client.create(
self.datastore_version_id,
name,
0,
data_type,
max_size,
min_size)
param_list = client.list_all_parameter_by_version(
self.datastore_version_id)
asserts.assert_true(name in [p.name for p in param_list])
param = client.get_any_parameter_by_version(
self.datastore_version_id, name)
asserts.assert_equal(False, param.deleted)

View File

@@ -64,6 +64,7 @@ class FakeFlavors(object):
self._add(10, 2, "m1.rd-tiny", 512)
self._add(11, 0, "eph.rd-tiny", 512, 1)
self._add(12, 20, "eph.rd-smaller", 768, 2)
# self._add(13, 20, "m1.heat", 512)
def _add(self, *args, **kwargs):
new_flavor = FakeFlavor(*args, **kwargs)

View File

@@ -17,6 +17,7 @@
import jsonschema
from testtools import TestCase
from trove.configuration.service import ConfigurationsController
from trove.extensions.mgmt.configuration import service
from trove.common import configurations
@@ -124,3 +125,80 @@ class TestConfigurationController(TestCase):
self.assertIsNotNone(schema)
validator = jsonschema.Draft4Validator(schema)
self.assertTrue(validator.is_valid(body))
class TestConfigurationsParameterController(TestCase):
def setUp(self):
super(TestConfigurationsParameterController, self).setUp()
self.controller = service.ConfigurationsParameterController()
def test_validate_create_configuration_param(self):
body = {
'configuration-parameter': {
'name': 'test',
'restart_required': 1,
'data_type': 'string',
'min_size': '0',
'max_size': '255'
}
}
schema = self.controller.get_schema('create', body)
self.assertIsNotNone(schema)
validator = jsonschema.Draft4Validator(schema)
self.assertTrue(validator.is_valid(body))
def test_validate_create_invalid_restart_required(self):
body = {
'configuration-parameter': {
'name': 'test',
'restart_required': 5,
'data_type': 'string',
'min_size': 0,
'max_size': 255
}
}
schema = self.controller.get_schema('create', body)
self.assertIsNotNone(schema)
validator = jsonschema.Draft4Validator(schema)
self.assertFalse(validator.is_valid(body))
errors = sorted(validator.iter_errors(body), key=lambda e: e.path)
error_messages = [error.message for error in errors]
self.assertIn("5 is greater than the maximum of 1", error_messages)
self.assertIn("0 is not of type 'string'", error_messages)
self.assertIn("255 is not of type 'string'", error_messages)
def test_validate_create_invalid_restart_required_2(self):
body = {
'configuration-parameter': {
'name': 'test',
'restart_required': -1,
'data_type': 'string',
'min_size': '0',
'max_size': '255'
}
}
schema = self.controller.get_schema('create', body)
self.assertIsNotNone(schema)
validator = jsonschema.Draft4Validator(schema)
self.assertFalse(validator.is_valid(body))
errors = sorted(validator.iter_errors(body), key=lambda e: e.path)
error_messages = [error.message for error in errors]
self.assertIn("-1 is less than the minimum of 0", error_messages)
def test_validate_create_invalid_restart_required_3(self):
body = {
'configuration-parameter': {
'name': 'test',
'restart_required': 'yes',
'data_type': 'string',
'min_size': '0',
'max_size': '255'
}
}
schema = self.controller.get_schema('create', body)
self.assertIsNotNone(schema)
validator = jsonschema.Draft4Validator(schema)
self.assertFalse(validator.is_valid(body))
errors = sorted(validator.iter_errors(body), key=lambda e: e.path)
error_messages = [error.message for error in errors]
self.assertIn("'yes' is not of type 'integer'", error_messages)