Files
horizon/openstack_dashboard/dashboards/project/volume_groups/workflows.py
Akihiro Motoki 4c9cf5f00e Hide default_cgsnapshot_type from cinder group types
The generic group type named "default_cgsnapshot_type" is reserved
for the consistency group and we cannot use a generic group using
"default_cgsnapshot_type". "default_cgsnapshot_type" should not be
listed in "Group Type" selection in "Create Group" form.

In addition, "Group" and "Group Snapshot" panels make no sense
when there is no group type other than "default_cgsnapshot_type".
This commit adds a suggestion to hide "Group" and "Group Snapshot"
panels if there is no valid group type to the release notes.

Closes-Bug: #1785178
Change-Id: I2dce39bcbcf6bedc8dc0c94d11f3c0a85ea61490
2018-08-08 21:10:38 +09:00

382 lines
15 KiB
Python

# 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 ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import workflows
from openstack_dashboard import api
from openstack_dashboard.api import cinder
INDEX_URL = "horizon:project:volume_groups:index"
CGROUP_VOLUME_MEMBER_SLUG = "update_members"
def cinder_az_supported(request):
try:
return cinder.extension_supported(request, 'AvailabilityZones')
except Exception:
exceptions.handle(request, _('Unable to determine if availability '
'zones extension is supported.'))
return False
def availability_zones(request):
zone_list = []
if cinder_az_supported(request):
try:
zones = api.cinder.availability_zone_list(request)
zone_list = [(zone.zoneName, zone.zoneName)
for zone in zones if zone.zoneState['available']]
zone_list.sort()
except Exception:
exceptions.handle(request, _('Unable to retrieve availability '
'zones.'))
if not zone_list:
zone_list.insert(0, ("", _("No availability zones found")))
elif len(zone_list) > 1:
zone_list.insert(0, ("", _("Any Availability Zone")))
return zone_list
class AddGroupInfoAction(workflows.Action):
name = forms.CharField(label=_("Name"),
max_length=255)
description = forms.CharField(widget=forms.widgets.Textarea(
attrs={'rows': 4}),
label=_("Description"),
required=False)
group_type = forms.ChoiceField(
label=_("Group Type"),
widget=forms.ThemableSelectWidget())
availability_zone = forms.ChoiceField(
label=_("Availability Zone"),
required=False,
widget=forms.ThemableSelectWidget(
attrs={'class': 'switched',
'data-switch-on': 'source',
'data-source-no_source_type': _('Availability Zone'),
'data-source-image_source': _('Availability Zone')}))
def __init__(self, request, *args, **kwargs):
super(AddGroupInfoAction, self).__init__(request,
*args,
**kwargs)
self.fields['availability_zone'].choices = \
availability_zones(request)
try:
# Group type name 'default_cgsnapshot_type' is reserved for
# consistency group and it cannot be used for a group type.
# Let's exclude it.
group_types = [(t.id, t.name) for t
in api.cinder.group_type_list(request)
if t.name != 'default_cgsnapshot_type']
except Exception:
exceptions.handle(request, _('Unable to retrieve group types.'))
if group_types:
group_types.insert(0, ("", _("Select group type")))
else:
group_types.insert(0, ("", _("No valid group type")))
self.fields['group_type'].choices = group_types
class Meta(object):
name = _("Group Information")
help_text = _("Volume groups provide a mechanism for "
"creating snapshots of multiple volumes at the same "
"point-in-time to ensure data consistency\n\n"
"A volume group can support more than one volume "
"type, but it can only contain volumes hosted by the "
"same back end.")
slug = "set_group_info"
def clean(self):
cleaned_data = super(AddGroupInfoAction, self).clean()
name = cleaned_data.get('name')
try:
groups = cinder.group_list(self.request)
except Exception:
msg = _('Unable to get group list')
exceptions.check_message(["Connection", "refused"], msg)
raise
if groups is not None and name is not None:
for group in groups:
if group.name.lower() == name.lower():
# ensure new name has reasonable length
formatted_name = name
if len(name) > 20:
formatted_name = name[:14] + "..." + name[-3:]
raise forms.ValidationError(
_('The name "%s" is already used by '
'another group.')
% formatted_name
)
return cleaned_data
class AddGroupInfoStep(workflows.Step):
action_class = AddGroupInfoAction
contributes = ("availability_zone", "group_type",
"description",
"name")
class AddVolumeTypesToGroupAction(workflows.MembershipAction):
def __init__(self, request, *args, **kwargs):
super(AddVolumeTypesToGroupAction, self).__init__(request,
*args,
**kwargs)
err_msg = _('Unable to get the available volume types')
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)
vtypes = []
try:
vtypes = cinder.volume_type_list(request)
except Exception:
exceptions.handle(request, err_msg)
vtype_list = [(vtype.id, vtype.name)
for vtype in vtypes]
self.fields[field_name].choices = vtype_list
class Meta(object):
name = _("Manage Volume Types")
slug = "add_vtypes_to_group"
def clean(self):
cleaned_data = super(AddVolumeTypesToGroupAction, self).clean()
volume_types = cleaned_data.get('add_vtypes_to_group_role_member')
if not volume_types:
raise forms.ValidationError(
_('At least one volume type must be assigned '
'to a group.')
)
return cleaned_data
class AddVolTypesToGroupStep(workflows.UpdateMembersStep):
action_class = AddVolumeTypesToGroupAction
help_text = _("Add volume types to this group. "
"Multiple volume types can be added to the same "
"group only if they are associated with "
"same back end.")
available_list_title = _("All available volume types")
members_list_title = _("Selected volume types")
no_available_text = _("No volume types found.")
no_members_text = _("No volume types selected.")
show_roles = False
contributes = ("volume_types",)
def contribute(self, data, context):
if data:
member_field_name = self.get_member_field_name('member')
context['volume_types'] = data.get(member_field_name, [])
return context
class AddVolumesToGroupAction(workflows.MembershipAction):
def __init__(self, request, *args, **kwargs):
super(AddVolumesToGroupAction, self).__init__(request,
*args,
**kwargs)
err_msg = _('Unable to get the available volumes')
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)
vtypes = self.initial['vtypes']
try:
# get names of volume types associated with group
vtype_names = []
volume_types = cinder.volume_type_list(request)
for volume_type in volume_types:
if volume_type.id in vtypes:
vtype_names.append(volume_type.name)
# collect volumes that are associated with volume types
vol_list = []
volumes = cinder.volume_list(request)
for volume in volumes:
if volume.volume_type in vtype_names:
group_id = None
vol_is_available = False
in_this_group = False
if hasattr(volume, 'group_id'):
# this vol already belongs to a group
# only include it here if it belongs to this group
group_id = volume.group_id
if not group_id:
# put this vol in the available list
vol_is_available = True
elif group_id == self.initial['group_id']:
# put this vol in the assigned to group list
vol_is_available = True
in_this_group = True
if vol_is_available:
vol_list.append({'volume_name': volume.name,
'volume_id': volume.id,
'in_group': in_this_group,
'is_duplicate': False})
sorted_vol_list = sorted(vol_list, key=lambda k: k['volume_name'])
# mark any duplicate volume names
for index, volume in enumerate(sorted_vol_list):
if index < len(sorted_vol_list) - 1:
if volume['volume_name'] == \
sorted_vol_list[index + 1]['volume_name']:
volume['is_duplicate'] = True
sorted_vol_list[index + 1]['is_duplicate'] = True
# update display with all available vols and those already
# assigned to group
available_vols = []
assigned_vols = []
for volume in sorted_vol_list:
if volume['is_duplicate']:
# add id to differentiate volumes to user
entry = volume['volume_name'] + \
" [" + volume['volume_id'] + "]"
else:
entry = volume['volume_name']
available_vols.append((volume['volume_id'], entry))
if volume['in_group']:
assigned_vols.append(volume['volume_id'])
except Exception:
exceptions.handle(request, err_msg)
self.fields[field_name].choices = available_vols
self.fields[field_name].initial = assigned_vols
class Meta(object):
name = _("Manage Volumes")
slug = "add_volumes_to_group"
class AddVolumesToGroupStep(workflows.UpdateMembersStep):
action_class = AddVolumesToGroupAction
help_text = _("Add/remove volumes to/from this group. "
"Only volumes associated with the volume type(s) assigned "
"to this group will be available for selection.")
available_list_title = _("All available volumes")
members_list_title = _("Selected volumes")
no_available_text = _("No volumes found.")
no_members_text = _("No volumes selected.")
show_roles = False
depends_on = ("group_id", "name", "vtypes")
contributes = ("volumes",)
def contribute(self, data, context):
if data:
member_field_name = self.get_member_field_name('member')
context['volumes'] = data.get(member_field_name, [])
return context
class CreateGroupWorkflow(workflows.Workflow):
slug = "create_group"
name = _("Create Group")
finalize_button_name = _("Create Group")
failure_message = _('Unable to create group.')
success_message = _('Created new volume group')
success_url = INDEX_URL
default_steps = (AddGroupInfoStep,
AddVolTypesToGroupStep)
def handle(self, request, context):
try:
self.object = cinder.group_create(
request,
context['name'],
context['group_type'],
context['volume_types'],
description=context['description'],
availability_zone=context['availability_zone'])
except Exception:
exceptions.handle(request, _('Unable to create group.'))
return False
return True
class UpdateGroupWorkflow(workflows.Workflow):
slug = "update_group"
name = _("Add/Remove Group Volumes")
finalize_button_name = _("Submit")
success_message = _('Updated volumes for group.')
failure_message = _('Unable to update volumes for group')
success_url = INDEX_URL
default_steps = (AddVolumesToGroupStep,)
def handle(self, request, context):
group_id = context['group_id']
add_vols = []
remove_vols = []
try:
selected_volumes = context['volumes']
volumes = cinder.volume_list(request)
# scan all volumes and make correct consistency group is set
for volume in volumes:
selected = False
for selection in selected_volumes:
if selection == volume.id:
selected = True
break
if selected:
# ensure this volume is in this consistency group
if hasattr(volume, 'group_id'):
if volume.group_id != group_id:
add_vols.append(volume.id)
else:
add_vols.append(volume.id)
else:
# ensure this volume is not in our consistency group
if hasattr(volume, 'group_id'):
if volume.group_id == group_id:
# remove from this group
remove_vols.append(volume.id)
if not add_vols and not remove_vols:
# nothing to change
return True
cinder.group_update(request, group_id,
add_volumes=add_vols,
remove_volumes=remove_vols)
except Exception:
# error message supplied by form
return False
return True