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 cfg
from trove.common import exception from trove.common import exception
from trove.common import utils from trove.common import utils
from trove.configuration import models as config_models
from trove.db import get_db_api from trove.db import get_db_api
from trove.openstack.common import log as logging from trove.openstack.common import log as logging
from trove.datastore import models as datastore_models from trove.datastore import models as datastore_models
@@ -80,6 +81,14 @@ class Commands(object):
self.db_api.drop_db(CONF) self.db_api.drop_db(CONF)
self.db_sync(repo_path) 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): def params_of(self, command_name):
if Commands.has(command_name): if Commands.has(command_name):
return utils.MethodInspector(getattr(self, command_name)) return utils.MethodInspector(getattr(self, command_name))
@@ -142,6 +151,19 @@ def main():
parser = subparser.add_parser( parser = subparser.add_parser(
'db_recreate', description='Drop the database and recreate it.') 'db_recreate', description='Drop the database and recreate it.')
parser.add_argument('repo_path', help=repo_path_help) 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.custom_parser('action', actions)
cfg.parse_args(sys.argv) cfg.parse_args(sys.argv)

View File

@@ -24,6 +24,31 @@ url_ref = {
'|(?:%[0-9a-fA-F][0-9a-fA-F]))+' '|(?:%[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 = { flavorref = {
'oneOf': [ 'oneOf': [
url_ref, 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 = { account = {
'create': { 'create': {
"type": "object", "type": "object",

View File

@@ -14,48 +14,9 @@
# under the License. # under the License.
import io 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 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): class MySQLConfParser(object):
"""MySQLConfParser""" """MySQLConfParser"""
def __init__(self, config): def __init__(self, config):

View File

@@ -421,7 +421,12 @@ class ConfigurationDatastoreNotMatchInstance(TroveError):
class ConfigurationParameterDeleted(TroveError): class ConfigurationParameterDeleted(TroveError):
message = _("%(parameter_name)s parameter can no longer be " 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): class ConfigurationAlreadyAttached(TroveError):

View File

@@ -13,12 +13,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
from datetime import datetime from datetime import datetime
from trove.common import cfg 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.common.exception import ModelNotFoundError
from trove.datastore import models as dstore_models from trove.datastore import models as dstore_models
from trove.db import get_db_api
from trove.db import models as dbmodels from trove.db import models as dbmodels
from trove.openstack.common import log as logging from trove.openstack.common import log as logging
from trove.openstack.common.gettextutils import _ from trove.openstack.common.gettextutils import _
@@ -66,6 +69,10 @@ class Configurations(object):
class Configuration(object): class Configuration(object):
def __init__(self, context, configuration_id):
self.context = context
self.configuration_id = configuration_id
@property @property
def instances(self): def instances(self):
return self.instances return self.instances
@@ -89,7 +96,7 @@ class Configuration(object):
"values: %s" % (cfg_id, values)) "values: %s" % (cfg_id, values))
config_items = [] config_items = []
for key, val in values.iteritems(): for key, val in values.iteritems():
config_item = ConfigurationParameter.create( config_item = DBConfigurationParameter.create(
configuration_id=cfg_id, configuration_id=cfg_id,
configuration_key=key, configuration_key=key,
configuration_value=val) configuration_value=val)
@@ -106,8 +113,8 @@ class Configuration(object):
@staticmethod @staticmethod
def remove_all_items(context, id, deleted_at): def remove_all_items(context, id, deleted_at):
items = ConfigurationParameter.find_all(configuration_id=id, items = DBConfigurationParameter.find_all(configuration_id=id,
deleted=False).all() deleted=False).all()
LOG.debug("Removing all configuration values for %s" % id) LOG.debug("Removing all configuration values for %s" % id)
for item in items: for item in items:
item.deleted = True item.deleted = True
@@ -125,59 +132,84 @@ class Configuration(object):
def load(context, id): def load(context, id):
try: try:
if context.is_admin: if context.is_admin:
config_info = DBConfiguration.find_by(id=id, return DBConfiguration.find_by(id=id, deleted=False)
deleted=False)
else: else:
config_info = DBConfiguration.find_by(id=id, return DBConfiguration.find_by(id=id,
tenant_id=context.tenant, tenant_id=context.tenant,
deleted=False) deleted=False)
except ModelNotFoundError: except ModelNotFoundError:
msg = _("Configuration group with ID %s could not be found.") % id msg = _("Configuration group with ID %s could not be found.") % id
raise ModelNotFoundError(msg) 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 @staticmethod
def load_items(context, id): def load_items(context, id):
datastore = Configuration.load_configuration_datastore_version(context, datastore_v = Configuration.load_configuration_datastore_version(
id) context,
config_items = ConfigurationParameter.find_all(configuration_id=id, id)
deleted=False).all() config_items = DBConfigurationParameter.find_all(
rules = configurations.get_validation_rules( configuration_id=id, deleted=False).all()
datastore_manager=datastore.manager)
def _get_rule(key): detail_list = DatastoreConfigurationParameters.load_parameters(
for rule in rules['configuration-parameters']: datastore_v.id)
if str(rule.get('name')) == key:
return rule
for item in config_items: for item in config_items:
rule = _get_rule(str(item.configuration_key)) rule = Configuration.find_parameter_details(
if rule.get('type') == 'boolean': str(item.configuration_key), detail_list)
if not rule:
continue
if rule.data_type == 'boolean':
item.configuration_value = bool(int(item.configuration_value)) 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) item.configuration_value = int(item.configuration_value)
else: else:
item.configuration_value = str(item.configuration_value) item.configuration_value = str(item.configuration_value)
return config_items return config_items
@staticmethod def get_configuration_overrides(self):
def get_configuration_overrides(context, configuration_id):
"""Gets the overrides dictionary to apply to an instance.""" """Gets the overrides dictionary to apply to an instance."""
overrides = {} overrides = {}
if configuration_id: if self.configuration_id:
config_items = Configuration.load_items(context, config_items = Configuration.load_items(self.context,
id=configuration_id) id=self.configuration_id)
for i in config_items: for i in config_items:
overrides[i.configuration_key] = i.configuration_value overrides[i.configuration_key] = i.configuration_value
return overrides 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 @staticmethod
def save(context, configuration, configuration_items, instances): def save(context, configuration, configuration_items, instances):
DBConfiguration.save(configuration) DBConfiguration.save(configuration)
for item in configuration_items: for item in configuration_items:
item["deleted_at"] = None item["deleted_at"] = None
ConfigurationParameter.save(item) DBConfigurationParameter.save(item)
items = Configuration.load_items(context, configuration.id) items = Configuration.load_items(context, configuration.id)
@@ -210,7 +242,7 @@ class DBConfiguration(dbmodels.DatabaseModelBase):
return datastore_version return datastore_version
class ConfigurationParameter(dbmodels.DatabaseModelBase): class DBConfigurationParameter(dbmodels.DatabaseModelBase):
_data_fields = ['configuration_id', 'configuration_key', _data_fields = ['configuration_id', 'configuration_key',
'configuration_value', 'deleted', 'configuration_value', 'deleted',
'deleted_at'] 'deleted_at']
@@ -219,8 +251,164 @@ class ConfigurationParameter(dbmodels.DatabaseModelBase):
return self.configuration_key.__hash__() 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(): def persisted_models():
return { return {
'configurations': DBConfiguration, 'configurations': DBConfiguration,
'configuration_parameters': ConfigurationParameter 'configuration_parameters': DBConfigurationParameter,
'datastore_configuration_parameters': DBDatastoreConfigurationParameters, # noqa
} }

View File

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

View File

@@ -112,10 +112,33 @@ class DetailedConfigurationView(object):
return {"configuration": configuration_dict} return {"configuration": configuration_dict}
class ConfigurationParametersView(object): class ConfigurationParameterView(object):
def __init__(self, configuration_parameters): def __init__(self, config):
self.configuration_parameters = configuration_parameters self.config = config
def data(self): 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)) Table('conductor_lastseen', meta, autoload=True))
orm.mapper(models['clusters'], orm.mapper(models['clusters'],
Table('clusters', meta, autoload=True)) Table('clusters', meta, autoload=True))
orm.mapper(models['datastore_configuration_parameters'],
Table('datastore_configuration_parameters', meta,
autoload=True))
def mapping_exists(model): 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.common import extensions
from trove.extensions.mgmt.clusters.service import ClusterController 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.instances.service import MgmtInstanceController
from trove.extensions.mgmt.host.service import HostController from trove.extensions.mgmt.host.service import HostController
from trove.extensions.mgmt.quota.service import QuotaController from trove.extensions.mgmt.quota.service import QuotaController
@@ -94,4 +95,10 @@ class Mgmt(extensions.ExtensionDescriptor):
member_actions={}) member_actions={})
resources.append(upgrade) 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 return resources

