Trove add cluster grow and shrink support
Add support for Trove commands cluster-grow and cluster-shrink. Added the grow and shrink actions to the clusters list table. The grow and shrink actions are only available for MongoDB and Redis clusters. Added the grow panel table to list the new instances to be added to the cluster. There is a table action Add Instance where the instance details are specified then added to the new instances table. A Remove Instance table and row action is available to remove any instances from the table. A Grow Cluster table action will add the instances to the instances in the table to the cluster. Removed the add shard action as it is now deprecated and is replaced by the grow action. Added a cluster manager helper to keep track of the newly added instances in the grow panel. Added the shrink panel table that lists the instances belonging to the cluster in a table. The selected instance(s) can then be removed from the cluster with the shrink command. Added the cluster_grow and cluster_shrink commands to the api. Change-Id: I05dbc73282b333e3ed8cfd4cdbda673ec86f57fd Co-Authored-By: Duk Loi <duk@tesora.com> Implements: blueprint trove-support-cluster-grow-shrink
This commit is contained in:
parent
6a7d58193c
commit
3ea4a7ca62
@ -76,8 +76,25 @@ def cluster_create(request, name, volume, flavor, num_instances,
|
|||||||
instances=instances)
|
instances=instances)
|
||||||
|
|
||||||
|
|
||||||
def cluster_add_shard(request, cluster_id):
|
def cluster_grow(request, cluster_id, new_instances):
|
||||||
return troveclient(request).clusters.add_shard(cluster_id)
|
instances = []
|
||||||
|
for new_instance in new_instances:
|
||||||
|
instance = {}
|
||||||
|
instance["flavorRef"] = new_instance.flavor_id
|
||||||
|
if new_instance.volume > 0:
|
||||||
|
instance["volume"] = {'size': new_instance.volume}
|
||||||
|
if new_instance.name:
|
||||||
|
instance["name"] = new_instance.name
|
||||||
|
if new_instance.type:
|
||||||
|
instance["type"] = new_instance.type
|
||||||
|
if new_instance.related_to:
|
||||||
|
instance["related_to"] = new_instance.related_to
|
||||||
|
instances.append(instance)
|
||||||
|
return troveclient(request).clusters.grow(cluster_id, instances)
|
||||||
|
|
||||||
|
|
||||||
|
def cluster_shrink(request, cluster_id, instances):
|
||||||
|
return troveclient(request).clusters.shrink(cluster_id, instances)
|
||||||
|
|
||||||
|
|
||||||
def create_cluster_root(request, cluster_id, password):
|
def create_cluster_root(request, cluster_id, password):
|
||||||
|
86
trove_dashboard/content/database_clusters/cluster_manager.py
Normal file
86
trove_dashboard/content/database_clusters/cluster_manager.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# Copyright 2016 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
|
||||||
|
|
||||||
|
|
||||||
|
def get(cluster_id):
|
||||||
|
if not has_cluster(cluster_id):
|
||||||
|
manager = ClusterInstanceManager(cluster_id)
|
||||||
|
cache.cache.set(cluster_id, manager)
|
||||||
|
|
||||||
|
return cache.cache.get(cluster_id)
|
||||||
|
|
||||||
|
|
||||||
|
def delete(cluster_id):
|
||||||
|
manager = get(cluster_id)
|
||||||
|
manager.clear_instances()
|
||||||
|
cache.cache.delete(cluster_id)
|
||||||
|
|
||||||
|
|
||||||
|
def update(cluster_id, manager):
|
||||||
|
cache.cache.set(cluster_id, manager)
|
||||||
|
|
||||||
|
|
||||||
|
def has_cluster(cluster_id):
|
||||||
|
if cache.cache.get(cluster_id):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterInstanceManager(object):
|
||||||
|
|
||||||
|
instances = []
|
||||||
|
|
||||||
|
def __init__(self, cluster_id):
|
||||||
|
self.cluster_id = cluster_id
|
||||||
|
|
||||||
|
def get_instances(self):
|
||||||
|
return self.instances
|
||||||
|
|
||||||
|
def get_instance(self, id):
|
||||||
|
for instance in self.instances:
|
||||||
|
if instance.id == id:
|
||||||
|
return instance
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_instance(self, id, name, flavor_id,
|
||||||
|
flavor, volume, type, related_to):
|
||||||
|
instance = ClusterInstance(id, name, flavor_id, flavor,
|
||||||
|
volume, type, related_to)
|
||||||
|
self.instances.append(instance)
|
||||||
|
update(self.cluster_id, self)
|
||||||
|
return self.instances
|
||||||
|
|
||||||
|
def delete_instance(self, id):
|
||||||
|
instance = self.get_instance(id)
|
||||||
|
if instance:
|
||||||
|
self.instances.remove(instance)
|
||||||
|
update(self.cluster_id, self)
|
||||||
|
|
||||||
|
def clear_instances(self):
|
||||||
|
del self.instances[:]
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterInstance(object):
|
||||||
|
def __init__(self, id, name, flavor_id, flavor, volume, type, related_to):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.flavor_id = flavor_id
|
||||||
|
self.flavor = flavor
|
||||||
|
self.volume = volume
|
||||||
|
self.type = type
|
||||||
|
self.related_to = related_to
|
@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@ -26,6 +27,8 @@ from horizon.utils import memoized
|
|||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
|
|
||||||
from trove_dashboard import api as trove_api
|
from trove_dashboard import api as trove_api
|
||||||
|
from trove_dashboard.content.database_clusters \
|
||||||
|
import cluster_manager
|
||||||
from trove_dashboard.content.databases import db_capability
|
from trove_dashboard.content.databases import db_capability
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -314,39 +317,78 @@ class LaunchForm(forms.SelfHandlingForm):
|
|||||||
redirect=redirect)
|
redirect=redirect)
|
||||||
|
|
||||||
|
|
||||||
class AddShardForm(forms.SelfHandlingForm):
|
class ClusterAddInstanceForm(forms.SelfHandlingForm):
|
||||||
name = forms.CharField(
|
cluster_id = forms.CharField(
|
||||||
label=_("Cluster Name"),
|
required=False,
|
||||||
max_length=80,
|
|
||||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
|
||||||
num_shards = forms.IntegerField(
|
|
||||||
label=_("Number of Shards"),
|
|
||||||
initial=1,
|
|
||||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
|
||||||
num_instances = forms.IntegerField(label=_("Instances Per Shard"),
|
|
||||||
initial=3,
|
|
||||||
widget=forms.TextInput(
|
|
||||||
attrs={'readonly': 'readonly'}))
|
|
||||||
cluster_id = forms.CharField(required=False,
|
|
||||||
widget=forms.HiddenInput())
|
widget=forms.HiddenInput())
|
||||||
|
flavor = forms.ChoiceField(
|
||||||
|
label=_("Flavor"),
|
||||||
|
help_text=_("Size of image to launch."))
|
||||||
|
volume = forms.IntegerField(
|
||||||
|
label=_("Volume Size"),
|
||||||
|
min_value=0,
|
||||||
|
initial=1,
|
||||||
|
help_text=_("Size of the volume in GB."))
|
||||||
|
name = forms.CharField(
|
||||||
|
label=_("Name"),
|
||||||
|
required=False,
|
||||||
|
help_text=_("Optional name of the instance."))
|
||||||
|
type = forms.CharField(
|
||||||
|
label=_("Instance Type"),
|
||||||
|
required=False,
|
||||||
|
help_text=_("Optional datastore specific type of the instance."))
|
||||||
|
related_to = forms.CharField(
|
||||||
|
label=_("Related To"),
|
||||||
|
required=False,
|
||||||
|
help_text=_("Optional datastore specific value that defines the "
|
||||||
|
"relationship from one instance in the cluster to "
|
||||||
|
"another."))
|
||||||
|
|
||||||
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
super(ClusterAddInstanceForm, self).__init__(request, *args, **kwargs)
|
||||||
|
|
||||||
|
self.fields['flavor'].choices = self.populate_flavor_choices(request)
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def flavors(self, request):
|
||||||
|
try:
|
||||||
|
datastore = None
|
||||||
|
datastore_version = None
|
||||||
|
datastore_dict = self.initial.get('datastore', None)
|
||||||
|
if datastore_dict:
|
||||||
|
datastore = datastore_dict.get('type', None)
|
||||||
|
datastore_version = datastore_dict.get('version', None)
|
||||||
|
return trove_api.trove.datastore_flavors(
|
||||||
|
request,
|
||||||
|
datastore_name=datastore,
|
||||||
|
datastore_version=datastore_version)
|
||||||
|
except Exception:
|
||||||
|
LOG.exception("Exception while obtaining flavors list")
|
||||||
|
self._flavors = []
|
||||||
|
redirect = reverse('horizon:project:database_clusters:index')
|
||||||
|
exceptions.handle(request,
|
||||||
|
_('Unable to obtain flavors.'),
|
||||||
|
redirect=redirect)
|
||||||
|
|
||||||
|
def populate_flavor_choices(self, request):
|
||||||
|
flavor_list = [(f.id, "%s" % f.name) for f in self.flavors(request)]
|
||||||
|
return sorted(flavor_list)
|
||||||
|
|
||||||
def handle(self, request, data):
|
def handle(self, request, data):
|
||||||
try:
|
try:
|
||||||
LOG.info("Adding shard with parameters "
|
flavor = trove_api.trove.flavor_get(request, data['flavor'])
|
||||||
"{name=%s, num_shards=%s, num_instances=%s, "
|
manager = cluster_manager.get(data['cluster_id'])
|
||||||
"cluster_id=%s}",
|
manager.add_instance(str(uuid.uuid4()),
|
||||||
data['name'],
|
data.get('name', None),
|
||||||
data['num_shards'],
|
data['flavor'],
|
||||||
data['num_instances'],
|
flavor.name,
|
||||||
data['cluster_id'])
|
data['volume'],
|
||||||
trove_api.trove.cluster_add_shard(request, data['cluster_id'])
|
data.get('type', None),
|
||||||
|
data.get('related_to', None))
|
||||||
messages.success(request,
|
|
||||||
_('Added shard to "%s"') % data['name'])
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
redirect = reverse("horizon:project:database_clusters:index")
|
redirect = reverse("horizon:project:database_clusters:index")
|
||||||
exceptions.handle(request,
|
exceptions.handle(request,
|
||||||
_('Unable to add shard. %s') % e.message,
|
_('Unable to grow cluster. %s') % e.message,
|
||||||
redirect=redirect)
|
redirect=redirect)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -14,19 +14,27 @@
|
|||||||
# 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 logging
|
||||||
|
|
||||||
from django.core import urlresolvers
|
from django.core import urlresolvers
|
||||||
|
from django import shortcuts
|
||||||
from django.template.defaultfilters import title # noqa
|
from django.template.defaultfilters import title # noqa
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ungettext_lazy
|
from django.utils.translation import ungettext_lazy
|
||||||
|
|
||||||
|
from horizon import messages
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
from horizon.templatetags import sizeformat
|
from horizon.templatetags import sizeformat
|
||||||
from horizon.utils import filters
|
from horizon.utils import filters
|
||||||
|
from horizon.utils import functions
|
||||||
from horizon.utils import memoized
|
from horizon.utils import memoized
|
||||||
|
|
||||||
from trove_dashboard import api
|
from trove_dashboard import api
|
||||||
|
from trove_dashboard.content.database_clusters import cluster_manager
|
||||||
from trove_dashboard.content.databases import db_capability
|
from trove_dashboard.content.databases import db_capability
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
ACTIVE_STATES = ("ACTIVE",)
|
ACTIVE_STATES = ("ACTIVE",)
|
||||||
|
|
||||||
|
|
||||||
@ -64,16 +72,29 @@ class LaunchLink(tables.LinkAction):
|
|||||||
icon = "cloud-upload"
|
icon = "cloud-upload"
|
||||||
|
|
||||||
|
|
||||||
class AddShard(tables.LinkAction):
|
class ClusterGrow(tables.LinkAction):
|
||||||
name = "add_shard"
|
name = "cluster_grow"
|
||||||
verbose_name = _("Add Shard")
|
verbose_name = _("Grow Cluster")
|
||||||
url = "horizon:project:database_clusters:add_shard"
|
url = "horizon:project:database_clusters:cluster_grow_details"
|
||||||
classes = ("ajax-modal",)
|
|
||||||
icon = "plus"
|
icon = "plus"
|
||||||
|
|
||||||
def allowed(self, request, cluster=None):
|
def allowed(self, request, cluster=None):
|
||||||
if (cluster and cluster.task["name"] == 'NONE' and
|
if (cluster and cluster.task["name"] == 'NONE' and
|
||||||
db_capability.is_mongodb_datastore(cluster.datastore['type'])):
|
db_capability.can_modify_cluster(cluster.datastore['type'])):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterShrink(tables.LinkAction):
|
||||||
|
name = "cluster_shrink"
|
||||||
|
verbose_name = _("Shrink Cluster")
|
||||||
|
url = "horizon:project:database_clusters:cluster_shrink_details"
|
||||||
|
classes = ("btn-danger",)
|
||||||
|
icon = "remove"
|
||||||
|
|
||||||
|
def allowed(self, request, cluster=None):
|
||||||
|
if (cluster and cluster.task["name"] == 'NONE' and
|
||||||
|
db_capability.can_modify_cluster(cluster.datastore['type'])):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -163,7 +184,8 @@ class ClustersTable(tables.DataTable):
|
|||||||
status_columns = ["task"]
|
status_columns = ["task"]
|
||||||
row_class = UpdateRow
|
row_class = UpdateRow
|
||||||
table_actions = (LaunchLink, DeleteCluster)
|
table_actions = (LaunchLink, DeleteCluster)
|
||||||
row_actions = (AddShard, ResetPassword, DeleteCluster)
|
row_actions = (ClusterGrow, ClusterShrink, ResetPassword,
|
||||||
|
DeleteCluster)
|
||||||
|
|
||||||
|
|
||||||
def get_instance_size(instance):
|
def get_instance_size(instance):
|
||||||
@ -206,3 +228,208 @@ class InstancesTable(tables.DataTable):
|
|||||||
class Meta(object):
|
class Meta(object):
|
||||||
name = "instances"
|
name = "instances"
|
||||||
verbose_name = _("Instances")
|
verbose_name = _("Instances")
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterShrinkAction(tables.BatchAction):
|
||||||
|
name = "cluster_shrink_action"
|
||||||
|
icon = "remove"
|
||||||
|
classes = ('btn-danger',)
|
||||||
|
success_url = 'horizon:project:database_clusters:index'
|
||||||
|
help_text = _("Shrinking a cluster is not recoverable.")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_present(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Shrink Cluster",
|
||||||
|
u"Shrink Cluster",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_past(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Scheduled Shrinking of Cluster",
|
||||||
|
u"Scheduled Shrinking of Cluster",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, table, request, obj_ids):
|
||||||
|
datum_display_objs = []
|
||||||
|
for datum_id in obj_ids:
|
||||||
|
datum = table.get_object_by_id(datum_id)
|
||||||
|
datum_display = table.get_object_display(datum) or datum_id
|
||||||
|
datum_display_objs.append(datum_display)
|
||||||
|
display_str = functions.lazy_join(", ", datum_display_objs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cluster_id = table.kwargs['cluster_id']
|
||||||
|
data = [{'id': instance_id} for instance_id in obj_ids]
|
||||||
|
api.trove.cluster_shrink(request, cluster_id, data)
|
||||||
|
LOG.info('%s: "%s"' %
|
||||||
|
(self._get_action_name(past=True),
|
||||||
|
display_str))
|
||||||
|
msg = _('Removed instances from cluster.')
|
||||||
|
messages.info(request, msg)
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error('Action %(action)s failed with %(ex)s for %(data)s' %
|
||||||
|
{'action': self._get_action_name(past=True).lower(),
|
||||||
|
'ex': ex.message,
|
||||||
|
'data': display_str})
|
||||||
|
msg = _('Unable to remove instances from cluster: %s')
|
||||||
|
messages.error(request, msg % ex.message)
|
||||||
|
|
||||||
|
return shortcuts.redirect(self.get_success_url(request))
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterShrinkInstancesTable(tables.DataTable):
|
||||||
|
name = tables.Column("name",
|
||||||
|
verbose_name=_("Name"))
|
||||||
|
status = tables.Column("status",
|
||||||
|
filters=(title, filters.replace_underscores),
|
||||||
|
verbose_name=_("Status"))
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = "shrink_cluster_table"
|
||||||
|
verbose_name = _("Instances")
|
||||||
|
table_actions = (ClusterShrinkAction,)
|
||||||
|
row_actions = (ClusterShrinkAction,)
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterGrowAddInstance(tables.LinkAction):
|
||||||
|
name = "cluster_grow_add_instance"
|
||||||
|
verbose_name = _("Add Instance")
|
||||||
|
url = "horizon:project:database_clusters:add_instance"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
|
||||||
|
def get_link_url(self):
|
||||||
|
return urlresolvers.reverse(
|
||||||
|
self.url, args=[self.table.kwargs['cluster_id']])
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterGrowRemoveInstance(tables.BatchAction):
|
||||||
|
name = "cluster_grow_remove_instance"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_present(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Remove Instance",
|
||||||
|
u"Remove Instances",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_past(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Removed Instance",
|
||||||
|
u"Removed Instances",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
def action(self, request, datum_id):
|
||||||
|
manager = cluster_manager.get(self.table.kwargs['cluster_id'])
|
||||||
|
manager.delete_instance(datum_id)
|
||||||
|
|
||||||
|
def handle(self, table, request, obj_ids):
|
||||||
|
action_success = []
|
||||||
|
action_failure = []
|
||||||
|
action_not_allowed = []
|
||||||
|
for datum_id in obj_ids:
|
||||||
|
datum = table.get_object_by_id(datum_id)
|
||||||
|
datum_display = table.get_object_display(datum) or datum_id
|
||||||
|
if not table._filter_action(self, request, datum):
|
||||||
|
action_not_allowed.append(datum_display)
|
||||||
|
LOG.warning('Permission denied to %s: "%s"' %
|
||||||
|
(self._get_action_name(past=True).lower(),
|
||||||
|
datum_display))
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
self.action(request, datum_id)
|
||||||
|
# Call update to invoke changes if needed
|
||||||
|
self.update(request, datum)
|
||||||
|
action_success.append(datum_display)
|
||||||
|
self.success_ids.append(datum_id)
|
||||||
|
LOG.info('%s: "%s"' %
|
||||||
|
(self._get_action_name(past=True), datum_display))
|
||||||
|
except Exception as ex:
|
||||||
|
# Handle the exception but silence it since we'll display
|
||||||
|
# an aggregate error message later. Otherwise we'd get
|
||||||
|
# multiple error messages displayed to the user.
|
||||||
|
action_failure.append(datum_display)
|
||||||
|
action_description = (
|
||||||
|
self._get_action_name(past=True).lower(), datum_display)
|
||||||
|
LOG.error(
|
||||||
|
'Action %(action)s Failed for %(reason)s', {
|
||||||
|
'action': action_description, 'reason': ex})
|
||||||
|
|
||||||
|
if action_not_allowed:
|
||||||
|
msg = _('You are not allowed to %(action)s: %(objs)s')
|
||||||
|
params = {"action":
|
||||||
|
self._get_action_name(action_not_allowed).lower(),
|
||||||
|
"objs": functions.lazy_join(", ", action_not_allowed)}
|
||||||
|
messages.error(request, msg % params)
|
||||||
|
if action_failure:
|
||||||
|
msg = _('Unable to %(action)s: %(objs)s')
|
||||||
|
params = {"action": self._get_action_name(action_failure).lower(),
|
||||||
|
"objs": functions.lazy_join(", ", action_failure)}
|
||||||
|
messages.error(request, msg % params)
|
||||||
|
|
||||||
|
return shortcuts.redirect(self.get_success_url(request))
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterGrowAction(tables.Action):
|
||||||
|
name = "grow_cluster_action"
|
||||||
|
verbose_name = _("Grow Cluster")
|
||||||
|
verbose_name_plural = _("Grow Cluster")
|
||||||
|
requires_input = False
|
||||||
|
icon = "plus"
|
||||||
|
|
||||||
|
def handle(self, table, request, obj_ids):
|
||||||
|
if not table.data:
|
||||||
|
msg = _("Cannot grow cluster. No instances specified.")
|
||||||
|
messages.info(request, msg)
|
||||||
|
return shortcuts.redirect(request.build_absolute_uri())
|
||||||
|
|
||||||
|
datum_display_objs = []
|
||||||
|
for instance in table.data:
|
||||||
|
msg = _("[flavor=%(flavor)s, volume=%(volume)s, name=%(name)s, "
|
||||||
|
"type=%(type)s, related_to=%(related_to)s]")
|
||||||
|
params = {"flavor": instance.flavor_id, "volume": instance.volume,
|
||||||
|
"name": instance.name, "type": instance.type,
|
||||||
|
"related_to": instance.related_to}
|
||||||
|
datum_display_objs.append(msg % params)
|
||||||
|
display_str = functions.lazy_join(", ", datum_display_objs)
|
||||||
|
|
||||||
|
cluster_id = table.kwargs['cluster_id']
|
||||||
|
try:
|
||||||
|
api.trove.cluster_grow(request, cluster_id, table.data)
|
||||||
|
LOG.info('%s: "%s"' % (_("Grow Cluster"), display_str))
|
||||||
|
msg = _('Scheduled growing of cluster.')
|
||||||
|
messages.success(request, msg)
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error('Action grow cluster failed with %(ex)s for %(data)s' %
|
||||||
|
{'ex': ex.message,
|
||||||
|
'data': display_str})
|
||||||
|
msg = _('Unable to grow cluster: %s')
|
||||||
|
messages.error(request, msg % ex.message)
|
||||||
|
finally:
|
||||||
|
cluster_manager.delete(cluster_id)
|
||||||
|
|
||||||
|
return shortcuts.redirect(urlresolvers.reverse(
|
||||||
|
"horizon:project:database_clusters:index"))
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterGrowInstancesTable(tables.DataTable):
|
||||||
|
id = tables.Column("id", hidden=True)
|
||||||
|
name = tables.Column("name", verbose_name=_("Name"))
|
||||||
|
flavor = tables.Column("flavor", verbose_name=_("Flavor"))
|
||||||
|
flavor_id = tables.Column("flavor_id", hidden=True)
|
||||||
|
volume = tables.Column("volume", verbose_name=_("Volume"))
|
||||||
|
type = tables.Column("type", verbose_name=_("Instance Type"))
|
||||||
|
related_to = tables.Column("related_to", verbose_name=_("Related To"))
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = "cluster_grow_instances_table"
|
||||||
|
verbose_name = _("Instances")
|
||||||
|
table_actions = (ClusterGrowAddInstance, ClusterGrowRemoveInstance,
|
||||||
|
ClusterGrowAction)
|
||||||
|
row_actions = (ClusterGrowRemoveInstance,)
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-body-right %}
|
||||||
|
<p>{% trans "Specify the details of the instance to be added to the cluster." %}</p>
|
||||||
|
<p>{% trans "The name field is optional. If the field is left blank a name will be generated when the cluster is grown." %}</p>
|
||||||
|
<p>{% trans "The 'Instance Type' and 'Related To' fields are datastore specific and optional. See the Trove documentation for more information on using these fields." %}</p>
|
||||||
|
{% endblock %}
|
@ -1,25 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load url from future %}
|
|
||||||
|
|
||||||
{% block form_id %}add_shard_form{% endblock %}
|
|
||||||
{% block form_action %}{% url "horizon:project:database_clusters:add_shard" cluster_id %}{% endblock %}
|
|
||||||
|
|
||||||
{% block modal_id %}add_shard_modal{% endblock %}
|
|
||||||
{% block modal-header %}{% trans "Add Shard" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<div class="left">
|
|
||||||
<fieldset>
|
|
||||||
{% include "horizon/common/_form_fields.html" %}
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<p>{% blocktrans %}Specify the details for adding additional shards.{% endblocktrans %}</p>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-footer %}
|
|
||||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Add Shard" %}" />
|
|
||||||
<a href="{% url "horizon:project:database_clusters:index" %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
|
||||||
{% endblock %}
|
|
@ -0,0 +1,5 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "project/database_clusters/_add_instance.html" %}
|
||||||
|
{% endblock %}
|
@ -1,7 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Add Shard" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include "project/database_clusters/_add_shard.html" %}
|
|
||||||
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<hr>
|
||||||
|
<div class="help_text">
|
||||||
|
{% trans "Specify the instances to be added to the cluster. When all the instances are specified click 'Grow Cluster' to perform the grow operation." %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{{ table.render }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,14 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<hr>
|
||||||
|
<div class="help_text">
|
||||||
|
{% trans "Select the instance(s) that will be removed from the cluster." %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{{ table.render }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -23,12 +23,14 @@ from openstack_dashboard import api
|
|||||||
from troveclient import common
|
from troveclient import common
|
||||||
|
|
||||||
from trove_dashboard import api as trove_api
|
from trove_dashboard import api as trove_api
|
||||||
|
from trove_dashboard.content.database_clusters \
|
||||||
|
import cluster_manager
|
||||||
|
from trove_dashboard.content.database_clusters import tables
|
||||||
from trove_dashboard.test import helpers as test
|
from trove_dashboard.test import helpers as test
|
||||||
|
|
||||||
INDEX_URL = reverse('horizon:project:database_clusters:index')
|
INDEX_URL = reverse('horizon:project:database_clusters:index')
|
||||||
LAUNCH_URL = reverse('horizon:project:database_clusters:launch')
|
LAUNCH_URL = reverse('horizon:project:database_clusters:launch')
|
||||||
DETAILS_URL = reverse('horizon:project:database_clusters:detail', args=['id'])
|
DETAILS_URL = reverse('horizon:project:database_clusters:detail', args=['id'])
|
||||||
ADD_SHARD_VIEWNAME = 'horizon:project:database_clusters:add_shard'
|
|
||||||
RESET_PASSWORD_VIEWNAME = 'horizon:project:database_clusters:reset_password'
|
RESET_PASSWORD_VIEWNAME = 'horizon:project:database_clusters:reset_password'
|
||||||
|
|
||||||
|
|
||||||
@ -376,6 +378,179 @@ class ClustersTests(test.TestCase):
|
|||||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
||||||
self.assertContains(res, cluster.ip[0])
|
self.assertContains(res, cluster.ip[0])
|
||||||
|
|
||||||
|
@test.create_stubs(
|
||||||
|
{trove_api.trove: ('cluster_get',
|
||||||
|
'cluster_grow'),
|
||||||
|
cluster_manager: ('get',)})
|
||||||
|
def test_grow_cluster(self):
|
||||||
|
cluster = self.trove_clusters.first()
|
||||||
|
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
||||||
|
.AndReturn(cluster)
|
||||||
|
cluster_volume = 1
|
||||||
|
flavor = self.flavors.first()
|
||||||
|
cluster_flavor = flavor.id
|
||||||
|
cluster_flavor_name = flavor.name
|
||||||
|
instances = [
|
||||||
|
cluster_manager.ClusterInstance("id1", "name1", cluster_flavor,
|
||||||
|
cluster_flavor_name,
|
||||||
|
cluster_volume, "master", None),
|
||||||
|
cluster_manager.ClusterInstance("id2", "name2", cluster_flavor,
|
||||||
|
cluster_flavor_name,
|
||||||
|
cluster_volume, "slave", "master"),
|
||||||
|
cluster_manager.ClusterInstance("id3", None, cluster_flavor,
|
||||||
|
cluster_flavor_name,
|
||||||
|
cluster_volume, None, None),
|
||||||
|
]
|
||||||
|
|
||||||
|
manager = cluster_manager.ClusterInstanceManager(cluster.id)
|
||||||
|
manager.instances = instances
|
||||||
|
cluster_manager.get(cluster.id).MultipleTimes().AndReturn(manager)
|
||||||
|
trove_api.trove.cluster_grow(IsA(http.HttpRequest),
|
||||||
|
cluster.id,
|
||||||
|
instances)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
url = reverse('horizon:project:database_clusters:cluster_grow_details',
|
||||||
|
args=[cluster.id])
|
||||||
|
res = self.client.get(url)
|
||||||
|
self.assertTemplateUsed(
|
||||||
|
res, 'project/database_clusters/cluster_grow_details.html')
|
||||||
|
table = res.context_data[
|
||||||
|
"".join([tables.ClusterGrowInstancesTable.Meta.name, '_table'])]
|
||||||
|
self.assertEqual(len(cluster.instances), len(table.data))
|
||||||
|
|
||||||
|
action = "".join([tables.ClusterGrowInstancesTable.Meta.name, '__',
|
||||||
|
tables.ClusterGrowRemoveInstance.name, '__',
|
||||||
|
'id1'])
|
||||||
|
self.client.post(url, {'action': action})
|
||||||
|
self.assertEqual(len(cluster.instances) - 1, len(table.data))
|
||||||
|
|
||||||
|
action = "".join([tables.ClusterGrowInstancesTable.Meta.name, '__',
|
||||||
|
tables.ClusterGrowAction.name, '__',
|
||||||
|
cluster.id])
|
||||||
|
res = self.client.post(url, {'action': action})
|
||||||
|
self.assertMessageCount(success=1)
|
||||||
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
|
||||||
|
@test.create_stubs({trove_api.trove: ('cluster_get',)})
|
||||||
|
def test_grow_cluster_no_instances(self):
|
||||||
|
cluster = self.trove_clusters.first()
|
||||||
|
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
||||||
|
.AndReturn(cluster)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
url = reverse('horizon:project:database_clusters:cluster_grow_details',
|
||||||
|
args=[cluster.id])
|
||||||
|
res = self.client.get(url)
|
||||||
|
self.assertTemplateUsed(
|
||||||
|
res, 'project/database_clusters/cluster_grow_details.html')
|
||||||
|
|
||||||
|
action = "".join([tables.ClusterGrowInstancesTable.Meta.name, '__',
|
||||||
|
tables.ClusterGrowAction.name, '__',
|
||||||
|
cluster.id])
|
||||||
|
self.client.post(url, {'action': action})
|
||||||
|
self.assertMessageCount(info=1)
|
||||||
|
|
||||||
|
@test.create_stubs(
|
||||||
|
{trove_api.trove: ('cluster_get',
|
||||||
|
'cluster_grow',),
|
||||||
|
cluster_manager: ('get',)})
|
||||||
|
def test_grow_cluster_exception(self):
|
||||||
|
cluster = self.trove_clusters.first()
|
||||||
|
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
||||||
|
.AndReturn(cluster)
|
||||||
|
cluster_volume = 1
|
||||||
|
flavor = self.flavors.first()
|
||||||
|
cluster_flavor = flavor.id
|
||||||
|
cluster_flavor_name = flavor.name
|
||||||
|
instances = [
|
||||||
|
cluster_manager.ClusterInstance("id1", "name1", cluster_flavor,
|
||||||
|
cluster_flavor_name,
|
||||||
|
cluster_volume, "master", None),
|
||||||
|
cluster_manager.ClusterInstance("id2", "name2", cluster_flavor,
|
||||||
|
cluster_flavor_name,
|
||||||
|
cluster_volume, "slave", "master"),
|
||||||
|
cluster_manager.ClusterInstance("id3", None, cluster_flavor,
|
||||||
|
cluster_flavor_name,
|
||||||
|
cluster_volume, None, None),
|
||||||
|
]
|
||||||
|
|
||||||
|
manager = cluster_manager.ClusterInstanceManager(cluster.id)
|
||||||
|
manager.instances = instances
|
||||||
|
cluster_manager.get(cluster.id).MultipleTimes().AndReturn(manager)
|
||||||
|
trove_api.trove.cluster_grow(IsA(http.HttpRequest),
|
||||||
|
cluster.id,
|
||||||
|
instances).AndRaise(self.exceptions.trove)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
url = reverse('horizon:project:database_clusters:cluster_grow_details',
|
||||||
|
args=[cluster.id])
|
||||||
|
res = self.client.get(url)
|
||||||
|
self.assertTemplateUsed(
|
||||||
|
res, 'project/database_clusters/cluster_grow_details.html')
|
||||||
|
|
||||||
|
action = "".join([tables.ClusterGrowInstancesTable.Meta.name, '__',
|
||||||
|
tables.ClusterGrowAction.name, '__',
|
||||||
|
cluster.id])
|
||||||
|
res = self.client.post(url, {'action': action})
|
||||||
|
self.assertMessageCount(error=1)
|
||||||
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
|
||||||
|
@test.create_stubs({trove_api.trove: ('cluster_get',
|
||||||
|
'cluster_shrink')})
|
||||||
|
def test_shrink_cluster(self):
|
||||||
|
cluster = self.trove_clusters.first()
|
||||||
|
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
||||||
|
.MultipleTimes().AndReturn(cluster)
|
||||||
|
instance_id = cluster.instances[0]['id']
|
||||||
|
cluster_instances = [{'id': instance_id}]
|
||||||
|
trove_api.trove.cluster_shrink(IsA(http.HttpRequest),
|
||||||
|
cluster.id,
|
||||||
|
cluster_instances)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
url = reverse(
|
||||||
|
'horizon:project:database_clusters:cluster_shrink_details',
|
||||||
|
args=[cluster.id])
|
||||||
|
res = self.client.get(url)
|
||||||
|
self.assertTemplateUsed(
|
||||||
|
res, 'project/database_clusters/cluster_shrink_details.html')
|
||||||
|
table = res.context_data[
|
||||||
|
"".join([tables.ClusterShrinkInstancesTable.Meta.name, '_table'])]
|
||||||
|
self.assertEqual(len(cluster.instances), len(table.data))
|
||||||
|
|
||||||
|
action = "".join([tables.ClusterShrinkInstancesTable.Meta.name, '__',
|
||||||
|
tables.ClusterShrinkAction.name, '__',
|
||||||
|
instance_id])
|
||||||
|
res = self.client.post(url, {'action': action})
|
||||||
|
self.assertNoFormErrors(res)
|
||||||
|
self.assertMessageCount(info=1)
|
||||||
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
|
||||||
|
@test.create_stubs({trove_api.trove: ('cluster_get',
|
||||||
|
'cluster_shrink')})
|
||||||
|
def test_shrink_cluster_exception(self):
|
||||||
|
cluster = self.trove_clusters.first()
|
||||||
|
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
||||||
|
.MultipleTimes().AndReturn(cluster)
|
||||||
|
cluster_id = cluster.instances[0]['id']
|
||||||
|
cluster_instances = [cluster_id]
|
||||||
|
trove_api.trove.cluster_shrink(IsA(http.HttpRequest),
|
||||||
|
cluster.id,
|
||||||
|
cluster_instances)\
|
||||||
|
.AndRaise(self.exceptions.trove)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
url = reverse(
|
||||||
|
'horizon:project:database_clusters:cluster_shrink_details',
|
||||||
|
args=[cluster.id])
|
||||||
|
action = "".join([tables.ClusterShrinkInstancesTable.Meta.name, '__',
|
||||||
|
tables.ClusterShrinkAction.name, '__',
|
||||||
|
cluster_id])
|
||||||
|
res = self.client.post(url, {'action': action})
|
||||||
|
self.assertMessageCount(error=1)
|
||||||
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
|
||||||
def _get_filtered_datastores(self, datastore):
|
def _get_filtered_datastores(self, datastore):
|
||||||
filtered_datastore = []
|
filtered_datastore = []
|
||||||
for ds in self.datastores.list():
|
for ds in self.datastores.list():
|
||||||
|
@ -27,8 +27,16 @@ urlpatterns = patterns(
|
|||||||
url(r'^launch$', views.LaunchClusterView.as_view(), name='launch'),
|
url(r'^launch$', views.LaunchClusterView.as_view(), name='launch'),
|
||||||
url(r'^(?P<cluster_id>[^/]+)/$', views.DetailView.as_view(),
|
url(r'^(?P<cluster_id>[^/]+)/$', views.DetailView.as_view(),
|
||||||
name='detail'),
|
name='detail'),
|
||||||
url(CLUSTERS % 'add_shard', views.AddShardView.as_view(),
|
url(CLUSTERS % 'cluster_grow_details',
|
||||||
name='add_shard'),
|
views.ClusterGrowView.as_view(),
|
||||||
url(CLUSTERS % 'reset_password', views.ResetPasswordView.as_view(),
|
name='cluster_grow_details'),
|
||||||
|
url(CLUSTERS % 'add_instance',
|
||||||
|
views.ClusterAddInstancesView.as_view(),
|
||||||
|
name='add_instance'),
|
||||||
|
url(CLUSTERS % 'cluster_shrink_details',
|
||||||
|
views.ClusterShrinkView.as_view(),
|
||||||
|
name='cluster_shrink_details'),
|
||||||
|
url(CLUSTERS % 'reset_password',
|
||||||
|
views.ResetPasswordView.as_view(),
|
||||||
name='reset_password'),
|
name='reset_password'),
|
||||||
)
|
)
|
||||||
|
@ -33,6 +33,8 @@ from horizon import tabs as horizon_tabs
|
|||||||
from horizon.utils import memoized
|
from horizon.utils import memoized
|
||||||
|
|
||||||
from trove_dashboard import api
|
from trove_dashboard import api
|
||||||
|
from trove_dashboard.content.database_clusters \
|
||||||
|
import cluster_manager
|
||||||
from trove_dashboard.content.database_clusters import forms
|
from trove_dashboard.content.database_clusters import forms
|
||||||
from trove_dashboard.content.database_clusters import tables
|
from trove_dashboard.content.database_clusters import tables
|
||||||
from trove_dashboard.content.database_clusters import tabs
|
from trove_dashboard.content.database_clusters import tabs
|
||||||
@ -137,57 +139,92 @@ class DetailView(horizon_tabs.TabbedTableView):
|
|||||||
return self.tab_group_class(request, cluster=cluster, **kwargs)
|
return self.tab_group_class(request, cluster=cluster, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AddShardView(horizon_forms.ModalFormView):
|
class ClusterGrowView(horizon_tables.DataTableView):
|
||||||
form_class = forms.AddShardForm
|
table_class = tables.ClusterGrowInstancesTable
|
||||||
template_name = 'project/database_clusters/add_shard.html'
|
template_name = 'project/database_clusters/cluster_grow_details.html'
|
||||||
success_url = reverse_lazy('horizon:project:database_clusters:index')
|
page_title = _("Grow Cluster: {{cluster_name}}")
|
||||||
page_title = _("Add Shard")
|
|
||||||
|
def get_data(self):
|
||||||
|
manager = cluster_manager.get(self.kwargs['cluster_id'])
|
||||||
|
return manager.get_instances()
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(AddShardView, self).get_context_data(**kwargs)
|
context = super(ClusterGrowView, self).get_context_data(**kwargs)
|
||||||
context["cluster_id"] = self.kwargs['cluster_id']
|
context['cluster_id'] = self.kwargs['cluster_id']
|
||||||
|
cluster = self.get_cluster(self.kwargs['cluster_id'])
|
||||||
|
context['cluster_name'] = cluster.name
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_object(self, *args, **kwargs):
|
@memoized.memoized_method
|
||||||
if not hasattr(self, "_object"):
|
def get_cluster(self, cluster_id):
|
||||||
cluster_id = self.kwargs['cluster_id']
|
|
||||||
try:
|
try:
|
||||||
self._object = api.trove.cluster_get(self.request, cluster_id)
|
return api.trove.cluster_get(self.request, cluster_id)
|
||||||
# TODO(michayu): assumption that cluster is homogeneous
|
|
||||||
flavor_id = self._object.instances[0]['flavor']['id']
|
|
||||||
flavors = self.get_flavors()
|
|
||||||
if flavor_id in flavors:
|
|
||||||
self._object.flavor_name = flavors[flavor_id].name
|
|
||||||
else:
|
|
||||||
flavor = api.trove.flavor_get(self.request, flavor_id)
|
|
||||||
self._object.flavor_name = flavor.name
|
|
||||||
except Exception:
|
except Exception:
|
||||||
redirect = reverse("horizon:project:database_clusters:index")
|
redirect = reverse("horizon:project:database_clusters:index")
|
||||||
msg = _('Unable to retrieve cluster details.')
|
msg = _('Unable to retrieve cluster details.')
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
exceptions.handle(self.request, msg, redirect=redirect)
|
||||||
return self._object
|
|
||||||
|
|
||||||
def get_flavors(self, *args, **kwargs):
|
|
||||||
if not hasattr(self, "_flavors"):
|
class ClusterAddInstancesView(horizon_forms.ModalFormView):
|
||||||
|
form_class = forms.ClusterAddInstanceForm
|
||||||
|
form_id = "cluster_add_instances_form"
|
||||||
|
modal_header = _("Add Instance")
|
||||||
|
modal_id = "cluster_add_instances_modal"
|
||||||
|
template_name = "project/database_clusters/add_instance.html"
|
||||||
|
submit_label = _("Add")
|
||||||
|
submit_url = "horizon:project:database_clusters:add_instance"
|
||||||
|
success_url = "horizon:project:database_clusters:cluster_grow_details"
|
||||||
|
cancel_url = "horizon:project:database_clusters:cluster_grow_details"
|
||||||
|
page_title = _("Add Instance")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = (super(ClusterAddInstancesView, self)
|
||||||
|
.get_context_data(**kwargs))
|
||||||
|
context['cluster_id'] = self.kwargs['cluster_id']
|
||||||
|
args = (self.kwargs['cluster_id'],)
|
||||||
|
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(self.success_url, args=[self.kwargs['cluster_id']])
|
||||||
|
|
||||||
|
def get_cancel_url(self):
|
||||||
|
return reverse(self.cancel_url, args=[self.kwargs['cluster_id']])
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterInstance(object):
|
||||||
|
def __init__(self, id, name, status):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterShrinkView(horizon_tables.DataTableView):
|
||||||
|
table_class = tables.ClusterShrinkInstancesTable
|
||||||
|
template_name = "project/database_clusters/cluster_shrink_details.html"
|
||||||
|
page_title = _("Shrink Cluster: {{cluster_name}}")
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_cluster(self, cluster_id):
|
||||||
try:
|
try:
|
||||||
flavors = api.trove.flavor_list(self.request)
|
return api.trove.cluster_get(self.request, cluster_id)
|
||||||
self._flavors = OrderedDict([(str(flavor.id), flavor)
|
|
||||||
for flavor in flavors])
|
|
||||||
except Exception:
|
except Exception:
|
||||||
redirect = reverse("horizon:project:database_clusters:index")
|
redirect = reverse("horizon:project:database_clusters:index")
|
||||||
exceptions.handle(
|
msg = _('Unable to retrieve cluster details.')
|
||||||
self.request,
|
exceptions.handle(self.request, msg, redirect=redirect)
|
||||||
_('Unable to retrieve flavors.'), redirect=redirect)
|
|
||||||
return self._flavors
|
|
||||||
|
|
||||||
def get_initial(self):
|
def get_data(self):
|
||||||
initial = super(AddShardView, self).get_initial()
|
cluster = self.get_cluster(self.kwargs['cluster_id'])
|
||||||
_object = self.get_object()
|
instances = [ClusterInstance(i['id'], i['name'], i['status'])
|
||||||
if _object:
|
for i in cluster.instances]
|
||||||
initial.update(
|
return instances
|
||||||
{'cluster_id': self.kwargs['cluster_id'],
|
|
||||||
'name': getattr(_object, 'name', None)})
|
def get_context_data(self, **kwargs):
|
||||||
return initial
|
context = super(ClusterShrinkView, self).get_context_data(**kwargs)
|
||||||
|
context['cluster_id'] = self.kwargs['cluster_id']
|
||||||
|
cluster = self.get_cluster(self.kwargs['cluster_id'])
|
||||||
|
context['cluster_name'] = cluster.name
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordView(horizon_forms.ModalFormView):
|
class ResetPasswordView(horizon_forms.ModalFormView):
|
||||||
|
@ -21,6 +21,10 @@ VERTICA = "vertica"
|
|||||||
_cluster_capable_datastores = (MONGODB, PERCONA_CLUSTER, REDIS, VERTICA)
|
_cluster_capable_datastores = (MONGODB, PERCONA_CLUSTER, REDIS, VERTICA)
|
||||||
|
|
||||||
|
|
||||||
|
def can_modify_cluster(datastore):
|
||||||
|
return (is_mongodb_datastore(datastore) or is_redis_datastore(datastore))
|
||||||
|
|
||||||
|
|
||||||
def is_mongodb_datastore(datastore):
|
def is_mongodb_datastore(datastore):
|
||||||
return (datastore is not None) and (MONGODB in datastore.lower())
|
return (datastore is not None) and (MONGODB in datastore.lower())
|
||||||
|
|
||||||
|
@ -40,6 +40,8 @@ CLUSTER_DATA_ONE = {
|
|||||||
{
|
{
|
||||||
"id": "416b0b16-ba55-4302-bbd3-ff566032e1c1",
|
"id": "416b0b16-ba55-4302-bbd3-ff566032e1c1",
|
||||||
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
||||||
|
"name": "inst1",
|
||||||
|
"status": "ACTIVE",
|
||||||
"flavor": {
|
"flavor": {
|
||||||
"id": "7",
|
"id": "7",
|
||||||
"links": []
|
"links": []
|
||||||
@ -51,6 +53,8 @@ CLUSTER_DATA_ONE = {
|
|||||||
{
|
{
|
||||||
"id": "965ef811-7c1d-47fc-89f2-a89dfdd23ef2",
|
"id": "965ef811-7c1d-47fc-89f2-a89dfdd23ef2",
|
||||||
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
||||||
|
"name": "inst2",
|
||||||
|
"status": "ACTIVE",
|
||||||
"flavor": {
|
"flavor": {
|
||||||
"id": "7",
|
"id": "7",
|
||||||
"links": []
|
"links": []
|
||||||
@ -62,6 +66,8 @@ CLUSTER_DATA_ONE = {
|
|||||||
{
|
{
|
||||||
"id": "3642f41c-e8ad-4164-a089-3891bf7f2d2b",
|
"id": "3642f41c-e8ad-4164-a089-3891bf7f2d2b",
|
||||||
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
||||||
|
"name": "inst3",
|
||||||
|
"status": "ACTIVE",
|
||||||
"flavor": {
|
"flavor": {
|
||||||
"id": "7",
|
"id": "7",
|
||||||
"links": []
|
"links": []
|
||||||
@ -91,6 +97,8 @@ CLUSTER_DATA_TWO = {
|
|||||||
"instances": [
|
"instances": [
|
||||||
{
|
{
|
||||||
"id": "416b0b16-ba55-4302-bbd3-ff566032e1c1",
|
"id": "416b0b16-ba55-4302-bbd3-ff566032e1c1",
|
||||||
|
"name": "inst1",
|
||||||
|
"status": "ACTIVE",
|
||||||
"flavor": {
|
"flavor": {
|
||||||
"id": "7",
|
"id": "7",
|
||||||
"links": []
|
"links": []
|
||||||
@ -101,6 +109,8 @@ CLUSTER_DATA_TWO = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "965ef811-7c1d-47fc-89f2-a89dfdd23ef2",
|
"id": "965ef811-7c1d-47fc-89f2-a89dfdd23ef2",
|
||||||
|
"name": "inst2",
|
||||||
|
"status": "ACTIVE",
|
||||||
"flavor": {
|
"flavor": {
|
||||||
"id": "7",
|
"id": "7",
|
||||||
"links": []
|
"links": []
|
||||||
@ -111,6 +121,8 @@ CLUSTER_DATA_TWO = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "3642f41c-e8ad-4164-a089-3891bf7f2d2b",
|
"id": "3642f41c-e8ad-4164-a089-3891bf7f2d2b",
|
||||||
|
"name": "inst3",
|
||||||
|
"status": "ACTIVE",
|
||||||
"flavor": {
|
"flavor": {
|
||||||
"id": "7",
|
"id": "7",
|
||||||
"links": []
|
"links": []
|
||||||
|
Loading…
x
Reference in New Issue
Block a user