gui/cgcs_dashboard/dashboards/admin/inventory/storages/tables.py

632 lines
22 KiB
Python
Executable File

#
# Copyright (c) 2013-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
import re
from django.core.urlresolvers import reverse # noqa
from django import template
from django.template import defaultfilters as filters
from django.utils.translation import string_concat # noqa
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import tables
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
# ##########
# ACTIONS
# ##########
class CreateStorageVolume(tables.LinkAction):
name = "createstoragevolume"
verbose_name = ("Assign Storage Function")
url = "horizon:admin:inventory:addstoragevolume"
classes = ("ajax-modal", "btn-create")
icon = "plus"
def get_link_url(self, datum=None):
host_id = self.table.kwargs['host_id']
return reverse(self.url, args=(host_id,))
def allowed(self, request, datum):
host = self.table.kwargs['host']
self.verbose_name = _("Assign Storage Function")
classes = [c for c in self.classes if c != "disabled"]
self.classes = classes
if host._personality != 'storage':
return False
if host._administrative == 'unlocked':
if 'storage' in host._subfunctions:
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
self.verbose_name = string_concat(self.verbose_name, ' ',
_("(Node Unlocked)"))
return True
class CreateDiskProfile(tables.LinkAction):
name = "creatediskprofile"
verbose_name = ("Create Storage Profile")
url = "horizon:admin:inventory:adddiskprofile"
classes = ("ajax-modal", "btn-create")
def get_link_url(self, datum=None):
host_id = self.table.kwargs['host_id']
return reverse(self.url, args=(host_id,))
def allowed(self, request, datum):
return True
class CreatePartition(tables.LinkAction):
name = "createpartition"
verbose_name = ("Create a new partition")
url = "horizon:admin:inventory:createpartition"
classes = ("ajax-modal", "btn-create")
icon = "plus"
def get_link_url(self, datum=None):
host_id = self.table.kwargs['host_id']
return reverse(self.url, args=(host_id,))
def allowed(self, request, datum):
host = self.table.kwargs['host']
self.verbose_name = _("Create a new partition")
classes = [c for c in self.classes if c != "disabled"]
self.classes = classes
if host._personality != 'storage':
return True
return True
class DeletePartition(tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Partition",
u"Delete Partitions",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Partition",
u"Deleted Partitions",
count
)
def allowed(self, request, partition=None):
host = self.table.kwargs['host']
PARTITION_IN_USE_STATUS = api.sysinv.PARTITION_IN_USE_STATUS
PARTITION_STATUS_MSG = api.sysinv.PARTITION_STATUS_MSG
if partition:
if partition.type_guid != api.sysinv.USER_PARTITION_PHYS_VOL:
return False
if (partition.status ==
PARTITION_STATUS_MSG[PARTITION_IN_USE_STATUS]):
return False
if partition.ipv_uuid:
return False
# Get all the partitions from the same disk.
disk_partitions = \
api.sysinv.host_disk_partition_list(request, host.uuid,
partition.idisk_uuid)
if partition.device_path:
partition_number = re.match('.*?([0-9]+)$',
partition.device_path).group(1)
for dpart in disk_partitions:
dpart_number = re.match('.*?([0-9]+)$',
dpart.device_path).group(1)
if int(dpart_number) > int(partition_number):
return False
return True
def delete(self, request, partition_id):
host_id = self.table.kwargs['host_id']
try:
api.sysinv.host_disk_partition_delete(request, partition_id)
except Exception as e:
msg = _('Failed to delete host %(hid)s partition %(pv)s. '
'%(e_msg)s') % {'hid': host_id,
'pv': partition_id,
'e_msg': e}
LOG.info(msg)
redirect = reverse('horizon:admin:inventory:detail',
args=(host_id,))
exceptions.handle(request, msg, redirect=redirect)
class EditPartition(tables.LinkAction):
name = "editpartition"
verbose_name = _("Edit")
url = "horizon:admin:inventory:editpartition"
classes = ("ajax-modal", "btn-edit")
def get_link_url(self, partition):
host_id = self.table.kwargs['host_id']
return reverse(self.url, args=(host_id, partition.uuid))
def allowed(self, request, partition=None):
host = self.table.kwargs['host']
PARTITION_IN_USE_STATUS = api.sysinv.PARTITION_IN_USE_STATUS
PARTITION_STATUS_MSG = api.sysinv.PARTITION_STATUS_MSG
if partition:
if partition.type_guid != api.sysinv.USER_PARTITION_PHYS_VOL:
return False
if (partition.status ==
PARTITION_STATUS_MSG[PARTITION_IN_USE_STATUS]):
return False
if partition.ipv_uuid:
return False
# Get all the partitions from the same disk.
disk_partitions = \
api.sysinv.host_disk_partition_list(request,
host.uuid,
partition.idisk_uuid)
if partition.device_path:
partition_number = re.match('.*?([0-9]+)$',
partition.device_path).group(1)
for dpart in disk_partitions:
dpart_number = re.match('.*?([0-9]+)$',
dpart.device_path).group(1)
if int(dpart_number) > int(partition_number):
return False
return True
# ##########
# TABLES
# ##########
def get_model_num(disk):
return disk.get_model_num()
def get_disk_info(disk):
template_name = 'admin/inventory/_disk_info.html'
context = {
"disk": disk,
"disk_info": disk.device_path,
"id": disk.uuid,
}
return template.loader.render_to_string(template_name, context)
class DisksTable(tables.DataTable):
uuid = tables.Column('uuid',
verbose_name=('UUID'))
disk_info = tables.Column(get_disk_info,
verbose_name=_("Disk info"),
attrs={'data-type': 'disk_info'})
type = tables.Column('device_type',
verbose_name=('Type'))
size = tables.Column('size_mib',
verbose_name=('Size (MiB)'))
available_size = tables.Column('available_mib',
verbose_name=('Available Size (MiB)'))
rpm = tables.Column('rpm',
verbose_name=('RPM'))
serial_id = tables.Column('serial_id',
verbose_name=('Serial ID'))
model_num = tables.Column(get_model_num,
verbose_name=('Model'))
def get_object_id(self, datum):
return unicode(datum.uuid)
class Meta(object):
name = "disks"
verbose_name = ("Disks")
columns = ('uuid', 'disk_info', 'type', 'size', 'available_size',
'rpm', 'serial_id', 'model_num')
multi_select = False
table_actions = ()
class EditStor(tables.LinkAction):
name = "editstoragevolume"
verbose_name = _("Edit")
url = "horizon:admin:inventory:editstoragevolume"
classes = ("ajax-modal", "btn-edit")
def get_link_url(self, stor):
host_id = self.table.kwargs['host_id']
return reverse(self.url, args=(host_id, stor.uuid))
def allowed(self, request, stor=None):
host = self.table.kwargs['host']
classes = [c for c in self.classes if c != "disabled"]
self.classes = classes
if host._administrative == 'unlocked':
if 'storage' in host._subfunctions:
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
if stor and stor.function == 'osd':
forihostuuid = self.table.kwargs['host'].uuid
journal_stors = \
api.sysinv.host_stor_get_by_function(request, forihostuuid,
'journal')
if not journal_stors:
self.classes = [c for c in self.classes] + ['disabled']
return True
class DeleteStor(tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Journal",
u"Delete Journals",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Journal",
u"Deleted Journals",
count
)
def allowed(self, request, stor):
host = self.table.kwargs['host']
classes = [c for c in self.classes if c != "disabled"]
self.classes = classes
if host._administrative == 'unlocked':
if 'storage' in host._subfunctions:
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
if stor:
return stor.function == 'journal'
def delete(self, request, obj_id):
api.sysinv.host_stor_delete(request, obj_id)
class StorageVolumesTable(tables.DataTable):
uuid = tables.Column('uuid',
verbose_name=('UUID'))
osdid = tables.Column('osdid',
verbose_name=('OSD ID'))
function = tables.Column('function',
verbose_name=('Function'))
idisk_uuid = tables.Column('idisk_uuid',
verbose_name=('Disk UUID'))
journal_path = tables.Column('journal_path',
verbose_name=('Journal Path'))
journal_size_mib = tables.Column('journal_size_mib',
verbose_name=('Journal MiB'))
journal_location = tables.Column('journal_location',
verbose_name=('Journal Location'))
def get_object_id(self, datum):
return unicode(datum.uuid)
class Meta(object):
name = "storagevolumes"
verbose_name = ("Storage Functions")
columns = ('uuid', 'function', 'osdid', 'idisk_uuid', 'journal_path',
'journal_size_mib', 'journal_location')
multi_select = False
row_actions = (DeleteStor, EditStor,)
table_actions = (CreateStorageVolume, CreateDiskProfile,)
class AddLocalVolumeGroup(tables.LinkAction):
name = "addlocalvolumegroup"
verbose_name = ("Add Local Volume Group")
url = "horizon:admin:inventory:addlocalvolumegroup"
classes = ("ajax-modal", "btn-create")
icon = "plus"
def get_link_url(self, datum=None):
host_id = self.table.kwargs['host_id']
return reverse(self.url, args=(host_id,))
def allowed(self, request, datum):
host = self.table.kwargs['host']
self.verbose_name = _("Add Local Volume Group")
classes = [c for c in self.classes if c != "disabled"]
self.classes = classes
if not host._administrative == 'locked':
if 'compute' in host._subfunctions and \
host.compute_config_required is False:
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
self.verbose_name = string_concat(self.verbose_name, ' ',
_("(Node Unlocked)"))
# LVGs that are considered as "present" in the system are those
# in an adding or provisioned state.
current_lvg_states = [api.sysinv.LVG_ADD, api.sysinv.LVG_PROV]
ilvg_list = api.sysinv.host_lvg_list(request, host.uuid)
current_lvgs = [lvg.lvm_vg_name for lvg in ilvg_list
if lvg.vg_state in current_lvg_states]
compatible_lvgs = []
if host._personality == 'controller':
compatible_lvgs += [api.sysinv.LVG_CINDER_VOLUMES]
if 'compute' in host._subfunctions:
compatible_lvgs += [api.sysinv.LVG_NOVA_LOCAL]
allowed_lvgs = set(compatible_lvgs) - set(current_lvgs)
if not any(allowed_lvgs):
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
self.verbose_name = string_concat(self.verbose_name, ' ',
_("(All Added)"))
return True # The action should always be displayed
class RemoveLocalVolumeGroup(tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Local Volume Group",
u"Delete Local Volume Groups",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Local Volume Group",
u"Deleted Local Volume Groups",
count
)
def allowed(self, request, lvg=None):
host = self.table.kwargs['host']
return ((((host._administrative == 'locked') or
(('compute' in host._subfunctions) and
(host.compute_config_required is True))) and
(lvg.lvm_vg_name == api.sysinv.LVG_NOVA_LOCAL)) or
((api.sysinv.CINDER_BACKEND_LVM in
api.sysinv.get_cinder_backend(request)) and
(lvg.lvm_vg_name == api.sysinv.LVG_CINDER_VOLUMES) and
(api.sysinv.LVG_ADD in lvg.vg_state)))
def delete(self, request, lvg_id):
host_id = self.table.kwargs['host_id']
try:
api.sysinv.host_lvg_delete(request, lvg_id)
except Exception as e:
msg = _('Failed to delete host %(hid)s local '
'volume group %(lvg)s '
'%(e_msg)s') % \
{'hid': host_id, 'lvg': lvg_id, 'e_msg': e}
redirect = reverse('horizon:admin:inventory:detail',
args=(host_id,))
exceptions.handle(request, msg, redirect=redirect)
class LocalVolumeGroupsTable(tables.DataTable):
name = tables.Column('lvm_vg_name',
link="horizon:admin:inventory:localvolumegroupdetail",
verbose_name=('Name'))
state = tables.Column('vg_state',
verbose_name=('State'))
access = tables.Column('lvm_vg_access',
verbose_name=('Access'))
size = tables.Column('lvm_vg_size',
verbose_name=('Size'),
filters=(filters.filesizeformat,))
pvs = tables.Column('lvm_cur_pv',
verbose_name=('Current Physical Volumes'))
lvs = tables.Column('lvm_cur_lv',
verbose_name=('Current Logical Volumes'))
def get_object_id(self, datum):
return unicode(datum.uuid)
def get_object_display(self, datum):
msg = datum.uuid
if datum.lvm_vg_name:
msg += " (%s)" % datum.lvm_vg_name
return unicode(msg)
class Meta(object):
name = "localvolumegroups"
verbose_name = ("Local Volume Groups")
columns = ('name', 'state', 'access', 'size', 'pvs', 'lvs',)
multi_select = False
row_actions = (RemoveLocalVolumeGroup,)
table_actions = (AddLocalVolumeGroup, CreateDiskProfile)
class AddPhysicalVolume(tables.LinkAction):
name = "addphysicalvolume"
verbose_name = ("Add Physical Volume")
url = "horizon:admin:inventory:addphysicalvolume"
classes = ("ajax-modal", "btn-create")
icon = "plus"
def get_link_url(self, datum=None):
host_id = self.table.kwargs['host_id']
return reverse(self.url, args=(host_id,))
def allowed(self, request, datum):
host = self.table.kwargs['host']
self.verbose_name = _("Add Physical Volume")
classes = [c for c in self.classes if c != "disabled"]
self.classes = classes
# cgts-vg, cinder-volumes: Allow adding to any controller
if host._personality == api.sysinv.PERSONALITY_CONTROLLER:
return True
# nova-local: Allow adding to any locked host with a compute
# subfunction. On an AIO, the previous check superceeds this.
if host._administrative != 'locked':
if 'compute' in host._subfunctions and \
host.compute_config_required is False:
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
self.verbose_name = string_concat(self.verbose_name, ' ',
_("(Node Unlocked)"))
elif "nova-local" not in [
lvg.lvm_vg_name for lvg in
api.sysinv.host_lvg_list(request, host.uuid)]:
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
self.verbose_name = string_concat(self.verbose_name, ' ',
_("(No nova-local LVG)"))
return True # The action should always be displayed
class RemovePhysicalVolume(tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Physical Volume",
u"Delete Physical Volumes",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Physical Volume",
u"Deleted Physical Volumes",
count
)
def allowed(self, request, pv=None):
host = self.table.kwargs['host']
return ((((host._administrative == 'locked') or
(('compute' in host._subfunctions) and
(host.compute_config_required is True))) and
(pv.lvm_vg_name == api.sysinv.LVG_NOVA_LOCAL)) or
((api.sysinv.CINDER_BACKEND_LVM in
api.sysinv.get_cinder_backend(request)) and
((pv.lvm_vg_name == api.sysinv.LVG_CINDER_VOLUMES) and
(api.sysinv.PV_ADD in pv.pv_state))))
def delete(self, request, pv_id):
host_id = self.table.kwargs['host_id']
try:
api.sysinv.host_pv_delete(request, pv_id)
except Exception as e:
msg = _('Failed to delete host %(hid)s physical volume %(pv)s. '
'%(e_msg)s') % {'hid': host_id, 'pv': pv_id, 'e_msg': e}
LOG.info(msg)
redirect = reverse('horizon:admin:inventory:detail',
args=(host_id,))
exceptions.handle(request, msg, redirect=redirect)
class PhysicalVolumesTable(tables.DataTable):
name = tables.Column('lvm_pv_name',
link="horizon:admin:inventory:physicalvolumedetail",
verbose_name=('Name'))
pv_state = tables.Column('pv_state',
verbose_name=('State'))
pv_type = tables.Column('pv_type',
verbose_name=('Type'))
disk_or_part_uuid = tables.Column('disk_or_part_uuid',
verbose_name=('Disk or Partition UUID'))
disk_or_part_device_path = tables.Column('disk_or_part_device_path',
verbose_name=('Disk or Partition'
' Device Path'))
lvm_vg_name = tables.Column('lvm_vg_name',
verbose_name=('LVM Volume Group Name'))
def get_object_id(self, datum):
return unicode(datum.uuid)
def get_object_display(self, datum):
msg = datum.uuid
if datum.lvm_pv_name:
msg += " (%s)" % datum.lvm_pv_name
return unicode(msg)
class Meta(object):
name = "physicalvolumes"
verbose_name = ("Physical Volumes")
columns = ('name', 'pv_state', 'pv_type', 'disk_or_part_uuid',
'disk_or_part_device_node', 'disk_or_part_device_path',
'lvm_vg_name')
multi_select = False
table_actions = (AddPhysicalVolume,)
row_actions = (RemovePhysicalVolume,)
class PartitionsTable(tables.DataTable):
uuid = tables.Column('uuid',
verbose_name=('UUID'))
size_mib = tables.Column('size_mib',
verbose_name=('Size (MiB)'))
device_path = tables.Column('device_path',
verbose_name=('Partition Device Path'))
type_name = tables.Column('type_name',
verbose_name=('Partition Type'))
ipv_uuid = tables.Column('ipv_uuid',
verbose_name=('Physical Volume UUID'))
idisk_uuid = tables.Column('disk_uuid',
verbose_name=('Disk UUID'))
status = tables.Column('status',
verbose_name=('Status'))
def get_object_id(self, datum):
return unicode(datum.uuid)
def get_object_display(self, datum):
msg = datum.uuid
if datum.device_path:
msg += " (%s)" % datum.device_path
return unicode(msg)
class Meta(object):
name = "partitions"
verbose_name = ("Partitions")
columns = ('uuid', 'device_path', 'size_mib', 'type_name', 'status')
multi_select = False
row_actions = (EditPartition, DeletePartition,)
table_actions = (CreatePartition,)