horizon/openstack_dashboard/dashboards/admin/flavors/workflows.py
Tatiana Ovchinnikova af7e6c4f75 Fix flavor id auto generation
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
2022-11-02 14:13:36 -05:00

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