Add support for Trove configuration groups

Added attach/detach configuration group actions to the instances table.

Added tab to launch dialog to select a configuration group to
associate with the instance.

Added "Configuration Groups" panel which displays existing
configuration groups and allows the user to create new or delete
existing configuration groups.

Added a details screen with three tabs (Values, Instances, Details).
* The Values tab displays the list of all name/value parameters
associated with a configuration group along with actions to add new
parameters, delete existing parameters, discard changes and
apply changes.  Values can be modified in the table directly.
* The Instances tab displays the instances that the configuration
group is associated with along with actions to detach the
configuration group from the instance.
* The Details tab displays additional information on the
configuration group.

Added a configuration defaults tab to the database instance details.
Use a cache for storing changes to the configuration group.

Change-Id: Idbd6775342968c1659ca1e9b02dcb697750530b6
Co-Authored-By: Andrew Bramley <andrew@tesora.com>
Co-Authored-By: Duk Loi <duk@tesora.com>
Implements: blueprint trove-configuration-group-support
This commit is contained in:
Matt Van Dijk 2016-01-14 12:48:26 -05:00 committed by Ali Asgar Adil
parent 77fcb993ae
commit 949522f875
32 changed files with 2039 additions and 22 deletions

View File

@ -0,0 +1,7 @@
---
features:
- Support configuration groups in the dashboard. This
includes creating and deleting groups; adding,
editing and removing parameters; attaching and
detaching groups to running instances; and specifying
a group during instance creation.

View File

@ -135,7 +135,7 @@ def instance_create(request, name, volume, flavor, databases=None,
users=None, restore_point=None, nics=None, users=None, restore_point=None, nics=None,
datastore=None, datastore_version=None, datastore=None, datastore_version=None,
replica_of=None, replica_count=None, replica_of=None, replica_count=None,
volume_type=None): volume_type=None, configuration=None):
# TODO(dklyle): adding conditional to support trove without volume # TODO(dklyle): adding conditional to support trove without volume
# support for now until API supports checking for volume support # support for now until API supports checking for volume support
if volume > 0: if volume > 0:
@ -155,7 +155,8 @@ def instance_create(request, name, volume, flavor, databases=None,
datastore=datastore, datastore=datastore,
datastore_version=datastore_version, datastore_version=datastore_version,
replica_of=replica_of, replica_of=replica_of,
replica_count=replica_count) replica_count=replica_count,
configuration=configuration)
def instance_resize_volume(request, instance_id, size): def instance_resize_volume(request, instance_id, size):
@ -189,6 +190,15 @@ def eject_replica_source(request, instance_id):
return troveclient(request).instances.eject_replica_source(instance_id) return troveclient(request).instances.eject_replica_source(instance_id)
def instance_attach_configuration(request, instance_id, configuration):
return troveclient(request).instances.modify(instance_id,
configuration=configuration)
def instance_detach_configuration(request, instance_id):
return troveclient(request).instances.modify(instance_id)
def database_list(request, instance_id): def database_list(request, instance_id):
return troveclient(request).databases.list(instance_id) return troveclient(request).databases.list(instance_id)
@ -338,3 +348,45 @@ def log_tail(request, instance_id, log_name, publish, lines, swift=None):
publish=publish, publish=publish,
lines=lines, lines=lines,
swift=swift) swift=swift)
def configuration_list(request):
return troveclient(request).configurations.list()
def configuration_get(request, group_id):
return troveclient(request).configurations.get(group_id)
def configuration_parameters_list(request, datastore, datastore_version):
return troveclient(request).configuration_parameters.parameters(
datastore, datastore_version)
def configuration_create(request,
name,
values,
description=None,
datastore=None,
datastore_version=None):
return troveclient(request).configurations.create(name,
values,
description,
datastore,
datastore_version)
def configuration_delete(request, group_id):
return troveclient(request).configurations.delete(group_id)
def configuration_instances(request, group_id):
return troveclient(request).configurations.instances(group_id)
def configuration_update(request, group_id, values):
return troveclient(request).configurations.update(group_id, values)
def configuration_default(request, instance_id):
return troveclient(request).instances.configuration(instance_id)

View File

@ -0,0 +1,193 @@
# Copyright 2015 Tesora Inc.
#
# 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 django.core import cache
from django.utils.translation import ugettext_lazy as _
from trove_dashboard import api
from oslo_serialization import jsonutils
def get(request, configuration_group_id):
if not has_config(configuration_group_id):
manager = ConfigParamManager(configuration_group_id)
manager.configuration_get(request)
cache.cache.set(configuration_group_id, manager)
return cache.cache.get(configuration_group_id)
def delete(configuration_group_id):
cache.cache.delete(configuration_group_id)
def update(configuration_group_id, manager):
cache.cache.set(configuration_group_id, manager)
def has_config(configuration_group_id):
if cache.cache.get(configuration_group_id):
return True
else:
return False
def dict_has_changes(original, other):
if len(other) != len(original):
return True
diffs = (set(original.keys()) - set(other.keys()))
if len(diffs).__nonzero__():
return True
for key in original:
if original[key] != other[key]:
return True
return False
class ConfigParamManager(object):
original_configuration_values = None
configuration = None
def __init__(self, configuration_id):
self.configuration_id = configuration_id
def configuration_get(self, request):
if self.configuration is None:
configuration = api.trove.configuration_get(
request, self.configuration_id)
# need to make one that can be cached
self.configuration = Configuration(
self.configuration_id,
configuration.name,
configuration.description,
configuration.datastore_name,
configuration.datastore_version_name,
configuration.created,
configuration.updated)
self.configuration.values = dict.copy(configuration.values)
self.original_configuration_values = dict.copy(
self.configuration.values)
return self.get_configuration()
def get_configuration(self):
return self.configuration
def create_config_value(self, name, value):
return ConfigParam(self.configuration_id, name, value)
def get_param(self, name):
for key_name in self.configuration.values:
if key_name == name:
return self.create_config_value(
key_name, self.configuration.values[key_name])
return None
def update_param(self, name, value):
self.configuration.values[name] = value
update(self.configuration_id, self)
def delete_param(self, name):
del self.configuration.values[name]
update(self.configuration_id, self)
def add_param(self, name, value):
self.update_param(name, value)
def to_json(self):
return jsonutils.dumps(self.configuration.values)
def has_changes(self):
return dict_has_changes(self.original_configuration_values,
self.configuration.values)
class ConfigParam(object):
def __init__(self, configuration_id, name, value):
self.configuration_id = configuration_id
self.name = name
self.value = value
class Configuration(object):
def __init__(self, id, name, description, datastore_name,
datastore_version_name, created, updated):
self.id = id
self.name = name
self.description = description
self.datastore_name = datastore_name
self.datastore_version_name = datastore_version_name
self.created = created
self.updated = updated
def validate_config_param_value(config_param, value):
if (config_param.type in (u"boolean", u"float", u"integer", u"long")):
if config_param.type == u"boolean":
if (value.lower() not in ("true", "false")):
return _('Value must be "true" or "false".')
else:
try:
float(value)
except ValueError:
return _('Value must be a number.')
min = getattr(config_param, "min", None)
max = getattr(config_param, "max", None)
try:
val = adjust_type(config_param.type, value)
except ValueError:
return (_('Value must be of type %s.') % config_param.type)
if min is not None and max is not None:
if val < min or val > max:
return (_('Value must be a number '
'between %(min)s and %(max)s.') %
{"min": min, "max": max})
elif min is not None:
if val < min:
return _('Value must be a number greater '
'than or equal to %s.') % min
elif max is not None:
if val > max:
return _('Value must be a number '
'less than or equal to %s.') % max
return None
def find_parameter(name, config_params):
for param in config_params:
if param.name == name:
return param
return None
def adjust_type(data_type, value):
if not value:
return value
if data_type == "float":
new_value = float(value)
elif data_type == "long":
new_value = long(value)
elif data_type == "integer":
new_value = int(value)
else:
new_value = value
return new_value

View File

