gui/cgcs_dashboard/dashboards/admin/inventory/storages/lvg_params/forms.py

355 lines
14 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2015-2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import logging
from django.core.urlresolvers import reverse # noqa
from django.core import validators # noqa
from django.utils.translation import ugettext_lazy as _ # noqa
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard import api
from oslo_serialization import jsonutils
from openstack_dashboard.api import sysinv
LOG = logging.getLogger(__name__)
NOVA_PARAMS_FIELD_MAP = {
sysinv.LVG_NOVA_PARAM_BACKING:
sysinv.LVG_NOVA_PARAM_BACKING,
sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB:
sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB,
sysinv.LVG_NOVA_PARAM_DISK_OPS:
sysinv.LVG_NOVA_PARAM_DISK_OPS,
}
CINDER_PARAMS_FIELD_MAP = {
sysinv.LVG_CINDER_PARAM_LVM_TYPE:
sysinv.LVG_CINDER_PARAM_LVM_TYPE,
}
NOVA_PARAMS_KEY_MAP = (
(sysinv.LVG_NOVA_PARAM_BACKING,
_("Instance Backing")),
(sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB,
_("Instances LV Size [in MiB]")),
(sysinv.LVG_NOVA_PARAM_DISK_OPS,
_("Concurrent Disk Operations")),
)
CINDER_PARAMS_KEY_MAP = (
(sysinv.LVG_CINDER_PARAM_LVM_TYPE,
_("LVM Provisioning Type")),
)
PARAMS_HELP = {
sysinv.LVG_NOVA_PARAM_BACKING:
'Determines the format and location of instance disks. Local CoW image \
file backed, local RAW LVM logical volume backed, or remote RAW Ceph \
storage backed',
sysinv.LVG_NOVA_PARAM_DISK_OPS:
'Number of parallel disk I/O intensive operations (glance image downloads, \
image format conversions, etc.).',
sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB:
'An integer specifying the size (in MiB) of the instances logical volume. \
(.e.g. 10 GiB = 10240). Volume is created from nova-local and will be \
mounted at /etc/nova/instances.',
sysinv.LVG_CINDER_PARAM_LVM_TYPE:
'Cinder configuration setting which determines how the volume group is \
provisioned. Thick provisioning will be used if the value is set to: \
default. Thin provisioning will be used in the value is set to: thin',
}
NOVA_PARAMS_KEY_NAMES = dict(NOVA_PARAMS_KEY_MAP)
NOVA_PARAMS_CHOICES = NOVA_PARAMS_KEY_MAP
CINDER_PARAMS_KEY_NAMES = dict(CINDER_PARAMS_KEY_MAP)
CINDER_PARAMS_CHOICES = CINDER_PARAMS_KEY_MAP
BACKING_CHOICES = (
(sysinv.LVG_NOVA_BACKING_LVM, _("Local RAW LVM backed")),
(sysinv.LVG_NOVA_BACKING_IMAGE, _("Local CoW image backed")),
(sysinv.LVG_NOVA_BACKING_REMOTE, _("Remote RAW Ceph storage backed")),
)
LVM_TYPE_CHOICES = (
(sysinv.LVG_CINDER_LVM_TYPE_THICK, _("Thick Provisioning (default)")),
(sysinv.LVG_CINDER_LVM_TYPE_THIN, _("Thin Provisioning (thin)")),
)
def get_param_key_name(key):
name = NOVA_PARAMS_KEY_NAMES.get(key, None)
if not name:
name = CINDER_PARAMS_KEY_NAMES.get(key, None)
return name
class ParamMixin(object):
def _host_lvg_get(self, lvg_id):
try:
return api.sysinv.host_lvg_get(self.request, lvg_id)
except Exception:
exceptions.handle(
self.request,
_("Unable to retrieve local volume group data. "
"lvg=%s") % str(lvg_id))
def _host_pv_list(self, host_id):
try:
return api.sysinv.host_pv_list(self.request, host_id)
except Exception:
exceptions.handle(
self.request,
_("Unable to retrieve physical volume list. "
"host=%s") % str(host_id))
def _host_pv_disk_get(self, pv):
try:
return api.sysinv.host_disk_get(self.request, pv.disk_or_part_uuid)
except Exception:
exceptions.handle(
self.request,
_("Unable to retrieve disk %(disk)s for PV %(pv)s.") % {
'disk': pv.disk_or_part_uuid,
'pv': pv.uuid})
def get_lvg_lvm_info(self, lvg_id):
lvg = self._host_lvg_get(lvg_id)
caps = lvg.capabilities
info = {'lvg': lvg}
if caps.get(sysinv.LVG_NOVA_PARAM_BACKING) != \
sysinv.LVG_NOVA_BACKING_LVM:
return info
info['total'] = 0
for pv in self._host_pv_list(info['lvg'].ihost_uuid):
if pv.lvm_vg_name != lvg.lvm_vg_name:
continue
if pv.pv_state == sysinv.PV_DEL:
continue
disk = self._host_pv_disk_get(pv)
if not disk:
exceptions.handle(
self.request,
_("PV %s does not have an associated "
"disk.") % pv.uuid)
disk_caps = disk.capabilities
if 'pv_dev' in disk_caps:
if 'pv_size_mib' in disk_caps:
info['total'] += disk_caps['pv_size_mib']
else:
exceptions.handle(
self.request,
_("PV partition %s does not have a "
"recorded size.") % disk_caps['pv_dev'])
else:
info['total'] += disk.size_mib
info['used'] = caps[sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB]
info['free'] = info['total'] - info['used']
# Limit the allowed size to provide a usable configuration when
# provisioned. This is the same range that is enforced in sysinv:
# sysinv/api/controllers/v1/ipv.py:_instances_lv_min_allowed_mib().
# Here we calculate the values to display to the end user so that they
# know the acceptable range to use.
# The following comment below is from sysinv to provide context:
#
# 80GB is the cutoff in the kickstart files for a virtualbox disk vs. a
# normal disk. Use a similar cutoff here for the volume group size. If
# the volume group is large enough then bump the min_mib value. The
# min_mib value is set to provide a reasonable minimum amount of space
# for /etc/nova/instances
if info['total'] < (80 * 1024):
info['allowed_min'] = 2 * 1024
else:
info['allowed_min'] = 5 * 1024
info['allowed_max'] = info['total'] >> 1
return info
class ParamForm(ParamMixin, forms.SelfHandlingForm):
type = forms.ChoiceField(
label=_("Parameters"),
required=True,
widget=forms.Select(attrs={
'class': 'switchable',
'data-slug': 'type'}))
lvg_id = forms.CharField(widget=forms.widgets.HiddenInput)
failure_url = 'horizon:admin:inventory:localvolumegroupdetail'
def __init__(self, *args, **kwargs):
super(ParamForm, self).__init__(*args, **kwargs)
self._lvg = self.get_lvg_lvm_info(kwargs['initial']['lvg_id'])
caps = self._lvg['lvg'].capabilities
if self._lvg['lvg'].lvm_vg_name == sysinv.LVG_NOVA_LOCAL:
self.fields[sysinv.LVG_NOVA_PARAM_BACKING] = forms.ChoiceField(
label=_("Instance Backing"),
initial=caps.get(sysinv.LVG_NOVA_PARAM_BACKING),
required=True,
choices=BACKING_CHOICES,
help_text=(_("%s") %
PARAMS_HELP.get(sysinv.LVG_NOVA_PARAM_BACKING,
None)),
widget=forms.Select(attrs={
'class': 'switched',
'data-switch-on': 'type',
'data-type-instance_backing': ''}))
self.fields[sysinv.LVG_NOVA_PARAM_DISK_OPS] = forms.IntegerField(
label=_("Concurrent Disk Operations"),
initial=caps.get(sysinv.LVG_NOVA_PARAM_DISK_OPS),
required=True,
help_text=(_("%s") %
PARAMS_HELP.get(sysinv.LVG_NOVA_PARAM_DISK_OPS,
None)),
widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'type',
'data-type-concurrent_disk_operations': ''}))
if caps.get(sysinv.LVG_NOVA_PARAM_BACKING) == \
sysinv.LVG_NOVA_BACKING_LVM:
inst_size_mib = sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB
self.fields[inst_size_mib] = \
forms.IntegerField(
label=_("Instances Logical Volume Size"),
initial=caps.get(inst_size_mib),
required=True,
help_text=(_("%s") %
PARAMS_HELP.get(inst_size_mib, None)),
widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'type',
'data-type-instances_lv_size_mib': ''}))
elif self._lvg['lvg'].lvm_vg_name == sysinv.LVG_CINDER_VOLUMES:
self.fields[sysinv.LVG_CINDER_PARAM_LVM_TYPE] = forms.ChoiceField(
label=_("LVM Provisioning Type"),
initial=caps.get(sysinv.LVG_CINDER_PARAM_LVM_TYPE),
required=True,
choices=LVM_TYPE_CHOICES,
help_text=(_("%s") %
PARAMS_HELP.get(sysinv.LVG_CINDER_PARAM_LVM_TYPE,
None)),
widget=forms.Select(attrs={
'class': 'switched',
'data-switch-on': 'type',
'data-type-lvm_type': ''}))
def clean(self):
cleaned_data = super(ParamForm, self).clean()
key = cleaned_data.get('type', None)
if self._lvg['lvg'].lvm_vg_name == sysinv.LVG_NOVA_LOCAL:
field = NOVA_PARAMS_FIELD_MAP.get(key, None)
elif self._lvg['lvg'].lvm_vg_name == sysinv.LVG_CINDER_VOLUMES:
field = CINDER_PARAMS_FIELD_MAP.get(key, None)
if field is not None:
value = cleaned_data.get(field)
cleaned_data['key'] = key
cleaned_data['value'] = value
return cleaned_data
def _clean_required_value(self, key, field):
"""Validate required fields for a specific key type."""
keytype = self.cleaned_data.get('type', None)
if keytype == key:
value = self.cleaned_data.get(field, None)
if value in validators.EMPTY_VALUES:
raise forms.ValidationError(_('This field is required.'))
return value
def clean_instances_lv_size_mib(self):
data = self.cleaned_data[sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB]
if self.cleaned_data[sysinv.LVG_NOVA_PARAM_BACKING] == sysinv.LVG_NOVA_BACKING_LVM \
and 'allowed_min' in self._lvg:
validators.MinValueValidator(self._lvg['allowed_min'])(
data)
validators.MaxValueValidator(self._lvg['allowed_max'])(
data)
return data
def get_context_data(self, **kwargs):
context = super(EditParam, self).get_context_data(**kwargs)
context.update(self._lvg)
return context
class EditParam(ParamForm):
def __init__(self, *args, **kwargs):
super(EditParam, self).__init__(*args, **kwargs)
# cannot change the type/key during edit
self.fields['type'].widget.attrs['readonly'] = True
key = self.initial['key']
value = self.initial['value']
# ensure checkboxes receive a boolean value as the initial value
# so that they don't get an override value attribute
if isinstance(value, basestring) and value.lower() == 'false':
value = False
elif isinstance(value, basestring) and value.lower() == 'true':
value = True
# setup initial values for the fields based on the defined key/value
if self._lvg['lvg'].lvm_vg_name == sysinv.LVG_NOVA_LOCAL:
field = NOVA_PARAMS_FIELD_MAP.get(key, None)
param_choices = NOVA_PARAMS_CHOICES
elif self._lvg['lvg'].lvm_vg_name == sysinv.LVG_CINDER_VOLUMES:
field = CINDER_PARAMS_FIELD_MAP.get(key, None)
param_choices = CINDER_PARAMS_CHOICES
if field is not None:
self.initial['type'] = key
self.initial[field] = value
# instances_lv_size_mib only valid for lvm backing
if self._lvg['lvg'].capabilities.get(sysinv.LVG_NOVA_PARAM_BACKING) \
== sysinv.LVG_NOVA_BACKING_IMAGE:
self.fields['type'].choices = \
[(k, v) for k, v in param_choices
if k != sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB]
else:
self.fields['type'].choices = [(k, v) for k, v in param_choices]
def handle(self, request, data):
lvg_id = data['lvg_id']
try:
if isinstance(data['value'], bool):
value = str(data['value'])
data['value'] = value
metadata = {data['key']: data['value']}
patch = []
patch.append({'path': '/capabilities',
'value': jsonutils.dumps(metadata),
'op': 'replace'})
api.sysinv.host_lvg_update(request, lvg_id, patch)
msg = _('Updated parameter "%s".') % data['key']
messages.success(request, msg)
return True
except Exception as e:
msg = _('Unable to edit parameter "{0}".'
' Details: {1}').format(data['key'], e)
redirect = reverse(self.failure_url, args=[lvg_id])
exceptions.handle(request, msg, redirect=redirect)