Merge remote-tracking branch 'origin/master' into f/centos8

Signed-off-by: Charles Short <charles.short@windriver.com>
Change-Id: Ia0115b20fb4325983b0a039fb7a7fd145e81f650
This commit is contained in:
Charles Short 2021-05-19 15:35:50 -04:00
commit 63d6de4701
28 changed files with 285 additions and 145 deletions

View File

@ -3,6 +3,7 @@
templates:
- publish-stx-docs
- stx-release-notes-jobs
- stx-bandit-jobs
check:
jobs:
- openstack-tox-pep8

View File

@ -1,5 +1,5 @@
sphinx>=1.6.2
openstackdocstheme>=1.26.0 # Apache-2.0
sphinx>=2.0.0,!=2.1.0 # BSD
openstackdocstheme>=2.2.1 # Apache-2.0
# Release Notes documentation
reno>=0.1.1 # Apache2
reno>=3.1.0 # Apache-2.0

View File

@ -27,12 +27,6 @@ project = u'StarlingX GUI'
copyright = '2018, StarlingX'
author = 'StarlingX'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '0.1'
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@ -58,8 +52,11 @@ source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
bug_project = '1027'
bug_tag = 'stx.bug'
# openstackdocstheme options
openstackdocs_repo_name = 'starlingx/gui'
openstackdocs_use_storyboard = True
openstackdocs_auto_name = False
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -74,7 +71,7 @@ language = None
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = 'native'
# -- Options for HTML output -------------------------------------------------

View File

@ -26,6 +26,7 @@ load-plugins=
# can either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once).
# W0107 unnecessary-pass
# W0201 attribute-defined-outside-init
# W0212 protected-access var starting with _ used outside class or descendant
# W0221 arguments-differ
@ -37,8 +38,13 @@ load-plugins=
# E0202 method-hidden
# E0203 access-member-before-definition
# E1101 no-member
disable=C, R, W0201, W0212, W0221, W0235, W0403, W0511, W0613, W0703,
E0202, E0203, E1101
# The next two error codes are because Django 1.11.20 cannot be specified
# without a custom upper constraints file
# E0401: Unable to import 'django.core.urlresolvers' (import-error)
# E0611: No name 'urlresolvers' in module 'django.core' (no-name-in-module)
disable=C, R, W0107, W0201, W0212, W0221, W0235, W0403, W0511, W0613, W0703,
E0202, E0203, E1101,
E0401, E0611
[REPORTS]

View File

@ -31,8 +31,10 @@ extensions = [
'reno.sphinxext',
]
bug_project = '1027'
bug_tag = 'stx.bug'
# openstackdocstheme options
openstackdocs_repo_name = 'starlingx/gui'
openstackdocs_use_storyboard = True
openstackdocs_auto_name = False
# Add any paths that contain templates here, relative to this directory.
# templates_path = ['_templates']
@ -82,7 +84,7 @@ exclude_patterns = []
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = 'native'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
@ -131,10 +133,6 @@ html_theme = 'starlingxdocs'
# directly to the root of the documentation.
# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True

View File

@ -68,7 +68,7 @@ def subcloud_create(request, data):
def subcloud_update(request, subcloud_id, changes):
response = dcmanagerclient(request).subcloud_manager.update_subcloud(
subcloud_id, **changes.get('updated'))
subcloud_id, data=changes.get('updated'))
# Updating returns a list of subclouds for some reason
return [Subcloud(subcloud) for subcloud in response]
@ -90,8 +90,8 @@ class Strategy(base.APIResourceWrapper):
def get_strategy(request):
try:
response = dcmanagerclient(request).sw_update_manager.\
patch_strategy_detail()
response = dcmanagerclient(request).sw_patch_manager.\
update_sw_strategy_detail()
except APIException as e:
if e.error_code == 404:
return None
@ -103,21 +103,22 @@ def get_strategy(request):
def strategy_create(request, data):
response = dcmanagerclient(request).sw_update_manager.\
create_patch_strategy(**data)
response = dcmanagerclient(request).sw_patch_manager.\
create_sw_update_strategy(**data)
return Strategy(response)
def strategy_apply(request):
return dcmanagerclient(request).sw_update_manager.apply_patch_strategy()
return dcmanagerclient(request).sw_patch_manager.apply_sw_update_strategy()
def strategy_abort(request):
return dcmanagerclient(request).sw_update_manager.abort_patch_strategy()
return dcmanagerclient(request).sw_patch_manager.abort_sw_update_strategy()
def strategy_delete(request):
return dcmanagerclient(request).sw_update_manager.delete_patch_strategy()
return dcmanagerclient(request).sw_patch_manager.\
delete_sw_update_strategy()
class Step(base.APIResourceWrapper):