@ -0,0 +1,190 @@
# Copyright 2015 Tesora Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from horizon.utils import memoized
from trove_dashboard import api
from trove_dashboard.content.database_configurations \
import config_param_manager
LOG = logging.getLogger(__name__)
class CreateConfigurationForm(forms.SelfHandlingForm):
name = forms.CharField(label=_("Name"))
description = forms.CharField(label=_("Description"), required=False)
datastore = forms.ChoiceField(
label=_("Datastore"),
help_text=_("Type and version of datastore."))
def __init__(self, request, *args, **kwargs):
super(CreateConfigurationForm, self).__init__(request, *args, **kwargs)
choices = self.get_datastore_choices(request)
self.fields['datastore'].choices = choices
@memoized.memoized_method
def datastores(self, request):
try:
return api.trove.datastore_list(request)
except Exception:
LOG.exception("Exception while obtaining datastores list")
redirect = reverse('horizon:project:database_configurations:index')
exceptions.handle(request,
_('Unable to obtain datastores.'),
redirect=redirect)
@memoized.memoized_method
def datastore_versions(self, request, datastore):
try:
return api.trove.datastore_version_list(request, datastore)
except Exception:
LOG.exception("Exception while obtaining datastore version list")
redirect = reverse('horizon:project:database_configurations:index')
exceptions.handle(request,
_('Unable to obtain datastore versions.'),
redirect=redirect)
def get_datastore_choices(self, request):
choices = ()
set_initial = False
datastores = self.datastores(request)
if datastores is not None:
num_datastores_with_one_version = 0
for ds in datastores:
versions = self.datastore_versions(request, ds.name)
if not set_initial:
if len(versions) >= 2:
set_initial = True
elif len(versions) == 1:
num_datastores_with_one_version += 1
if num_datastores_with_one_version > 1:
set_initial = True
if len(versions) > 0:
# only add to choices if datastore has at least one version
version_choices = ()
for v in versions:
version_choices = (version_choices +
((ds.name + ',' + v.name, v.name),))
datastore_choices = (ds.name, version_choices)
choices = choices + (datastore_choices,)
if set_initial:
# prepend choice to force user to choose
initial = ('', _('Select datastore type and version'))
choices = (initial,) + choices
return choices
def handle(self, request, data):
try:
datastore = data['datastore'].split(',')[0]
datastore_version = data['datastore'].split(',')[1]
api.trove.configuration_create(request, data['name'], "{}",
description=data['description'],
datastore=datastore,
datastore_version=datastore_version)
messages.success(request, _('Created configuration group'))
except Exception as e:
redirect = reverse("horizon:project:database_configurations:index")
exceptions.handle(request, _('Unable to create configuration '
'group. %s')
% e.message, redirect=redirect)
return True
class AddParameterForm(forms.SelfHandlingForm):
name = forms.ChoiceField(label=_("Name"))
value = forms.CharField(label=_("Value"))
def __init__(self, request, *args, **kwargs):
super(AddParameterForm, self).__init__(request, *args, **kwargs)
configuration = (config_param_manager
.get(request, kwargs["initial"]["configuration_id"])
.get_configuration())
self.fields['name'].choices = self.get_parameters(
request, configuration.datastore_name,
configuration.datastore_version_name)
self.fields['value'].parameters = self.parameters
@memoized.memoized_method
def parameters(self, request, datastore, datastore_version):
try:
return api.trove.configuration_parameters_list(
request, datastore, datastore_version)
except Exception:
LOG.exception(
"Exception while obtaining configuration parameter list")
redirect = reverse('horizon:project:database_configurations:index')
exceptions.handle(request,
_('Unable to obtain list of parameters.'),
redirect=redirect)
def get_parameters(self, request, datastore, datastore_version):
try:
choices = []
self.parameters = self.parameters(
request, datastore, datastore_version)
for parameter in self.parameters:
choices.append((parameter.name, parameter.name))
return sorted(choices)
except Exception:
LOG.exception(
"Exception while obtaining configuration parameters list")
redirect = reverse('horizon:project:database_configurations:index')
exceptions.handle(request,
_('Unable to create list of parameters.'),
redirect=redirect)
def clean(self):
cleaned_data = super(AddParameterForm, self).clean()
if "value" in cleaned_data:
config_param = config_param_manager.find_parameter(
cleaned_data["name"], self.parameters)
if config_param:
error_msg = config_param_manager.validate_config_param_value(
config_param, cleaned_data["value"])
if error_msg:
self._errors['value'] = self.error_class([error_msg])
return cleaned_data
def handle(self, request, data):
try:
(config_param_manager
.get(request, self.initial["configuration_id"])
.add_param(data["name"],
config_param_manager.adjust_type(
config_param_manager.find_parameter(
data["name"], self.parameters).type,
data["value"])))
messages.success(request, _('Successfully added parameter'))
except Exception as e:
redirect = reverse("horizon:project:database_configurations:index")
exceptions.handle(request, _('Unable to add new parameter: %s')
% e.message, redirect=redirect)
return True

View File

@ -0,0 +1,27 @@
# Copyright 2015 Tesora Inc.
#
# 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 django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.dashboards.project import dashboard
class Configurations(horizon.Panel):
name = _("Configuration Groups")
slug = 'database_configurations'
permissions = ('openstack.services.database',)
dashboard.Project.register(Configurations)

View File

@ -0,0 +1,256 @@
# Copyright 2015 Tesora Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import types
from django.core import exceptions as core_exceptions
from django.core import urlresolvers
from django import shortcuts
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import forms
from horizon import messages
from horizon import tables
from horizon.utils import memoized
from trove_dashboard import api
from trove_dashboard.content.database_configurations \
import config_param_manager
LOG = logging.getLogger(__name__)
class CreateConfiguration(tables.LinkAction):
name = "create_configuration"
verbose_name = _("Create Configuration Group")
url = "horizon:project:database_configurations:create"
classes = ('ajax-modal', )
icon = "plus"
class DeleteConfiguration(tables.DeleteAction):
data_type_singular = _("Configuration Group")
data_type_plural = _("Configuration Groups")
def delete(self, request, obj_id):
api.trove.configuration_delete(request, obj_id)
class ConfigurationsTable(tables.DataTable):
name = tables.Column(
'name',
verbose_name=_('Configuration Group Name'),
link="horizon:project:database_configurations:detail")
description = tables.Column(
lambda obj: getattr(obj, 'description', None),
verbose_name=_('Description'))
datastore = tables.Column(
'datastore_name',
verbose_name=_('Datastore'))
datastore_version = tables.Column(
'datastore_version_name',
verbose_name=_('Datastore Version'))
class Meta(object):
name = "configurations"
verbose_name = _("Configuration Groups")
table_actions = [CreateConfiguration, DeleteConfiguration]
row_actions = [DeleteConfiguration]
class AddParameter(tables.LinkAction):
name = "add_parameter"
verbose_name = _("Add Parameter")
url = "horizon:project:database_configurations:add"
classes = ('ajax-modal', )
icon = "plus"
def get_link_url(self, datum=None):
configuration_id = self.table.kwargs['configuration_id']
return urlresolvers.reverse(self.url, args=[configuration_id])
class ApplyChanges(tables.Action):
name = "apply_changes"
verbose_name = _("Apply Changes")
verbose_name_plural = _("Apply Changes")
icon = "pencil"
def __init__(self, **kwargs):
super(ApplyChanges, self).__init__(**kwargs)
self.requires_input = False
def handle(self, table, request, obj_ids):
configuration_id = table.kwargs['configuration_id']
if config_param_manager.get(request, configuration_id).has_changes():
try:
api.trove.configuration_update(
request, configuration_id,
config_param_manager.get(
request, configuration_id).to_json())
messages.success(request, _('Applied changes to server'))
except Exception:
messages.error(request, _('Error applying changes'))
finally:
config_param_manager.delete(configuration_id)
return shortcuts.redirect(request.build_absolute_uri())
class DiscardChanges(tables.Action):
name = "discard_changes"
verbose_name = _("Discard Changes")
verbose_name_plural = _("Discard Changes")
def __init__(self, **kwargs):
super(DiscardChanges, self).__init__(**kwargs)
self.requires_input = False
def handle(self, table, request, obj_ids):
configuration_id = table.kwargs['configuration_id']
if config_param_manager.get(request, configuration_id).has_changes():
try:
config_param_manager.delete(configuration_id)
messages.success(request, _('Reset Parameters'))
except Exception as ex:
messages.error(
request,
_('Error resetting parameters: %s') % ex.message)
return shortcuts.redirect(request.build_absolute_uri())
class DeleteParameter(tables.DeleteAction):
data_type_singular = _("Parameter")
data_type_plural = _("Parameters")
def delete(self, request, obj_ids):
configuration_id = self.table.kwargs['configuration_id']
(config_param_manager
.get(request, configuration_id)
.delete_param(obj_ids))
class UpdateRow(tables.Row):
def get_data(self, request, name):
return config_param_manager.get(
request, self.table.kwargs["configuration_id"]).get_param(name)
class UpdateCell(tables.UpdateAction):
def update_cell(self, request, datum, name,
cell_name, new_cell_value):
config_param = datum
config = config_param_manager.get(request,
config_param.configuration_id)
validation_param = config_param_manager.find_parameter(
name,
self.parameters(request,
config.configuration.datastore_name,
config.configuration.datastore_version_name))
if validation_param:
error_msg = config_param_manager.validate_config_param_value(
validation_param, new_cell_value)
if error_msg:
raise core_exceptions.ValidationError(error_msg)
if isinstance(config_param.value, types.IntType):
value = int(new_cell_value)
elif isinstance(config_param.value, types.LongType):
value = long(new_cell_value)
else:
value = new_cell_value
setattr(datum, cell_name, value)
(config_param_manager
.get(request, config_param.configuration_id)
.update_param(name, value))
return True
@memoized.memoized_method
def parameters(self, request, datastore, datastore_version):
return api.trove.configuration_parameters_list(
request, datastore, datastore_version)
def _adjust_type(self, data_type, value):
if not value:
return value
if data_type == "float":
new_value = float(value)
elif data_type == "long":
new_value = long(value)
elif data_type == "integer":
new_value = int(value)
else:
new_value = value
return new_value
class ValuesTable(tables.DataTable):
name = tables.Column("name", verbose_name=_("Name"))
value = tables.Column("value", verbose_name=_("Value"),
form_field=forms.CharField(required=False),
update_action=UpdateCell)
class Meta(object):
name = "values"
verbose_name = _("Configuration Group Values")
table_actions = [ApplyChanges, DiscardChanges,
AddParameter, DeleteParameter]
row_class = UpdateRow
row_actions = [DeleteParameter]
def get_object_id(self, datum):
return datum.name
class DetachConfiguration(tables.BatchAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Detach Configuration Group",
u"Detach Configuration Groups",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Detached Configuration Group",
u"Detached Configuration Groups",
count
)
name = "detach_configuration"
classes = ('btn-danger', 'btn-detach-config')
def action(self, request, obj_id):
api.trove.instance_detach_configuration(request, obj_id)
class InstancesTable(tables.DataTable):
name = tables.Column("name",
link="horizon:project:databases:detail",
verbose_name=_("Name"))
class Meta(object):
name = "instances"
verbose_name = _("Configuration Group Instances")
multi_select = False
row_actions = [DetachConfiguration]

