
Adds local boot_option to capabilities when creating node, renames pxe_deploy_kernel/ramdisk to deploy_kernel/ramdisk Change-Id: Iaab70df78d5ea83998498a4c9af2502deeea4435
379 lines
13 KiB
Python
379 lines
13 KiB
Python
# 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.
|
|
|
|
import itertools
|
|
|
|
from django.core import urlresolvers
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from horizon import tabs
|
|
from horizon.utils import functions
|
|
from openstack_dashboard.api import base as api_base
|
|
|
|
from tuskar_ui import api
|
|
from tuskar_ui.cached_property import cached_property # noqa
|
|
from tuskar_ui.infrastructure.nodes import tables
|
|
from tuskar_ui.utils import metering as metering_utils
|
|
from tuskar_ui.utils import utils
|
|
|
|
|
|
def filter_extra(nodes, index, value):
|
|
return (node for node in nodes
|
|
if node.extra.get(index, None) == value)
|
|
|
|
|
|
class OverviewTab(tabs.Tab):
|
|
name = _("Overview")
|
|
slug = "overview"
|
|
template_name = "infrastructure/nodes/_overview.html"
|
|
|
|
def get_context_data(self, request):
|
|
nodes = self.tab_group.kwargs['nodes']
|
|
cpus = sum(int(node.cpus) for node in nodes if node.cpus)
|
|
memory_mb = sum(int(node.memory_mb) for node in nodes if
|
|
node.memory_mb)
|
|
local_gb = sum(int(node.local_gb) for node in nodes if node.local_gb)
|
|
|
|
nodes_provisioned = set(utils.filter_items(
|
|
nodes, provision_state__in=api.node.PROVISION_STATE_PROVISIONED))
|
|
nodes_free = set(utils.filter_items(
|
|
nodes, provision_state__in=api.node.PROVISION_STATE_FREE))
|
|
nodes_deleting = set(utils.filter_items(
|
|
nodes, provision_state__in=api.node.PROVISION_STATE_DELETING))
|
|
nodes_error = set(utils.filter_items(
|
|
nodes, provision_state__in=api.node.PROVISION_STATE_ERROR))
|
|
|
|
nodes_provisioned_maintenance = set(utils.filter_items(
|
|
nodes_provisioned, maintenance=True))
|
|
nodes_provisioned_not_maintenance = (
|
|
nodes_provisioned - nodes_provisioned_maintenance)
|
|
|
|
nodes_provisioning = set(utils.filter_items(
|
|
nodes,
|
|
provision_state__in=api.node.PROVISION_STATE_PROVISIONING))
|
|
|
|
nodes_free_maintenance = set(utils.filter_items(
|
|
nodes_free, maintenance=True))
|
|
nodes_free_not_maintenance = (
|
|
nodes_free - nodes_free_maintenance)
|
|
|
|
nodes_maintenance = (
|
|
nodes_provisioned_maintenance | nodes_free_maintenance)
|
|
|
|
nodes_provisioned_down = utils.filter_items(
|
|
nodes_provisioned, power_state__not_in=api.node.POWER_ON_STATES)
|
|
nodes_free_down = utils.filter_items(
|
|
nodes_free, power_state__not_in=api.node.POWER_ON_STATES)
|
|
|
|
nodes_on_discovery = filter_extra(
|
|
nodes_maintenance, 'on_discovery', 'true')
|
|
nodes_discovered = filter_extra(
|
|
nodes_maintenance, 'newly_discovered', 'true')
|
|
nodes_discovery_failed = filter_extra(
|
|
nodes_maintenance, 'discovery_failed', 'true')
|
|
|
|
nodes_down = itertools.chain(nodes_provisioned_down, nodes_free_down)
|
|
nodes_up = utils.filter_items(
|
|
nodes, power_state__in=api.node.POWER_ON_STATES)
|
|
|
|
nodes_free_count = len(nodes_free_not_maintenance)
|
|
nodes_provisioned_count = len(
|
|
nodes_provisioned_not_maintenance)
|
|
nodes_provisioning_count = len(nodes_provisioning)
|
|
nodes_maintenance_count = len(nodes_maintenance)
|
|
nodes_deleting_count = len(nodes_deleting)
|
|
nodes_error_count = len(nodes_error)
|
|
|
|
context = {
|
|
'cpus': cpus,
|
|
'memory_gb': memory_mb / 1024.0,
|
|
'local_gb': local_gb,
|
|
'nodes_up_count': utils.length(nodes_up),
|
|
'nodes_down_count': utils.length(nodes_down),
|
|
'nodes_provisioned_count': nodes_provisioned_count,
|
|
'nodes_provisioning_count': nodes_provisioning_count,
|
|
'nodes_free_count': nodes_free_count,
|
|
'nodes_deleting_count': nodes_deleting_count,
|
|
'nodes_error_count': nodes_error_count,
|
|
'nodes_maintenance_count': nodes_maintenance_count,
|
|
'nodes_all_count': len(nodes),
|
|
'nodes_on_discovery_count': utils.length(nodes_on_discovery),
|
|
'nodes_discovered_count': utils.length(nodes_discovered),
|
|
'nodes_discovery_failed_count': utils.length(
|
|
nodes_discovery_failed),
|
|
'nodes_status_data':
|
|
'Provisioned={0}|Free={1}|Maintenance={2}'.format(
|
|
nodes_provisioned_count, nodes_free_count,
|
|
nodes_maintenance_count)
|
|
}
|
|
# additional node status pie chart data, showing only if it appears
|
|
if nodes_provisioning_count:
|
|
context['nodes_status_data'] += '|Provisioning={0}'.format(
|
|
nodes_provisioning_count)
|
|
if nodes_deleting_count:
|
|
context['nodes_status_data'] += '|Deleting={0}'.format(
|
|
nodes_deleting_count)
|
|
if nodes_error_count:
|
|
context['nodes_status_data'] += '|Error={0}'.format(
|
|
nodes_error_count)
|
|
|
|
if api_base.is_service_enabled(self.request, 'metering'):
|
|
context['meter_conf'] = (
|
|
(_('System Load'),
|
|
metering_utils.url_part('hardware.cpu.load.1min', False),
|
|
None),
|
|
(_('CPU Utilization'),
|
|
metering_utils.url_part('hardware.system_stats.cpu.util',
|
|
True),
|
|
'100'),
|
|
(_('Swap Utilization'),
|
|
metering_utils.url_part('hardware.memory.swap.util',
|
|
True),
|
|
'100'),
|
|
)
|
|
|
|
# TODO(akrivoka): Ajaxize these calls so that they don't hold up the
|
|
# whole page load
|
|
context['top_5'] = {
|
|
'fan': metering_utils.get_top_5(request, 'hardware.ipmi.fan'),
|
|
'voltage': metering_utils.get_top_5(
|
|
request, 'hardware.ipmi.voltage'),
|
|
'temperature': metering_utils.get_top_5(
|
|
request, 'hardware.ipmi.temperature'),
|
|
'current': metering_utils.get_top_5(
|
|
request, 'hardware.ipmi.current'),
|
|
}
|
|
|
|
return context
|
|
|
|
|
|
class BaseTab(tabs.TableTab):
|
|
table_classes = (tables.BaseNodesTable,)
|
|
name = _("Nodes")
|
|
slug = "nodes"
|
|
template_name = "horizon/common/_detail_table.html"
|
|
|
|
def __init__(self, tab_group, request):
|
|
super(BaseTab, self).__init__(tab_group, request)
|
|
|
|
@cached_property
|
|
def _nodes(self):
|
|
return []
|
|
|
|
def get_items_count(self):
|
|
return len(self._nodes)
|
|
|
|
@cached_property
|
|
def _nodes_info(self):
|
|
page_size = functions.get_page_size(self.request)
|
|
|
|
prev_marker = self.request.GET.get(
|
|
self.table_classes[0]._meta.prev_pagination_param, None)
|
|
|
|
if prev_marker is not None:
|
|
sort_dir = 'asc'
|
|
marker = prev_marker
|
|
else:
|
|
sort_dir = 'desc'
|
|
marker = self.request.GET.get(
|
|
self.table_classes[0]._meta.pagination_param, None)
|
|
|
|
nodes = self._nodes
|
|
|
|
if marker:
|
|
node_ids = [node.uuid for node in self._nodes]
|
|
position = node_ids.index(marker)
|
|
if sort_dir == 'asc':
|
|
start = max(0, position - page_size)
|
|
end = position
|
|
else:
|
|
start = position + 1
|
|
end = start + page_size
|
|
else:
|
|
start = 0
|
|
end = page_size
|
|
|
|
prev = start != 0
|
|
more = len(nodes) > end
|
|
return nodes[start:end], prev, more
|
|
|
|
def get_base_nodes_table_data(self):
|
|
nodes, prev, more = self._nodes_info
|
|
return nodes
|
|
|
|
def has_prev_data(self, table):
|
|
return self._nodes_info[1]
|
|
|
|
def has_more_data(self, table):
|
|
return self._nodes_info[2]
|
|
|
|
|
|
class AllTab(BaseTab):
|
|
table_classes = (tables.AllNodesTable,)
|
|
name = _("All")
|
|
slug = "all"
|
|
|
|
def __init__(self, tab_group, request):
|
|
super(AllTab, self).__init__(tab_group, request)
|
|
|
|
@cached_property
|
|
def _nodes(self):
|
|
return self.tab_group.kwargs['nodes']
|
|
|
|
def get_all_nodes_table_data(self):
|
|
nodes, prev, more = self._nodes_info
|
|
return nodes
|
|
|
|
|
|
class ProvisionedTab(BaseTab):
|
|
table_classes = (tables.ProvisionedNodesTable,)
|
|
name = _("Provisioned")
|
|
slug = "provisioned"
|
|
|
|
def __init__(self, tab_group, request):
|
|
super(ProvisionedTab, self).__init__(tab_group, request)
|
|
|
|
@cached_property
|
|
def _nodes(self):
|
|
redirect = urlresolvers.reverse('horizon:infrastructure:nodes:index')
|
|
return api.node.Node.list(self.request, associated=True,
|
|
maintenance=False, _error_redirect=redirect)
|
|
|
|
def get_provisioned_nodes_table_data(self):
|
|
nodes, prev, more = self._nodes_info
|
|
|
|
if nodes:
|
|
for node in nodes:
|
|
try:
|
|
resource = api.heat.Resource.get_by_node(
|
|
self.request, node)
|
|
except LookupError:
|
|
node.role_name = '-'
|
|
else:
|
|
node.role_name = resource.role.name
|
|
node.role_id = resource.role.id
|
|
node.stack_id = resource.stack.id
|
|
|
|
return nodes
|
|
|
|
|
|
class FreeTab(BaseTab):
|
|
table_classes = (tables.FreeNodesTable,)
|
|
name = _("Free")
|
|
slug = "free"
|
|
|
|
def __init__(self, tab_group, request):
|
|
super(FreeTab, self).__init__(tab_group, request)
|
|
|
|
@cached_property
|
|
def _nodes(self):
|
|
redirect = urlresolvers.reverse('horizon:infrastructure:nodes:index')
|
|
return api.node.Node.list(self.request, associated=False,
|
|
maintenance=False, _error_redirect=redirect)
|
|
|
|
def get_free_nodes_table_data(self):
|
|
nodes, prev, more = self._nodes_info
|
|
return nodes
|
|
|
|
|
|
class MaintenanceTab(BaseTab):
|
|
table_classes = (tables.MaintenanceNodesTable,)
|
|
name = _("Maintenance")
|
|
slug = "maintenance"
|
|
|
|
def __init__(self, tab_group, request):
|
|
super(MaintenanceTab, self).__init__(tab_group, request)
|
|
|
|
@cached_property
|
|
def _nodes(self):
|
|
nodes = self.tab_group.kwargs['nodes']
|
|
return list(utils.filter_items(nodes, maintenance=True))
|
|
|
|
def get_maintenance_nodes_table_data(self):
|
|
return self._nodes
|
|
|
|
|
|
class DetailOverviewTab(tabs.Tab):
|
|
name = _("Overview")
|
|
slug = "detail_overview"
|
|
template_name = 'infrastructure/nodes/_detail_overview.html'
|
|
|
|
def get_context_data(self, request):
|
|
node = self.tab_group.kwargs['node']
|
|
context = {'node': node}
|
|
try:
|
|
resource = api.heat.Resource.get_by_node(self.request, node)
|
|
except LookupError:
|
|
pass
|
|
else:
|
|
context['role'] = resource.role
|
|
context['stack'] = resource.stack
|
|
|
|
kernel_id = node.driver_info.get('deploy_kernel')
|
|
if kernel_id:
|
|
context['kernel_image'] = api.node.image_get(request, kernel_id)
|
|
|
|
ramdisk_id = node.driver_info.get('deploy_ramdisk')
|
|
if ramdisk_id:
|
|
context['ramdisk_image'] = api.node.image_get(request, ramdisk_id)
|
|
|
|
if node.instance_uuid:
|
|
if api_base.is_service_enabled(self.request, 'metering'):
|
|
# Meter configuration in the following format:
|
|
# (meter label, url part, y_max)
|
|
context['meter_conf'] = (
|
|
(_('System Load'),
|
|
metering_utils.url_part('hardware.cpu.load.1min', False),
|
|
None),
|
|
(_('CPU Utilization'),
|
|
metering_utils.url_part('hardware.system_stats.cpu.util',
|
|
True),
|
|
'100'),
|
|
(_('Swap Utilization'),
|
|
metering_utils.url_part('hardware.memory.swap.util',
|
|
True),
|
|
'100'),
|
|
(_('Current'),
|
|
metering_utils.url_part('hardware.ipmi.current', False),
|
|
None),
|
|
(_('Network IO'),
|
|
metering_utils.url_part('network-io', False),
|
|
None),
|
|
(_('Disk IO'),
|
|
metering_utils.url_part('disk-io', False),
|
|
None),
|
|
(_('Temperature'),
|
|
metering_utils.url_part('hardware.ipmi.temperature',
|
|
False),
|
|
None),
|
|
(_('Fan Speed'),
|
|
metering_utils.url_part('hardware.ipmi.fan', False),
|
|
None),
|
|
(_('Voltage'),
|
|
metering_utils.url_part('hardware.ipmi.voltage', False),
|
|
None),
|
|
)
|
|
return context
|
|
|
|
|
|
class NodeTabs(tabs.TabGroup):
|
|
slug = "nodes"
|
|
tabs = (OverviewTab, AllTab, ProvisionedTab, FreeTab, MaintenanceTab,)
|
|
sticky = True
|
|
template_name = "horizon/common/_items_count_tab_group.html"
|
|
|
|
|
|
class NodeDetailTabs(tabs.TabGroup):
|
|
slug = "node_details"
|
|
tabs = (DetailOverviewTab,)
|