View File

@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2020 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
from __future__ import absolute_import
@ -966,8 +966,8 @@ class Host(base.APIResourceWrapper):
self._subfunction_avail)
@property
def worker_config_required(self):
return self.config_status == 'Worker config required'
def config_required(self):
return self.config_status == 'config required'
@property
def location(self):
@ -2004,7 +2004,7 @@ class Interface(base.APIResourceWrapper):
"""Wrapper for Inventory Interfaces"""
_attrs = ['id', 'uuid', 'ifname', 'ifclass', 'iftype', 'imtu', 'imac',
'aemode', 'txhashpolicy', 'vlan_id',
'aemode', 'txhashpolicy', 'primary_reselect', 'vlan_id',
'uses', 'used_by', 'ihost_uuid',
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
'sriov_numvfs', 'sriov_vf_driver']

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2020 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -76,7 +76,6 @@ class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
attrs, renderer)
else:
hi = forms.HiddenInput(self.attrs)
hi.is_hidden = False # ensure text is rendered
return mark_safe(self.empty_value + hi.render(name, None, attrs))
@ -153,6 +152,7 @@ class AddInterface(forms.SelfHandlingForm):
('ae', _("aggregated ethernet")),
('vlan', _("vlan")),
('vf', _("vf")),
('ethernet', _("ethernet")),
)
SRIOV_VF_DRIVER_CHOICES = (
@ -173,6 +173,12 @@ class AddInterface(forms.SelfHandlingForm):
('layer2', _("layer2")),
)
AE_PRIMARY_RESELECT_CHOICES = (
('always', _("always")),
('better', _("better")),
('failure', _("failure")),
)
IPV4_MODE_CHOICES = (
('disabled', _("Disabled")),
('static', _("Static")),
@ -252,6 +258,16 @@ class AddInterface(forms.SelfHandlingForm):
'data-ae_mode-balanced': 'Aggregated Ethernet - Tx Policy',
'data-ae_mode-802.3ad': 'Aggregated Ethernet - Tx Policy'}))
primary_reselect = forms.ChoiceField(
label=_("Aggregated Ethernet - Primary Reselect"),
required=False,
choices=AE_PRIMARY_RESELECT_CHOICES,
widget=forms.Select(
attrs={
'class': 'switched',
'data-switch-on': 'ae_mode',
'data-ae_mode-active_standby': 'Primary Reselect'}))
vlan_id = forms.IntegerField(
label=_("Vlan ID"),
initial=1,
@ -452,15 +468,15 @@ class AddInterface(forms.SelfHandlingForm):
nt_choices = self.fields['ifclass'].choices
self.fields['ifclass'].choices = [i for i in nt_choices if
i[0] != 'data']
else:
datanets = sysinv.data_network_list(self.request)
for dn in datanets:
label = "{} (mtu={})".format(dn.name, dn.mtu)
datanet = (str(dn.name), label)
datanet_choices.append(datanet)
if dn.name not in used_datanets:
datanet_filtered.append(datanet)
initial_datanet_name.append(str(dn.name))
datanets = sysinv.data_network_list(self.request)
for dn in datanets:
label = "{} (mtu={})".format(dn.name, dn.mtu)
datanet = (str(dn.name), label)
datanet_choices.append(datanet)
if dn.name not in used_datanets:
datanet_filtered.append(datanet)
initial_datanet_name.append(str(dn.name))
self.fields['datanetworks_data'].choices = datanet_filtered
self.fields['datanetworks_sriov'].choices = datanet_filtered
@ -608,8 +624,11 @@ class AddInterface(forms.SelfHandlingForm):
if data['iftype'] != 'ae':
del data['txhashpolicy']
del data['aemode']
del data['primary_reselect']
elif data['aemode'] == 'active_standby':
del data['txhashpolicy']
elif data['aemode'] != 'active_standby':
del data['primary_reselect']
if 'sriov_numvfs' in data:
data['sriov_numvfs'] = str(data['sriov_numvfs'])
@ -912,8 +931,11 @@ class UpdateInterface(AddInterface):
if data['iftype'] != 'ae':
del data['txhashpolicy']
del data['aemode']
del data['primary_reselect']
elif data['aemode'] == 'active_standby':
del data['txhashpolicy']
elif data['aemode'] != 'active_standby':
del data['primary_reselect']
if not data['ifclass'] or data['ifclass'] == 'none':
avail_port_list = sysinv.host_port_list(
@ -969,14 +991,8 @@ class UpdateInterface(AddInterface):
current_interface = sysinv.host_interface_get(
self.request, interface_id)
ifnet_data['interface_uuid'] = current_interface.uuid
if data['networks_to_add']:
for n in data['networks_to_add']:
ifnet_data['network_uuid'] = n
sysinv.interface_network_assign(request, **ifnet_data)
elif data['datanetworks_to_add']:
for n in data['datanetworks_to_add']:
ifnet_data['datanetwork_uuid'] = n
sysinv.interface_datanetwork_assign(request, **ifnet_data)
networks_to_add = data['networks_to_add']
datanetworks_to_add = data['datanetworks_to_add']
del data['networks']
del data['networks_to_add']
@ -984,9 +1000,18 @@ class UpdateInterface(AddInterface):
del data['datanetworks']
del data['datanetworks_to_add']
del data['interface_datanetworks_to_remove']
interface = sysinv.host_interface_update(request,
interface_id,
**data)
if networks_to_add:
for n in networks_to_add:
ifnet_data['network_uuid'] = n
sysinv.interface_network_assign(request, **ifnet_data)
elif datanetworks_to_add:
for n in datanetworks_to_add:
ifnet_data['datanetwork_uuid'] = n
sysinv.interface_datanetwork_assign(request, **ifnet_data)
msg = _('Interface "%s" was'
' successfully updated.') % data['ifname']

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -15,6 +15,7 @@ from horizon import exceptions
from horizon import tables
from starlingx_dashboard import api as stx_api
import sysinv.common.constants as sysinv_const
LOG = logging.getLogger(__name__)
@ -40,8 +41,12 @@ class DeleteInterface(tables.DeleteAction):
def allowed(self, request, interface=None):
host = self.table.kwargs['host']
return (host._administrative == 'locked' and
interface.iftype != 'ethernet')
if interface.uses:
if (stx_api.sysinv.is_system_mode_simplex(request)
and interface.iftype == sysinv_const.INTERFACE_TYPE_VF):
return True
else:
return host._administrative == 'locked'
def delete(self, request, interface_id):
host_id = self.table.kwargs['host_id']
@ -83,13 +88,20 @@ class CreateInterface(tables.LinkAction):
def allowed(self, request, datum):
host = self.table.kwargs['host']
if host._administrative != 'locked':
is_aio_sx = stx_api.sysinv.is_system_mode_simplex(request)
if (host._administrative != 'locked' and not is_aio_sx):
return False
count = 0
sriov_count = 0
for i in host.interfaces:
if i.ifclass:
count = count + 1
if i.ifclass == sysinv_const.INTERFACE_CLASS_PCI_SRIOV:
sriov_count += 1
if is_aio_sx and host._administrative != 'locked' and sriov_count == 0:
return False
if host.subfunctions and 'worker' not in host.subfunctions and \
count >= len(INTERFACE_CLASS_TYPES):
@ -110,7 +122,13 @@ class EditInterface(tables.LinkAction):
def allowed(self, request, datum):
host = self.table.kwargs['host']
return host._administrative == 'locked'
intf = datum
if (stx_api.sysinv.is_system_mode_simplex(request)
and intf.iftype == sysinv_const.INTERFACE_TYPE_ETHERNET
and intf.ifclass != sysinv_const.INTERFACE_CLASS_PCI_SRIOV):
return True
else:
return host._administrative == 'locked'
def get_attributes(interface):
@ -120,6 +138,10 @@ def get_attributes(interface):
if interface.aemode in ['balanced', '802.3ad']:
attr_str = "%s, AE_XMIT_HASH_POLICY=%s" % (
attr_str, interface.txhashpolicy)
elif (interface.aemode == 'active_standby' and
interface.primary_reselect):
attr_str = "%s, primary_reselect=%s" % (
attr_str, interface.primary_reselect)
if interface.ifclass and interface.ifclass == 'data':
attrs = [attr.strip() for attr in attr_str.split(",")]
for a in attrs:

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -281,6 +281,7 @@ class UpdateView(forms.ModalFormView):
'iftype': interface.iftype,
'aemode': interface.aemode,
'txhashpolicy': interface.txhashpolicy,
'primary_reselect': interface.primary_reselect,
# 'ports': interface.ports,
# 'uses': interface.uses,
'ifclass': interface.ifclass,

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -125,6 +125,7 @@ class EditStorageVolume(forms.SelfHandlingForm):
if journal:
data['journal_location'] = journal
data['journal_size_mib'] = int(data['journal_size_gib']) * 1024
else:
data['journal_location'] = None
data['journal_size_mib'] = \
@ -404,12 +405,10 @@ class AddLocalVolumeGroup(forms.SelfHandlingForm):
if lvg.vg_state in current_lvg_states]
compatible_lvgs = []
if host.personality.lower().startswith(
stx_api.sysinv.PERSONALITY_CONTROLLER):
compatible_lvgs += [stx_api.sysinv.LVG_CINDER_VOLUMES]
if stx_api.sysinv.SUBFUNCTIONS_WORKER in subfunctions:
compatible_lvgs += [stx_api.sysinv.LVG_NOVA_LOCAL]
compatible_lvgs += [stx_api.sysinv.LVG_NOVA_LOCAL,
stx_api.sysinv.LVG_CGTS_VG]
allowed_lvgs = set(compatible_lvgs) - set(current_lvgs)
@ -539,11 +538,11 @@ class AddPhysicalVolume(forms.SelfHandlingForm):
compatible_lvgs = []
if host.personality.lower().startswith(
stx_api.sysinv.PERSONALITY_CONTROLLER):
compatible_lvgs += [stx_api.sysinv.LVG_CGTS_VG,
stx_api.sysinv.LVG_CINDER_VOLUMES]
compatible_lvgs += [stx_api.sysinv.LVG_CGTS_VG]
if stx_api.sysinv.SUBFUNCTIONS_WORKER in subfunctions:
compatible_lvgs += [stx_api.sysinv.LVG_NOVA_LOCAL]
compatible_lvgs += [stx_api.sysinv.LVG_NOVA_LOCAL,
stx_api.sysinv.LVG_CGTS_VG]
avail_disk_list = stx_api.sysinv.host_disk_list(self.request,
host_uuid)
@ -551,21 +550,15 @@ class AddPhysicalVolume(forms.SelfHandlingForm):
partitions = stx_api.sysinv.host_disk_partition_list(self.request,
host_uuid)
ipv_list = stx_api.sysinv.host_pv_list(self.request, host_uuid)
disk_tuple_list = []
partitions_tuple_list = []
ilvg_tuple_list = []
pv_cinder_volumes = next(
(pv for pv in ipv_list
if pv.lvm_vg_name == stx_api.sysinv.LVG_CINDER_VOLUMES), None)
for lvg in ilvg_list:
if (lvg.lvm_vg_name in compatible_lvgs and
lvg.vg_state in [stx_api.sysinv.LVG_ADD,
stx_api.sysinv.LVG_PROV]):
if (lvg.lvm_vg_name == stx_api.sysinv.LVG_CINDER_VOLUMES and
pv_cinder_volumes):
continue
ilvg_tuple_list.append((lvg.uuid, lvg.lvm_vg_name))
for disk in avail_disk_list:
@ -663,16 +656,16 @@ class AddPhysicalVolume(forms.SelfHandlingForm):
messages.success(request, msg)
return stor
except exc.ClientException as ce:
msg = _('Failed to create physical volume.')
msg = _('Failed to create physical volume. ')
# Allow REST API error message to appear on UI
w_msg = str(ce)
if ('Warning:' in w_msg):
if 'Warning:' in w_msg:
LOG.info(ce)
messages.warning(request, w_msg.split(':', 1)[-1])
messages.warning(request, msg + (w_msg.split(':', 1)[-1]))
else:
LOG.error(ce)
messages.error(request, w_msg)
messages.error(request, msg + w_msg)
# Redirect to host details pg
redirect = reverse(self.failure_url, args=[host_id])

View File

@ -386,8 +386,7 @@ class AddLocalVolumeGroup(tables.LinkAction):
self.classes = classes
if not host._administrative == 'locked':
if 'worker' in host._subfunctions and \
host.worker_config_required is False:
if host.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, ' ',
@ -401,11 +400,8 @@ class AddLocalVolumeGroup(tables.LinkAction):
if lvg.vg_state in current_lvg_states]
compatible_lvgs = []
if host._personality == 'controller':
compatible_lvgs += [sysinv.LVG_CINDER_VOLUMES]
if 'worker' in host._subfunctions:
compatible_lvgs += [sysinv.LVG_NOVA_LOCAL]
compatible_lvgs += [sysinv.LVG_NOVA_LOCAL, sysinv.LVG_CGTS_VG]
allowed_lvgs = set(compatible_lvgs) - set(current_lvgs)
if not any(allowed_lvgs):
@ -440,8 +436,7 @@ class RemoveLocalVolumeGroup(tables.DeleteAction):
if lvg.lvm_vg_name == sysinv.LVG_NOVA_LOCAL:
return ((host._administrative == 'locked') or
(('worker' in host._subfunctions) and
(host.worker_config_required is True)))
(host.config_required is True))
elif lvg.lvm_vg_name == sysinv.LVG_CINDER_VOLUMES:
return (sysinv.STORAGE_BACKEND_LVM not in storage_backend and
sysinv.LVG_ADD in lvg.vg_state)
@ -518,26 +513,18 @@ class AddPhysicalVolume(tables.LinkAction):
classes = [c for c in self.classes if c != "disabled"]
self.classes = classes
# cgts-vg, cinder-volumes: Allow adding to any controller
# Allow adding to any controller
if host._personality == sysinv.PERSONALITY_CONTROLLER:
return True
# nova-local: Allow adding to any locked host with a worker
# subfunction. On an AIO, the previous check superceeds this.
# Allow adding to any locked host
if host._administrative != 'locked':
if 'worker' in host._subfunctions and \
host.worker_config_required is False:
host.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
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
@ -565,8 +552,7 @@ class RemovePhysicalVolume(tables.DeleteAction):
if pv.lvm_vg_name == sysinv.LVG_NOVA_LOCAL:
return ((host._administrative == 'locked') or
(('worker' in host._subfunctions) and
(host.worker_config_required is True)))
(host.config_required is True))
elif pv.lvm_vg_name == sysinv.LVG_CINDER_VOLUMES:
return (sysinv.STORAGE_BACKEND_LVM not in storage_backend and
sysinv.PV_ADD in pv.pv_state)