View File

@ -0,0 +1,73 @@
# Copyright 2015 Tesora Inc.
#
# 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 django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from trove_dashboard import api
from trove_dashboard.content.database_configurations \
import config_param_manager
from trove_dashboard.content.database_configurations \
import tables
class DetailsTab(tabs.Tab):
name = _("Details")
slug = "details_tab"
template_name = "project/database_configurations/_detail_overview.html"
def get_context_data(self, request):
return {"configuration": self.tab_group.kwargs['configuration']}
class ValuesTab(tabs.TableTab):
table_classes = [tables.ValuesTable]
name = _("Values")
slug = "values_tab"
template_name = "project/database_configurations/detail_param.html"
def get_values_data(self):
values_data = []
manager = config_param_manager.get(
self.request, self.tab_group.kwargs['configuration_id'])
for k, v in manager.get_configuration().values.items():
manager.add_param(k, v)
values_data.append(manager.create_config_value(k, v))
return values_data
class InstancesTab(tabs.TableTab):
table_classes = [tables.InstancesTable]
name = _("Instances")
slug = "instances_tab"
template_name = "horizon/common/_detail_table.html"
def get_instances_data(self):
configuration = self.tab_group.kwargs['configuration']
try:
data = api.trove.configuration_instances(self.request,
configuration.id)
except Exception:
msg = _('Unable to get configuration data.')
exceptions.handle(self.request, msg)
data = []
return data
class ConfigurationDetailTabs(tabs.TabGroup):
slug = "configuration_details"
tabs = (ValuesTab, InstancesTab, DetailsTab)
sticky = True

View File

@ -0,0 +1,6 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<p>{% trans "Select a parameter and provide a value for the configuration parameter." %}</p>
{% endblock %}

View File

@ -0,0 +1,9 @@
{% extends "horizon/common/_modal_form.html" %}
{% block modal-body %}
<div class="center">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% load i18n sizeformat %}
<h3>{% trans "Configuration Group Overview" %}</h3>
<div class="status row-fluid detail">
<h4>{% trans "Info" %}</h4>
<hr class="header_rule">
<dl>
<dt>{% trans "Name" %}</dt>
<dd>{{ configuration.name }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ configuration.description|linebreaksbr }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ configuration.id }}</dd>
<dt>{% trans "Datastore" %}</dt>
<dd>{{ configuration.datastore_name }}</dd>
<dt>{% trans "Datastore Version" %}</dt>
<dd>{{ configuration.datastore_version_name }}</dd>
<dt>{% trans "Created" %}</dt>
<dd>{{ configuration.created|parse_isotime }}</dd>
<dt>{% trans "Updated" %}</dt>
<dd>{{ configuration.updated|parse_isotime }}</dd>
</dl>
</div>

View File

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block main %}
{% include "project/database_configurations/_add_parameter.html" %}
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block main %}
{% include "project/database_configurations/_create.html" %}
{% endblock %}

View File

@ -0,0 +1,8 @@
{% load i18n %}
<br>
<div class="help_text">
{% trans "Add parameters to the configuration group. When all the parameters are added click 'Apply Changes' to persist changes." %}
</div>
<br>
{{ table.render }}

View File

