Add support for uppercase fieldname to cluster
The datastore flavor field was not properly implemented because not every datastore flavor had it's own flavor pulldown. Properly implemented a datastore pulldown for every flavor that is only visible when the corresponding datastore value is selected. Also added code to protect against uppercase letters in the datastore or datastore version name. Change-Id: I7a8083733a7d613c56652f3884d2682bb54379b3 Closes-Bug: #1614650
This commit is contained in:
parent
76833761ed
commit
f7d0b9f771
@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import binascii
|
||||
import collections
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
@ -26,10 +28,14 @@ from horizon import messages
|
||||
from horizon.utils import memoized
|
||||
from openstack_dashboard import api
|
||||
|
||||
from openstack_dashboard.dashboards.project.instances \
|
||||
import utils as instance_utils
|
||||
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.workflows \
|
||||
import create_instance
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -44,22 +50,6 @@ class LaunchForm(forms.SelfHandlingForm):
|
||||
'class': 'switchable',
|
||||
'data-slug': 'datastore'
|
||||
}))
|
||||
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."),
|
||||
@ -120,7 +110,6 @@ class LaunchForm(forms.SelfHandlingForm):
|
||||
|
||||
# (name of field variable, label)
|
||||
default_fields = [
|
||||
('flavor', _('Flavor')),
|
||||
('num_instances', _('Number of Instances'))
|
||||
]
|
||||
mongodb_fields = default_fields + [
|
||||
@ -128,7 +117,6 @@ class LaunchForm(forms.SelfHandlingForm):
|
||||
]
|
||||
vertica_fields = [
|
||||
('num_instances_vertica', ('Number of Instances')),
|
||||
('vertica_flavor', _('Flavor')),
|
||||
('root_password', _('Root Password')),
|
||||
]
|
||||
|
||||
@ -137,27 +125,27 @@ class LaunchForm(forms.SelfHandlingForm):
|
||||
|
||||
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]
|
||||
datastore, datastore_version = (
|
||||
create_instance.parse_datastore_and_version_text(
|
||||
binascii.unhexlify(datastore_field_value)))
|
||||
|
||||
flavor_field_name = self._build_widget_field_name(
|
||||
datastore, datastore_version)
|
||||
if not self.data.get(flavor_field_name, None):
|
||||
msg = _("The flavor must be specified.")
|
||||
self._errors[flavor_field_name] = self.error_class([msg])
|
||||
|
||||
if 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])
|
||||
else:
|
||||
if not self.data.get("flavor", None):
|
||||
msg = _("The flavor must be specified.")
|
||||
self._errors["flavor"] = self.error_class([msg])
|
||||
if int(self.data.get("num_instances", 0)) < 1:
|
||||
msg = _("The number of instances must be greater than 1.")
|
||||
self._errors["num_instances"] = self.error_class([msg])
|
||||
@ -185,24 +173,6 @@ class LaunchForm(forms.SelfHandlingForm):
|
||||
_('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
|
||||
field_name = 'flavor'
|
||||
if db_capability.is_vertica_datastore(ds.name):
|
||||
field_name = 'vertica_flavor'
|
||||
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
for version in versions:
|
||||
if hasattr(version, 'active') and not version.active:
|
||||
continue
|
||||
valid_flavor = self.datastore_flavors(request, ds.name,
|
||||
versions[0].name)
|
||||
if valid_flavor:
|
||||
self.fields[field_name].choices = sorted(
|
||||
[(f.id, "%s" % f.name) for f in valid_flavor])
|
||||
|
||||
@memoized.memoized_method
|
||||
def populate_network_choices(self, request):
|
||||
network_list = []
|
||||
@ -259,6 +229,7 @@ class LaunchForm(forms.SelfHandlingForm):
|
||||
choices = ()
|
||||
datastores = self.filter_cluster_datastores(request)
|
||||
if datastores is not None:
|
||||
datastore_flavor_fields = {}
|
||||
for ds in datastores:
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
if versions:
|
||||
@ -267,18 +238,74 @@ class LaunchForm(forms.SelfHandlingForm):
|
||||
for v in versions:
|
||||
if hasattr(v, 'active') and not v.active:
|
||||
continue
|
||||
selection_text = ds.name + ' - ' + v.name
|
||||
widget_text = ds.name + '-' + v.name
|
||||
selection_text = self._build_datastore_display_text(
|
||||
ds.name, v.name)
|
||||
widget_text = self._build_widget_field_name(
|
||||
ds.name, v.name)
|
||||
version_choices = (version_choices +
|
||||
((widget_text, selection_text),))
|
||||
k, v = self._add_datastore_flavor_field(request,
|
||||
ds.name,
|
||||
v.name)
|
||||
datastore_flavor_fields[k] = v
|
||||
self._add_attr_to_optional_fields(ds.name,
|
||||
widget_text)
|
||||
|
||||
choices = choices + version_choices
|
||||
self._insert_datastore_version_fields(datastore_flavor_fields)
|
||||
return choices
|
||||
|
||||
def _add_datastore_flavor_field(self,
|
||||
request,
|
||||
datastore,
|
||||
datastore_version):
|
||||
name = self._build_widget_field_name(datastore, datastore_version)
|
||||
attr_key = 'data-datastore-' + name
|
||||
field = forms.ChoiceField(
|
||||
label=_("Flavor"),
|
||||
help_text=_("Size of image to launch."),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
attr_key: _("Flavor")
|
||||
}))
|
||||
valid_flavors = self.datastore_flavors(request,
|
||||
datastore,
|
||||
datastore_version)
|
||||
if valid_flavors:
|
||||
field.choices = instance_utils.sort_flavor_list(
|
||||
request, valid_flavors)
|
||||
|
||||
return name, field
|
||||
|
||||
def _build_datastore_display_text(self, datastore, datastore_version):
|
||||
return datastore + ' - ' + datastore_version
|
||||
|
||||
def _build_widget_field_name(self, datastore, datastore_version):
|
||||
# Since the fieldnames cannot contain an uppercase character
|
||||
# we generate a hex encoded string representation of the
|
||||
# datastore and version as the fieldname
|
||||
return binascii.hexlify(
|
||||
self._build_datastore_display_text(datastore, datastore_version))
|
||||
|
||||
def _insert_datastore_version_fields(self, datastore_flavor_fields):
|
||||
fields_to_restore_at_the_end = collections.OrderedDict()
|
||||
while True:
|
||||
k, v = self.fields.popitem()
|
||||
if k == 'datastore':
|
||||
self.fields[k] = v
|
||||
break
|
||||
else:
|
||||
fields_to_restore_at_the_end[k] = v
|
||||
|
||||
for k, v in datastore_flavor_fields.iteritems():
|
||||
self.fields[k] = v
|
||||
|
||||
for k in reversed(fields_to_restore_at_the_end.keys()):
|
||||
self.fields[k] = fields_to_restore_at_the_end[k]
|
||||
|
||||
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):
|
||||
@ -301,26 +328,29 @@ class LaunchForm(forms.SelfHandlingForm):
|
||||
@sensitive_variables('data')
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
datastore, datastore_version = data['datastore'].split('-', 1)
|
||||
datastore, datastore_version = (
|
||||
create_instance.parse_datastore_and_version_text(
|
||||
binascii.unhexlify(data['datastore'])))
|
||||
|
||||
final_flavor = data['flavor']
|
||||
flavor_field_name = self._build_widget_field_name(
|
||||
datastore, datastore_version)
|
||||
flavor = data[flavor_field_name]
|
||||
num_instances = data['num_instances']
|
||||
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",
|
||||
"locality=%s",
|
||||
data['name'], data['volume'], final_flavor,
|
||||
data['name'], data['volume'], flavor,
|
||||
datastore, datastore_version, self._get_locality(data))
|
||||
|
||||
trove_api.trove.cluster_create(request,
|
||||
data['name'],
|
||||
data['volume'],
|
||||
final_flavor,
|
||||
flavor,
|
||||
num_instances,
|
||||
datastore=datastore,
|
||||
datastore_version=datastore_version,
|
||||
|
@ -14,6 +14,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import binascii
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
@ -130,54 +131,60 @@ class ClustersTests(test.TestCase):
|
||||
|
||||
def test_launch_cluster_mongo_fields(self):
|
||||
datastore = 'mongodb'
|
||||
fields = self.launch_cluster_fields_setup(datastore, '2.6')
|
||||
datastore_version = '2.6'
|
||||
fields = self.launch_cluster_fields_setup(datastore,
|
||||
datastore_version)
|
||||
field_name = self._build_flavor_widget_name(datastore,
|
||||
datastore_version)
|
||||
|
||||
self.assertTrue(self._contains_datastore_in_attribute(
|
||||
fields['flavor'], datastore))
|
||||
fields[field_name], field_name))
|
||||
self.assertTrue(self._contains_datastore_in_attribute(
|
||||
fields['num_instances'], datastore))
|
||||
fields['num_instances'], field_name))
|
||||
self.assertTrue(self._contains_datastore_in_attribute(
|
||||
fields['num_shards'], datastore))
|
||||
fields['num_shards'], field_name))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['root_password'], datastore))
|
||||
fields['root_password'], field_name))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['num_instances_vertica'], datastore))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['vertica_flavor'], datastore))
|
||||
fields['num_instances_vertica'], field_name))
|
||||
|
||||
def test_launch_cluster_redis_fields(self):
|
||||
datastore = 'redis'
|
||||
fields = self.launch_cluster_fields_setup(datastore, '3.0')
|
||||
datastore_version = '3.0'
|
||||
fields = self.launch_cluster_fields_setup(datastore,
|
||||
datastore_version)
|
||||
field_name = self._build_flavor_widget_name(datastore,
|
||||
datastore_version)
|
||||
|
||||
self.assertTrue(self._contains_datastore_in_attribute(
|
||||
fields['flavor'], datastore))
|
||||
fields[field_name], field_name))
|
||||
self.assertTrue(self._contains_datastore_in_attribute(
|
||||
fields['num_instances'], datastore))
|
||||
fields['num_instances'], field_name))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['num_shards'], datastore))
|
||||
fields['num_shards'], field_name))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['root_password'], datastore))
|
||||
fields['root_password'], field_name))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['num_instances_vertica'], datastore))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['vertica_flavor'], datastore))
|
||||
fields['num_instances_vertica'], field_name))
|
||||
|
||||
def test_launch_cluster_vertica_fields(self):
|
||||
datastore = 'vertica'
|
||||
fields = self.launch_cluster_fields_setup(datastore, '7.1')
|
||||
datastore_version = '7.1'
|
||||
fields = self.launch_cluster_fields_setup(datastore,
|
||||
datastore_version)
|
||||
field_name = self._build_flavor_widget_name(datastore,
|
||||
datastore_version)
|
||||
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['flavor'], datastore))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['num_instances'], datastore))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['num_shards'], datastore))
|
||||
self.assertTrue(self._contains_datastore_in_attribute(
|
||||
fields['root_password'], datastore))
|
||||
fields[field_name], field_name))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['num_instances'], field_name))
|
||||
self.assertFalse(self._contains_datastore_in_attribute(
|
||||
fields['num_shards'], field_name))
|
||||
self.assertTrue(self._contains_datastore_in_attribute(
|
||||
fields['num_instances_vertica'], datastore))
|
||||
fields['root_password'], field_name))
|
||||
self.assertTrue(self._contains_datastore_in_attribute(
|
||||
fields['vertica_flavor'], datastore))
|
||||
fields['num_instances_vertica'], field_name))
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('datastore_flavors',
|
||||
'datastore_list',
|
||||
@ -238,16 +245,16 @@ class ClustersTests(test.TestCase):
|
||||
root_password=None,
|
||||
locality=None).AndReturn(self.trove_clusters.first())
|
||||
|
||||
field_name = self._build_flavor_widget_name(cluster_datastore,
|
||||
cluster_datastore_version)
|
||||
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,
|
||||
'flavor': cluster_flavor,
|
||||
'network': cluster_network
|
||||
'datastore': field_name,
|
||||
field_name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
@ -295,16 +302,17 @@ class ClustersTests(test.TestCase):
|
||||
root_password=None,
|
||||
locality=None).AndReturn(self.trove_clusters.first())
|
||||
|
||||
field_name = self._build_flavor_widget_name(cluster_datastore,
|
||||
cluster_datastore_version)
|
||||
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,
|
||||
'flavor': cluster_flavor,
|
||||
'network': cluster_network
|
||||
'datastore': field_name,
|
||||
field_name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'network': cluster_network,
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
@ -349,16 +357,16 @@ class ClustersTests(test.TestCase):
|
||||
root_password=None,
|
||||
locality=None).AndReturn(self.trove_clusters.first())
|
||||
|
||||
field_name = self._build_flavor_widget_name(cluster_datastore,
|
||||
cluster_datastore_version)
|
||||
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,
|
||||
'flavor': cluster_flavor,
|
||||
'network': cluster_network
|
||||
'datastore': field_name,
|
||||
field_name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
@ -649,3 +657,10 @@ class ClustersTests(test.TestCase):
|
||||
if datastore in key:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _build_datastore_display_text(self, datastore, datastore_version):
|
||||
return datastore + ' - ' + datastore_version
|
||||
|
||||
def _build_flavor_widget_name(self, datastore, datastore_version):
|
||||
return binascii.hexlify(self._build_datastore_display_text(
|
||||
datastore, datastore_version))
|
||||
|
Loading…
Reference in New Issue
Block a user