376 lines
15 KiB
Python
376 lines
15 KiB
Python
# 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", 0) < 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
|