View File

@@ -23,7 +23,6 @@ from oslo.config.cfg import NoSuchOptError
from trove.common import cfg from trove.common import cfg
from trove.common import exception from trove.common import exception
from trove.common import template from trove.common import template
from trove.common.configurations import do_configs_require_restart
import trove.common.instance as tr_instance import trove.common.instance as tr_instance
from trove.common.remote import create_dns_client from trove.common.remote import create_dns_client
from trove.common.remote import create_guest_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 # generate an overrides dict to pass into the instance creation
# method # method
overrides = Configuration.get_configuration_overrides( config = Configuration(context, configuration_id)
context, configuration_id) overrides = config.get_configuration_overrides()
datastore_status = InstanceServiceStatus.create( service_status = InstanceServiceStatus.create(
instance_id=db_info.id, instance_id=db_info.id,
status=tr_instance.ServiceStatuses.NEW) status=tr_instance.ServiceStatuses.NEW)
@@ -761,7 +760,7 @@ class Instance(BuiltInstance):
overrides, slave_of_id, overrides, slave_of_id,
cluster_config) cluster_config)
return SimpleInstance(context, db_info, datastore_status, return SimpleInstance(context, db_info, service_status,
root_password) root_password)
return run_with_quotas(context.tenant, return run_with_quotas(context.tenant,
@@ -963,19 +962,16 @@ class Instance(BuiltInstance):
config_datastore_version=config_ds_v, config_datastore_version=config_ds_v,
instance_datastore_version=inst_ds_v) instance_datastore_version=inst_ds_v)
overrides = Configuration.get_configuration_overrides( config = Configuration(self.context, configuration.id)
self.context, configuration.id) LOG.debug("Config config is %s." % config)
LOG.debug("Config overrides is %s." % overrides)
self.update_overrides(overrides)
self.update_db(configuration_id=configuration.id) 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." LOG.debug("Updating or removing overrides for instance %s."
% self.id) % self.id)
need_restart = do_configs_require_restart( overrides = config.get_configuration_overrides()
overrides, datastore_manager=self.ds_version.manager) need_restart = config.does_configuration_need_restart()
LOG.debug("Config overrides has non-dynamic settings, " LOG.debug("Config overrides has non-dynamic settings, "
"requires a restart: %s." % need_restart) "requires a restart: %s." % need_restart)
if 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 template
from trove.common import utils from trove.common import utils
from trove.common.utils import try_recover 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 BackupCreationError
from trove.common.exception import GuestError from trove.common.exception import GuestError
from trove.common.exception import GuestTimeout from trove.common.exception import GuestTimeout
@@ -976,9 +975,10 @@ class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin):
"%s.") % self.id) "%s.") % self.id)
LOG.debug("overrides: %s" % overrides) LOG.debug("overrides: %s" % overrides)
LOG.debug("self.ds_version: %s" % self.ds_version.__dict__) LOG.debug("self.ds_version: %s" % self.ds_version.__dict__)
# todo(cp16net) How do we know what datastore type we have? LOG.debug("self.configuration.id: %s" % self.configuration.id)
need_restart = do_configs_require_restart( # check if the configuration requires a restart of the instance
overrides, datastore_manager=self.ds_version.manager) config = Configuration(self.context, self.configuration.id)
need_restart = config.does_configuration_need_restart()
LOG.debug("do we need a restart?: %s" % need_restart) LOG.debug("do we need a restart?: %s" % need_restart)
if need_restart: if need_restart:
status = inst_models.InstanceTasks.RESTART_REQUIRED status = inst_models.InstanceTasks.RESTART_REQUIRED

