Add support for instance datastore-flavors
Update the launch wizard to use datastore-flavors instead of flavor-list. When selecting a datastore the flavor list will change to display only flavors for the selected datastore. If the datastore flavor is not configured then the default flavor list will be used. Updated unit tests to use the datastore flavors. Change-Id: I4bf0033a3e6b2a117f93db2c0c16a6d02642f368 Closes-Bug: #1549367
This commit is contained in:
parent
acb3d96ef0
commit
e1c44b2092
@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import binascii
|
||||
import logging
|
||||
|
||||
import django
|
||||
@ -128,7 +129,7 @@ class DatabaseTests(test.TestCase):
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({
|
||||
api.trove: ('flavor_list', 'backup_list',
|
||||
api.trove: ('datastore_flavors', 'backup_list',
|
||||
'datastore_list', 'datastore_version_list',
|
||||
'instance_list'),
|
||||
dash_api.cinder: ('volume_type_list',),
|
||||
@ -137,8 +138,10 @@ class DatabaseTests(test.TestCase):
|
||||
})
|
||||
def test_launch_instance(self):
|
||||
policy.check((), IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
|
||||
api.trove.flavor_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.flavors.list())
|
||||
api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
IsA(six.string_types),
|
||||
IsA(six.string_types)).\
|
||||
MultipleTimes().AndReturn(self.flavors.list())
|
||||
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.database_backups.list())
|
||||
api.trove.instance_list(IsA(http.HttpRequest)).AndReturn(
|
||||
@ -197,7 +200,7 @@ class DatabaseTests(test.TestCase):
|
||||
log.setLevel(level)
|
||||
|
||||
@test.create_stubs({
|
||||
api.trove: ('flavor_list', 'backup_list', 'instance_create',
|
||||
api.trove: ('datastore_flavors', 'backup_list', 'instance_create',
|
||||
'datastore_list', 'datastore_version_list',
|
||||
'instance_list'),
|
||||
dash_api.cinder: ('volume_type_list',),
|
||||
@ -206,8 +209,10 @@ class DatabaseTests(test.TestCase):
|
||||
})
|
||||
def test_create_simple_instance(self):
|
||||
policy.check((), IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
|
||||
api.trove.flavor_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.flavors.list())
|
||||
api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
IsA(six.string_types),
|
||||
IsA(six.string_types)).\
|
||||
MultipleTimes().AndReturn(self.flavors.list())
|
||||
|
||||
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.database_backups.list())
|
||||
@ -236,6 +241,10 @@ class DatabaseTests(test.TestCase):
|
||||
|
||||
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]
|
||||
|
||||
datastore = 'mysql'
|
||||
datastore_version = '5.5'
|
||||
field_name = self._build_flavor_widget_name(datastore,
|
||||
datastore_version)
|
||||
# Actual create database call
|
||||
api.trove.instance_create(
|
||||
IsA(http.HttpRequest),
|
||||
@ -243,8 +252,8 @@ class DatabaseTests(test.TestCase):
|
||||
IsA(int),
|
||||
IsA(six.text_type),
|
||||
databases=None,
|
||||
datastore=IsA(six.text_type),
|
||||
datastore_version=IsA(six.text_type),
|
||||
datastore=datastore,
|
||||
datastore_version=datastore_version,
|
||||
restore_point=None,
|
||||
replica_of=None,
|
||||
users=None,
|
||||
@ -257,8 +266,9 @@ class DatabaseTests(test.TestCase):
|
||||
'name': "MyDB",
|
||||
'volume': '1',
|
||||
'flavor': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'datastore': field_name,
|
||||
field_name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'network': self.networks.first().id,
|
||||
'datastore': 'mysql,5.5',
|
||||
'volume_type': 'no_type'
|
||||
}
|
||||
|
||||
@ -266,7 +276,7 @@ class DatabaseTests(test.TestCase):
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({
|
||||
api.trove: ('flavor_list', 'backup_list', 'instance_create',
|
||||
api.trove: ('datastore_flavors', 'backup_list', 'instance_create',
|
||||
'datastore_list', 'datastore_version_list',
|
||||
'instance_list'),
|
||||
dash_api.cinder: ('volume_type_list',),
|
||||
@ -276,8 +286,10 @@ class DatabaseTests(test.TestCase):
|
||||
def test_create_simple_instance_exception(self):
|
||||
policy.check((), IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
|
||||
trove_exception = self.exceptions.nova
|
||||
api.trove.flavor_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.flavors.list())
|
||||
api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
IsA(six.string_types),
|
||||
IsA(six.string_types)).\
|
||||
MultipleTimes().AndReturn(self.flavors.list())
|
||||
|
||||
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.database_backups.list())
|
||||
@ -306,6 +318,10 @@ class DatabaseTests(test.TestCase):
|
||||
|
||||
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]
|
||||
|
||||
datastore = 'mysql'
|
||||
datastore_version = '5.5'
|
||||
field_name = self._build_flavor_widget_name(datastore,
|
||||
datastore_version)
|
||||
# Actual create database call
|
||||
api.trove.instance_create(
|
||||
IsA(http.HttpRequest),
|
||||
@ -313,8 +329,8 @@ class DatabaseTests(test.TestCase):
|
||||
IsA(int),
|
||||
IsA(six.text_type),
|
||||
databases=None,
|
||||
datastore=IsA(six.text_type),
|
||||
datastore_version=IsA(six.text_type),
|
||||
datastore=datastore,
|
||||
datastore_version=datastore_version,
|
||||
restore_point=None,
|
||||
replica_of=None,
|
||||
users=None,
|
||||
@ -327,8 +343,9 @@ class DatabaseTests(test.TestCase):
|
||||
'name': "MyDB",
|
||||
'volume': '1',
|
||||
'flavor': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'datastore': field_name,
|
||||
field_name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'network': self.networks.first().id,
|
||||
'datastore': 'mysql,5.5',
|
||||
'volume_type': 'no_type'
|
||||
}
|
||||
|
||||
@ -964,7 +981,7 @@ class DatabaseTests(test.TestCase):
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({
|
||||
api.trove: ('flavor_list', 'backup_list', 'instance_create',
|
||||
api.trove: ('datastore_flavors', 'backup_list', 'instance_create',
|
||||
'datastore_list', 'datastore_version_list',
|
||||
'instance_list_all', 'instance_get'),
|
||||
dash_api.cinder: ('volume_type_list',),
|
||||
@ -973,8 +990,10 @@ class DatabaseTests(test.TestCase):
|
||||
})
|
||||
def test_create_replica_instance(self):
|
||||
policy.check((), IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
|
||||
api.trove.flavor_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.flavors.list())
|
||||
api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
IsA(six.string_types),
|
||||
IsA(six.string_types)).\
|
||||
MultipleTimes().AndReturn(self.flavors.list())
|
||||
|
||||
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.database_backups.list())
|
||||
@ -1005,6 +1024,10 @@ class DatabaseTests(test.TestCase):
|
||||
api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))\
|
||||
.AndReturn(self.databases.first())
|
||||
|
||||
datastore = 'mysql'
|
||||
datastore_version = '5.5'
|
||||
field_name = self._build_flavor_widget_name(datastore,
|
||||
datastore_version)
|
||||
# Actual create database call
|
||||
api.trove.instance_create(
|
||||
IsA(http.HttpRequest),
|
||||
@ -1012,8 +1035,8 @@ class DatabaseTests(test.TestCase):
|
||||
IsA(int),
|
||||
IsA(six.text_type),
|
||||
databases=None,
|
||||
datastore=IsA(six.text_type),
|
||||
datastore_version=IsA(six.text_type),
|
||||
datastore=datastore,
|
||||
datastore_version=datastore_version,
|
||||
restore_point=None,
|
||||
replica_of=self.databases.first().id,
|
||||
users=None,
|
||||
@ -1026,8 +1049,9 @@ class DatabaseTests(test.TestCase):
|
||||
'name': "MyDB",
|
||||
'volume': '1',
|
||||
'flavor': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'datastore': field_name,
|
||||
field_name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'network': self.networks.first().id,
|
||||
'datastore': 'mysql,5.5',
|
||||
'initial_state': 'master',
|
||||
'master': self.databases.first().id,
|
||||
'replica_count': 2,
|
||||
@ -1159,3 +1183,10 @@ class DatabaseTests(test.TestCase):
|
||||
advanced_page = create_instance.AdvancedAction(request, None)
|
||||
choices = advanced_page.populate_master_choices(request, None)
|
||||
self.assertTrue(len(choices) == len(self.databases.list()) + 1)
|
||||
|
||||
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))
|
||||
|
@ -12,6 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import binascii
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
@ -34,10 +35,15 @@ from trove_dashboard import api
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_datastore_and_version_text(datastore_and_version):
|
||||
if datastore_and_version:
|
||||
datastore, datastore_version = datastore_and_version.split('-', 1)
|
||||
return datastore.strip(), datastore_version.strip()
|
||||
return None, None
|
||||
|
||||
|
||||
class SetInstanceDetailsAction(workflows.Action):
|
||||
name = forms.CharField(max_length=80, label=_("Instance Name"))
|
||||
flavor = forms.ChoiceField(label=_("Flavor"),
|
||||
help_text=_("Size of image to launch."))
|
||||
volume = forms.IntegerField(label=_("Volume Size"),
|
||||
min_value=0,
|
||||
initial=1,
|
||||
@ -46,24 +52,53 @@ class SetInstanceDetailsAction(workflows.Action):
|
||||
label=_("Volume Type"),
|
||||
required=False,
|
||||
help_text=_("Applicable only if the volume size is specified."))
|
||||
datastore = forms.ChoiceField(label=_("Datastore"),
|
||||
help_text=_(
|
||||
"Type and version of datastore."))
|
||||
datastore = forms.ChoiceField(
|
||||
label=_("Datastore"),
|
||||
help_text=_("Type and version of datastore."),
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'datastore'
|
||||
}))
|
||||
|
||||
class Meta(object):
|
||||
name = _("Details")
|
||||
help_text_template = "project/databases/_launch_details_help.html"
|
||||
|
||||
def clean(self):
|
||||
if self.data.get("datastore", None) == "select_datastore_type_version":
|
||||
datastore_and_version = self.data.get("datastore", None)
|
||||
if not datastore_and_version:
|
||||
msg = _("You must select a datastore type and version.")
|
||||
self._errors["datastore"] = self.error_class([msg])
|
||||
else:
|
||||
datastore, datastore_version = parse_datastore_and_version_text(
|
||||
binascii.unhexlify(datastore_and_version))
|
||||
field_name = self._build_flavor_field_name(datastore,
|
||||
datastore_version)
|
||||
flavor = self.data.get(field_name, None)
|
||||
if not flavor:
|
||||
msg = _("You must select a flavor.")
|
||||
self._errors[field_name] = self.error_class([msg])
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
def handle(self, request, context):
|
||||
datastore_and_version = context["datastore"]
|
||||
if datastore_and_version:
|
||||
datastore, datastore_version = parse_datastore_and_version_text(
|
||||
binascii.unhexlify(context["datastore"]))
|
||||
field_name = self._build_flavor_field_name(datastore,
|
||||
datastore_version)
|
||||
flavor = self.data[field_name]
|
||||
if flavor:
|
||||
context["flavor"] = flavor
|
||||
return context
|
||||
return None
|
||||
|
||||
@memoized.memoized_method
|
||||
def flavors(self, request):
|
||||
def datastore_flavors(self, request, datastore_name, datastore_version):
|
||||
try:
|
||||
return api.trove.flavor_list(request)
|
||||
return api.trove.datastore_flavors(
|
||||
request, datastore_name, datastore_version)
|
||||
except Exception:
|
||||
LOG.exception("Exception while obtaining flavors list")
|
||||
redirect = reverse("horizon:project:databases:index")
|
||||
@ -71,12 +106,6 @@ class SetInstanceDetailsAction(workflows.Action):
|
||||
_('Unable to obtain flavors.'),
|
||||
redirect=redirect)
|
||||
|
||||
def populate_flavor_choices(self, request, context):
|
||||
flavors = self.flavors(request)
|
||||
if flavors:
|
||||
return instance_utils.sort_flavor_list(request, flavors)
|
||||
return []
|
||||
|
||||
@memoized.memoized_method
|
||||
def populate_volume_type_choices(self, request, context):
|
||||
try:
|
||||
@ -106,36 +135,66 @@ class SetInstanceDetailsAction(workflows.Action):
|
||||
|
||||
def populate_datastore_choices(self, request, context):
|
||||
choices = ()
|
||||
set_initial = False
|
||||
datastores = self.datastores(request)
|
||||
if datastores is not None:
|
||||
num_datastores_with_one_version = 0
|
||||
for ds in datastores:
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
if not set_initial:
|
||||
if len(versions) >= 2:
|
||||
set_initial = True
|
||||
elif len(versions) == 1:
|
||||
num_datastores_with_one_version += 1
|
||||
if num_datastores_with_one_version > 1:
|
||||
set_initial = True
|
||||
if versions:
|
||||
# only add to choices if datastore has at least one version
|
||||
version_choices = ()
|
||||
for v in versions:
|
||||
if hasattr(v, 'active') and not v.active:
|
||||
continue
|
||||
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 +
|
||||
((ds.name + ',' + v.name, v.name),))
|
||||
datastore_choices = (ds.name, version_choices)
|
||||
choices = choices + (datastore_choices,)
|
||||
if set_initial:
|
||||
# prepend choice to force user to choose
|
||||
initial = (('select_datastore_type_version',
|
||||
_('Select datastore type and version')))
|
||||
choices = (initial,) + choices
|
||||
((widget_text, selection_text),))
|
||||
self._add_datastore_flavor_field(request,
|
||||
ds.name,
|
||||
v.name)
|
||||
choices = choices + version_choices
|
||||
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_name = self._build_flavor_field_name(datastore,
|
||||
datastore_version)
|
||||
self.fields[field_name] = 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:
|
||||
self.fields[field_name].choices = instance_utils.sort_flavor_list(
|
||||
request, valid_flavors)
|
||||
|
||||
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 _build_flavor_field_name(self, datastore, datastore_version):
|
||||
return self._build_widget_field_name(datastore,
|
||||
datastore_version)
|
||||
|
||||
|
||||
TROVE_ADD_USER_PERMS = getattr(settings, 'TROVE_ADD_USER_PERMS', [])
|
||||
TROVE_ADD_DATABASE_PERMS = getattr(settings, 'TROVE_ADD_DATABASE_PERMS', [])
|
||||
@ -387,8 +446,8 @@ class LaunchInstance(workflows.Workflow):
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
datastore = self.context['datastore'].split(',')[0]
|
||||
datastore_version = self.context['datastore'].split(',')[1]
|
||||
datastore, datastore_version = parse_datastore_and_version_text(
|
||||
binascii.unhexlify(self.context['datastore']))
|
||||
LOG.info("Launching database instance with parameters "
|
||||
"{name=%s, volume=%s, volume_type=%s, flavor=%s, "
|
||||
"datastore=%s, datastore_version=%s, "
|
||||
|
Loading…
Reference in New Issue
Block a user