af7e6c4f75
Currently the flavor creation form always check for the uniqueness of the UUID field, even when it is set to "auto". That means that if we create a flavor with UUID value of "auto", the check will fail. This patch disable the check when UUID is automatically generated. Change-Id: Ie31307d67cf3857e3bb80c124e92c81e0c6a2982
281 lines
11 KiB
Python
281 lines
11 KiB
Python
# Copyright 2012 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# All Rights Reserved.
|
|
#
|
|
# Copyright 2012 Nebula, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from horizon import exceptions
|
|
from horizon import forms
|
|
from horizon import workflows
|
|
|
|
from openstack_dashboard import api
|
|
|
|
|
|
class CreateFlavorInfoAction(workflows.Action):
|
|
_flavor_id_regex = (r'^[a-zA-Z0-9. _-]+$')
|
|
_flavor_id_help_text = _("flavor id can only contain alphanumeric "
|
|
"characters, underscores, periods, hyphens, "
|
|
"spaces. Use 'auto' to automatically generate id")
|
|
name = forms.CharField(
|
|
label=_("Name"),
|
|
max_length=255)
|
|
flavor_id = forms.RegexField(label=_("ID"),
|
|
regex=_flavor_id_regex,
|
|
required=False,
|
|
initial='auto',
|
|
max_length=255,
|
|
help_text=_flavor_id_help_text)
|
|
vcpus = forms.IntegerField(label=_("VCPUs"),
|
|
min_value=1,
|
|
max_value=2147483647)
|
|
memory_mb = forms.IntegerField(label=_("RAM (MB)"),
|
|
min_value=1,
|
|
max_value=2147483647)
|
|
disk_gb = forms.IntegerField(label=_("Root Disk (GB)"),
|
|
min_value=0,
|
|
max_value=2147483647)
|
|
eph_gb = forms.IntegerField(label=_("Ephemeral Disk (GB)"),
|
|
required=False,
|
|
initial=0,
|
|
min_value=0)
|
|
swap_mb = forms.IntegerField(label=_("Swap Disk (MB)"),
|
|
required=False,
|
|
initial=0,
|
|
min_value=0)
|
|
rxtx_factor = forms.FloatField(label=_("RX/TX Factor"),
|
|
required=False,
|
|
initial=1,
|
|
min_value=1)
|
|
|
|
class Meta(object):
|
|
name = _("Flavor Information")
|
|
help_text = _("Flavors define the sizes for RAM, disk, number of "
|
|
"cores, and other resources and can be selected when "
|
|
"users deploy instances.")
|
|
|
|
def clean_name(self):
|
|
name = self.cleaned_data.get('name').strip()
|
|
if not name:
|
|
msg = _('Flavor name cannot be empty.')
|
|
self._errors['name'] = self.error_class([msg])
|
|
return name
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
name = cleaned_data.get('name')
|
|
flavor_id = cleaned_data.get('flavor_id')
|
|
|
|
try:
|
|
flavors = api.nova.flavor_list(self.request, None)
|
|
except Exception:
|
|
flavors = []
|
|
msg = _('Unable to get flavor list')
|
|
exceptions.handle(self.request, msg)
|
|
raise
|
|
if flavors is not None and name is not None:
|
|
for flavor in flavors:
|
|
if flavor.name.lower() == name.lower():
|
|
error_msg = _('The name "%s" is already used by '
|
|
'another flavor.') % name
|
|
self._errors['name'] = self.error_class([error_msg])
|
|
if (flavor.id != 'auto') and (flavor.id == flavor_id):
|
|
error_msg = _('The ID "%s" is already used by '
|
|
'another flavor.') % flavor_id
|
|
self._errors['flavor_id'] = self.error_class([error_msg])
|
|
return cleaned_data
|
|
|
|
|
|
class CreateFlavorInfo(workflows.Step):
|
|
action_class = CreateFlavorInfoAction
|
|
contributes = ("flavor_id",
|
|
"name",
|
|
"vcpus",
|
|
"memory_mb",
|
|
"disk_gb",
|
|
"eph_gb",
|
|
"swap_mb",
|
|
"rxtx_factor")
|
|
|
|
|
|
class FlavorAccessAction(workflows.MembershipAction):
|
|
def __init__(self, request, *args, **kwargs):
|
|
super().__init__(request, *args, **kwargs)
|
|
err_msg = _('Unable to retrieve flavor access list. '
|
|
'Please try again later.')
|
|
context = args[0]
|
|
|
|
default_role_field_name = self.get_default_role_field_name()
|
|
self.fields[default_role_field_name] = forms.CharField(required=False)
|
|
self.fields[default_role_field_name].initial = 'member'
|
|
|
|
field_name = self.get_member_field_name('member')
|
|
self.fields[field_name] = forms.MultipleChoiceField(required=False)
|
|
|
|
# Get list of available projects.
|
|
all_projects = []
|
|
try:
|
|
all_projects, has_more = api.keystone.tenant_list(request)
|
|
except Exception:
|
|
exceptions.handle(request, err_msg)
|
|
projects_list = [(project.id, project.name)
|
|
for project in all_projects]
|
|
|
|
self.fields[field_name].choices = projects_list
|
|
|
|
# If we have a POST from the CreateFlavor workflow, the flavor id
|
|
# isn't an existing flavor. For the UpdateFlavor case, we don't care
|
|
# about the access list for the current flavor anymore as we're about
|
|
# to replace it.
|
|
if request.method == 'POST':
|
|
return
|
|
|
|
# Get list of flavor projects if the flavor is not public.
|
|
flavor = context.get('flavor')
|
|
flavor_access = []
|
|
try:
|
|
if flavor and not flavor.is_public:
|
|
flavor_access = [project.tenant_id for project in
|
|
context['current_flavor_access']]
|
|
except Exception:
|
|
exceptions.handle(request, err_msg)
|
|
|
|
self.fields[field_name].initial = flavor_access
|
|
|
|
class Meta(object):
|
|
name = _("Flavor Access")
|
|
slug = "flavor_access"
|
|
|
|
|
|
class FlavorAccess(workflows.UpdateMembersStep):
|
|
action_class = FlavorAccessAction
|
|
help_text = _("Select the projects where the flavors will be used. If no "
|
|
"projects are selected, then the flavor will be available "
|
|
"in all projects.")
|
|
available_list_title = _("All Projects")
|
|
members_list_title = _("Selected Projects")
|
|
no_available_text = _("No projects found.")
|
|
no_members_text = _("No projects selected. "
|
|
"All projects can use the flavor.")
|
|
show_roles = False
|
|
|
|
def contribute(self, data, context):
|
|
if data:
|
|
member_field_name = self.get_member_field_name('member')
|
|
context['flavor_access'] = data.get(member_field_name, [])
|
|
return context
|
|
|
|
|
|
class CreateFlavorAccess(FlavorAccess):
|
|
contributes = ("flavor_access",)
|
|
|
|
|
|
class UpdateFlavorAccess(FlavorAccess):
|
|
depends_on = ("flavor", "current_flavor_access")
|
|
contributes = ("flavor_access",)
|
|
|
|
|
|
class CreateFlavor(workflows.Workflow):
|
|
slug = "create_flavor"
|
|
name = _("Create Flavor")
|
|
finalize_button_name = _("Create Flavor")
|
|
success_message = _('Created new flavor "%s".')
|
|
failure_message = _('Unable to create flavor "%s".')
|
|
success_url = "horizon:admin:flavors:index"
|
|
default_steps = (CreateFlavorInfo,
|
|
CreateFlavorAccess)
|
|
|
|
def format_status_message(self, message):
|
|
return message % self.context['name']
|
|
|
|
def handle(self, request, data):
|
|
flavor_id = data.get('flavor_id') or 'auto'
|
|
swap = data.get('swap_mb') or 0
|
|
ephemeral = data.get('eph_gb') or 0
|
|
flavor_access = data['flavor_access']
|
|
is_public = not flavor_access
|
|
rxtx_factor = data.get('rxtx_factor') or 1
|
|
|
|
# Create the flavor
|
|
try:
|
|
self.object = api.nova.flavor_create(request,
|
|
name=data['name'],
|
|
memory=data['memory_mb'],
|
|
vcpu=data['vcpus'],
|
|
disk=data['disk_gb'],
|
|
ephemeral=ephemeral,
|
|
swap=swap,
|
|
flavorid=flavor_id,
|
|
is_public=is_public,
|
|
rxtx_factor=rxtx_factor)
|
|
except Exception:
|
|
exceptions.handle(request, _('Unable to create flavor.'))
|
|
return False
|
|
|
|
# Update flavor access if the new flavor is not public
|
|
flavor_id = self.object.id
|
|
for project in flavor_access:
|
|
try:
|
|
api.nova.add_tenant_to_flavor(
|
|
request, flavor_id, project)
|
|
except Exception:
|
|
exceptions.handle(
|
|
request,
|
|
_('Unable to set flavor access for project %s.') % project)
|
|
return True
|
|
|
|
|
|
class UpdateFlavor(workflows.Workflow):
|
|
slug = "update_flavor"
|
|
name = _("Edit Flavor")
|
|
finalize_button_name = _("Save")
|
|
success_message = _('Modified flavor access of "%s".')
|
|
failure_message = _('Unable to modify flavor access of "%s".')
|
|
success_url = "horizon:admin:flavors:index"
|
|
default_steps = (UpdateFlavorAccess,)
|
|
|
|
def format_status_message(self, message):
|
|
return message % self.context['flavor'].name
|
|
|
|
def handle(self, request, data):
|
|
flavor_projects = data["flavor_access"]
|
|
flavor = self.context['flavor']
|
|
|
|
# Check if the flavor info is not actually changed
|
|
try:
|
|
if flavor.is_public:
|
|
old_flavor_projects = []
|
|
else:
|
|
old_flavor_projects = [project.tenant_id for project in
|
|
self.context['current_flavor_access']]
|
|
to_remove = [project for project in old_flavor_projects if project
|
|
not in flavor_projects]
|
|
to_add = [project for project in flavor_projects if project not in
|
|
old_flavor_projects]
|
|
for project in to_remove:
|
|
api.nova.remove_tenant_from_flavor(request,
|
|
flavor.id,
|
|
project)
|
|
for project in to_add:
|
|
api.nova.add_tenant_to_flavor(request,
|
|
flavor.id,
|
|
project)
|
|
return True
|
|
except Exception:
|
|
# Error message will be shown by the workflow view.
|
|
return False
|