Merge "Initial support for database clustering in Horizon"
This commit is contained in:
commit
e8f84ef157
@ -42,6 +42,57 @@ def troveclient(request):
|
||||
return c
|
||||
|
||||
|
||||
def cluster_list(request, marker=None):
|
||||
page_size = utils.get_page_size(request)
|
||||
return troveclient(request).clusters.list(limit=page_size, marker=marker)
|
||||
|
||||
|
||||
def cluster_get(request, cluster_id):
|
||||
return troveclient(request).clusters.get(cluster_id)
|
||||
|
||||
|
||||
def cluster_delete(request, cluster_id):
|
||||
return troveclient(request).clusters.delete(cluster_id)
|
||||
|
||||
|
||||
def cluster_create(request, name, volume, flavor, num_instances,
|
||||
datastore, datastore_version,
|
||||
nics=None, root_password=None):
|
||||
# TODO(dklyle): adding to support trove without volume
|
||||
# support for now until API supports checking for volume support
|
||||
if volume > 0:
|
||||
volume_params = {'size': volume}
|
||||
else:
|
||||
volume_params = None
|
||||
instances = []
|
||||
for i in range(num_instances):
|
||||
instance = {}
|
||||
instance["flavorRef"] = flavor
|
||||
instance["volume"] = volume_params
|
||||
if nics:
|
||||
instance["nics"] = [{"net-id": nics}]
|
||||
instances.append(instance)
|
||||
|
||||
# TODO(saurabhs): vertica needs root password on cluster create
|
||||
return troveclient(request).clusters.create(
|
||||
name,
|
||||
datastore,
|
||||
datastore_version,
|
||||
instances=instances)
|
||||
|
||||
|
||||
def cluster_add_shard(request, cluster_id):
|
||||
return troveclient(request).clusters.add_shard(cluster_id)
|
||||
|
||||
|
||||
def create_cluster_root(request, cluster_id, password):
|
||||
# It appears the code below depends on this trove change
|
||||
# https://review.openstack.org/#/c/166954/. Comment out when that
|
||||
# change merges.
|
||||
# return troveclient(request).cluster.reset_root_password(cluster_id)
|
||||
troveclient(request).root.create_cluster_root(cluster_id, password)
|
||||
|
||||
|
||||
def instance_list(request, marker=None):
|
||||
page_size = utils.get_page_size(request)
|
||||
return troveclient(request).instances.list(limit=page_size, marker=marker)
|
||||
@ -130,6 +181,19 @@ def flavor_list(request):
|
||||
return troveclient(request).flavors.list()
|
||||
|
||||
|
||||
def datastore_flavors(request, datastore_name=None,
|
||||
datastore_version=None):
|
||||
# if datastore info is available then get datastore specific flavors
|
||||
if datastore_name and datastore_version:
|
||||
try:
|
||||
return troveclient(request).flavors.\
|
||||
list_datastore_version_associated_flavors(datastore_name,
|
||||
datastore_version)
|
||||
except Exception:
|
||||
LOG.warn("Failed to retrieve datastore specific flavors")
|
||||
return flavor_list(request)
|
||||
|
||||
|
||||
def flavor_get(request, flavor_id):
|
||||
return troveclient(request).flavors.get(flavor_id)
|
||||
|
||||
|
@ -0,0 +1,375 @@
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.debug import sensitive_variables # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.contrib.trove import api as trove_api
|
||||
from openstack_dashboard.contrib.trove.content.databases import db_capability
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LaunchForm(forms.SelfHandlingForm):
|
||||
name = forms.CharField(label=_("Cluster Name"),
|
||||
max_length=80)
|
||||
datastore = forms.ChoiceField(
|
||||
label=_("Datastore"),
|
||||
help_text=_("Type and version of datastore."),
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'datastore'
|
||||
}))
|
||||
mongodb_flavor = forms.ChoiceField(
|
||||
label=_("Flavor"),
|
||||
help_text=_("Size of instance to launch."),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
vertica_flavor = forms.ChoiceField(
|
||||
label=_("Flavor"),
|
||||
help_text=_("Size of instance to launch."),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
network = forms.ChoiceField(
|
||||
label=_("Network"),
|
||||
help_text=_("Network attached to instance."),
|
||||
required=False)
|
||||
volume = forms.IntegerField(
|
||||
label=_("Volume Size"),
|
||||
min_value=0,
|
||||
initial=1,
|
||||
help_text=_("Size of the volume in GB."))
|
||||
root_password = forms.CharField(
|
||||
label=_("Root Password"),
|
||||
required=False,
|
||||
help_text=_("Password for root user."),
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
num_instances_vertica = forms.IntegerField(
|
||||
label=_("Number of Instances"),
|
||||
min_value=3,
|
||||
initial=3,
|
||||
required=False,
|
||||
help_text=_("Number of instances in the cluster. (Read only)"),
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly',
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
num_shards = forms.IntegerField(
|
||||
label=_("Number of Shards"),
|
||||
min_value=1,
|
||||
initial=1,
|
||||
required=False,
|
||||
help_text=_("Number of shards. (Read only)"),
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly',
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
num_instances_per_shards = forms.IntegerField(
|
||||
label=_("Instances Per Shard"),
|
||||
initial=3,
|
||||
required=False,
|
||||
help_text=_("Number of instances per shard. (Read only)"),
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly',
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
|
||||
# (name of field variable, label)
|
||||
mongodb_fields = [
|
||||
('mongodb_flavor', _('Flavor')),
|
||||
('num_shards', _('Number of Shards')),
|
||||
('num_instances_per_shards', _('Instances Per Shard'))
|
||||
]
|
||||
vertica_fields = [
|
||||
('num_instances_vertica', ('Number of Instances')),
|
||||
('vertica_flavor', _('Flavor')),
|
||||
('root_password', _('Root Password')),
|
||||
]
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(LaunchForm, self).__init__(request, *args, **kwargs)
|
||||
|
||||
self.fields['datastore'].choices = self.populate_datastore_choices(
|
||||
request)
|
||||
self.populate_flavor_choices(request)
|
||||
|
||||
self.fields['network'].choices = self.populate_network_choices(
|
||||
request)
|
||||
|
||||
def clean(self):
|
||||
datastore_field_value = self.data.get("datastore", None)
|
||||
if datastore_field_value:
|
||||
datastore = datastore_field_value.split(',')[0]
|
||||
|
||||
if db_capability.is_mongodb_datastore(datastore):
|
||||
if self.data.get("num_shards", None) < 1:
|
||||
msg = _("The number of shards must be greater than 1.")
|
||||
self._errors["num_shards"] = self.error_class([msg])
|
||||
|
||||
elif db_capability.is_vertica_datastore(datastore):
|
||||
if not self.data.get("vertica_flavor", None):
|
||||
msg = _("The flavor must be specified.")
|
||||
self._errors["vertica_flavor"] = self.error_class([msg])
|
||||
if not self.data.get("root_password", None):
|
||||
msg = _("Password for root user must be specified.")
|
||||
self._errors["root_password"] = self.error_class([msg])
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
@memoized.memoized_method
|
||||
def datastore_flavors(self, request, datastore_name, datastore_version):
|
||||
try:
|
||||
return trove_api.trove.datastore_flavors(
|
||||
request, datastore_name, 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):
|
||||
valid_flavor = []
|
||||
for ds in self.datastores(request):
|
||||
# TODO(michayu): until capabilities lands
|
||||
if db_capability.is_mongodb_datastore(ds.name):
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
for version in versions:
|
||||
if version.name == "inactive":
|
||||
continue
|
||||
valid_flavor = self.datastore_flavors(request, ds.name,
|
||||
versions[0].name)
|
||||
if valid_flavor:
|
||||
self.fields['mongodb_flavor'].choices = sorted(
|
||||
[(f.id, "%s" % f.name) for f in valid_flavor])
|
||||
|
||||
if db_capability.is_vertica_datastore(ds.name):
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
for version in versions:
|
||||
if version.name == "inactive":
|
||||
continue
|
||||
valid_flavor = self.datastore_flavors(request, ds.name,
|
||||
versions[0].name)
|
||||
if valid_flavor:
|
||||
self.fields['vertica_flavor'].choices = sorted(
|
||||
[(f.id, "%s" % f.name) for f in valid_flavor])
|
||||
|
||||
@memoized.memoized_method
|
||||
def populate_network_choices(self, request):
|
||||
network_list = []
|
||||
try:
|
||||
if api.base.is_service_enabled(request, 'network'):
|
||||
tenant_id = self.request.user.tenant_id
|
||||
networks = api.neutron.network_list_for_tenant(request,
|
||||
tenant_id)
|
||||
network_list = [(network.id, network.name_or_id)
|
||||
for network in networks]
|
||||
else:
|
||||
self.fields['network'].widget = forms.HiddenInput()
|
||||
except exceptions.ServiceCatalogException:
|
||||
network_list = []
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve networks.'),
|
||||
redirect=redirect)
|
||||
return network_list
|
||||
|
||||
@memoized.memoized_method
|
||||
def datastores(self, request):
|
||||
try:
|
||||
return trove_api.trove.datastore_list(request)
|
||||
except Exception:
|
||||
LOG.exception("Exception while obtaining datastores list")
|
||||
self._datastores = []
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
exceptions.handle(request,
|
||||
_('Unable to obtain datastores.'),
|
||||
redirect=redirect)
|
||||
|
||||
def filter_cluster_datastores(self, request):
|
||||
datastores = []
|
||||
for ds in self.datastores(request):
|
||||
# TODO(michayu): until capabilities lands
|
||||
if (db_capability.is_vertica_datastore(ds.name)
|
||||
or db_capability.is_mongodb_datastore(ds.name)):
|
||||
datastores.append(ds)
|
||||
return datastores
|
||||
|
||||
@memoized.memoized_method
|
||||
def datastore_versions(self, request, datastore):
|
||||
try:
|
||||
return trove_api.trove.datastore_version_list(request, datastore)
|
||||
except Exception:
|
||||
LOG.exception("Exception while obtaining datastore version list")
|
||||
self._datastore_versions = []
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
exceptions.handle(request,
|
||||
_('Unable to obtain datastore versions.'),
|
||||
redirect=redirect)
|
||||
|
||||
def populate_datastore_choices(self, request):
|
||||
choices = ()
|
||||
datastores = self.filter_cluster_datastores(request)
|
||||
if datastores is not None:
|
||||
for ds in datastores:
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
if versions:
|
||||
# only add to choices if datastore has at least one version
|
||||
version_choices = ()
|
||||
for v in versions:
|
||||
if "inactive" in v.name:
|
||||
continue
|
||||
selection_text = ds.name + ' - ' + v.name
|
||||
widget_text = ds.name + '-' + v.name
|
||||
version_choices = (version_choices +
|
||||
((widget_text, selection_text),))
|
||||
self._add_attr_to_optional_fields(ds.name,
|
||||
widget_text)
|
||||
|
||||
choices = choices + version_choices
|
||||
return choices
|
||||
|
||||
def _add_attr_to_optional_fields(self, datastore, selection_text):
|
||||
fields = []
|
||||
if db_capability.is_mongodb_datastore(datastore):
|
||||
fields = self.mongodb_fields
|
||||
elif db_capability.is_vertica_datastore(datastore):
|
||||
fields = self.vertica_fields
|
||||
|
||||
for field in fields:
|
||||
attr_key = 'data-datastore-' + selection_text
|
||||
widget = self.fields[field[0]].widget
|
||||
if attr_key not in widget.attrs:
|
||||
widget.attrs[attr_key] = field[1]
|
||||
|
||||
@sensitive_variables('data')
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
datastore = data['datastore'].split('-')[0]
|
||||
datastore_version = data['datastore'].split('-')[1]
|
||||
|
||||
final_flavor = data['mongodb_flavor']
|
||||
num_instances = data['num_instances_per_shards']
|
||||
root_password = None
|
||||
if db_capability.is_vertica_datastore(datastore):
|
||||
final_flavor = data['vertica_flavor']
|
||||
root_password = data['root_password']
|
||||
num_instances = data['num_instances_vertica']
|
||||
LOG.info("Launching cluster with parameters "
|
||||
"{name=%s, volume=%s, flavor=%s, "
|
||||
"datastore=%s, datastore_version=%s",
|
||||
data['name'], data['volume'], final_flavor,
|
||||
datastore, datastore_version)
|
||||
|
||||
trove_api.trove.cluster_create(request,
|
||||
data['name'],
|
||||
data['volume'],
|
||||
final_flavor,
|
||||
num_instances,
|
||||
datastore=datastore,
|
||||
datastore_version=datastore_version,
|
||||
nics=data['network'],
|
||||
root_password=root_password)
|
||||
messages.success(request,
|
||||
_('Launched cluster "%s"') % data['name'])
|
||||
return True
|
||||
except Exception as e:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to launch cluster. %s') % e.message,
|
||||
redirect=redirect)
|
||||
|
||||
|
||||
class AddShardForm(forms.SelfHandlingForm):
|
||||
name = forms.CharField(
|
||||
label=_("Cluster Name"),
|
||||
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())
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.info("Adding shard with parameters "
|
||||
"{name=%s, num_shards=%s, num_instances=%s, "
|
||||
"cluster_id=%s}",
|
||||
data['name'],
|
||||
data['num_shards'],
|
||||
data['num_instances'],
|
||||
data['cluster_id'])
|
||||
trove_api.trove.cluster_add_shard(request, data['cluster_id'])
|
||||
|
||||
messages.success(request,
|
||||
_('Added shard to "%s"') % data['name'])
|
||||
except Exception as e:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to add shard. %s') % e.message,
|
||||
redirect=redirect)
|
||||
return True
|
||||
|
||||
|
||||
class ResetPasswordForm(forms.SelfHandlingForm):
|
||||
cluster_id = forms.CharField(widget=forms.HiddenInput())
|
||||
password = forms.CharField(widget=forms.PasswordInput(),
|
||||
label=_("New Password"),
|
||||
required=True,
|
||||
help_text=_("New password for cluster access."))
|
||||
|
||||
@sensitive_variables('data')
|
||||
def handle(self, request, data):
|
||||
password = data.get("password")
|
||||
cluster_id = data.get("cluster_id")
|
||||
try:
|
||||
trove_api.trove.create_cluster_root(request,
|
||||
cluster_id,
|
||||
password)
|
||||
messages.success(request, _('Root password updated for '
|
||||
'cluster "%s"') % cluster_id)
|
||||
except Exception as e:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
exceptions.handle(request, _('Unable to reset password. %s') %
|
||||
e.message, redirect=redirect)
|
||||
return True
|
@ -0,0 +1,26 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
|
||||
class Clusters(horizon.Panel):
|
||||
name = _("Clusters")
|
||||
slug = 'database_clusters'
|
||||
permissions = ('openstack.services.database',
|
||||
'openstack.services.object-store',)
|
@ -0,0 +1,205 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core import urlresolvers
|
||||
from django.template.defaultfilters import title # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import tables
|
||||
from horizon.templatetags import sizeformat
|
||||
from horizon.utils import filters
|
||||
from horizon.utils import memoized
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.databases import db_capability
|
||||
|
||||
ACTIVE_STATES = ("ACTIVE",)
|
||||
|
||||
|
||||
class TerminateCluster(tables.BatchAction):
|
||||
name = "terminate"
|
||||
icon = "remove"
|
||||
classes = ('btn-danger',)
|
||||
help_text = _("Terminated cluster is not recoverable.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Terminate Cluster",
|
||||
u"Terminate Clusters",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Scheduled termination of Cluster",
|
||||
u"Scheduled termination of Clusters",
|
||||
count
|
||||
)
|
||||
|
||||
def action(self, request, obj_id):
|
||||
api.trove.cluster_delete(request, obj_id)
|
||||
|
||||
|
||||
class LaunchLink(tables.LinkAction):
|
||||
name = "launch"
|
||||
verbose_name = _("Launch Cluster")
|
||||
url = "horizon:project:database_clusters:launch"
|
||||
classes = ("btn-launch", "ajax-modal")
|
||||
icon = "cloud-upload"
|
||||
|
||||
|
||||
class AddShard(tables.LinkAction):
|
||||
name = "add_shard"
|
||||
verbose_name = _("Add Shard")
|
||||
url = "horizon:project:database_clusters:add_shard"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
|
||||
def allowed(self, request, cluster=None):
|
||||
if (cluster and cluster.task["name"] == 'NONE' and
|
||||
db_capability.is_mongodb_datastore(cluster.datastore['type'])):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ResetPassword(tables.LinkAction):
|
||||
name = "reset_password"
|
||||
verbose_name = _("Reset Root Password")
|
||||
url = "horizon:project:database_clusters:reset_password"
|
||||
classes = ("ajax-modal",)
|
||||
|
||||
def allowed(self, request, cluster=None):
|
||||
if (cluster and cluster.task["name"] == 'NONE' and
|
||||
db_capability.is_vertica_datastore(cluster.datastore['type'])):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_link_url(self, datum):
|
||||
cluster_id = self.table.get_object_id(datum)
|
||||
return urlresolvers.reverse(self.url, args=[cluster_id])
|
||||
|
||||
|
||||
class UpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_data(self, request, cluster_id):
|
||||
cluster = api.trove.cluster_get(request, cluster_id)
|
||||
try:
|
||||
# TODO(michayu): assumption that cluster is homogeneous
|
||||
flavor_id = cluster.instances[0]['flavor']['id']
|
||||
cluster.full_flavor = api.trove.flavor_get(request, flavor_id)
|
||||
except Exception:
|
||||
pass
|
||||
return cluster
|
||||
|
||||
|
||||
def get_datastore(cluster):
|
||||
return cluster.datastore["type"]
|
||||
|
||||
|
||||
def get_datastore_version(cluster):
|
||||
return cluster.datastore["version"]
|
||||
|
||||
|
||||
def get_size(cluster):
|
||||
if db_capability.is_vertica_datastore(cluster.datastore['type']):
|
||||
return "3"
|
||||
|
||||
if hasattr(cluster, "full_flavor"):
|
||||
size_string = _("%(name)s | %(RAM)s RAM | %(instances)s instances")
|
||||
vals = {'name': cluster.full_flavor.name,
|
||||
'RAM': sizeformat.mbformat(cluster.full_flavor.ram),
|
||||
'instances': len(cluster.instances)}
|
||||
return size_string % vals
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_task(cluster):
|
||||
return cluster.task["name"]
|
||||
|
||||
|
||||
class ClustersTable(tables.DataTable):
|
||||
TASK_CHOICES = (
|
||||
("none", True),
|
||||
)
|
||||
name = tables.Column("name",
|
||||
link=("horizon:project:database_clusters:detail"),
|
||||
verbose_name=_("Cluster Name"))
|
||||
datastore = tables.Column(get_datastore,
|
||||
verbose_name=_("Datastore"))
|
||||
datastore_version = tables.Column(get_datastore_version,
|
||||
verbose_name=_("Datastore Version"))
|
||||
size = tables.Column(get_size,
|
||||
verbose_name=_("Cluster Size"),
|
||||
attrs={'data-type': 'size'})
|
||||
task = tables.Column(get_task,
|
||||
filters=(title, filters.replace_underscores),
|
||||
verbose_name=_("Current Task"),
|
||||
status=True,
|
||||
status_choices=TASK_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "clusters"
|
||||
verbose_name = _("Clusters")
|
||||
status_columns = ["task"]
|
||||
row_class = UpdateRow
|
||||
table_actions = (LaunchLink, TerminateCluster)
|
||||
row_actions = (AddShard, ResetPassword, TerminateCluster)
|
||||
|
||||
|
||||
def get_instance_size(instance):
|
||||
if hasattr(instance, "full_flavor"):
|
||||
size_string = _("%(name)s | %(RAM)s RAM")
|
||||
vals = {'name': instance.full_flavor.name,
|
||||
'RAM': sizeformat.mbformat(instance.full_flavor.ram)}
|
||||
return size_string % vals
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_instance_type(instance):
|
||||
if hasattr(instance, "type"):
|
||||
return instance.type
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_host(instance):
|
||||
if hasattr(instance, "hostname"):
|
||||
return instance.hostname
|
||||
elif hasattr(instance, "ip") and instance.ip:
|
||||
return instance.ip[0]
|
||||
return _("Not Assigned")
|
||||
|
||||
|
||||
class InstancesTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
verbose_name=_("Name"))
|
||||
type = tables.Column(get_instance_type,
|
||||
verbose_name=_("Type"))
|
||||
host = tables.Column(get_host,
|
||||
verbose_name=_("Host"))
|
||||
size = tables.Column(get_instance_size,
|
||||
verbose_name=_("Size"),
|
||||
attrs={'data-type': 'size'})
|
||||
status = tables.Column("status",
|
||||
filters=(title, filters.replace_underscores),
|
||||
verbose_name=_("Status"))
|
||||
|
||||
class Meta(object):
|
||||
name = "instances"
|
||||
verbose_name = _("Instances")
|
@ -0,0 +1,84 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django import template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import tables
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
|
||||
def get_context_data(self, request):
|
||||
return {"cluster": self.tab_group.kwargs['cluster']}
|
||||
|
||||
def get_template_name(self, request):
|
||||
cluster = self.tab_group.kwargs['cluster']
|
||||
template_file = ('project/database_clusters/_detail_overview_%s.html'
|
||||
% cluster.datastore['type'])
|
||||
try:
|
||||
template.loader.get_template(template_file)
|
||||
return template_file
|
||||
except template.TemplateDoesNotExist:
|
||||
# This datastore type does not have a template file
|
||||
# Just use the base template file
|
||||
return ('project/database_clusters/_detail_overview.html')
|
||||
|
||||
|
||||
class InstancesTab(tabs.TableTab):
|
||||
table_classes = (tables.InstancesTable,)
|
||||
name = _("Instances")
|
||||
slug = "instances_tab"
|
||||
cluster = None
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
preload = True
|
||||
|
||||
def get_instances_data(self):
|
||||
cluster = self.tab_group.kwargs['cluster']
|
||||
data = []
|
||||
try:
|
||||
instances = api.trove.cluster_get(self.request,
|
||||
cluster.id).instances
|
||||
for instance in instances:
|
||||
instance_info = api.trove.instance_get(self.request,
|
||||
instance['id'])
|
||||
flavor_id = instance_info.flavor['id']
|
||||
instance_info.full_flavor = api.trove.flavor_get(self.request,
|
||||
flavor_id)
|
||||
if "type" in instance:
|
||||
instance_info.type = instance["type"]
|
||||
if "ip" in instance:
|
||||
instance_info.ip = instance["ip"]
|
||||
if "hostname" in instance:
|
||||
instance_info.hostname = instance["hostname"]
|
||||
|
||||
data.append(instance_info)
|
||||
except Exception:
|
||||
msg = _('Unable to get instances data.')
|
||||
exceptions.handle(self.request, msg)
|
||||
data = []
|
||||
return data
|
||||
|
||||
|
||||
class ClusterDetailTabs(tabs.TabGroup):
|
||||
slug = "cluster_details"
|
||||
tabs = (OverviewTab, InstancesTab)
|
||||
sticky = True
|
@ -0,0 +1,25 @@
|
||||
{% 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,27 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<h3>{% trans "Cluster Overview" %}</h3>
|
||||
|
||||
<div class="status row detail">
|
||||
<h4>{% trans "Information" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ cluster.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ cluster.id }}</dd>
|
||||
<dt>{% trans "Datastore" %}</dt>
|
||||
<dd>{{ cluster.datastore.type }}</dd>
|
||||
<dt>{% trans "Datastore Version" %}</dt>
|
||||
<dd>{{ cluster.datastore.version }}</dd>
|
||||
<dt>{% trans "Current Task" %}</dt>
|
||||
<dd>{{ cluster.task.name|title }}</dd>
|
||||
<dt>{% trans "RAM" %}</dt>
|
||||
<dd>{{ cluster.full_flavor.ram|mbformat }}</dd>
|
||||
<dt>{% trans "Number of Instances" %}</dt>
|
||||
<dd>{{ cluster.num_instances }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{% block connection_info %}
|
||||
{% endblock %}
|
@ -0,0 +1,27 @@
|
||||
{% extends "project/database_clusters/_detail_overview.html" %}
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block connection_info %}
|
||||
<div class="addresses row detail">
|
||||
<h4>{% trans "Connection Information" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
{% with cluster.ip.0 as ip %}
|
||||
<dt>{% trans "Host" %}</dt>
|
||||
<dd>
|
||||
{% if not ip %}
|
||||
{% trans "Not Assigned" %}
|
||||
</dd>
|
||||
{% else %}
|
||||
{{ ip }}
|
||||
</dd>
|
||||
<dt>{% trans "Database Port" %}</dt>
|
||||
<dd>27017</dd>
|
||||
<dt>{% trans "Connection Examples" %}</dt>
|
||||
<dd>mongo --host {{ ip }}</dd>
|
||||
<dd>mongodb://[{% trans "USERNAME" %}:{% trans "PASSWORD" %}@]{{ ip }}:27017/{% trans "DATABASE" %}</dd>
|
||||
{% endif %} <!-- ends else block -->
|
||||
{% endwith %}
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,29 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<h3>{% trans "Cluster Overview" %}</h3>
|
||||
|
||||
<div class="status row detail">
|
||||
<h4>{% trans "Information" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ cluster.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ cluster.id }}</dd>
|
||||
<dt>{% trans "Datastore" %}</dt>
|
||||
<dd>{{ cluster.datastore.type }}</dd>
|
||||
<dt>{% trans "Datastore Version" %}</dt>
|
||||
<dd>{{ cluster.datastore.version }}</dd>
|
||||
<dt>{% trans "Current Task" %}</dt>
|
||||
<dd>{{ cluster.task.name|title }}</dd>
|
||||
<dt>{% trans "RAM" %}</dt>
|
||||
<dd>{{ cluster.full_flavor.ram|mbformat }}</dd>
|
||||
<dt>{% trans "Number of Instances" %}</dt>
|
||||
<dd>{{ cluster.num_instances }}</dd>
|
||||
<dt>{% trans "Managment Console" %}</dt>
|
||||
<dd><a href="{{ cluster.mgmt_url }}" target="_blank">{{ cluster.mgmt_url }}</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{% block connection_info %}
|
||||
{% endblock %}
|
@ -0,0 +1,22 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}launch_form{% endblock %}
|
||||
{% block form_action %}{% url "horizon:project:database_clusters:launch" %}{% endblock %}
|
||||
|
||||
{% block modal_id %}launch_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Launch Cluster" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="center">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Launch" %}" />
|
||||
<a href="{% url "horizon:project:database_clusters:index" %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,25 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}reset_password_form{% endblock %}
|
||||
{% block form_action %}{% url "horizon:project:database_clusters:reset_password" cluster_id %}{% endblock %}
|
||||
|
||||
{% block modal_id %}reset_password_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Reset Root Password" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<p>{% blocktrans %}Specify the new root password for vertica cluster.{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Reset Root Password" %}" />
|
||||
<a href="{% url "horizon:project:database_clusters:index" %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Add Shard" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/database_clusters/_add_shard.html" %}
|
||||
{% endblock %}
|
@ -0,0 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n sizeformat %}
|
||||
{% block title %}{% trans "Cluster Detail" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Clusters" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Clusters") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Launch Cluster" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Launch Cluster") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/database_clusters/_launch.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Reset Root Password" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/database_clusters/_reset_password.html" %}
|
||||
{% endblock %}
|
@ -0,0 +1,295 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import http
|
||||
|
||||
from mox3.mox import IsA # noqa
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.contrib.trove import api as trove_api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
from troveclient import common
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:project:database_clusters:index')
|
||||
LAUNCH_URL = reverse('horizon:project:database_clusters:launch')
|
||||
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'
|
||||
|
||||
|
||||
class ClustersTests(test.TestCase):
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
||||
'flavor_list')})
|
||||
def test_index(self):
|
||||
clusters = common.Paginated(self.trove_clusters.list())
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(clusters)
|
||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
||||
'flavor_list')})
|
||||
def test_index_flavor_exception(self):
|
||||
clusters = common.Paginated(self.trove_clusters.list())
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(clusters)
|
||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',)})
|
||||
def test_index_list_exception(self):
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
||||
'flavor_list')})
|
||||
def test_index_pagination(self):
|
||||
clusters = self.trove_clusters.list()
|
||||
last_record = clusters[0]
|
||||
clusters = common.Paginated(clusters, next_marker="foo")
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(clusters)
|
||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
self.assertContains(
|
||||
res, 'marker=' + last_record.id)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
||||
'flavor_list')})
|
||||
def test_index_flavor_list_exception(self):
|
||||
clusters = common.Paginated(self.trove_clusters.list())
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(clusters)
|
||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('datastore_flavors',
|
||||
'datastore_list',
|
||||
'datastore_version_list'),
|
||||
api.base: ['is_service_enabled']})
|
||||
def test_launch_cluster(self):
|
||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
||||
.AndReturn(False)
|
||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
'mongodb', '2.6')\
|
||||
.AndReturn(self.flavors.list())
|
||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest),
|
||||
IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(LAUNCH_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/launch.html')
|
||||
|
||||
@test.create_stubs({trove_api.trove: ['datastore_flavors',
|
||||
'cluster_create',
|
||||
'datastore_list',
|
||||
'datastore_version_list'],
|
||||
api.base: ['is_service_enabled']})
|
||||
def test_create_simple_cluster(self):
|
||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
||||
.AndReturn(False)
|
||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
'mongodb', '2.6')\
|
||||
.AndReturn(self.flavors.list())
|
||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
|
||||
cluster_name = u'MyCluster'
|
||||
cluster_volume = 1
|
||||
cluster_flavor = u'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
cluster_instances = 3
|
||||
cluster_datastore = u'mongodb'
|
||||
cluster_datastore_version = u'2.6'
|
||||
cluster_network = u''
|
||||
trove_api.trove.cluster_create(
|
||||
IsA(http.HttpRequest),
|
||||
cluster_name,
|
||||
cluster_volume,
|
||||
cluster_flavor,
|
||||
cluster_instances,
|
||||
datastore=cluster_datastore,
|
||||
datastore_version=cluster_datastore_version,
|
||||
nics=cluster_network,
|
||||
root_password=None).AndReturn(self.trove_clusters.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
post = {
|
||||
'name': cluster_name,
|
||||
'volume': cluster_volume,
|
||||
'num_instances': cluster_instances,
|
||||
'num_shards': 1,
|
||||
'num_instances_per_shards': cluster_instances,
|
||||
'datastore': cluster_datastore + u'-' + cluster_datastore_version,
|
||||
'mongodb_flavor': cluster_flavor,
|
||||
'network': cluster_network
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertMessageCount(success=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ['datastore_flavors',
|
||||
'cluster_create',
|
||||
'datastore_list',
|
||||
'datastore_version_list'],
|
||||
api.neutron: ['network_list_for_tenant'],
|
||||
api.base: ['is_service_enabled']})
|
||||
def test_create_simple_cluster_neutron(self):
|
||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
||||
.AndReturn(True)
|
||||
api.neutron.network_list_for_tenant(IsA(http.HttpRequest), '1')\
|
||||
.AndReturn(self.networks.list())
|
||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
'mongodb', '2.6')\
|
||||
.AndReturn(self.flavors.list())
|
||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
|
||||
cluster_name = u'MyCluster'
|
||||
cluster_volume = 1
|
||||
cluster_flavor = u'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
cluster_instances = 3
|
||||
cluster_datastore = u'mongodb'
|
||||
cluster_datastore_version = u'2.6'
|
||||
cluster_network = u'82288d84-e0a5-42ac-95be-e6af08727e42'
|
||||
trove_api.trove.cluster_create(
|
||||
IsA(http.HttpRequest),
|
||||
cluster_name,
|
||||
cluster_volume,
|
||||
cluster_flavor,
|
||||
cluster_instances,
|
||||
datastore=cluster_datastore,
|
||||
datastore_version=cluster_datastore_version,
|
||||
nics=cluster_network,
|
||||
root_password=None).AndReturn(self.trove_clusters.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
post = {
|
||||
'name': cluster_name,
|
||||
'volume': cluster_volume,
|
||||
'num_instances': cluster_instances,
|
||||
'num_shards': 1,
|
||||
'num_instances_per_shards': cluster_instances,
|
||||
'datastore': cluster_datastore + u'-' + cluster_datastore_version,
|
||||
'mongodb_flavor': cluster_flavor,
|
||||
'network': cluster_network
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertMessageCount(success=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ['datastore_flavors',
|
||||
'cluster_create',
|
||||
'datastore_list',
|
||||
'datastore_version_list'],
|
||||
api.neutron: ['network_list_for_tenant']})
|
||||
def test_create_simple_cluster_exception(self):
|
||||
api.neutron.network_list_for_tenant(IsA(http.HttpRequest), '1')\
|
||||
.AndReturn(self.networks.list())
|
||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
'mongodb', '2.6')\
|
||||
.AndReturn(self.flavors.list())
|
||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
|
||||
cluster_name = u'MyCluster'
|
||||
cluster_volume = 1
|
||||
cluster_flavor = u'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
cluster_instances = 3
|
||||
cluster_datastore = u'mongodb'
|
||||
cluster_datastore_version = u'2.6'
|
||||
cluster_network = u'82288d84-e0a5-42ac-95be-e6af08727e42'
|
||||
trove_api.trove.cluster_create(
|
||||
IsA(http.HttpRequest),
|
||||
cluster_name,
|
||||
cluster_volume,
|
||||
cluster_flavor,
|
||||
cluster_instances,
|
||||
datastore=cluster_datastore,
|
||||
datastore_version=cluster_datastore_version,
|
||||
nics=cluster_network,
|
||||
root_password=None).AndReturn(self.trove_clusters.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
post = {
|
||||
'name': cluster_name,
|
||||
'volume': cluster_volume,
|
||||
'num_instances': cluster_instances,
|
||||
'num_shards': 1,
|
||||
'num_instances_per_shards': cluster_instances,
|
||||
'datastore': cluster_datastore + u'-' + cluster_datastore_version,
|
||||
'mongodb_flavor': cluster_flavor,
|
||||
'network': cluster_network
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_get',
|
||||
'instance_get',
|
||||
'flavor_get',)})
|
||||
def test_details(self):
|
||||
cluster = self.trove_clusters.first()
|
||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
||||
.MultipleTimes().AndReturn(cluster)
|
||||
trove_api.trove.instance_get(IsA(http.HttpRequest), IsA(str))\
|
||||
.MultipleTimes().AndReturn(self.databases.first())
|
||||
trove_api.trove.flavor_get(IsA(http.HttpRequest), IsA(str))\
|
||||
.MultipleTimes().AndReturn(self.flavors.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
details_url = reverse('horizon:project:database_clusters:detail',
|
||||
args=[cluster.id])
|
||||
res = self.client.get(details_url)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/detail.html')
|
||||
self.assertContains(res, cluster.ip[0])
|
@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import patterns # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import views
|
||||
|
||||
CLUSTERS = r'^(?P<cluster_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^launch$', views.LaunchClusterView.as_view(), name='launch'),
|
||||
url(r'^(?P<cluster_id>[^/]+)/$', views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(CLUSTERS % 'add_shard', views.AddShardView.as_view(),
|
||||
name='add_shard'),
|
||||
url(CLUSTERS % 'reset_password', views.ResetPasswordView.as_view(),
|
||||
name='reset_password'),
|
||||
)
|
@ -0,0 +1,212 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Views for managing database clusters.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
|
||||
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 openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import forms
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import tables
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import tabs
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(horizon_tables.DataTableView):
|
||||
table_class = tables.ClustersTable
|
||||
template_name = 'project/database_clusters/index.html'
|
||||
|
||||
def has_more_data(self, table):
|
||||
return self._more
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_flavors(self):
|
||||
try:
|
||||
flavors = api.trove.flavor_list(self.request)
|
||||
except Exception:
|
||||
flavors = []
|
||||
msg = _('Unable to retrieve database size information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return OrderedDict((unicode(flavor.id), flavor) for flavor in flavors)
|
||||
|
||||
def _extra_data(self, cluster):
|
||||
try:
|
||||
cluster_flavor = cluster.instances[0]["flavor"]["id"]
|
||||
flavors = self.get_flavors()
|
||||
flavor = flavors.get(cluster_flavor)
|
||||
if flavor is not None:
|
||||
cluster.full_flavor = flavor
|
||||
except Exception:
|
||||
# ignore any errors and just return cluster unaltered
|
||||
pass
|
||||
return cluster
|
||||
|
||||
def get_data(self):
|
||||
marker = self.request.GET.get(
|
||||
tables.ClustersTable._meta.pagination_param)
|
||||
# Gather our clusters
|
||||
try:
|
||||
clusters = api.trove.cluster_list(self.request, marker=marker)
|
||||
self._more = clusters.next or False
|
||||
except Exception:
|
||||
self._more = False
|
||||
clusters = []
|
||||
msg = _('Unable to retrieve database clusters.')
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
map(self._extra_data, clusters)
|
||||
|
||||
return clusters
|
||||
|
||||
|
||||
class LaunchClusterView(horizon_forms.ModalFormView):
|
||||
form_class = forms.LaunchForm
|
||||
template_name = 'project/database_clusters/launch.html'
|
||||
success_url = reverse_lazy('horizon:project:database_clusters:index')
|
||||
|
||||
|
||||
class DetailView(horizon_tabs.TabbedTableView):
|
||||
tab_group_class = tabs.ClusterDetailTabs
|
||||
template_name = 'project/database_clusters/detail.html'
|
||||
|
||||
page_title = _("Cluster Details: {{ cluster.name }}")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
context["cluster"] = self.get_data()
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_data(self):
|
||||
try:
|
||||
cluster_id = self.kwargs['cluster_id']
|
||||
cluster = api.trove.cluster_get(self.request, cluster_id)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
msg = _('Unable to retrieve details '
|
||||
'for database cluster: %s') % cluster_id
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
try:
|
||||
cluster.full_flavor = api.trove.flavor_get(
|
||||
self.request, cluster.instances[0]["flavor"]["id"])
|
||||
except Exception:
|
||||
LOG.error('Unable to retrieve flavor details'
|
||||
' for database cluster: %s' % cluster_id)
|
||||
cluster.num_instances = len(cluster.instances)
|
||||
|
||||
# Todo(saurabhs) Set mgmt_url to dispaly Mgmt Console URL on
|
||||
# cluster details page
|
||||
# for instance in cluster.instances:
|
||||
# if instance['type'] == "master":
|
||||
# cluster.mgmt_url = "https://%s:5450/webui" % instance['ip'][0]
|
||||
|
||||
return cluster
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
cluster = self.get_data()
|
||||
return self.tab_group_class(request, cluster=cluster, **kwargs)
|
||||
|
||||
|
||||
class AddShardView(horizon_forms.ModalFormView):
|
||||
form_class = forms.AddShardForm
|
||||
template_name = 'project/database_clusters/add_shard.html'
|
||||
success_url = reverse_lazy('horizon:project:database_clusters:index')
|
||||
page_title = _("Add Shard")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddShardView, self).get_context_data(**kwargs)
|
||||
context["cluster_id"] = self.kwargs['cluster_id']
|
||||
return context
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
cluster_id = self.kwargs['cluster_id']
|
||||
try:
|
||||
self._object = 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:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
msg = _('Unable to retrieve cluster details.')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_flavors(self, *args, **kwargs):
|
||||
if not hasattr(self, "_flavors"):
|
||||
try:
|
||||
flavors = api.trove.flavor_list(self.request)
|
||||
self._flavors = OrderedDict([(str(flavor.id), flavor)
|
||||
for flavor in flavors])
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
_('Unable to retrieve flavors.'), redirect=redirect)
|
||||
return self._flavors
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddShardView, self).get_initial()
|
||||
_object = self.get_object()
|
||||
if _object:
|
||||
initial.update(
|
||||
{'cluster_id': self.kwargs['cluster_id'],
|
||||
'name': getattr(_object, 'name', None)})
|
||||
return initial
|
||||
|
||||
|
||||
class ResetPasswordView(horizon_forms.ModalFormView):
|
||||
form_class = forms.ResetPasswordForm
|
||||
template_name = 'project/database_clusters/reset_password.html'
|
||||
success_url = reverse_lazy('horizon:project:database_clusters:index')
|
||||
page_title = _("Reset Root Password")
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self, *args, **kwargs):
|
||||
cluster_id = self.kwargs['cluster_id']
|
||||
try:
|
||||
return api.trove.cluster_get(self.request, cluster_id)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve cluster details.')
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ResetPasswordView, self).get_context_data(**kwargs)
|
||||
context['cluster_id'] = self.kwargs['cluster_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
return {'cluster_id': self.kwargs['cluster_id']}
|
@ -0,0 +1,25 @@
|
||||
# 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.
|
||||
|
||||
|
||||
MONGODB = "mongodb"
|
||||
VERTICA = "vertica"
|
||||
|
||||
|
||||
def is_mongodb_datastore(datastore):
|
||||
return (datastore is not None) and (MONGODB in datastore.lower())
|
||||
|
||||
|
||||
def is_vertica_datastore(datastore):
|
||||
return (datastore is not None) and (VERTICA in datastore.lower())
|
@ -3,7 +3,7 @@
|
||||
|
||||
{% block connection_info %}
|
||||
<div class="addresses row detail">
|
||||
<h4>{% trans "Connection Info" %}</h4>
|
||||
<h4>{% trans "Connection Information" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
{% with instance.host as host %}
|
||||
@ -20,4 +20,4 @@
|
||||
{% endwith %}
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@ -3,22 +3,30 @@
|
||||
|
||||
{% block connection_info %}
|
||||
<div class="addresses row detail">
|
||||
<h4>{% trans "Connection Info" %}</h4>
|
||||
<h4>{% trans "Connection Information" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
{% with instance.host as host %}
|
||||
<dt>{% trans "Host" %}</dt>
|
||||
{% if not host %}
|
||||
<dd>{% trans "Not Assigned" %}</dd>
|
||||
{% else %}
|
||||
<dd>{{ host }}</dd>
|
||||
<dt>{% trans "Database Port" %}</dt>
|
||||
<dd>27017</dd>
|
||||
<dt>{% trans "Connection Examples" %}</dt>
|
||||
<dd>mongo --host {{ host }}</dd>
|
||||
<dd>mongodb://[{% trans "USERNAME" %}:{% trans "PASSWORD" %}@]{{ host }}:27017/{% trans "DATABASE" %}</dd>
|
||||
{% endif %} <!-- ends else block -->
|
||||
{% endwith %}
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% if instance.cluster_id %}
|
||||
<a href="/project/clusters/{{instance.cluster_id}}">Link</a> to Cluster Details for Connection Info
|
||||
{% else %}
|
||||
<dl>
|
||||
{% with instance.host as host %}
|
||||
<dt>{% trans "Host" %}</dt>
|
||||
<dd>
|
||||
{% if not host %}
|
||||
{% trans "Not Assigned" %}
|
||||
</dd>
|
||||
{% else %}
|
||||
{{ host }}
|
||||
</dd>
|
||||
<dt>{% trans "Database Port" %}</dt>
|
||||
<dd>27017</dd>
|
||||
<dt>{% trans "Connection Examples" %}</dt>
|
||||
<dd>mongo --host {{ host }}</dd>
|
||||
<dd>mongodb://[{% trans "USERNAME" %}:{% trans "PASSWORD" %}@]{{ host }}:27017/{% trans "DATABASE" %}</dd>
|
||||
{% endif %} <!-- ends else block -->
|
||||
{% endwith %}
|
||||
</dl>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -140,7 +140,7 @@ class DatabaseTests(test.TestCase):
|
||||
self.datastores.list())
|
||||
# Mock datastore versions
|
||||
api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str)).\
|
||||
AndReturn(self.datastore_versions.list())
|
||||
MultipleTimes().AndReturn(self.datastore_versions.list())
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id,
|
||||
@ -207,7 +207,7 @@ class DatabaseTests(test.TestCase):
|
||||
|
||||
# Mock datastore versions
|
||||
api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
.MultipleTimes().AndReturn(self.datastore_versions.list())
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id,
|
||||
@ -268,7 +268,7 @@ class DatabaseTests(test.TestCase):
|
||||
|
||||
# Mock datastore versions
|
||||
api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
.MultipleTimes().AndReturn(self.datastore_versions.list())
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id,
|
||||
@ -499,7 +499,7 @@ class DatabaseTests(test.TestCase):
|
||||
|
||||
api.trove.datastore_version_list(IsA(http.HttpRequest),
|
||||
IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
.MultipleTimes().AndReturn(self.datastore_versions.list())
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id,
|
||||
|
@ -102,7 +102,7 @@ class SetInstanceDetailsAction(workflows.Action):
|
||||
num_datastores_with_one_version += 1
|
||||
if num_datastores_with_one_version > 1:
|
||||
set_initial = True
|
||||
if len(versions) > 0:
|
||||
if versions:
|
||||
# only add to choices if datastore has at least one version
|
||||
version_choices = ()
|
||||
for v in versions:
|
||||
|
@ -0,0 +1,25 @@
|
||||
# Copyright [2015] Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'database_clusters'
|
||||
# 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 = ('openstack_dashboard.contrib.trove.'
|
||||
'content.database_clusters.panel.Clusters')
|
@ -1,4 +1,5 @@
|
||||
# Copyright 2013 Rackspace Hosting.
|
||||
# Copyright 2015 HP Software, LLC
|
||||
#
|
||||
# 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
|
||||
@ -13,6 +14,7 @@
|
||||
# under the License.
|
||||
|
||||
from troveclient.v1 import backups
|
||||
from troveclient.v1 import clusters
|
||||
from troveclient.v1 import databases
|
||||
from troveclient.v1 import datastores
|
||||
from troveclient.v1 import flavors
|
||||
@ -22,6 +24,104 @@ from troveclient.v1 import users
|
||||
from openstack_dashboard.test.test_data import utils
|
||||
|
||||
|
||||
CLUSTER_DATA_ONE = {
|
||||
"status": "ACTIVE",
|
||||
"id": "dfbbd9ca-b5e1-4028-adb7-f78643e17998",
|
||||
"name": "Test Cluster",
|
||||
"created": "2014-04-25T20:19:23",
|
||||
"updated": "2014-04-25T20:19:23",
|
||||
"links": [],
|
||||
"datastore": {
|
||||
"type": "mongodb",
|
||||
"version": "2.6"
|
||||
},
|
||||
"ip": ["10.0.0.1"],
|
||||
"instances": [
|
||||
{
|
||||
"id": "416b0b16-ba55-4302-bbd3-ff566032e1c1",
|
||||
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "965ef811-7c1d-47fc-89f2-a89dfdd23ef2",
|
||||
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3642f41c-e8ad-4164-a089-3891bf7f2d2b",
|
||||
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
}
|
||||
],
|
||||
"task": {
|
||||
"name": "test_task"
|
||||
}
|
||||
}
|
||||
|
||||
CLUSTER_DATA_TWO = {
|
||||
"status": "ACTIVE",
|
||||
"id": "dfbbd9ca-b5e1-4028-adb7-f78643e17998",
|
||||
"name": "Test Cluster",
|
||||
"created": "2014-04-25T20:19:23",
|
||||
"updated": "2014-04-25T20:19:23",
|
||||
"links": [],
|
||||
"datastore": {
|
||||
"type": "vertica",
|
||||
"version": "7.1"
|
||||
},
|
||||
"ip": ["10.0.0.1"],
|
||||
"instances": [
|
||||
{
|
||||
"id": "416b0b16-ba55-4302-bbd3-ff566032e1c1",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "965ef811-7c1d-47fc-89f2-a89dfdd23ef2",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3642f41c-e8ad-4164-a089-3891bf7f2d2b",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
DATABASE_DATA_ONE = {
|
||||
"status": "ACTIVE",
|
||||
"updated": "2013-08-12T22:00:09",
|
||||
@ -130,6 +230,12 @@ DATASTORE_TWO = {
|
||||
"name": "mysql"
|
||||
}
|
||||
|
||||
DATASTORE_MONGODB = {
|
||||
"id": "ccb31517-c472-409d-89b4-1a13db6bdd37",
|
||||
"links": [],
|
||||
"name": "mongodb"
|
||||
}
|
||||
|
||||
VERSION_ONE = {
|
||||
"name": "5.5",
|
||||
"links": [],
|
||||
@ -171,8 +277,22 @@ FLAVOR_THREE = {
|
||||
"name": "test.1"
|
||||
}
|
||||
|
||||
VERSION_MONGODB_2_6 = {
|
||||
"name": "2.6",
|
||||
"links": [],
|
||||
"image": "c7956bb5-920e-4299-b68e-2347d830d937",
|
||||
"active": 1,
|
||||
"datastore": "ccb31517-c472-409d-89b4-1a13db6bdd37",
|
||||
"packages": "2.6",
|
||||
"id": "600a6d52-8347-4e00-8e4c-f4fa9cf96ae9"
|
||||
}
|
||||
|
||||
|
||||
def data(TEST):
|
||||
cluster1 = clusters.Cluster(clusters.Clusters(None),
|
||||
CLUSTER_DATA_ONE)
|
||||
cluster2 = clusters.Cluster(clusters.Clusters(None),
|
||||
CLUSTER_DATA_TWO)
|
||||
database1 = instances.Instance(instances.Instances(None),
|
||||
DATABASE_DATA_ONE)
|
||||
database2 = instances.Instance(instances.Instances(None),
|
||||
@ -186,18 +306,22 @@ def data(TEST):
|
||||
|
||||
datastore1 = datastores.Datastore(datastores.Datastores(None),
|
||||
DATASTORE_ONE)
|
||||
|
||||
version1 = datastores.\
|
||||
DatastoreVersion(datastores.DatastoreVersions(None),
|
||||
VERSION_ONE)
|
||||
version2 = datastores.\
|
||||
DatastoreVersion(datastores.DatastoreVersions(None),
|
||||
VERSION_TWO)
|
||||
|
||||
flavor1 = flavors.Flavor(flavors.Flavors(None), FLAVOR_ONE)
|
||||
flavor2 = flavors.Flavor(flavors.Flavors(None), FLAVOR_TWO)
|
||||
flavor3 = flavors.Flavor(flavors.Flavors(None), FLAVOR_THREE)
|
||||
datastore_mongodb = datastores.Datastore(datastores.Datastores(None),
|
||||
DATASTORE_MONGODB)
|
||||
version_mongodb_2_6 = datastores.\
|
||||
DatastoreVersion(datastores.DatastoreVersions(None),
|
||||
VERSION_MONGODB_2_6)
|
||||
|
||||
TEST.trove_clusters = utils.TestDataContainer()
|
||||
TEST.trove_clusters.add(cluster1)
|
||||
TEST.trove_clusters.add(cluster2)
|
||||
TEST.databases = utils.TestDataContainer()
|
||||
TEST.database_backups = utils.TestDataContainer()
|
||||
TEST.database_users = utils.TestDataContainer()
|
||||
@ -213,7 +337,8 @@ def data(TEST):
|
||||
TEST.database_user_dbs.add(user_db1)
|
||||
TEST.datastores = utils.TestDataContainer()
|
||||
TEST.datastores.add(datastore1)
|
||||
TEST.datastore_versions = utils.TestDataContainer()
|
||||
TEST.datastore_versions.add(version1)
|
||||
TEST.datastore_versions.add(version2)
|
||||
TEST.datastores.add(datastore_mongodb)
|
||||
TEST.database_flavors.add(flavor1, flavor2, flavor3)
|
||||
TEST.datastore_versions = utils.TestDataContainer()
|
||||
TEST.datastore_versions.add(version_mongodb_2_6)
|
||||
TEST.datastore_versions.add(version1)
|
||||
|
Loading…
Reference in New Issue
Block a user