@ -0,0 +1,10 @@
{% extends 'base.html' %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends 'base.html' %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,540 @@
# Copyright 2015 Tesora Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import logging
from django.core.urlresolvers import reverse
from django import http
from mox3.mox import IsA # noqa
from trove_dashboard import api
from trove_dashboard.content.database_configurations \
import config_param_manager
from trove_dashboard.test import helpers as test
INDEX_URL = reverse('horizon:project:database_configurations:index')
CREATE_URL = reverse('horizon:project:database_configurations:create')
DETAIL_URL = 'horizon:project:database_configurations:detail'
ADD_URL = 'horizon:project:database_configurations:add'
class DatabaseConfigurationsTests(test.TestCase):
@test.create_stubs({api.trove: ('configuration_list',)})
def test_index(self):
api.trove.configuration_list(IsA(http.HttpRequest)) \
.AndReturn(self.database_configurations.list())
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res,
'project/database_configurations/index.html')
@test.create_stubs({api.trove: ('configuration_list',)})
def test_index_exception(self):
api.trove.configuration_list(IsA(http.HttpRequest)) \
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(
res, 'project/database_configurations/index.html')
self.assertEqual(res.status_code, 200)
self.assertMessageCount(res, error=1)
@test.create_stubs({
api.trove: ('datastore_list', 'datastore_version_list')})
def test_create_configuration(self):
api.trove.datastore_list(IsA(http.HttpRequest)) \
.AndReturn(self.datastores.list())
api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str)) \
.MultipleTimes().AndReturn(self.datastore_versions.list())
self.mox.ReplayAll()
res = self.client.get(CREATE_URL)
self.assertTemplateUsed(res,
'project/database_configurations/create.html')
@test.create_stubs({api.trove: ('datastore_list',)})
def test_create_configuration_exception_on_datastore(self):
api.trove.datastore_list(IsA(http.HttpRequest)) \
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
toSuppress = ["trove_dashboard.content."
"database_configurations.forms", ]
# Suppress expected log messages in the test output
loggers = []
for cls in toSuppress:
logger = logging.getLogger(cls)
loggers.append((logger, logger.getEffectiveLevel()))
logger.setLevel(logging.CRITICAL)
try:
res = self.client.get(CREATE_URL)
self.assertEqual(res.status_code, 302)
finally:
# Restore the previous log levels
for (log, level) in loggers:
log.setLevel(level)
@test.create_stubs({
api.trove: ('datastore_list', 'datastore_version_list',
'configuration_create')})
def _test_create_test_configuration(
self, config_description=u''):
api.trove.datastore_list(IsA(http.HttpRequest)) \
.AndReturn(self.datastores.list())
api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str)) \
.MultipleTimes().AndReturn(self.datastore_versions.list())
name = u'config1'
values = "{}"
ds = self._get_test_datastore('mysql')
dsv = self._get_test_datastore_version(ds.id, '5.5')
config_datastore = ds.name
config_datastore_version = dsv.name
api.trove.configuration_create(
IsA(http.HttpRequest),
name,
values,
description=config_description,
datastore=config_datastore,
datastore_version=config_datastore_version) \
.AndReturn(self.database_configurations.first())
self.mox.ReplayAll()
post = {
'method': 'CreateConfigurationForm',
'name': name,
'description': config_description,
'datastore': (config_datastore + ',' + config_datastore_version)}
res = self.client.post(CREATE_URL, post)
self.assertNoFormErrors(res)
self.assertMessageCount(success=1)
def test_create_test_configuration(self):
self._test_create_test_configuration(u'description of config1')
def test_create_test_configuration_with_no_description(self):
self._test_create_test_configuration()
@test.create_stubs({
api.trove: ('datastore_list', 'datastore_version_list',
'configuration_create')})
def test_create_test_configuration_exception(self):
api.trove.datastore_list(IsA(http.HttpRequest)) \
.AndReturn(self.datastores.list())
api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str)) \
.MultipleTimes().AndReturn(self.datastore_versions.list())
name = u'config1'
values = "{}"
config_description = u'description of config1'
ds = self._get_test_datastore('mysql')
dsv = self._get_test_datastore_version(ds.id, '5.5')
config_datastore = ds.name
config_datastore_version = dsv.name
api.trove.configuration_create(
IsA(http.HttpRequest),
name,
values,
description=config_description,
datastore=config_datastore,
datastore_version=config_datastore_version) \
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
post = {'method': 'CreateConfigurationForm',
'name': name,
'description': config_description,
'datastore': config_datastore + ',' + config_datastore_version}
res = self.client.post(CREATE_URL, post)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.trove: ('configuration_get',
'configuration_instances',)})
def test_details_tab(self):
config = self.database_configurations.first()
api.trove.configuration_get(IsA(http.HttpRequest),
config.id) \
.AndReturn(config)
self.mox.ReplayAll()
details_url = self._get_url_with_arg(DETAIL_URL, config.id)
url = details_url + '?tab=configuration_details__details'
res = self.client.get(url)
self.assertTemplateUsed(res,
'project/database_configurations/details.html')
@test.create_stubs({api.trove: ('configuration_get',)})
def test_overview_tab_exception(self):
config = self.database_configurations.first()
api.trove.configuration_get(IsA(http.HttpRequest),
config.id) \
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
details_url = self._get_url_with_arg(DETAIL_URL, config.id)
url = details_url + '?tab=configuration_details__overview'
res = self.client.get(url)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({
api.trove: ('configuration_get', 'configuration_parameters_list',),
config_param_manager.ConfigParamManager:
('get_configuration', 'configuration_get',)})
def test_add_parameter(self):
config = config_param_manager.ConfigParamManager.get_configuration() \
.AndReturn(self.database_configurations.first())
config_param_manager.ConfigParamManager \
.configuration_get(IsA(http.HttpRequest)) \
.AndReturn(config)
ds = self._get_test_datastore('mysql')
dsv = self._get_test_datastore_version(ds.id, '5.5')
api.trove.configuration_parameters_list(
IsA(http.HttpRequest),
ds.name,
dsv.name) \
.AndReturn(self.configuration_parameters.list())
self.mox.ReplayAll()
res = self.client.get(self._get_url_with_arg(ADD_URL, 'id'))
self.assertTemplateUsed(
res, 'project/database_configurations/add_parameter.html')
@test.create_stubs({
api.trove: ('configuration_get', 'configuration_parameters_list',),
config_param_manager.ConfigParamManager:
('get_configuration', 'configuration_get',)})
def test_add_parameter_exception_on_parameters(self):
try:
config = (config_param_manager.ConfigParamManager
.get_configuration()
.AndReturn(self.database_configurations.first()))
config_param_manager.ConfigParamManager \
.configuration_get(IsA(http.HttpRequest)) \
.AndReturn(config)
ds = self._get_test_datastore('mysql')
dsv = self._get_test_datastore_version(ds.id, '5.5')
api.trove.configuration_parameters_list(
IsA(http.HttpRequest),
ds.name,
dsv.name) \
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
toSuppress = ["trove_dashboard.content."
"database_configurations.forms", ]
# Suppress expected log messages in the test output
loggers = []
for cls in toSuppress:
logger = logging.getLogger(cls)
loggers.append((logger, logger.getEffectiveLevel()))
logger.setLevel(logging.CRITICAL)
try:
res = self.client.get(
self._get_url_with_arg(ADD_URL, config.id))
self.assertEqual(res.status_code, 302)
finally:
# Restore the previous log levels
for (log, level) in loggers:
log.setLevel(level)
finally:
config_param_manager.delete(config.id)
@test.create_stubs({
api.trove: ('configuration_get', 'configuration_parameters_list',),
config_param_manager.ConfigParamManager:
('get_configuration', 'add_param', 'configuration_get',)})
def test_add_new_parameter(self):
config = (config_param_manager.ConfigParamManager
.get_configuration()
.AndReturn(self.database_configurations.first()))
try:
config_param_manager.ConfigParamManager \
.configuration_get(IsA(http.HttpRequest)) \
.AndReturn(config)
ds = self._get_test_datastore('mysql')
dsv = self._get_test_datastore_version(ds.id, '5.5')
api.trove.configuration_parameters_list(
IsA(http.HttpRequest),
ds.name,
dsv.name) \
.AndReturn(self.configuration_parameters.list())
name = self.configuration_parameters.first().name
value = 1
config_param_manager.ConfigParamManager.add_param(name, value) \
.AndReturn(value)
self.mox.ReplayAll()
post = {
'method': 'AddParameterForm',
'name': name,
'value': value}
res = self.client.post(self._get_url_with_arg(ADD_URL, config.id),
post)
self.assertNoFormErrors(res)
self.assertMessageCount(success=1)
finally:
config_param_manager.delete(config.id)
@test.create_stubs({
api.trove: ('configuration_get', 'configuration_parameters_list',),
config_param_manager: ('get',)})
def test_add_parameter_invalid_value(self):
try:
config = self.database_configurations.first()
# setup the configuration parameter manager
config_param_mgr = config_param_manager.ConfigParamManager(
config.id)
config_param_mgr.configuration = config
config_param_mgr.original_configuration_values = \
dict.copy(config.values)
config_param_manager.get(IsA(http.HttpRequest), config.id) \
.MultipleTimes().AndReturn(config_param_mgr)
ds = self._get_test_datastore('mysql')
dsv = self._get_test_datastore_version(ds.id, '5.5')
api.trove.configuration_parameters_list(
IsA(http.HttpRequest),
ds.name,
dsv.name) \
.AndReturn(self.configuration_parameters.list())
name = self.configuration_parameters.first().name
value = "non-numeric"
self.mox.ReplayAll()
post = {
'method': 'AddParameterForm',
'name': name,
'value': value}
res = self.client.post(self._get_url_with_arg(ADD_URL, config.id),
post)
self.assertFormError(res, "form", 'value',
['Value must be a number.'])
finally:
config_param_manager.delete(config.id)
@test.create_stubs({api.trove: ('configuration_get',
'configuration_instances',)})
def test_values_tab_discard_action(self):
config = self.database_configurations.first()
api.trove.configuration_get(IsA(http.HttpRequest), config.id) \
.MultipleTimes().AndReturn(config)
self.mox.ReplayAll()
details_url = self._get_url_with_arg(DETAIL_URL, config.id)
url = details_url + '?tab=configuration_details__value'
self._test_create_altered_config_params(config, url)
# get the state of the configuration before discard action
changed_configuration_values = \
dict.copy(config_param_manager.get(self.request, config.id)
.get_configuration().values)
res = self.client.post(url, {'action': u"values__discard_changes"})
self.assertRedirectsNoFollow(res, url)
# get the state of the configuration after discard action
restored_configuration_values = \
dict.copy(config_param_manager.get(self.request, config.id)
.get_configuration().values)
self.assertTrue(config_param_manager.dict_has_changes(
changed_configuration_values, restored_configuration_values))
@test.create_stubs({api.trove: ('configuration_instances',
'configuration_update',),
config_param_manager: ('get',)})
def test_values_tab_apply_action(self):
config = copy.deepcopy(self.database_configurations.first())
# setup the configuration parameter manager
config_param_mgr = config_param_manager.ConfigParamManager(
config.id)
config_param_mgr.configuration = config
config_param_mgr.original_configuration_values = \
dict.copy(config.values)
config_param_manager.get(IsA(http.HttpRequest), config.id) \
.MultipleTimes().AndReturn(config_param_mgr)
api.trove.configuration_update(
IsA(http.HttpRequest),
config.id,
config_param_mgr.to_json()) \
.AndReturn(None)
self.mox.ReplayAll()
details_url = self._get_url_with_arg(DETAIL_URL, config.id)
url = details_url + '?tab=configuration_details__value'
self._test_create_altered_config_params(config, url)
# apply changes
res = self.client.post(url, {'action': u"values__apply_changes"})
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({api.trove: ('configuration_instances',
'configuration_update',),
config_param_manager: ('get',)})
def test_values_tab_apply_action_exception(self):
config = copy.deepcopy(self.database_configurations.first())
# setup the configuration parameter manager
config_param_mgr = config_param_manager.ConfigParamManager(
config.id)
config_param_mgr.configuration = config
config_param_mgr.original_configuration_values = \
dict.copy(config.values)
config_param_manager.get(IsA(http.HttpRequest), config.id) \
.MultipleTimes().AndReturn(config_param_mgr)
api.trove.configuration_update(
IsA(http.HttpRequest),
config.id,
config_param_mgr.to_json())\
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
details_url = self._get_url_with_arg(DETAIL_URL, config.id)
url = details_url + '?tab=configuration_details__value'
self._test_create_altered_config_params(config, url)
# apply changes
res = self.client.post(url, {'action': u"values__apply_changes"})
self.assertRedirectsNoFollow(res, url)
self.assertEqual(res.status_code, 302)
def _test_create_altered_config_params(self, config, url):
# determine the number of configuration group parameters in the list
res = self.client.get(url)
table_data = res.context['table'].data
number_params = len(table_data)
config_param = table_data[0]
# delete the first parameter
action_string = u"values__delete__%s" % config_param.name
form_data = {'action': action_string}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
# verify the test number of parameters is reduced by 1
res = self.client.get(url)
table_data = res.context['table'].data
new_number_params = len(table_data)
self.assertEqual((number_params - 1), new_number_params)
@test.create_stubs({api.trove: ('configuration_instances',),
config_param_manager: ('get',)})
def test_instances_tab(self):
try:
config = self.database_configurations.first()
# setup the configuration parameter manager
config_param_mgr = config_param_manager.ConfigParamManager(
config.id)
config_param_mgr.configuration = config
config_param_mgr.original_configuration_values = \
dict.copy(config.values)
config_param_manager.get(IsA(http.HttpRequest), config.id) \
.MultipleTimes().AndReturn(config_param_mgr)
api.trove.configuration_instances(IsA(http.HttpRequest),
config.id)\
.AndReturn(self.configuration_instances.list())
self.mox.ReplayAll()
details_url = self._get_url_with_arg(DETAIL_URL, config.id)
url = details_url + '?tab=configuration_details__instance'
res = self.client.get(url)
table_data = res.context['instances_table'].data
self.assertItemsEqual(
self.configuration_instances.list(), table_data)
self.assertTemplateUsed(
res, 'project/database_configurations/details.html')
finally:
config_param_manager.delete(config.id)
@test.create_stubs({api.trove: ('configuration_instances',),
config_param_manager: ('get',)})
def test_instances_tab_exception(self):
try:
config = self.database_configurations.first()
# setup the configuration parameter manager
config_param_mgr = config_param_manager.ConfigParamManager(
config.id)
config_param_mgr.configuration = config
config_param_mgr.original_configuration_values = \
dict.copy(config.values)
config_param_manager.get(IsA(http.HttpRequest), config.id) \
.MultipleTimes().AndReturn(config_param_mgr)
api.trove.configuration_instances(IsA(http.HttpRequest),
config.id) \
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
details_url = self._get_url_with_arg(DETAIL_URL, config.id)
url = details_url + '?tab=configuration_details__instance'
res = self.client.get(url)
table_data = res.context['instances_table'].data
self.assertNotEqual(len(self.configuration_instances.list()),
len(table_data))
self.assertTemplateUsed(
res, 'project/database_configurations/details.html')
finally:
config_param_manager.delete(config.id)
def _get_url_with_arg(self, url, arg):
return reverse(url, args=[arg])
def _get_test_datastore(self, datastore_name):
for ds in self.datastores.list():
if ds.name == datastore_name:
return ds
return None
def _get_test_datastore_version(self, datastore_id,
datastore_version_name):
for dsv in self.datastore_versions.list():
if (dsv.datastore == datastore_id and
dsv.name == datastore_version_name):
return dsv
return None

