Move Controller services tab to stx-gui
Add a new tab to the system information panel. The new tab displays the state of the controller services. This code was previously included in the stx-upstream horizon repo. Story: 2004552 Task: 30242 Change-Id: I223c60c18de782c637d0b67bad9b8dcfe71fd184 Signed-off-by: David Sullivan <david.sullivan@windriver.com>
This commit is contained in:
parent
88d5b2e3ba
commit
c75f3bb737
@ -29,4 +29,5 @@
|
||||
- openstack/stx-config
|
||||
- openstack/stx-fault
|
||||
- openstack/stx-update
|
||||
- openstack/stx-nfv
|
||||
- openstack/stx-nfv
|
||||
- openstack/stx-ha
|
49
starlingx-dashboard/starlingx-dashboard/starlingx_dashboard/api/iservice.py
Executable file
49
starlingx-dashboard/starlingx-dashboard/starlingx_dashboard/api/iservice.py
Executable file
@ -0,0 +1,49 @@
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from openstack_dashboard.api import base
|
||||
import sm_client as smc
|
||||
|
||||
# Swap out with SM API
|
||||
LOG = logging.getLogger(__name__)
|
||||
SM_API_SERVICENAME = "smapi"
|
||||
|
||||
|
||||
def sm_client(request):
|
||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||
|
||||
sm_api_path = base.url_for(request, SM_API_SERVICENAME)
|
||||
return smc.Client('1', sm_api_path,
|
||||
token=request.user.token.id,
|
||||
insecure=insecure)
|
||||
|
||||
|
||||
def sm_sda_list(request):
|
||||
sdas = sm_client(request).sm_sda.list()
|
||||
|
||||
return sdas
|
||||
|
||||
|
||||
def sm_nodes_list(request):
|
||||
nodes = sm_client(request).sm_nodes.list()
|
||||
|
||||
return nodes
|
@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
from django import template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
|
||||
class ControllerServiceFilterAction(tables.FilterAction):
|
||||
def filter(self, table, services, filter_string):
|
||||
q = filter_string.lower()
|
||||
|
||||
def comp(service):
|
||||
if q in service.type.lower():
|
||||
return True
|
||||
return False
|
||||
|
||||
return filter(comp, services)
|
||||
|
||||
|
||||
def cs_get_c0(iservice):
|
||||
template_name = 'controller_services/_services_c0.html'
|
||||
context = {"iservice": iservice}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
def cs_get_c1(iservice):
|
||||
template_name = 'controller_services/_services_c1.html'
|
||||
context = {"iservice": iservice}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
class ControllerServicesTable(tables.DataTable):
|
||||
servicename = tables.Column("servicename", verbose_name=_('Name'))
|
||||
c0 = tables.Column(cs_get_c0, verbose_name=_('controller-0'))
|
||||
c1 = tables.Column(cs_get_c1, verbose_name=_('controller-1'))
|
||||
|
||||
class Meta(object):
|
||||
name = "controller_services"
|
||||
verbose_name = _("Controller Services")
|
||||
table_actions = (ControllerServiceFilterAction,)
|
||||
multi_select = False
|
@ -0,0 +1,142 @@
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from starlingx_dashboard.api import iservice
|
||||
from starlingx_dashboard.api import sysinv as sysinv_api
|
||||
from starlingx_dashboard.dashboards.admin.controller_services import tables
|
||||
from starlingx_dashboard.utils import objectify
|
||||
|
||||
import logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ControllerServicesTab(tabs.TableTab):
|
||||
table_classes = (tables.ControllerServicesTable,)
|
||||
name = _("Controller Services")
|
||||
slug = "controller_services"
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
|
||||
def _find_service_group_names(self, sdas):
|
||||
service_group_names_set = set()
|
||||
for sda in sdas:
|
||||
service_group_names_set.add(sda.service_group_name)
|
||||
|
||||
service_group_names_list = list(service_group_names_set)
|
||||
|
||||
return service_group_names_list
|
||||
|
||||
def _update_service_group_states(self, service_group_name, sdas, nodes):
|
||||
entry = {}
|
||||
|
||||
for sda in sdas:
|
||||
for n in nodes:
|
||||
if n.name == sda.node_name:
|
||||
if n.administrative_state.lower() == "locked":
|
||||
dstate = "locked"
|
||||
elif n.operational_state.lower() == "enabled":
|
||||
dstate = "standby"
|
||||
else:
|
||||
dstate = n.operational_state.lower()
|
||||
|
||||
if sda.service_group_name == service_group_name:
|
||||
state_str = sda.state
|
||||
if sda.status != "":
|
||||
state_str += '-' + sda.status
|
||||
if sda.condition != "":
|
||||
state_str += ' [' + sda.condition + ']'
|
||||
|
||||
if sda.state == "active":
|
||||
if sda.node_name == "controller-0":
|
||||
entry.update({'c0_activity': 'active'})
|
||||
entry.update({'c0_hostname': sda.node_name})
|
||||
entry.update({'c0_state': state_str})
|
||||
elif sda.node_name == "controller-1":
|
||||
entry.update({'c1_activity': 'active'})
|
||||
entry.update({'c1_hostname': sda.node_name})
|
||||
entry.update({'c1_state': state_str})
|
||||
else:
|
||||
if dstate == "standby":
|
||||
dstate = state_str
|
||||
|
||||
if sda.node_name == "controller-0":
|
||||
entry.update({'c0_activity': sda.state})
|
||||
entry.update({'c0_hostname': sda.node_name})
|
||||
entry.update({'c0_state': dstate})
|
||||
elif sda.node_name == "controller-1":
|
||||
entry.update({'c1_activity': sda.state})
|
||||
entry.update({'c1_hostname': sda.node_name})
|
||||
entry.update({'c1_state': dstate})
|
||||
|
||||
return entry
|
||||
|
||||
def get_controller_services_data(self):
|
||||
"""Populate the data for the controller services tab"""
|
||||
|
||||
# Here we filter the controller-1 column if we're a simplex system
|
||||
# We should make this data driven in the future. This would allow us to
|
||||
# more easily support n controllers
|
||||
if sysinv_api.is_system_mode_simplex(self.tab_group.request):
|
||||
controller1_col = self._tables['controller_services'].columns['c1']
|
||||
controller1_col.classes.append("hide")
|
||||
|
||||
try:
|
||||
nodes = iservice.sm_nodes_list(self.tab_group.request)
|
||||
|
||||
sdas = iservice.sm_sda_list(self.tab_group.request)
|
||||
|
||||
services = []
|
||||
|
||||
sgs = self._find_service_group_names(sdas)
|
||||
|
||||
sdaid = 0
|
||||
for sg in sgs:
|
||||
sdaid += 1
|
||||
entry = {}
|
||||
entry.update({'id': sdaid})
|
||||
entry.update({'servicename': sg})
|
||||
sg_states = self._update_service_group_states(sg, sdas, nodes)
|
||||
entry.update(sg_states)
|
||||
|
||||
# Need to latch if any sg is enabled
|
||||
if 'c0_activity' in entry.keys():
|
||||
sgstate = entry['c0_activity']
|
||||
if sgstate == "active":
|
||||
entry.update({'sgstate': sgstate})
|
||||
elif 'c1_activity' in entry.keys():
|
||||
sgstate = entry['c1_activity']
|
||||
if sgstate == "active":
|
||||
entry.update({'sgstate': sgstate})
|
||||
|
||||
if sgstate != "active":
|
||||
entry.update({'sgstate': sgstate})
|
||||
|
||||
if entry != {}:
|
||||
entry_object = objectify.objectify(entry)
|
||||
services.append(entry_object)
|
||||
|
||||
except Exception:
|
||||
msg = _('Unable to get controller services list.')
|
||||
exceptions.check_message(["Connection", "refused"], msg)
|
||||
raise
|
||||
|
||||
return services
|
@ -0,0 +1,3 @@
|
||||
{% if iservice.c0_hostname == "controller-0" %}
|
||||
{{ iservice.c0_state|linebreaksbr }}
|
||||
{% endif %}
|
@ -0,0 +1,3 @@
|
||||
{% if iservice.c1_hostname == "controller-1" %}
|
||||
{{ iservice.c1_state|linebreaksbr }}
|
||||
{% endif %}
|
@ -0,0 +1,16 @@
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'info'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'admin'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'admin'
|
||||
|
||||
ADD_INSTALLED_APPS = \
|
||||
['starlingx_dashboard.dashboards.admin.controller_services', ]
|
||||
|
||||
EXTRA_TABS = {
|
||||
'openstack_dashboard.dashboards.admin.info.tabs.SystemInfoTabs': (
|
||||
'starlingx_dashboard.dashboards.admin.controller_services.tabs.'
|
||||
'ControllerServicesTab',
|
||||
),
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
from functools import wraps # noqa
|
||||
|
||||
|
||||
def objectify(func):
|
||||
"""Mimic an object given a dictionary.
|
||||
|
||||
Given a dictionary, create an object and make sure that each of its
|
||||
keys are accessible via attributes.
|
||||
Ignore everything if the given value is not a dictionary.
|
||||
:param value: A dictionary or another kind of object.
|
||||
:returns: Either the created object or the given value.
|
||||
|
||||
>>> obj = {'old_key': 'old_value'}
|
||||
>>> oobj = objectify(obj)
|
||||
>>> oobj['new_key'] = 'new_value'
|
||||
>>> print oobj['old_key'], oobj['new_key'], oobj.old_key, oobj.new_key
|
||||
|
||||
>>> @objectify
|
||||
... def func():
|
||||
... return {'old_key': 'old_value'}
|
||||
>>> obj = func()
|
||||
>>> obj['new_key'] = 'new_value'
|
||||
>>> print obj['old_key'], obj['new_key'], obj.old_key, obj.new_key
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def create_object(value):
|
||||
if isinstance(value, dict):
|
||||
# Build a simple generic object.
|
||||
class Object(dict):
|
||||
def __setitem__(self, key, val):
|
||||
setattr(self, key, val)
|
||||
return super(Object, self).__setitem__(key, val)
|
||||
|
||||
# Create that simple generic object.
|
||||
ret_obj = Object()
|
||||
# Assign the attributes given the dictionary keys.
|
||||
for key, val in value.iteritems():
|
||||
ret_obj[key] = val
|
||||
setattr(ret_obj, key, val)
|
||||
return ret_obj
|
||||
else:
|
||||
return value
|
||||
|
||||
# If func is a function, wrap around and act like a decorator.
|
||||
if hasattr(func, '__call__'):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
"""Wrapper function for the decorator.
|
||||
|
||||
:returns: The return value of the decorated function.
|
||||
|
||||
"""
|
||||
value = func(*args, **kwargs)
|
||||
return create_object(value)
|
||||
|
||||
return wrapper
|
||||
|
||||
# Else just try to objectify the value given.
|
||||
else:
|
||||
return create_object(func)
|
Loading…
x
Reference in New Issue
Block a user