View File

@ -13,6 +13,9 @@
{% if interfaces.aemode == 'balanced' %}
{{" | "}} {{ interfaces.txhashpolicy }}
{% endif %}
{% if interfaces.aemode == 'active_standby' %}
{{" | "}} {{ interfaces.primary_reselect }}
{% endif %}
{% endif %}
{{ " | MTU =" }} {{ interfaces.imtu }}
</li>

View File

@ -40,6 +40,9 @@
{% if interfaces.aemode == 'balanced' %}
{{" | "}} {{ interfaces.txhashpolicy }}
{% endif %}
{% if interfaces.aemode == 'active_standby' %}
{{" | "}} {{ interfaces.primary_reselect }}
{% endif %}
{% endif %}
{{" | MTU="}} {{ interfaces.imtu }}
</li>

View File

@ -41,14 +41,9 @@
function confirm_pv_device() {
lvg = lvg_dropdown.options[lvg_dropdown.selectedIndex].text;
if (lvg.indexOf("cgts-vg") !== -1) {
var str = "This operation is irreversible. Are you sure you want to add the selected device to cgts-vg?";
var confirm = window.confirm(str);
if (confirm != true) {
return false;
}
return confirm('This operation is irreversible. Are you sure you want to add the selected device to cgts-vg?');
}
document.getElementById('add_physicalvolume_form').submit();
return "";
}
/* Obtain the stor function dropdown. */