View File

@ -0,0 +1,39 @@
# Copyright 2015 Tesora Inc.
#
# 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 django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa
from trove_dashboard.content.database_configurations \
import views
CONFIGS = r'^(?P<configuration_id>[^/]+)/%s$'
urlpatterns = patterns(
'',
url(r'^$',
views.IndexView.as_view(),
name='index'),
url(r'^create$',
views.CreateConfigurationView.as_view(),
name='create'),
url(CONFIGS % '',
views.DetailView.as_view(),
name='detail'),
url(CONFIGS % 'add',
views.AddParameterView.as_view(),
name='add')
)

View File

@ -0,0 +1,115 @@
# Copyright 2015 Tesora Inc.
#
# 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 django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms as horizon_forms
from horizon import tables as horizon_tables
from horizon import tabs as horizon_tabs
from horizon.utils import memoized
from trove_dashboard import api
from trove_dashboard.content.database_configurations \
import config_param_manager
from trove_dashboard.content.database_configurations \
import forms
from trove_dashboard.content.database_configurations \
import tables
from trove_dashboard.content.database_configurations \
import tabs
class IndexView(horizon_tables.DataTableView):
table_class = tables.ConfigurationsTable
template_name = 'project/database_configurations/index.html'
page_title = _("Configuration Groups")
def get_data(self):
try:
configurations = api.trove.configuration_list(self.request)
except Exception:
configurations = []
msg = _('Error getting configuration group list.')
exceptions.handle(self.request, msg)
return configurations
class DetailView(horizon_tabs.TabbedTableView):
tab_group_class = tabs.ConfigurationDetailTabs
template_name = "project/database_configurations/details.html"
page_title = _("Configuration Group Details: {{configuration.name}}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
context["configuration"] = self.get_data()
return context
@memoized.memoized_method
def get_data(self):
try:
configuration_id = self.kwargs['configuration_id']
return (config_param_manager
.get(self.request, configuration_id)
.configuration_get(self.request))
except Exception:
redirect = reverse('horizon:project:database_configurations:index')
msg = _('Unable to retrieve details for configuration '
'group: %s') % configuration_id
exceptions.handle(self.request, msg, redirect=redirect)
def get_tabs(self, request, *args, **kwargs):
configuration = self.get_data()
return self.tab_group_class(request,
configuration=configuration,
**kwargs)
class CreateConfigurationView(horizon_forms.ModalFormView):
form_class = forms.CreateConfigurationForm
form_id = "create_configuration_form"
modal_header = _("Create Configuration Group")
modal_id = "create_configuration_modal"
template_name = 'project/database_configurations/create.html'
submit_label = "Create Configuration Group"
submit_url = reverse_lazy('horizon:project:database_configurations:create')
success_url = reverse_lazy('horizon:project:database_configurations:index')
class AddParameterView(horizon_forms.ModalFormView):
form_class = forms.AddParameterForm
form_id = "add_parameter_form"
modal_header = _("Add Parameter")
modal_id = "add_parameter_modal"
template_name = 'project/database_configurations/add_parameter.html'
submit_label = "Add Parameter"
submit_url = 'horizon:project:database_configurations:add'
success_url = 'horizon:project:database_configurations:detail'
def get_success_url(self):
return reverse(self.success_url,
args=(self.kwargs['configuration_id'],))
def get_context_data(self, **kwargs):
context = super(AddParameterView, self).get_context_data(**kwargs)
context["configuration_id"] = self.kwargs['configuration_id']
args = (self.kwargs['configuration_id'],)
context['submit_url'] = reverse(self.submit_url, args=args)
return context
def get_initial(self):
configuration_id = self.kwargs['configuration_id']
return {'configuration_id': configuration_id}

View File

@ -243,3 +243,41 @@ class EditUserForm(forms.SelfHandlingForm):
raise ValidationError(self.validation_error_message) raise ValidationError(self.validation_error_message)
return cleaned_data return cleaned_data
class AttachConfigurationForm(forms.SelfHandlingForm):
instance_id = forms.CharField(widget=forms.HiddenInput())
configuration = forms.ChoiceField(label=_("Configuration Group"))
def __init__(self, request, *args, **kwargs):
super(AttachConfigurationForm, self).__init__(request, *args, **kwargs)
instance_id = kwargs.get('initial', {}).get('instance_id')
datastore = kwargs.get('initial', {}).get('datastore')
datastore_version = kwargs.get('initial', {}).get('datastore_version')
self.fields['instance_id'].initial = instance_id
configurations = api.trove.configuration_list(request)
choices = [(c.id, c.name) for c in configurations
if (c.datastore_name == datastore and
c.datastore_version_name == datastore_version)]
if choices:
choices.insert(0, ("", _("Select configuration group")))
else:
choices.insert(0, ("", _("No configuration groups available")))
self.fields['configuration'].choices = choices
def handle(self, request, data):
instance_id = data.get('instance_id')
try:
api.trove.instance_attach_configuration(request,
instance_id,
data['configuration'])
messages.success(request, _('Attaching Configuration group "%s"')
% instance_id)
except Exception as e:
redirect = reverse("horizon:project:databases:index")
exceptions.handle(request, _('Unable to attach configuration '
'group. %s')
% e.message, redirect=redirect)
return True

View File

@ -88,7 +88,8 @@ class RestartInstance(tables.BatchAction):
def allowed(self, request, instance=None): def allowed(self, request, instance=None):
return ((instance.status in ACTIVE_STATES return ((instance.status in ACTIVE_STATES
or instance.status == 'SHUTDOWN')) or instance.status == 'SHUTDOWN'
or instance.status == 'RESTART_REQUIRED'))
def action(self, request, obj_id): def action(self, request, obj_id):
api.trove.instance_restart(request, obj_id) api.trove.instance_restart(request, obj_id)
@ -453,6 +454,45 @@ class ResizeInstance(tables.LinkAction):
return urlresolvers.reverse(self.url, args=[instance_id]) return urlresolvers.reverse(self.url, args=[instance_id])
class AttachConfiguration(tables.LinkAction):
name = "attach_configuration"
verbose_name = _("Attach Configuration Group")
url = "horizon:project:databases:attach_config"
classes = ("btn-attach-config", "ajax-modal")
def allowed(self, request, instance=None):
return (instance.status in ACTIVE_STATES
and not hasattr(instance, 'configuration'))
class DetachConfiguration(tables.BatchAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Detach Configuration Group",
u"Detach Configuration Groups",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Detached Configuration Group",
u"Detached Configuration Groups",
count
)
name = "detach_configuration"
classes = ('btn-danger', 'btn-detach-config')
def allowed(self, request, instance=None):
return (instance.status in ACTIVE_STATES and
hasattr(instance, 'configuration'))
def action(self, request, obj_id):
api.trove.instance_detach_configuration(request, obj_id)
class EnableRootAction(tables.Action): class EnableRootAction(tables.Action):
name = "enable_root_action" name = "enable_root_action"
verbose_name = _("Enable Root") verbose_name = _("Enable Root")
@ -638,6 +678,8 @@ class InstancesTable(tables.DataTable):
ResizeVolume, ResizeVolume,
ResizeInstance, ResizeInstance,
PromoteToReplicaSource, PromoteToReplicaSource,
AttachConfiguration,
DetachConfiguration,
ManageRoot, ManageRoot,
EjectReplicaSource, EjectReplicaSource,
DetachReplica, DetachReplica,
@ -704,3 +746,15 @@ class InstanceBackupsTable(tables.DataTable):
row_class = UpdateRow row_class = UpdateRow
table_actions = (backup_tables.LaunchLink, backup_tables.DeleteBackup) table_actions = (backup_tables.LaunchLink, backup_tables.DeleteBackup)
row_actions = (backup_tables.RestoreLink, backup_tables.DeleteBackup) row_actions = (backup_tables.RestoreLink, backup_tables.DeleteBackup)
class ConfigDefaultsTable(tables.DataTable):
name = tables.Column('name', verbose_name=_('Property'))
value = tables.Column('value', verbose_name=_('Value'))
class Meta(object):
name = 'config_defaults'
verbose_name = _('Configuration Defaults')
def get_object_id(self, datum):
return datum.name

View File

@ -20,6 +20,8 @@ from django.utils.translation import ugettext_lazy as _
from horizon import exceptions from horizon import exceptions
from horizon import tabs from horizon import tabs
from trove_dashboard import api from trove_dashboard import api
from trove_dashboard.content.database_configurations import (
config_param_manager)
from trove_dashboard.content.databases import db_capability from trove_dashboard.content.databases import db_capability
from trove_dashboard.content.databases.logs import tables as log_tables from trove_dashboard.content.databases.logs import tables as log_tables
from trove_dashboard.content.databases import tables from trove_dashboard.content.databases import tables
@ -120,6 +122,25 @@ class DatabaseTab(tabs.TableTab):
return tables.has_database_add_perm(request) return tables.has_database_add_perm(request)
class ConfigDefaultsTab(tabs.TableTab):
table_classes = [tables.ConfigDefaultsTable]
name = _("Defaults")
slug = "config_defaults"
instance = None
template_name = "horizon/common/_detail_table.html"
preload = False
def get_config_defaults_data(self):
instance = self.tab_group.kwargs['instance']
values_data = []
data = api.trove.configuration_default(self.request, instance.id)
if data is not None:
for k, v in data.configuration.items():
values_data.append(
config_param_manager.ConfigParam(None, k, v))
return sorted(values_data, key=lambda config: config.name)
class BackupsTab(tabs.TableTab): class BackupsTab(tabs.TableTab):
table_classes = [tables.InstanceBackupsTable] table_classes = [tables.InstanceBackupsTable]
name = _("Backups") name = _("Backups")
@ -163,5 +184,6 @@ class LogsTab(tabs.TableTab):
class InstanceDetailTabs(tabs.TabGroup): class InstanceDetailTabs(tabs.TabGroup):
slug = "instance_details" slug = "instance_details"
tabs = (OverviewTab, UserTab, DatabaseTab, BackupsTab, LogsTab) tabs = (OverviewTab, UserTab, DatabaseTab, BackupsTab, LogsTab,
ConfigDefaultsTab)
sticky = True sticky = True

View File

@ -0,0 +1,7 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<p>{% trans "Select a configuration group to attach to the database instance." %}</p>
<p><strong>{% trans "Please note:</strong> It may be necessary to reboot the database instance for this new configuration group to take effect." %}</strong></p>
{% endblock %}

View File

@ -12,6 +12,14 @@
<dd>{{ instance.datastore.version }}</dd> <dd>{{ instance.datastore.version }}</dd>
<dt>{% trans "Status" %}</dt> <dt>{% trans "Status" %}</dt>
<dd>{{ instance.status|title }}</dd> <dd>{{ instance.status|title }}</dd>
{% if instance.configuration %}
<dt>{% trans "Configuration Group" %}</dt>
<dd>
<a href="{% url 'horizon:project:database_configurations:detail' instance.configuration.id %}">
{{ instance.configuration.id }}
</a>
</dd>
{% endif %}
<dt>{% trans "Root Enabled" %}</dt> <dt>{% trans "Root Enabled" %}</dt>
<dd>{{ root_enabled|capfirst }}</dd> <dd>{{ root_enabled|capfirst }}</dd>
</dl> </dl>

View File

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block main %}
{% include "project/databases/_attach_config.html" %}
{% endblock %}