View File

@@ -196,8 +196,10 @@ class CreateConfigurations(ConfigurationsTestBase):
def test_expected_get_configuration_parameter(self): def test_expected_get_configuration_parameter(self):
# tests get on a single parameter to verify it has expected attributes # tests get on a single parameter to verify it has expected attributes
param = 'key_buffer_size' param = 'key_buffer_size'
expected_config_params = ['name', 'restart_required', 'max', expected_config_params = ['name', 'restart_required',
'min', 'type'] 'max_size', 'min_size', 'type',
'deleted', 'deleted_at',
'datastore_version_id']
instance_info.dbaas.configuration_parameters.get_parameter( instance_info.dbaas.configuration_parameters.get_parameter(
instance_info.dbaas_datastore, instance_info.dbaas_datastore,
instance_info.dbaas_datastore_version, instance_info.dbaas_datastore_version,
@@ -216,8 +218,14 @@ class CreateConfigurations(ConfigurationsTestBase):
def test_configurations_create_invalid_values(self): def test_configurations_create_invalid_values(self):
"""Test create configurations with invalid values.""" """Test create configurations with invalid values."""
values = '{"this_is_invalid": 123}' values = '{"this_is_invalid": 123}'
assert_unprocessable(instance_info.dbaas.configurations.create, try:
CONFIG_NAME, values, CONFIG_DESC) 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 @test
def test_configurations_create_invalid_value_type(self): def test_configurations_create_invalid_value_type(self):
@@ -587,7 +595,25 @@ class DeleteConfigurations(ConfigurationsTestBase):
instance_info.dbaas.configurations.delete, instance_info.dbaas.configurations.delete,
invalid_configuration_id) 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): def test_unable_delete_instance_configurations(self):
# test deleting a configuration that is assigned to # test deleting a configuration that is assigned to
# an instance is not allowed. # 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(10, 2, "m1.rd-tiny", 512)
self._add(11, 0, "eph.rd-tiny", 512, 1) self._add(11, 0, "eph.rd-tiny", 512, 1)
self._add(12, 20, "eph.rd-smaller", 768, 2) self._add(12, 20, "eph.rd-smaller", 768, 2)
# self._add(13, 20, "m1.heat", 512)
def _add(self, *args, **kwargs): def _add(self, *args, **kwargs):
new_flavor = FakeFlavor(*args, **kwargs) new_flavor = FakeFlavor(*args, **kwargs)

View File

@@ -17,6 +17,7 @@
import jsonschema import jsonschema
from testtools import TestCase from testtools import TestCase
from trove.configuration.service import ConfigurationsController from trove.configuration.service import ConfigurationsController
from trove.extensions.mgmt.configuration import service
from trove.common import configurations from trove.common import configurations
@@ -124,3 +125,80 @@ class TestConfigurationController(TestCase):
self.assertIsNotNone(schema) self.assertIsNotNone(schema)
validator = jsonschema.Draft4Validator(schema) validator = jsonschema.Draft4Validator(schema)
self.assertTrue(validator.is_valid(body)) 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)