View File

@ -1,4 +1,4 @@
% extends 'base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Storage Profile" %}{% endblock %}

View File

@ -1,4 +1,4 @@
% extends 'base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Local Volume Group" %}{% endblock %}

View File

@ -1,4 +1,4 @@
% extends 'base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Local Volume Group" %}{% endblock %}

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -47,7 +47,7 @@ class AddView(workflows.WorkflowView):
'personality': "",
'subfunctions': "",
'mgmt_mac': "",
'bm_type': stx_api.sysinv.BM_TYPE_NULL,
'bm_type': "",
'bm_ip': "",
'bm_username': ""}

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2020 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -14,7 +14,6 @@ import sysinv.common.constants as sysinv_const
from cgtsclient.common import constants
from cgtsclient import exc
from django.conf import settings
from django.utils.translation import ugettext_lazy as _ # noqa
from django.views.decorators.debug import sensitive_variables # noqa
@ -196,13 +195,6 @@ class AddHostInfoAction(workflows.Action):
self.fields['personality'].choices = \
PERSONALITY_CHOICES_WITHOUT_STORAGE
# Remove worker personality if in DC mode and region
if getattr(self.request.user, 'services_region', None) == 'RegionOne' \
and getattr(settings, 'DC_MODE', False):
self.fields['personality'].choices = \
[choice for choice in self.fields['personality'].choices
if choice[0] != stx_api.sysinv.PERSONALITY_WORKER]
def clean(self):
cleaned_data = super(AddHostInfoAction, self).clean()
return cleaned_data
@ -297,13 +289,6 @@ class UpdateHostInfoAction(workflows.Action):
self.fields['personality'].choices = \
PERSONALITY_CHOICES_WITHOUT_STORAGE
# Remove worker personality if in DC mode and region
if getattr(self.request.user, 'services_region', None) == 'RegionOne' \
and getattr(settings, 'DC_MODE', False):
self.fields['personality'].choices = \
[choice for choice in self.fields['personality'].choices
if choice[0] != stx_api.sysinv.PERSONALITY_WORKER]
# hostname cannot be modified once it is set
if self.initial['hostname']:
self.fields['hostname'].widget.attrs['readonly'] = 'readonly'

