manila-ui/manila_ui/dashboards/project/shares/forms.py

454 lines
19 KiB
Python

# Copyright (c) 2014 NetApp, Inc.
# All rights reserved.
# 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.conf import settings
from django.forms import ValidationError
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from horizon.utils.memoized import memoized
from manila_ui.api import manila
from manila_ui.dashboards import utils
from manila_ui import features
from manilaclient.common.apiclient import exceptions as m_exceptions
class CreateForm(forms.SelfHandlingForm):
name = forms.CharField(max_length="255", label=_("Share Name"))
description = forms.CharField(
label=_("Description"), required=False,
widget=forms.Textarea(attrs={'rows': 3}))
share_proto = forms.ChoiceField(label=_("Share Protocol"), required=True)
size = forms.IntegerField(min_value=1, label=_("Size (GiB)"))
share_type = forms.ChoiceField(
label=_("Share Type"), required=True,
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'sharetype'}))
availability_zone = forms.ChoiceField(
label=_("Availability Zone"),
required=False)
def __init__(self, request, *args, **kwargs):
super(CreateForm, self).__init__(request, *args, **kwargs)
# NOTE(vkmc): choose only those share protocols that are enabled
# FIXME(vkmc): this should be better implemented by having a
# capabilities endpoint on the control plane.
manila_features = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {})
self.enabled_share_protocols = manila_features.get(
'enabled_share_protocols',
['NFS', 'CIFS', 'GlusterFS', 'HDFS', 'CephFS', 'MapRFS'])
self.enable_public_shares = manila_features.get(
'enable_public_shares', True)
share_networks = manila.share_network_list(request)
share_types = manila.share_type_list(request)
self.fields['share_type'].choices = (
[("", "")] +
[(utils.transform_dashed_name(st.name), st.name)
for st in share_types]
)
availability_zones = manila.availability_zone_list(request)
self.fields['availability_zone'].choices = (
[("", "")] + [(az.name, az.name) for az in availability_zones])
if features.is_share_groups_enabled():
share_groups = manila.share_group_list(request)
self.fields['sg'] = forms.ChoiceField(
label=_("Share Group"),
choices=[("", "")] + [(sg.id, sg.name or sg.id)
for sg in share_groups],
required=False)
self.sn_field_name_prefix = 'share-network-choices-'
for st in share_types:
extra_specs = st.get_keys()
dhss = extra_specs.get('driver_handles_share_servers')
# NOTE(vponomaryov): Set and tie share-network field only for
# share types with enabled handling of share servers.
if (isinstance(dhss, str) and dhss.lower() in ['true', '1']):
sn_choices = (
[('', '')] +
[(sn.id, sn.name or sn.id) for sn in share_networks])
sn_field_name = (
self.sn_field_name_prefix +
utils.transform_dashed_name(st.name)
)
sn_field = forms.ChoiceField(
label=_("Share Network"), required=True,
choices=sn_choices,
widget=forms.Select(attrs={
'class': 'switched',
'data-switch-on': 'sharetype',
'data-sharetype-%s' % utils.transform_dashed_name(
st.name): _("Share Network"),
}))
self.fields[sn_field_name] = sn_field
self.fields['share_source_type'] = forms.ChoiceField(
label=_("Share Source"), required=False,
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
self.fields['snapshot'] = forms.ChoiceField(
label=_("Use snapshot as a source"),
widget=forms.fields.SelectWidget(
attrs={'class': 'switched',
'data-switch-on': 'source',
'data-source-snapshot': _('Snapshot')},
data_attrs=('size', 'name'),
transform=lambda x: "%s (%sGiB)" % (x.name, x.size)),
required=False)
self.fields['metadata'] = forms.CharField(
label=_("Metadata"), required=False,
widget=forms.Textarea(attrs={'rows': 4}))
if self.enable_public_shares:
self.fields['is_public'] = forms.BooleanField(
label=_("Make visible for all"), required=False,
help_text=(
"If set then all tenants will be able to see this share."))
self.fields['share_proto'].choices = [(sp, sp) for sp in
self.enabled_share_protocols]
if ("snapshot_id" in request.GET or
kwargs.get("data", {}).get("snapshot")):
try:
snapshot = self.get_snapshot(
request,
request.GET.get("snapshot_id",
kwargs.get("data", {}).get("snapshot")))
self.fields['name'].initial = snapshot.name
self.fields['size'].initial = snapshot.size
self.fields['snapshot'].choices = ((snapshot.id, snapshot),)
try:
# Set the share type from the original share
orig_share = manila.share_get(request, snapshot.share_id)
# NOTE(vponomaryov): we should use share type name, not ID,
# because we use names in our choices above.
self.fields['share_type'].initial = (
orig_share.share_type_name)
except Exception:
pass
self.fields['size'].help_text = _(
'Share size must be equal to or greater than the snapshot '
'size (%sGiB)') % snapshot.size
del self.fields['share_source_type']
except Exception:
exceptions.handle(request,
_('Unable to load the specified snapshot.'))
else:
source_type_choices = []
try:
snapshot_list = manila.share_snapshot_list(request)
snapshots = [s for s in snapshot_list
if s.status == 'available']
if snapshots:
source_type_choices.append(("snapshot",
_("Snapshot")))
choices = [('', _("Choose a snapshot"))] + \
[(s.id, s) for s in snapshots]
self.fields['snapshot'].choices = choices
else:
del self.fields['snapshot']
except Exception:
exceptions.handle(request, _("Unable to retrieve "
"share snapshots."))
if source_type_choices:
choices = ([('no_source_type',
_("No source, empty share"))] +
source_type_choices)
self.fields['share_source_type'].choices = choices
else:
del self.fields['share_source_type']
def clean(self):
cleaned_data = super(CreateForm, self).clean()
form_errors = list(self.errors)
chosen_share_type = cleaned_data.get('share_type')
if chosen_share_type:
# NOTE(vponomaryov): skip errors for share-network fields that are
# not related to chosen share type.
for error in form_errors:
st_name = error.split(self.sn_field_name_prefix)[-1]
if (error.startswith(self.sn_field_name_prefix) and
st_name != chosen_share_type):
cleaned_data[error] = 'Not set'
self.errors.pop(error, None)
cleaned_data['share_network'] = cleaned_data.get(
self.sn_field_name_prefix + cleaned_data.get('share_type'))
return cleaned_data
def handle(self, request, data):
try:
snapshot_id = None
source_type = data.get('share_source_type', None)
share_network = data.get('share_network', None)
if (data.get("snapshot", None) and
source_type in [None, 'snapshot']):
# Create from Snapshot
snapshot = self.get_snapshot(request,
data["snapshot"])
snapshot_id = snapshot.id
if (data['size'] < snapshot.size):
error_message = _('The share size cannot be less than the '
'snapshot size (%sGiB)') % snapshot.size
raise ValidationError(error_message)
else:
data['size'] = int(data['size'])
metadata = {}
try:
set_dict, unset_list = utils.parse_str_meta(data['metadata'])
if unset_list:
msg = _("Expected only pairs of key=value.")
raise ValidationError(message=msg)
metadata = set_dict
except ValidationError as e:
self.api_error(e.messages[0])
return False
is_public = self.enable_public_shares and data['is_public']
share = manila.share_create(
request,
size=data['size'],
name=data['name'],
description=data['description'],
proto=data['share_proto'],
share_network=share_network,
snapshot_id=snapshot_id,
share_type=utils.transform_dashed_name(data['share_type']),
is_public=is_public,
metadata=metadata,
availability_zone=data['availability_zone'],
share_group_id=data.get('sg') or None,
)
message = _('Creating share "%s"') % data['name']
messages.success(request, message)
return share
except ValidationError as e:
self.api_error(e.messages[0])
except m_exceptions.BadRequest as e:
self.api_error(_("Unable to create share. %s") % e.message)
except Exception:
exceptions.handle(request, ignore=True)
self.api_error(_("Unable to create share."))
return False
@memoized
def get_snapshot(self, request, id):
return manila.share_snapshot_get(request, id)
class UpdateForm(forms.SelfHandlingForm):
name = forms.CharField(max_length="255", label=_("Share Name"))
description = forms.CharField(widget=forms.Textarea,
label=_("Description"), required=False)
is_public = forms.ChoiceField(
choices=((None, 'Do not change share visibility'),
(False, "Make it 'Private'"), (True, "Make it 'Public'")),
label=_("Visibility"), required=False,
widget=forms.Select(
attrs={'class': 'switched', 'data-slug': 'sharetype'}))
def __init__(self, request, *args, **kwargs):
super(UpdateForm, self).__init__(request, *args, **kwargs)
manila_features = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {})
self.enable_public_shares = manila_features.get(
'enable_public_shares', True)
if not self.enable_public_shares:
self.fields.pop('is_public')
def handle(self, request, data):
share_id = self.initial['share_id']
is_public = data['is_public'] if self.enable_public_shares else False
try:
share = manila.share_get(self.request, share_id)
manila.share_update(
request, share, data['name'], data['description'],
is_public=is_public)
message = _('Updating share "%s"') % data['name']
messages.success(request, message)
return True
except Exception:
redirect = reverse("horizon:project:shares:index")
exceptions.handle(request,
_('Unable to update share.'),
redirect=redirect)
class UpdateMetadataForm(forms.SelfHandlingForm):
metadata = forms.CharField(widget=forms.Textarea,
label=_("Metadata"), required=False)
def __init__(self, *args, **kwargs):
super(UpdateMetadataForm, self).__init__(*args, **kwargs)
meta_str = ""
for k, v in self.initial["metadata"].items():
meta_str += "%s=%s\r\n" % (k, v)
self.initial["metadata"] = meta_str
def handle(self, request, data):
share_id = self.initial['share_id']
try:
share = manila.share_get(self.request, share_id)
set_dict, unset_list = utils.parse_str_meta(data['metadata'])
if set_dict:
manila.share_set_metadata(request, share, set_dict)
if unset_list:
manila.share_delete_metadata(request, share, unset_list)
message = _('Updating share metadata "%s"') % share.name
messages.success(request, message)
return True
except ValidationError as e:
self.api_error(e.messages[0])
return False
except Exception:
redirect = reverse("horizon:project:shares:index")
exceptions.handle(request,
_('Unable to update share metadata.'),
redirect=redirect)
class AddRule(forms.SelfHandlingForm):
access_type = forms.ChoiceField(
label=_("Access Type"), required=True,
choices=(('ip', 'ip'), ('user', 'user'), ('cert', 'cert'),
('cephx', 'cephx')))
access_level = forms.ChoiceField(
label=_("Access Level"), required=True,
choices=(('rw', 'read-write'), ('ro', 'read-only'),))
access_to = forms.CharField(
label=_("Access To"), max_length="255", required=True)
def handle(self, request, data):
share_id = self.initial['share_id']
try:
manila.share_allow(
request, share_id,
access_to=data['access_to'],
access_type=data['access_type'],
access_level=data['access_level'])
message = _('Creating rule for "%s"') % data['access_to']
messages.success(request, message)
return True
except Exception:
redirect = reverse("horizon:project:shares:manage_rules",
args=[self.initial['share_id']])
exceptions.handle(
request, _('Unable to add rule.'), redirect=redirect)
class ExtendForm(forms.SelfHandlingForm):
name = forms.CharField(
max_length="255", label=_("Share Name"),
widget=forms.TextInput(attrs={'readonly': 'readonly'}),
required=False,
)
orig_size = forms.IntegerField(
label=_("Current Size (GiB)"),
widget=forms.TextInput(attrs={'readonly': 'readonly'}),
required=False,
)
new_size = forms.IntegerField(
label=_("New Size (GiB)"),
)
def clean(self):
cleaned_data = super(ExtendForm, self).clean()
new_size = cleaned_data.get('new_size')
orig_size = self.initial['orig_size']
if new_size <= orig_size:
message = _("New size must be greater than current size.")
self._errors["new_size"] = self.error_class([message])
return cleaned_data
usages = manila.tenant_absolute_limits(self.request)
availableGB = (usages['maxTotalShareGigabytes'] -
usages['totalShareGigabytesUsed'])
if availableGB < (new_size - orig_size):
message = _('Share cannot be extended to %(req)iGiB as '
'you only have %(avail)iGiB of your quota '
'available.')
params = {'req': new_size, 'avail': availableGB + orig_size}
self._errors["new_size"] = self.error_class([message % params])
return cleaned_data
def handle(self, request, data):
share_id = self.initial['share_id']
try:
manila.share_extend(request, share_id, data['new_size'])
message = _('Extend share "%s"') % data['name']
messages.success(request, message)
return True
except Exception:
redirect = reverse("horizon:project:shares:index")
exceptions.handle(request,
_('Unable to extend share.'),
redirect=redirect)
class RevertForm(forms.SelfHandlingForm):
"""Form for reverting a share to a snapshot."""
snapshot = forms.ChoiceField(
label=_("Snapshot"),
required=True,
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'share_snapshot'}))
def __init__(self, req, *args, **kwargs):
super(self.__class__, self).__init__(req, *args, **kwargs)
# NOTE(vponomaryov): manila client does not allow to filter snapshots
# using "created_at" field, so, we need to get all snapshots of a share
# and do filtering here.
search_opts = {'share_id': self.initial['share_id']}
snapshots = manila.share_snapshot_list(req, search_opts=search_opts)
amount_of_snapshots = len(snapshots)
if amount_of_snapshots < 1:
self.fields['snapshot'].choices = [("", "")]
else:
snapshot = snapshots[0]
if amount_of_snapshots > 1:
for s in snapshots[1:]:
if s.created_at > snapshot.created_at:
snapshot = s
self.fields['snapshot'].choices = [
(snapshot.id, snapshot.name or snapshot.id)]
def handle(self, request, data):
share_id = self.initial['share_id']
snapshot_id = data['snapshot']
try:
manila.share_revert(request, share_id, snapshot_id)
message = _('Share "%(s)s" has been reverted to "%(ss)s" snapshot '
'successfully') % {'s': share_id, 'ss': snapshot_id}
messages.success(request, message)
return True
except Exception:
redirect = reverse("horizon:project:shares:index")
exceptions.handle(
request,
_('Unable to revert share to the snapshot.'),
redirect=redirect)