View File

@ -129,8 +129,8 @@ class DatabaseTests(test.TestCase):
self.assertMessageCount(res, error=1) self.assertMessageCount(res, error=1)
@test.create_stubs({ @test.create_stubs({
api.trove: ('datastore_flavors', 'backup_list', api.trove: ('backup_list', 'configuration_list', 'datastore_flavors',
'datastore_list', 'datastore_version_list', 'datastore_list', 'datastore_version_list', 'flavor_list',
'instance_list'), 'instance_list'),
dash_api.cinder: ('volume_type_list',), dash_api.cinder: ('volume_type_list',),
dash_api.neutron: ('network_list',), dash_api.neutron: ('network_list',),
@ -144,6 +144,7 @@ class DatabaseTests(test.TestCase):
MultipleTimes().AndReturn(self.flavors.list()) MultipleTimes().AndReturn(self.flavors.list())
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn( api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
self.database_backups.list()) self.database_backups.list())
api.trove.configuration_list(IsA(http.HttpRequest)).AndReturn([])
api.trove.instance_list(IsA(http.HttpRequest)).AndReturn( api.trove.instance_list(IsA(http.HttpRequest)).AndReturn(
self.databases.list()) self.databases.list())
# Mock datastores # Mock datastores
@ -200,9 +201,9 @@ class DatabaseTests(test.TestCase):
log.setLevel(level) log.setLevel(level)
@test.create_stubs({ @test.create_stubs({
api.trove: ('datastore_flavors', 'backup_list', 'instance_create', api.trove: ('backup_list', 'configuration_list', 'datastore_flavors',
'datastore_list', 'datastore_version_list', 'datastore_list', 'datastore_version_list', 'flavor_list',
'instance_list'), 'instance_create', 'instance_list'),
dash_api.cinder: ('volume_type_list',), dash_api.cinder: ('volume_type_list',),
dash_api.neutron: ('network_list',), dash_api.neutron: ('network_list',),
policy: ('check',), policy: ('check',),
@ -256,6 +257,7 @@ class DatabaseTests(test.TestCase):
datastore_version=datastore_version, datastore_version=datastore_version,
restore_point=None, restore_point=None,
replica_of=None, replica_of=None,
configuration=None,
users=None, users=None,
nics=nics, nics=nics,
replica_count=None, replica_count=None,
@ -276,9 +278,9 @@ class DatabaseTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL) self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({ @test.create_stubs({
api.trove: ('datastore_flavors', 'backup_list', 'instance_create', api.trove: ('backup_list', 'configuration_list', 'datastore_flavors',
'datastore_list', 'datastore_version_list', 'datastore_list', 'datastore_version_list', 'flavor_list',
'instance_list'), 'instance_create', 'instance_list'),
dash_api.cinder: ('volume_type_list',), dash_api.cinder: ('volume_type_list',),
dash_api.neutron: ('network_list',), dash_api.neutron: ('network_list',),
policy: ('check',), policy: ('check',),
@ -333,6 +335,7 @@ class DatabaseTests(test.TestCase):
datastore_version=datastore_version, datastore_version=datastore_version,
restore_point=None, restore_point=None,
replica_of=None, replica_of=None,
configuration=None,
users=None, users=None,
nics=nics, nics=nics,
replica_count=None, replica_count=None,
@ -981,9 +984,9 @@ class DatabaseTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL) self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({ @test.create_stubs({
api.trove: ('datastore_flavors', 'backup_list', 'instance_create', api.trove: ('backup_list', 'configuration_list', 'datastore_flavors',
'datastore_list', 'datastore_version_list', 'datastore_list', 'datastore_version_list', 'flavor_list',
'instance_list_all', 'instance_get'), 'instance_create', 'instance_get', 'instance_list_all'),
dash_api.cinder: ('volume_type_list',), dash_api.cinder: ('volume_type_list',),
dash_api.neutron: ('network_list',), dash_api.neutron: ('network_list',),
policy: ('check',), policy: ('check',),
@ -1039,6 +1042,7 @@ class DatabaseTests(test.TestCase):
datastore_version=datastore_version, datastore_version=datastore_version,
restore_point=None, restore_point=None,
replica_of=self.databases.first().id, replica_of=self.databases.first().id,
configuration=None,
users=None, users=None,
nics=nics, nics=nics,
replica_count=2, replica_count=2,
@ -1190,3 +1194,117 @@ class DatabaseTests(test.TestCase):
def _build_flavor_widget_name(self, datastore, datastore_version): def _build_flavor_widget_name(self, datastore, datastore_version):
return binascii.hexlify(self._build_datastore_display_text( return binascii.hexlify(self._build_datastore_display_text(
datastore, datastore_version)) datastore, datastore_version))
@test.create_stubs({
api.trove: ('instance_get',
'configuration_list',
'instance_attach_configuration'),
})
def test_attach_configuration(self):
database = self.databases.first()
configuration = self.database_configurations.first()
api.trove.instance_get(IsA(http.HttpRequest), IsA(unicode))\
.AndReturn(database)
api.trove.configuration_list(IsA(http.HttpRequest))\
.AndReturn(self.database_configurations.list())
api.trove.instance_attach_configuration(
IsA(http.HttpRequest), database.id, configuration.id)\
.AndReturn(None)
self.mox.ReplayAll()
url = reverse('horizon:project:databases:attach_config',
args=[database.id])
form = {
'instance_id': database.id,
'configuration': configuration.id,
}
res = self.client.post(url, form)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({
api.trove: ('instance_get',
'configuration_list',
'instance_attach_configuration'),
})
def test_attach_configuration_exception(self):
database = self.databases.first()
configuration = self.database_configurations.first()
api.trove.instance_get(IsA(http.HttpRequest), IsA(unicode))\
.AndReturn(database)
api.trove.configuration_list(IsA(http.HttpRequest))\
.AndReturn(self.database_configurations.list())
api.trove.instance_attach_configuration(
IsA(http.HttpRequest), database.id, configuration.id)\
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
url = reverse('horizon:project:databases:attach_config',
args=[database.id])
form = {
'instance_id': database.id,
'configuration': configuration.id,
}
res = self.client.post(url, form)
self.assertEqual(res.status_code, 302)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({
api.trove: ('instance_list',
'flavor_list',
'instance_detach_configuration',),
})
def test_detach_configuration(self):
databases = common.Paginated(self.databases.list())
database = databases[2]
api.trove.instance_list(IsA(http.HttpRequest), marker=None)\
.AndReturn(databases)
api.trove.flavor_list(IsA(http.HttpRequest))\
.AndReturn(self.flavors.list())
api.trove.instance_detach_configuration(
IsA(http.HttpRequest), database.id)\
.AndReturn(None)
self.mox.ReplayAll()
res = self.client.post(
INDEX_URL,
{'action': 'databases__detach_configuration__%s' % database.id})
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({
api.trove: ('instance_list',
'flavor_list',
'instance_detach_configuration',),
})
def test_detach_configuration_exception(self):
databases = common.Paginated(self.databases.list())
database = databases[2]
api.trove.instance_list(IsA(http.HttpRequest), marker=None)\
.AndReturn(databases)
api.trove.flavor_list(IsA(http.HttpRequest))\
.AndReturn(self.flavors.list())
api.trove.instance_detach_configuration(
IsA(http.HttpRequest), database.id)\
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
res = self.client.post(
INDEX_URL,
{'action': 'databases__detach_configuration__%s' % database.id})
self.assertRedirectsNoFollow(res, INDEX_URL)

View File

@ -44,6 +44,8 @@ urlpatterns = patterns(
url(INSTANCES % 'promote_to_replica_source', url(INSTANCES % 'promote_to_replica_source',
views.PromoteToReplicaSourceView.as_view(), views.PromoteToReplicaSourceView.as_view(),
name='promote_to_replica_source'), name='promote_to_replica_source'),
url(INSTANCES % 'attach_config', views.AttachConfigurationView.as_view(),
name='attach_config'),
url(INSTANCES % 'manage_root', views.ManageRootView.as_view(), url(INSTANCES % 'manage_root', views.ManageRootView.as_view(),
name='manage_root'), name='manage_root'),
url(BASEINSTANCES % 'logs/', include(logs_urls, namespace='logs')), url(BASEINSTANCES % 'logs/', include(logs_urls, namespace='logs')),

View File

@ -205,6 +205,41 @@ class AccessDetailView(horizon_tables.DataTableView):
return context return context
class AttachConfigurationView(horizon_forms.ModalFormView):
form_class = forms.AttachConfigurationForm
form_id = "attach_config_form"
modal_header = _("Attach Configuration Group")
modal_id = "attach_config_modal"
template_name = "project/databases/attach_config.html"
submit_label = "Attach Configuration"
submit_url = 'horizon:project:databases:attach_config'
success_url = reverse_lazy('horizon:project:databases:index')
@memoized.memoized_method
def get_object(self, *args, **kwargs):
instance_id = self.kwargs['instance_id']
try:
return api.trove.instance_get(self.request, instance_id)
except Exception:
msg = _('Unable to retrieve instance details.')
redirect = reverse('horizon:project:databases:index')
exceptions.handle(self.request, msg, redirect=redirect)
def get_context_data(self, **kwargs):
context = (super(AttachConfigurationView, self)
.get_context_data(**kwargs))
context['instance_id'] = self.kwargs['instance_id']
args = (self.kwargs['instance_id'],)
context['submit_url'] = reverse(self.submit_url, args=args)
return context
def get_initial(self):
instance = self.get_object()
return {'instance_id': self.kwargs['instance_id'],
'datastore': instance.datastore.get('type', ''),
'datastore_version': instance.datastore.get('version', '')}
class DetailView(horizon_tabs.TabbedTableView): class DetailView(horizon_tabs.TabbedTableView):
tab_group_class = tabs.InstanceDetailTabs tab_group_class = tabs.InstanceDetailTabs
template_name = 'horizon/common/_detail.html' template_name = 'horizon/common/_detail.html'

View File

@ -252,6 +252,10 @@ class InitializeDatabase(workflows.Step):
class AdvancedAction(workflows.Action): class AdvancedAction(workflows.Action):
config = forms.ChoiceField(
label=_("Configuration Group"),
required=False,
help_text=_('Select a configuration group'))
initial_state = forms.ChoiceField( initial_state = forms.ChoiceField(
label=_('Source for Initial State'), label=_('Source for Initial State'),
required=False, required=False,
@ -298,6 +302,24 @@ class AdvancedAction(workflows.Action):
name = _("Advanced") name = _("Advanced")
help_text_template = "project/databases/_launch_advanced_help.html" help_text_template = "project/databases/_launch_advanced_help.html"
def populate_config_choices(self, request, context):
try:
configs = api.trove.configuration_list(request)
config_name = "%(name)s (%(datastore)s - %(version)s)"
choices = [(c.id,
config_name % {'name': c.name,
'datastore': c.datastore_name,
'version': c.datastore_version_name})
for c in configs]
except Exception:
choices = []
if choices:
choices.insert(0, ("", _("Select configuration")))
else:
choices.insert(0, ("", _("No configurations available")))
return choices
def populate_backup_choices(self, request, context): def populate_backup_choices(self, request, context):
try: try:
backups = api.trove.backup_list(request) backups = api.trove.backup_list(request)
@ -339,6 +361,19 @@ class AdvancedAction(workflows.Action):
def clean(self): def clean(self):
cleaned_data = super(AdvancedAction, self).clean() cleaned_data = super(AdvancedAction, self).clean()
config = self.cleaned_data['config']
if config:
try:
# Make sure the user is not "hacking" the form
# and that they have access to this configuration
cfg = api.trove.configuration_get(self.request, config)
self.cleaned_data['config'] = cfg.id
except Exception:
raise forms.ValidationError(_("Unable to find configuration "
"group!"))
else:
self.cleaned_data['config'] = None
initial_state = cleaned_data.get("initial_state") initial_state = cleaned_data.get("initial_state")
if initial_state == 'backup': if initial_state == 'backup':
@ -377,7 +412,7 @@ class AdvancedAction(workflows.Action):
class Advanced(workflows.Step): class Advanced(workflows.Step):
action_class = AdvancedAction action_class = AdvancedAction
contributes = ['backup', 'master', 'replica_count'] contributes = ['config', 'backup', 'master', 'replica_count']
class LaunchInstance(workflows.Workflow): class LaunchInstance(workflows.Workflow):
@ -452,13 +487,16 @@ class LaunchInstance(workflows.Workflow):
"{name=%s, volume=%s, volume_type=%s, flavor=%s, " "{name=%s, volume=%s, volume_type=%s, flavor=%s, "
"datastore=%s, datastore_version=%s, " "datastore=%s, datastore_version=%s, "
"dbs=%s, users=%s, " "dbs=%s, users=%s, "
"backups=%s, nics=%s, replica_of=%s replica_count=%s}", "backups=%s, nics=%s, "
"replica_of=%s, replica_count=%s, "
"configuration=%s}",
context['name'], context['volume'], context['name'], context['volume'],
self._get_volume_type(context), context['flavor'], self._get_volume_type(context), context['flavor'],
datastore, datastore_version, datastore, datastore_version,
self._get_databases(context), self._get_users(context), self._get_databases(context), self._get_users(context),
self._get_backup(context), self._get_nics(context), self._get_backup(context), self._get_nics(context),
context.get('master'), context['replica_count']) context.get('master'), context['replica_count'],
context.get('config'))
api.trove.instance_create(request, api.trove.instance_create(request,
context['name'], context['name'],
context['volume'], context['volume'],
@ -472,7 +510,8 @@ class LaunchInstance(workflows.Workflow):
replica_of=context.get('master'), replica_of=context.get('master'),
replica_count=context['replica_count'], replica_count=context['replica_count'],
volume_type=self._get_volume_type( volume_type=self._get_volume_type(
context)) context),
configuration=context.get('config'))
return True return True
except Exception: except Exception:
exceptions.handle(request) exceptions.handle(request)

View File

@ -0,0 +1,30 @@
# 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_dashboard import exceptions
# The slug of the panel to be added to HORIZON_CONFIG. Required.
PANEL = 'database_configurations'
# The slug of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'project'
# The slug of the panel group the PANEL is associated with.
PANEL_GROUP = 'database'
# Python panel class of the PANEL to be added.
ADD_PANEL = ('trove_dashboard.content.database_configurations.panel.'
'Configurations')
ADD_EXCEPTIONS = {
'not_found': exceptions.NOT_FOUND,
'recoverable': exceptions.RECOVERABLE,
'unauthorized': exceptions.UNAUTHORIZED,
}

View File

@ -17,4 +17,5 @@ from openstack_dashboard.test.settings import * # noqa
INSTALLED_APPS = list(INSTALLED_APPS) INSTALLED_APPS = list(INSTALLED_APPS)
INSTALLED_APPS.append('trove_dashboard.content.database_backups') INSTALLED_APPS.append('trove_dashboard.content.database_backups')
INSTALLED_APPS.append('trove_dashboard.content.database_clusters') INSTALLED_APPS.append('trove_dashboard.content.database_clusters')
INSTALLED_APPS.append('trove_dashboard.content.database_configurations')
INSTALLED_APPS.append('trove_dashboard.content.databases') INSTALLED_APPS.append('trove_dashboard.content.databases')

View File

@ -15,6 +15,7 @@
from troveclient.v1 import backups from troveclient.v1 import backups
from troveclient.v1 import clusters from troveclient.v1 import clusters
from troveclient.v1 import configurations
from troveclient.v1 import databases from troveclient.v1 import databases
from troveclient.v1 import datastores from troveclient.v1 import datastores
from troveclient.v1 import flavors from troveclient.v1 import flavors
@ -225,7 +226,6 @@ BACKUP_ONE = {
"description": "Long description of backup", "description": "Long description of backup",
} }
BACKUP_TWO = { BACKUP_TWO = {
"instance_id": "4d7b3f57-44f5-41d2-8e86-36b88cad572a", "instance_id": "4d7b3f57-44f5-41d2-8e86-36b88cad572a",
"status": "COMPLETED", "status": "COMPLETED",
@ -238,7 +238,6 @@ BACKUP_TWO = {
"description": "Longer description of backup", "description": "Longer description of backup",
} }
BACKUP_TWO_INC = { BACKUP_TWO_INC = {
"instance_id": "4d7b3f57-44f5-41d2-8e86-36b88cad572a", "instance_id": "4d7b3f57-44f5-41d2-8e86-36b88cad572a",
"status": "COMPLETED", "status": "COMPLETED",
@ -252,6 +251,75 @@ BACKUP_TWO_INC = {
"parent_id": "e4602a3c-2bca-478f-b059-b6c215510fb4", "parent_id": "e4602a3c-2bca-478f-b059-b6c215510fb4",
} }
CONFIG_ONE = {
"updated": "2014-07-11T14:33:35",
"name": "config1",
"created": "2014-07-11T14:33:35",
"instance_count": 1,
"values": {
"collation_server": "latin1_swedish_ci",
"max_connections": 6000
},
"id": "0ef978d3-7c83-4192-ab86-b7a0a5010fa0",
"description": "Long description of configuration one",
"datastore_name": "mysql",
"datastore_version_name": "5.5"
}
CONFIG_TWO = {
"updated": "2014-08-11T14:33:35",
"name": "config2",
"created": "2014-08-11T14:33:35",
"instance_count": 0,
"values": {
"collation_server": "latin1_swedish_ci",
"max_connections": 5000
},
"id": "87948232-10e7-4636-a3d3-a5e1593b7d16",
"description": "Long description of configuration two",
"datastore_name": "mysql",
"datastore_version_name": "5.6"
}
CONFIG_INSTANCE_ONE = {
"id": "c3369597-b53a-4bd4-bf54-41957c1291b8",
"name": "Test Database with Config",
}
CONFIG_PARAMS_ONE = [
{
"name": "autocommit",
"restart_required": False,
"max": 1,
"min": 0,
"type": "integer",
},
{
"name": "connect_timeout",
"restart_required": False,
"max": 65535,
"min": 1,
"type": "integer",
},
{
"name": "sort_buffer_size",
"restart_required": False,
"max": 18446744073709547520,
"min": 32768,
"type": "integer",
},
{
"name": "character_set_client",
"restart_required": False,
"type": "string",
},
{
"name": "character_set_connection",
"restart_required": False,
"type": "string",
},
]
USER_ONE = { USER_ONE = {
"name": "Test_User", "name": "Test_User",
"host": "%", "host": "%",
@ -418,6 +486,12 @@ def data(TEST):
bkup1 = backups.Backup(backups.Backups(None), BACKUP_ONE) bkup1 = backups.Backup(backups.Backups(None), BACKUP_ONE)
bkup2 = backups.Backup(backups.Backups(None), BACKUP_TWO) bkup2 = backups.Backup(backups.Backups(None), BACKUP_TWO)
bkup3 = backups.Backup(backups.Backups(None), BACKUP_TWO_INC) bkup3 = backups.Backup(backups.Backups(None), BACKUP_TWO_INC)
cfg1 = configurations.Configuration(configurations.Configurations(None),
CONFIG_ONE)
cfg2 = configurations.Configuration(configurations.Configurations(None),
CONFIG_TWO)
user1 = users.User(users.Users(None), USER_ONE) user1 = users.User(users.Users(None), USER_ONE)
user_db1 = databases.Database(databases.Databases(None), user_db1 = databases.Database(databases.Databases(None),
USER_DB_ONE) USER_DB_ONE)
@ -429,6 +503,9 @@ def data(TEST):
version1 = datastores.\ version1 = datastores.\
DatastoreVersion(datastores.DatastoreVersions(None), DatastoreVersion(datastores.DatastoreVersions(None),
VERSION_ONE) VERSION_ONE)
version2 = datastores.\
DatastoreVersion(datastores.DatastoreVersions(None),
VERSION_TWO)
flavor1 = flavors.Flavor(flavors.Flavors(None), FLAVOR_ONE) flavor1 = flavors.Flavor(flavors.Flavors(None), FLAVOR_ONE)
flavor2 = flavors.Flavor(flavors.Flavors(None), FLAVOR_TWO) flavor2 = flavors.Flavor(flavors.Flavors(None), FLAVOR_TWO)
@ -459,6 +536,7 @@ def data(TEST):
TEST.trove_clusters.add(cluster2) TEST.trove_clusters.add(cluster2)
TEST.databases = utils.TestDataContainer() TEST.databases = utils.TestDataContainer()
TEST.database_backups = utils.TestDataContainer() TEST.database_backups = utils.TestDataContainer()
TEST.database_configurations = utils.TestDataContainer()
TEST.database_users = utils.TestDataContainer() TEST.database_users = utils.TestDataContainer()
TEST.database_user_dbs = utils.TestDataContainer() TEST.database_user_dbs = utils.TestDataContainer()
TEST.database_user_roots = utils.TestDataContainer() TEST.database_user_roots = utils.TestDataContainer()
@ -470,20 +548,36 @@ def data(TEST):
TEST.database_backups.add(bkup1) TEST.database_backups.add(bkup1)
TEST.database_backups.add(bkup2) TEST.database_backups.add(bkup2)
TEST.database_backups.add(bkup3) TEST.database_backups.add(bkup3)
TEST.database_configurations.add(cfg1)
TEST.database_configurations.add(cfg2)
TEST.configuration_parameters = utils.TestDataContainer()
for parameter in CONFIG_PARAMS_ONE:
TEST.configuration_parameters.add(
configurations.ConfigurationParameter(
configurations.ConfigurationParameters(None), parameter))
TEST.configuration_instances = utils.TestDataContainer()
TEST.configuration_instances.add(
configurations.Configuration(
configurations.Configurations(None), CONFIG_INSTANCE_ONE))
TEST.database_users.add(user1) TEST.database_users.add(user1)
TEST.database_user_dbs.add(user_db1) TEST.database_user_dbs.add(user_db1)
TEST.database_user_roots.add(user_root1) TEST.database_user_roots.add(user_root1)
TEST.datastores = utils.TestDataContainer() TEST.datastores = utils.TestDataContainer()
TEST.datastores.add(datastore1)
TEST.datastores.add(datastore_mongodb) TEST.datastores.add(datastore_mongodb)
TEST.datastores.add(datastore_redis) TEST.datastores.add(datastore_redis)
TEST.datastores.add(datastore_vertica) TEST.datastores.add(datastore_vertica)
TEST.datastores.add(datastore1)
TEST.database_flavors.add(flavor1, flavor2, flavor3) TEST.database_flavors.add(flavor1, flavor2, flavor3)
TEST.datastore_versions = utils.TestDataContainer() TEST.datastore_versions = utils.TestDataContainer()
TEST.datastore_versions.add(version_vertica_7_1) TEST.datastore_versions.add(version_vertica_7_1)
TEST.datastore_versions.add(version_redis_3_0) TEST.datastore_versions.add(version_redis_3_0)
TEST.datastore_versions.add(version_mongodb_2_6) TEST.datastore_versions.add(version_mongodb_2_6)
TEST.datastore_versions.add(version1) TEST.datastore_versions.add(version1)
TEST.datastore_versions.add(version2)
TEST.logs = utils.TestDataContainer() TEST.logs = utils.TestDataContainer()
TEST.logs.add(log1, log2, log3, log4) TEST.logs.add(log1, log2, log3, log4)