View File

@ -378,6 +378,11 @@
gettext('The subcloud must be in the managed state before you can access detailed views.'));
return;
}
if (cloud.availability_status != 'online') {
toast.add('error',
gettext('The subcloud must be online before you can access detailed views.'));
return;
}
keystone.getCurrentUserSession().success(function(session){
session.available_services_regions.indexOf(cloud.name)
@ -400,6 +405,11 @@
gettext('The subcloud must be in the managed management state before you can access detailed views.'));
return;
}
if (cloud.availability_status != 'online') {
toast.add('error',
gettext('The subcloud must be online before you can access detailed views.'));
return;
}
keystone.getCurrentUserSession().success(function(session){
if (session.available_services_regions.indexOf(cloud.name) > -1) {

View File

@ -53,11 +53,20 @@
<span ng-show="cloud.availability_status==='offline'" class="fa fa-circle status-danger"></span>
{$ cloud.availability_status $}</td>
<td class="rsp-p1">
<span ng-show="cloud.deploy_status==='pre-install-failed' || cloud.deploy_status==='install-failed' || cloud.deploy_status==='bootstrap-failed' || cloud.deploy_status==='deploy-failed'"
<span ng-show="cloud.deploy_status==='pre-install-failed' || cloud.deploy_status==='install-failed'
|| cloud.deploy_status==='bootstrap-failed' || cloud.deploy_status==='deploy-failed'
|| cloud.deploy_status==='deploy-prep-failed' || cloud.deploy_status==='data-migration-failed'
|| cloud.deploy_status==='restore-prep-failed' || cloud.deploy_status==='restore-failed'"
class="fa fa-circle status-danger"></span>
<span ng-show="cloud.deploy_status==='not-deployed' || cloud.deploy_status==='pre-install' || cloud.deploy_status==='installing' || cloud.deploy_status==='bootstrapping' || cloud.deploy_status==='deploying'"
<span ng-show="cloud.deploy_status==='not-deployed' || cloud.deploy_status==='pre-install'
|| cloud.deploy_status==='installing' || cloud.deploy_status==='bootstrapping'
|| cloud.deploy_status==='deploying' || cloud.deploy_status==='pre-deploy'
|| cloud.deploy_status==='migrating-data' || cloud.deploy_status==='pre-restore'
|| cloud.deploy_status==='restoring'"
class="fa fa-circle status-warning"></span>
<span ng-show="cloud.deploy_status=='complete'" class="fa fa-circle status-success"></span>
<span ng-show="cloud.deploy_status=='complete' || cloud.deploy_status==='installed'
|| cloud.deploy_status==='migrated'"
class="fa fa-circle status-success"></span>
{$ cloud.deploy_status $}
</td>
<td class="rsp-p1">

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 Wind River Systems, Inc.
* Copyright (c) 2017-2020 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*
@ -43,9 +43,17 @@
function getSummaries() {
return apiService.get('/api/dc_manager/alarm_summaries/')
.error(function () {
.error(function (error) {
toastService.clearErrors();
toastService.add('error', gettext('Unable to retrieve the subcloud alarm summaries.'));
// We do this check to prevent the user from seeing a popup with this
// error when they are about to switch from the SystemController's
// region to a subcloud. The error can be raised when there are still
// SystemController's pending requests in the queue.
if (error != "Invalid service catalog: dcmanager") {
toastService.add('error', gettext('Unable to retrieve the subcloud alarm summaries.'));
}
});
}
@ -119,9 +127,16 @@
function getSubClouds() {
return apiService.get('/api/dc_manager/subclouds/')
.error(function () {
.error(function (error) {
toastService.clearErrors();
toastService.add('error', gettext('Unable to retrieve the subclouds.'));
// We do this check to prevent the user from seeing a popup with this
// error when they are about to switch from the SystemController's
// region to a subcloud. The error can be raised when there are still
// SystemController's pending requests in the queue.
if (error != "Invalid service catalog: dcmanager") {
toastService.add('error', gettext('Unable to retrieve the subclouds.'));
}
});
}

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2019-2020 Wind River Systems, Inc.
# Copyright (c) 2019-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -69,11 +69,16 @@ DC_MODE = False
if distributed_cloud_role and distributed_cloud_role in ['systemcontroller',
'subcloud']:
DC_MODE = True
DEFAULT_SERVICE_REGIONS = {
'*': 'SystemController',
}
HORIZON_CONFIG["user_home"] = \
"starlingx_dashboard.utils.settings.get_user_home"
OPENSTACK_ENDPOINT_TYPE = "adminURL"
SECONDARY_ENDPOINT_TYPE = "adminURL"
# Override Django tempory file upload directory
# Directory in which upload streamed files will be temporarily saved. A value
@ -137,12 +142,15 @@ for root, _dirs, files in os.walk('/opt/branding/applied'):
ADD_TEMPLATE_DIRS = [os.path.join(ROOT_PATH, 'starlingx_templates')]
TEMPLATES[0]['DIRS'] = ADD_TEMPLATE_DIRS + TEMPLATES[0]['DIRS']
OPENRC_CUSTOM_TEMPLATE = 'starlingx-openrc.sh.template'
STATIC_ROOT = "/www/pages/static"
COMPRESS_OFFLINE = True
# Secure site configuration
SESSION_COOKIE_HTTPONLY = True
ENFORCE_PASSWORD_CHECK = True
HORIZON_CONFIG["disable_password_reveal"] = True
# Size of thread batch
THREAD_BATCH_SIZE = 100

View File

@ -0,0 +1,55 @@
{% load shellfilter %}#!/usr/bin/env bash
{% load align_auth_url %}
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# To use an OpenStack cloud you need to authenticate against the Identity
# service named keystone, which returns a **Token** and **Service Catalog**.
# The catalog contains the endpoints for all services the user/tenant has
# access to - such as Compute, Image Service, Identity, Object Storage, Block
# Storage, and Networking (code-named nova, glance, keystone, swift,
# cinder, and neutron).
#
# *NOTE*: Using the 3 *Identity API* does not necessarily mean any other
# OpenStack API is version 3. For example, your cloud provider may implement
# Image API v1.1, Block Storage API v2, and Compute API v2.0. OS_AUTH_URL is
# only for the Identity API served through keystone.
{% if region == 'SystemController' %}
export OS_AUTH_URL={{ auth_url|align_auth_url }}
{% else %}
export OS_AUTH_URL={{ auth_url }}
{% endif %}
# With the addition of Keystone we have standardized on the term **project**
# as the entity that owns the resources.
export OS_PROJECT_ID={{ tenant_id }}
export OS_PROJECT_NAME="{{ tenant_name|shellfilter }}"
export OS_USER_DOMAIN_NAME="{{ user_domain_name|shellfilter }}"
if [ -z "$OS_USER_DOMAIN_NAME" ]; then unset OS_USER_DOMAIN_NAME; fi
export OS_PROJECT_DOMAIN_ID="{{ project_domain_id|shellfilter }}"
if [ -z "$OS_PROJECT_DOMAIN_ID" ]; then unset OS_PROJECT_DOMAIN_ID; fi
# unset v2.0 items in case set
unset OS_TENANT_ID
unset OS_TENANT_NAME
# In addition to the owning entity (tenant), OpenStack stores the entity
# performing the action as the **user**.
export OS_USERNAME="{{ user.username|shellfilter }}"
# With Keystone you pass the keystone password.
echo "Please enter your OpenStack Password for project $OS_PROJECT_NAME as user $OS_USERNAME: "
read -sr OS_PASSWORD_INPUT
export OS_PASSWORD=$OS_PASSWORD_INPUT
# If your configuration has multiple regions, we set that information here.
# OS_REGION_NAME is optional and only valid in certain environments.
export OS_REGION_NAME="{{ region|shellfilter }}"
# Don't leave a blank variable, unset it if it was empty
if [ -z "$OS_REGION_NAME" ]; then unset OS_REGION_NAME; fi
export OS_INTERFACE={{ interface }}
export OS_IDENTITY_API_VERSION={{ os_identity_api_version }}

View File

@ -0,0 +1,16 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from django import template
register = template.Library()
@register.filter(name="align_auth_url")
def align_auth_url(url):
url_list = url.split(':')
url_list[-1] = '5000/v3'
return ':'.join(url_list)

View File

@ -7,22 +7,24 @@
# be installed in a specific order.
#
# Hacking should appear first in case something else depends on pep8
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
hacking>=1.1.0,<=2.0.0 # Apache-2.0
coverage>=3.6 # Apache-2.0
django-nose>=1.4.4 # BSD
mock>=2.0 # BSD
mox3>=0.7.0 # Apache-2.0
nodeenv>=0.9.4 # BSD License # BSD
nose # LGPL
nose-exclude # LGPL
nosehtmloutput>=0.0.3 # Apache-2.0
nosexcover # BSD
openstack.nose-plugin>=0.7 # Apache-2.0
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
requests>=2.10.0 # Apache-2.0
selenium>=2.50.1 # Apache-2.0
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
testtools>=1.4.0 # MIT
# This also needs xvfb library installed on your OS
xvfbwrapper>=0.1.3 #license: MIT
pyOpenSSL>=0.14
bandit;python_version>="3.0"
isort<5;python_version>="3.0"
pylint<2.1.0;python_version<"3.0" # GPLv2
pylint<2.4.0;python_version>="3.0" # GPLv2
Django

21
tox.ini
View File

@ -37,9 +37,11 @@ commands =
[flake8]
# H102 Apache 2.0 license header not found
# NOTE(Eric Barrett): H102 raises a false positive when using the SPDX license header
ignore = H102
# H106 Dont put vim configuration in source files (off by default).
# W503 line break before binary operator
# W504 line break after binary operator
# E741 ambiguous variable name 'l'
ignore = H102,W503,W504,E741
# H106 Do not put vim configuration in source files (off by default).
# H203 Use assertIs(Not)None to check for None (off by default).
# H904 Delay string interpolations at logging calls (off by default).
enable-extensions = H106,H203,H904
@ -52,7 +54,7 @@ commands =
flake8
[testenv:pylint]
basepython = python2.7
basepython = python3
usedevelop = False
skip_install = True
deps = {[testenv]deps}
@ -64,9 +66,8 @@ deps = {[testenv]deps}
-e{[tox]stxdir}/nfv/nfv/nfv-client
-e{[tox]stxdir}/ha/service-mgmt-client/sm-client
-e{[tox]stxdir}/utilities/ceph/python-cephclient/python-cephclient
horizon==15.2.0
horizon
requests-toolbelt
pylint
commands =
pylint starlingx-dashboard/starlingx-dashboard/starlingx_dashboard --rcfile=./pylint.rc
@ -77,6 +78,7 @@ commands = {posargs}
[testenv:docs]
basepython = python3
deps = -r{toxinidir}/doc/requirements.txt
install_command = pip install -U {opts} {packages}
commands =
rm -rf doc/build
sphinx-build -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html
@ -85,6 +87,7 @@ whitelist_externals = rm
[testenv:releasenotes]
basepython = python3
deps = -r{toxinidir}/doc/requirements.txt
install_command = pip install -U {opts} {packages}
commands =
rm -rf releasenotes/build
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
@ -97,4 +100,10 @@ basepython = python3
# Re-use the releasenotes venv
envdir = {toxworkdir}/releasenotes
deps = -r{toxinidir}/doc/requirements.txt
install_command = pip install -U {opts} {packages}
commands = reno new {posargs}
[testenv:bandit]
basepython = python3
description = Bandit code scan for *.py files under config folder
commands = bandit -r {toxinidir}/ -x '**/.tox/**,**/.eggs/**' -lll