Distributed Cloud Patching Dashboard

Introduction of software management panel to
distributed cloud admin dashboad

Change-Id: Id2933c1e2dec0492cccf4552388e6fca9a371125
Story: 2002833
Task: 22752
Signed-off-by: Tyler Smith <tyler.smith@windriver.com>
This commit is contained in:
Tyler Smith 2018-09-26 14:07:42 -04:00
parent e7aa10061c
commit 79c184ddfe
27 changed files with 1224 additions and 81 deletions

View File

@ -1,2 +1,2 @@
SRC_DIR="starlingx-dashboard"
TIS_PATCH_VER=12
TIS_PATCH_VER=13

View File

@ -1,79 +1,163 @@
# 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) 2017 Wind River Systems, Inc.
#
import logging
from dcmanagerclient.api.v1 import client
from horizon.utils.memoized import memoized # noqa
from openstack_dashboard.api import base
LOG = logging.getLogger(__name__)
@memoized
def dcmanagerclient(request):
endpoint = base.url_for(request, 'dcmanager', 'adminURL')
c = client.Client(project_id=request.user.project_id,
user_id=request.user.id,
auth_token=request.user.token.id,
dcmanager_url=endpoint)
return c
class Summary(base.APIResourceWrapper):
_attrs = ['name', 'critical', 'major', 'minor', 'warnings', 'status']
def alarm_summary_list(request):
summaries = dcmanagerclient(request).alarm_manager.list_alarms()
return [Summary(summary) for summary in summaries]
class Subcloud(base.APIResourceWrapper):
_attrs = ['subcloud_id', 'name', 'description', 'location',
'software_version', 'management_subnet', 'management_state',
'availability_status', 'management_start_ip',
'management_end_ip', 'management_gateway_ip',
'systemcontroller_gateway_ip', 'created_at', 'updated_at',
'sync_status', 'endpoint_sync_status', ]
def subcloud_list(request):
subclouds = dcmanagerclient(request).subcloud_manager.list_subclouds()
return [Subcloud(subcloud) for subcloud in subclouds]
def subcloud_create(request, data):
return dcmanagerclient(request).subcloud_manager.add_subcloud(
**data.get('data'))
def subcloud_update(request, subcloud_id, changes):
response = dcmanagerclient(request).subcloud_manager.update_subcloud(
subcloud_id, **changes.get('updated'))
# Updating returns a list of subclouds for some reason
return [Subcloud(subcloud) for subcloud in response]
def subcloud_delete(request, subcloud_id):
return dcmanagerclient(request).subcloud_manager.delete_subcloud(
subcloud_id)
def subcloud_generate_config(request, subcloud_id, data):
return dcmanagerclient(request).subcloud_manager.generate_config_subcloud(
subcloud_id, **data)
# 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) 2017-2018 Wind River Systems, Inc.
#
import logging
from dcmanagerclient.api.v1 import client
from dcmanagerclient.exceptions import APIException
from horizon.utils.memoized import memoized # noqa
from openstack_dashboard.api import base
LOG = logging.getLogger(__name__)
DEFAULT_CONFIG_NAME = "all clouds default"
@memoized
def dcmanagerclient(request):
endpoint = base.url_for(request, 'dcmanager', 'adminURL')
c = client.Client(project_id=request.user.project_id,
user_id=request.user.id,
auth_token=request.user.token.id,
dcmanager_url=endpoint)
return c
class Summary(base.APIResourceWrapper):
_attrs = ['name', 'critical', 'major', 'minor', 'warnings', 'status']
def alarm_summary_list(request):
summaries = dcmanagerclient(request).alarm_manager.list_alarms()
return [Summary(summary) for summary in summaries]
class Subcloud(base.APIResourceWrapper):
_attrs = ['subcloud_id', 'name', 'description', 'location',
'software_version', 'management_subnet', 'management_state',
'availability_status', 'management_start_ip',
'management_end_ip', 'management_gateway_ip',
'systemcontroller_gateway_ip', 'created_at', 'updated_at',
'sync_status', 'endpoint_sync_status', ]
def subcloud_list(request):
subclouds = dcmanagerclient(request).subcloud_manager.list_subclouds()
return [Subcloud(subcloud) for subcloud in subclouds]
def subcloud_create(request, data):
return dcmanagerclient(request).subcloud_manager.add_subcloud(
**data.get('data'))
def subcloud_update(request, subcloud_id, changes):
response = dcmanagerclient(request).subcloud_manager.update_subcloud(
subcloud_id, **changes.get('updated'))
# Updating returns a list of subclouds for some reason
return [Subcloud(subcloud) for subcloud in response]
def subcloud_delete(request, subcloud_id):
return dcmanagerclient(request).subcloud_manager.delete_subcloud(
subcloud_id)
def subcloud_generate_config(request, subcloud_id, data):
return dcmanagerclient(request).subcloud_manager.generate_config_subcloud(
subcloud_id, **data)
class Strategy(base.APIResourceWrapper):
_attrs = ['subcloud_apply_type', 'max_parallel_subclouds',
'stop_on_failure', 'state', 'created_at', 'updated_at']
def get_strategy(request):
try:
response = dcmanagerclient(request).sw_update_manager.\
patch_strategy_detail()
except APIException as e:
if e.error_code == 404:
return None
else:
raise e
if response and len(response):
return Strategy(response[0])
def strategy_create(request, data):
response = dcmanagerclient(request).sw_update_manager.\
create_patch_strategy(**data)
return Strategy(response)
def strategy_apply(request):
return dcmanagerclient(request).sw_update_manager.apply_patch_strategy()
def strategy_abort(request):
return dcmanagerclient(request).sw_update_manager.abort_patch_strategy()
def strategy_delete(request):
return dcmanagerclient(request).sw_update_manager.delete_patch_strategy()
class Step(base.APIResourceWrapper):
_attrs = ['cloud', 'stage', 'state', 'details', 'started_at',
'finished_at']
def step_list(request):
response = dcmanagerclient(request).strategy_step_manager.\
list_strategy_steps()
return [Step(step) for step in response]
class Config(base.APIResourceWrapper):
_attrs = ['cloud', 'storage_apply_type', 'compute_apply_type',
'max_parallel_computes', 'alarm_restriction_type',
'default_instance_action']
def config_list(request):
response = dcmanagerclient(request).sw_update_options_manager.\
sw_update_options_list()
return [Config(config) for config in response]
def config_update(request, subcloud, data):
response = dcmanagerclient(request).sw_update_options_manager.\
sw_update_options_update(subcloud, **data)
return Config(response)
def config_delete(request, subcloud):
return dcmanagerclient(request).sw_update_options_manager.\
sw_update_options_delete(subcloud)
def config_get(request, subcloud):
if subcloud == DEFAULT_CONFIG_NAME:
subcloud = None
response = dcmanagerclient(request).sw_update_options_manager.\
sw_update_options_detail(subcloud)
if response and len(response):
return Config(response[0])

View File

@ -19,12 +19,16 @@ class SoftwareManagement(horizon.Panel):
def allowed(self, context):
if not base.is_service_enabled(context['request'], 'platform'):
return False
elif context['request'].user.services_region == 'SystemController':
return False
else:
return super(SoftwareManagement, self).allowed(context)
def nav(self, context):
if not base.is_service_enabled(context['request'], 'platform'):
return False
elif context['request'].user.services_region == 'SystemController':
return False
else:
return True

View File

@ -42,6 +42,7 @@ class IndexView(tabs.TabbedTableView):
class DetailPatchView(views.HorizonTemplateView):
template_name = 'admin/software_management/_detail_patches.html'
failure_url = 'horizon:admin:software_management:index'
page_title = 'Patch Detail'
def get_context_data(self, **kwargs):
@ -59,7 +60,7 @@ class DetailPatchView(views.HorizonTemplateView):
patch.requires_display = "%s" % "\n".join(
filter(None, patch.requires))
except Exception:
redirect = reverse('horizon:admin:software_management:index')
redirect = reverse(self.failure_url)
exceptions.handle(self.request,
_('Unable to retrieve details for '
'patch "%s".') % patch_id,

View File

@ -0,0 +1,185 @@
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import logging
from dcmanagerclient import exceptions as exc
from django.core.urlresolvers import reverse # noqa
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from starlingx_dashboard import api
from starlingx_dashboard.dashboards.admin.software_management.forms \
import UploadPatchForm as AdminPatchForm
LOG = logging.getLogger(__name__)
class UploadPatchForm(AdminPatchForm):
failure_url = 'horizon:dc_admin:dc_software_management:index'
class CreateCloudPatchStrategyForm(forms.SelfHandlingForm):
failure_url = 'horizon:dc_admin:dc_software_management:index'
SUBCLOUD_APPLY_TYPES = (
('parallel', _("Parallel")),
('serial', _("Serial")),
)
subcloud_apply_type = forms.ChoiceField(
label=_("Subcloud Apply Type"),
required=True,
choices=SUBCLOUD_APPLY_TYPES,
widget=forms.Select())
max_parallel_subclouds = forms.IntegerField(
label=_("Maximum Parallel Subclouds"),
initial=20,
min_value=2,
max_value=100,
required=True,
error_messages={'invalid': _('Maximum Parallel Subclouds must be '
'between 2 and 100.')},
widget=forms.TextInput())
stop_on_failure = forms.BooleanField(
label=_("Stop on Failure"),
required=False,
initial=True,
help_text=_("Determines whether patch orchestration failure in a "
"subcloud prevents application to subsequent subclouds"))
def handle(self, request, data):
try:
# convert keys to use dashes
for k in data.keys():
if '_' in k:
data[k.replace('_', '-')] = data[k]
del data[k]
data['stop-on-failure'] = str(data['stop-on-failure']).lower()
response = api.dc_manager.strategy_create(request, data)
if not response:
messages.error(request, "Strategy creation failed")
except Exception:
redirect = reverse(self.failure_url)
exceptions.handle(request, "Strategy creation failed",
redirect=redirect)
return True
class CreateCloudPatchConfigForm(forms.SelfHandlingForm):
failure_url = 'horizon:dc_admin:dc_software_management:index'
APPLY_TYPES = (
('parallel', _("Parallel")),
('serial', _("Serial")),
)
INSTANCE_ACTIONS = (
('migrate', _("Migrate")),
('stop-start', _("Stop-Start")),
)
ALARM_RESTRICTION_TYPES = (
('relaxed', _("Relaxed")),
('strict', _("Strict")),
)
subcloud = forms.ChoiceField(
label=_("Subcloud"),
required=True,
widget=forms.Select())
storage_apply_type = forms.ChoiceField(
label=_("Storage Apply Type"),
required=True,
choices=APPLY_TYPES,
widget=forms.Select())
compute_apply_type = forms.ChoiceField(
label=_("Compute Apply Type"),
required=True,
choices=APPLY_TYPES,
widget=forms.Select(
attrs={
'class': 'switchable',
'data-slug': 'compute_apply_type'}))
max_parallel_computes = forms.IntegerField(
label=_("Maximum Parallel Compute Hosts"),
initial=2,
min_value=2,
max_value=100,
required=True,
error_messages={'invalid': _('Maximum Parallel Compute Hosts must be '
'between 2 and 100.')},
widget=forms.TextInput(
attrs={
'class': 'switched',
'data-switch-on': 'compute_apply_type',
'data-compute_apply_type-parallel':
'Maximum Parallel Compute Hosts'}))
default_instance_action = forms.ChoiceField(
label=_("Default Instance Action"),
required=True,
choices=INSTANCE_ACTIONS,
widget=forms.Select())
alarm_restriction_type = forms.ChoiceField(
label=_("Alarm Restrictions"),
required=True,
choices=ALARM_RESTRICTION_TYPES,
widget=forms.Select())
def __init__(self, request, *args, **kwargs):
super(CreateCloudPatchConfigForm, self).__init__(request, *args,
**kwargs)
subcloud_list = [(api.dc_manager.DEFAULT_CONFIG_NAME,
api.dc_manager.DEFAULT_CONFIG_NAME), ]
subclouds = api.dc_manager.subcloud_list(self.request)
subcloud_list.extend([(c.name, c.name) for c in subclouds])
self.fields['subcloud'].choices = subcloud_list
if self.initial.get('subcloud', None):
self.fields['subcloud'].widget.attrs['disabled'] = 'disabled'
def handle(self, request, data):
try:
for k in data.keys():
if '_' in k:
data[k.replace('_', '-')] = data[k]
del data[k]
subcloud = data['subcloud']
if subcloud == api.dc_manager.DEFAULT_CONFIG_NAME:
subcloud = None
del data['subcloud']
response = api.dc_manager.config_update(request, subcloud, data)
if not response:
messages.error(request, "Cloud Patching Configuration "
"creation failed")
except exc.APIException as e:
LOG.error(e.error_message)
messages.error(request, e.error_message)
redirect = reverse(self.failure_url)
exceptions.handle(request, e.error_message, redirect=redirect)
except Exception:
redirect = reverse(self.failure_url)
exceptions.handle(request,
"Cloud Patching Configuration creation failed",
redirect=redirect)
return True

View File

@ -0,0 +1,30 @@
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from django.utils.translation import ugettext_lazy as _
import horizon
from starlingx_dashboard.dashboards.dc_admin import dashboard
class DCSoftwareManagement(horizon.Panel):
name = _("Software Management")
slug = 'dc_software_management'
def allowed(self, context):
if context['request'].user.services_region != 'SystemController':
return False
return super(DCSoftwareManagement, self).allowed(context)
def nav(self, context):
if context['request'].user.services_region != 'SystemController':
return False
return super(DCSoftwareManagement, self).allowed(context)
dashboard.DCAdmin.register(DCSoftwareManagement)

View File

@ -0,0 +1,327 @@
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import logging
from django.core.urlresolvers import reverse # noqa
from django.template.defaultfilters import safe, title # noqa
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import messages
from horizon import tables
from starlingx_dashboard import api
from starlingx_dashboard.dashboards.admin.software_management import tables \
as AdminTables
LOG = logging.getLogger(__name__)
# Patching
class UploadPatch(AdminTables.UploadPatch):
url = "horizon:dc_admin:dc_software_management:dc_patchupload"
class PatchesTable(AdminTables.PatchesTable):
patch_id = tables.Column('patch_id',
link="horizon:dc_admin:dc_software_management:"
"dc_patchdetail",
verbose_name=_('Patch ID'))
class Meta(object):
name = "dc_patches"
multi_select = True
row_class = AdminTables.UpdatePatchRow
status_columns = ['patchstate']
row_actions = (AdminTables.ApplyPatch, AdminTables.RemovePatch,
AdminTables.DeletePatch)
table_actions = (
AdminTables.PatchFilterAction, UploadPatch, AdminTables.ApplyPatch,
AdminTables.RemovePatch, AdminTables.DeletePatch)
verbose_name = _("Patches")
hidden_title = False
# Cloud Patch Orchestration
def get_cached_cloud_strategy(request, table):
if 'cloudpatchstrategy' not in table.kwargs:
table.kwargs['cloudpatchstrategy'] = \
api.dc_manager.get_strategy(request)
return table.kwargs['cloudpatchstrategy']
class CreateCloudPatchStrategy(tables.LinkAction):
name = "createcloudpatchstrategy"
url = "horizon:dc_admin:dc_software_management:createcloudpatchstrategy"
verbose_name = _("Create Strategy")
classes = ("ajax-modal", "btn-create")
icon = "plus"
def allowed(self, request, datum):
try:
strategy = get_cached_cloud_strategy(request, self.table)
classes = [c for c in self.classes if c != "disabled"]
self.classes = classes
if strategy:
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
except Exception as ex:
LOG.exception(ex)
return True
class DeleteCloudPatchStrategy(tables.Action):
name = "delete_patch_strategy"
force = False
disabled = False
requires_input = False
icon = 'trash'
action_type = 'danger'
verbose_name = _("Delete Strategy")
confirm_message = "You have selected Delete Strategy. " \
"Please confirm your selection"
def allowed(self, request, datum):
try:
strategy = get_cached_cloud_strategy(request, self.table)
self.disabled = True
if strategy and strategy.state in ['initial', 'complete', 'failed',
'aborted']:
self.disabled = False
except Exception as ex:
LOG.exception(ex)
return True
def get_default_classes(self):
try:
if self.disabled:
return ['disabled']
return super(DeleteCloudPatchStrategy, self).get_default_classes()
except Exception as ex:
LOG.exception(ex)
def single(self, table, request, obj_id):
try:
result = api.dc_manager.strategy_delete(request)
if result:
messages.success(request, "Strategy Deleted")
else:
messages.error(request, "Strategy delete failed")
except Exception as ex:
LOG.exception(ex)
messages.error(request, ex.message)
class ApplyCloudPatchStrategy(tables.Action):
name = "apply_cloud_patch_strategy"
requires_input = False
disabled = False
verbose_name = _("Apply Strategy")
def allowed(self, request, datum):
try:
strategy = get_cached_cloud_strategy(request, self.table)
self.disabled = False
if not strategy or strategy.state != 'initial':
self.disabled = True
except Exception as ex:
LOG.exception(ex)
return True
def get_default_classes(self):
try:
if self.disabled:
return ['disabled']
return super(ApplyCloudPatchStrategy, self).get_default_classes()
except Exception as ex:
LOG.exception(ex)
def single(self, table, request, obj_id):
try:
result = api.dc_manager.strategy_apply(request)
if result:
messages.success(request, "Strategy apply in progress")
else:
messages.error(request, "Strategy apply failed")
except Exception as ex:
LOG.exception(ex)
messages.error(request, ex.message)
class AbortCloudPatchStrategy(tables.Action):
name = "abort_cloud_patch_strategy"
requires_input = False
disabled = False
action_type = 'danger'
verbose_name = _("Abort Strategy")
confirm_message = "You have selected Abort Strategy. " \
"Please confirm your selection"
def allowed(self, request, datum):
try:
strategy = get_cached_cloud_strategy(request, self.table)
self.disabled = False
if not strategy or strategy.state != 'applying':
self.disabled = True
except Exception as ex:
LOG.exception(ex)
return True
def get_default_classes(self):
try:
if self.disabled:
return ['disabled']
return super(AbortCloudPatchStrategy, self).get_default_classes()
except Exception as ex:
LOG.exception(ex)
def single(self, table, request, obj_id):
try:
result = api.dc_manager.strategy_abort(request)
if result:
messages.success(request, "Strategy abort in progress")
else:
messages.error(request, "Strategy abort failed")
except Exception as ex:
LOG.exception(ex)
messages.error(request, ex.message)
STEP_STATE_CHOICES = (
(None, True),
("", True),
("none", True),
("complete", True),
("initial", True),
("failed", False),
("timed-out", False),
("aborted", False),
)
def get_apply_percent(cell):
if '(' in cell and '%)' in cell:
percent = cell.split('(')[1].split('%)')[0]
return {'percent': "%d%%" % float(percent)}
return {}
def get_state(step):
state = step.state
if '%' in step.details:
percent = [s for s in step.details.split(' ') if '%' in s]
if percent and len(percent):
percent = percent[0]
state += " (%s)" % percent
return state
class CloudPatchStepsTable(tables.DataTable):
cloud = tables.Column('cloud', verbose_name=_('Cloud'))
stage = tables.Column('stage', verbose_name=_('Stage'))
state = tables.Column(get_state,
verbose_name=_('State'),
status=True,
status_choices=STEP_STATE_CHOICES,
filters=(safe,),
cell_attributes_getter=get_apply_percent)
details = tables.Column('details',
verbose_name=_("Details"),)
started_at = tables.Column('started_at',
verbose_name=_('Started At'))
finished_at = tables.Column('finished_at',
verbose_name=_('Finished At'))
def get_object_id(self, obj):
return "%s" % obj.cloud
class Meta(object):
name = "cloudpatchsteps"
multi_select = False
status_columns = ['state', ]
table_actions = (CreateCloudPatchStrategy, ApplyCloudPatchStrategy,
AbortCloudPatchStrategy, DeleteCloudPatchStrategy)
verbose_name = _("Steps")
hidden_title = False
# Cloud Patch Config
class CreateCloudPatchConfig(tables.LinkAction):
name = "createcloudpatchconfig"
url = "horizon:dc_admin:dc_software_management:createcloudpatchconfig"
verbose_name = _("Create New Cloud Patching Configuration")
classes = ("ajax-modal", "btn-create")
icon = "plus"
class EditCloudPatchConfig(tables.LinkAction):
name = "editcloudpatchconfig"
url = "horizon:dc_admin:dc_software_management:editcloudpatchconfig"
verbose_name = _("Edit Configuration")
classes = ("ajax-modal",)
class DeleteCloudPatchConfig(tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Cloud Patching Configuration",
u"Delete Cloud Patching Configurations",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Cloud Patching Configuration",
u"Deleted Cloud Patching Configurations",
count
)
def allowed(self, request, config=None):
if config and config.cloud == api.dc_manager.DEFAULT_CONFIG_NAME:
return False
return True
def delete(self, request, config):
try:
api.dc_manager.config_delete(request, config)
except Exception:
msg = _('Failed to delete configuration for subcloud %(cloud)') % \
{'cloud': config, }
redirect = reverse('horizon:dc_admin:dc_software_management:index')
exceptions.handle(request, msg, redirect=redirect)
class CloudPatchConfigTable(tables.DataTable):
cloud = tables.Column('cloud', verbose_name=_('Cloud'))
storage_apply_type = tables.Column('storage_apply_type',
verbose_name=_('Storage Apply Type'))
compute_apply_type = tables.Column('compute_apply_type',
verbose_name=_('Compute Apply Type'))
max_parallel_computes = tables.Column(
'max_parallel_computes', verbose_name=_('Max Parallel Computes'))
default_instance_action = tables.Column(
'default_instance_action', verbose_name=_('Default Instance Action'))
alarm_restriction_type = tables.Column(
'alarm_restriction_type', verbose_name=_('Alarm Restrictions'))
def get_object_id(self, obj):
return "%s" % obj.cloud
def get_object_display(self, obj):
return obj.cloud
class Meta(object):
name = "cloudpatchconfig"
multi_select = False
table_actions = (CreateCloudPatchConfig,)
row_actions = (EditCloudPatchConfig, DeleteCloudPatchConfig,)
verbose_name = _("Cloud Patching Configurations")
hidden_title = False

View File

@ -0,0 +1,93 @@
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import logging
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from starlingx_dashboard import api
from starlingx_dashboard.dashboards.dc_admin.dc_software_management \
import tables as tables
LOG = logging.getLogger(__name__)
class PatchesTab(tabs.TableTab):
table_classes = (tables.PatchesTable,)
name = _("Patches")
slug = "patches"
template_name = ("dc_admin/dc_software_management/_patches.html")
def get_dc_patches_data(self):
request = self.request
patches = []
try:
patches = api.patch.get_patches(request)
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve patch list.'))
return patches
class CloudPatchOrchestrationTab(tabs.TableTab):
table_classes = (tables.CloudPatchStepsTable,)
name = _("Cloud Patching Orchestration")
slug = "cloud_patch_orchestration"
template_name = ("dc_admin/dc_software_management/"
"_cloud_patch_orchestration.html")
def get_context_data(self, request):
context = super(CloudPatchOrchestrationTab, self).\
get_context_data(request)
strategy = None
try:
strategy = api.dc_manager.get_strategy(request)
except Exception as ex:
LOG.exception(ex)
exceptions.handle(request,
_('Unable to retrieve current strategy.'))
context['strategy'] = strategy
return context
def get_cloudpatchsteps_data(self):
request = self.request
steps = []
try:
steps = api.dc_manager.step_list(request)
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve steps list.'))
return steps
class CloudPatchConfigTab(tabs.TableTab):
table_classes = (tables.CloudPatchConfigTable,)
name = _("Cloud Patching Configuration")
slug = "cloud_patch_config"
template_name = ("dc_admin/dc_software_management/"
"_cloud_patch_config.html")
def get_cloudpatchconfig_data(self):
request = self.request
steps = []
try:
steps = api.dc_manager.config_list(request)
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve configuration list.'))
return steps
class DCSoftwareManagementTabs(tabs.TabGroup):
slug = "dc_software_management_tabs"
tabs = (PatchesTab, CloudPatchOrchestrationTab, CloudPatchConfigTab)
sticky = True

View File

@ -0,0 +1,5 @@
{% load i18n sizeformat %}
{% block main %}
{{ cloudpatchconfig_table.render }}
{% endblock %}

View File

@ -0,0 +1,29 @@
{% load i18n sizeformat %}
{% block main %}
<div class="info row-fluid detail">
<h3>{% trans "Cloud Patch Strategy" %}</h3>
<hr class="header_rule">
<div id="cloud-patch-strategy-detail">
{% if strategy %}
<dl class="dl-horizontal-wide">
<dt>{% trans "Subcloud Apply Type" %}</dt>
<dd>{{ strategy.subcloud_apply_type }}</dd>
<dt>{% trans "Max Parallel Subclouds" %}</dt>
<dd>{{ strategy.max_parallel_subclouds }}</dd>
<dt>{% trans "Stop On Failure" %}</dt>
<dd>{{ strategy.stop_on_failure }}</dd>
<dt>{% trans "State" %}</dt>
<dd>{{ strategy.state }}</dd>
</dl>
{% else %}
{% trans "No Strategy has been created" %}
{% endif %}
</div>
</div>
<br/>
{{ cloudpatchsteps_table.render }}
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_action %}{% url 'horizon:dc_admin:dc_software_management:createcloudpatchconfig' %}{% endblock %}
{% block modal-header %}{% trans "Create New Cloud Patching Configuration" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>
{% trans "Create a configuration for a specific subcloud to use the specified patch strategy settings instead of the defaults values." %}
</p>
<p>
{% trans "Note: for Simplex systems, the default instance action must be set to stop-start (since migration is not possible on a single-node system)." %}
</p>
</div>
{% endblock %}
{% block modal-footer %}
<a class="btn btn-default cancel" data-dismiss="modal">Cancel</a>
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Cloud Patching Configuration" %}" />
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_action %}{% url 'horizon:dc_admin:dc_software_management:createcloudpatchstrategy' %}{% endblock %}
{% block modal-header %}{% trans "Create Patch Strategy" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>
{% trans "Create a strategy to specify how the clouds should be patched." %}
</p>
</div>
{% endblock %}
{% block modal-footer %}
<a class="btn btn-default cancel" data-dismiss="modal">Cancel</a>
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Patch Strategy" %}" />
{% endblock %}

View File

@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% load i18n breadcrumb_nav %}
{% block title %}{% trans "Patch Details" %}{% endblock %}
{% block main %}
<h3> {{patch.patch_id }} </h3>
<div class="row-fluid">
<div class="col-sm-12">
<div class="info row-fluid detail">
<h4>{% trans "Info" %}</h4>
<hr class="header_rule">
<dl>
<dt>{% trans "Patch State" %}</dt>
<dd>{{ patch.patchstate }}</dd>
<dt>{% trans "Platform Release Version" %}</dt>
<dd>{{ patch.sw_version }}</dd>
<dt>{% trans "Reboot-Required" %}</dt>
<dd>{{ patch.reboot_required }}</dd>
<dt>{% trans "Patch Status Code" %}</dt>
<dd>{{ patch.status }}</dd>
{% if patch.unremovable == "Y" %}
<dt>{% trans "Unremovable" %}</dt>
<dd>{% trans "This patch cannot be removed" %}</dd>
{% endif %}
<dt>{% trans "Required Patches" %}</dt>
<dd>{{ patch.requires_display|linebreaks }}</dd>
</dl>
<dl>
<dt>{% trans "Summary" %}</dt>
<dd>{{ patch.summary|linebreaks }}</dd>
</dl>
<dl>
<dt>{% trans "Description" %}</dt>
<dd>{{ patch.description|linebreaks }}</dd>
</dl>
<dl>
<dt>{% trans "Warnings" %}</dt>
<dd>{{ patch.warnings|linebreaks }}</dd>
</dl>
<dl>
<dt>{% trans "Installation Instructions" %}</dt>
<dd>{{ patch.install_instructions|linebreaks }}</dd>
</dl>
<dl>
<dt>{% trans "Contents" %}</dt>
<dd>{{ patch.contents_display|linebreaks }}</dd>
</dl>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_action %}{% url 'horizon:dc_admin:dc_software_management:editcloudpatchconfig' subcloud %}{% endblock %}
{% block modal-header %}{% trans "Edit Cloud Patch Configuration" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>
{% trans "Edit a configuration for a specific subcloud to use the specified patch strategy settings instead of the defaults values." %}
</p>
<p>
{% trans "Note: for Simplex systems, the default instance action must be set to stop-start (since migration is not possible on a single-node system)." %}
</p>
</div>
{% endblock %}
{% block modal-footer %}
<a class="btn btn-default cancel" data-dismiss="modal">Cancel</a>
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Edit Cloud Patching Configuration" %}" />
{% endblock %}

View File

@ -0,0 +1,10 @@
{% load i18n sizeformat %}
{% block main %}
<div id="patches">
{{ dc_patches_table.render }}
</div>
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_action %}{% url 'horizon:dc_admin:dc_software_management:dc_patchupload' %}{% endblock %}
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
{% block modal-header %}{% trans "Upload Patches" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>
{% trans "Specify one or more patch files to upload to the Patching Service." %}
</p>
</div>
{% endblock %}
{% block modal-footer %}
<a class="btn btn-default cancel" data-dismiss="modal">Cancel</a>
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Upload Patches" %}" />
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create New Cloud Patching Configuration" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create New Cloud Patching Configuration") %}
{% endblock page_header %}
{% block main %}
{% include 'dc_admin/dc_software_management/_create_cloud_patch_config.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Patch Strategy" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create Patch Strategy") %}
{% endblock page_header %}
{% block main %}
{% include 'dc_admin/dc_software_management/_create_cloud_patch_strategy.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Edit Cloud Patching Configuration" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Edit Cloud Patching Configuration") %}
{% endblock page_header %}
{% block main %}
{% include 'dc_admin/dc_software_management/_edit_cloud_patch_config.html' %}
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Software Management" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Software Management") %}
{% endblock page_header %}
{% block main %}
<div class="row-fluid">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}
{% block js %}
{{ block.super }}
<script type="text/javascript" charset="utf-8">
horizon.refresh.addRefreshFunction(function (html) {
var $old_strategy = $('#cloud-patch-strategy-detail');
var $new_strategy = $(html).find('#cloud-patch-strategy-detail');
if ($new_strategy.html() != $old_strategy.html()) {
$old_strategy.replaceWith($new_strategy);
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Upload Patches" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Upload Patches") %}
{% endblock page_header %}
{% block main %}
{% include 'dc_admin/dc_software_management/_upload_patch.html' %}
{% endblock %}

View File

@ -0,0 +1,35 @@
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from django.conf.urls import url
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.views \
import CreateCloudPatchConfigView
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.views \
import CreateCloudPatchStrategyView
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.views \
import DetailPatchView
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.views \
import EditCloudPatchConfigView
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.views \
import IndexView
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.views \
import UploadPatchView
urlpatterns = [
url(r'^$', IndexView.as_view(), name='index'),
url(r'^(?P<patch_id>[^/]+)/patchdetail/$',
DetailPatchView.as_view(), name='dc_patchdetail'),
url(r'^dc_patchupload/$', UploadPatchView.as_view(),
name='dc_patchupload'),
url(r'^createcloudpatchstrategy/$', CreateCloudPatchStrategyView.as_view(),
name='createcloudpatchstrategy'),
url(r'^createcloudpatchconfig/$', CreateCloudPatchConfigView.as_view(),
name='createcloudpatchconfig'),
url(r'^(?P<subcloud>[^/]+)/editcloudpatchconfig/$',
EditCloudPatchConfigView.as_view(),
name='editcloudpatchconfig'),
]

View File

@ -0,0 +1,94 @@
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import logging
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tabs
from starlingx_dashboard import api
from starlingx_dashboard.dashboards.admin.software_management.views import \
DetailPatchView as AdminDetailPatchView
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.forms \
import CreateCloudPatchConfigForm
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.forms \
import CreateCloudPatchStrategyForm
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.forms \
import UploadPatchForm
from starlingx_dashboard.dashboards.dc_admin.dc_software_management.tabs \
import DCSoftwareManagementTabs
LOG = logging.getLogger(__name__)
class IndexView(tabs.TabbedTableView):
tab_group_class = DCSoftwareManagementTabs
template_name = 'dc_admin/dc_software_management/index.html'
page_title = _("Software Management")
def get_tabs(self, request, *args, **kwargs):
return self.tab_group_class(request, **kwargs)
class UploadPatchView(forms.ModalFormView):
form_class = UploadPatchForm
template_name = 'dc_admin/dc_software_management/upload_patch.html'
context_object_name = 'patch'
success_url = reverse_lazy("horizon:dc_admin:dc_software_management:index")
class DetailPatchView(AdminDetailPatchView):
template_name = 'dc_admin/dc_software_management/_detail_patches.html'
failure_url = 'horizon:dc_admin:dc_software_management:index'
class CreateCloudPatchStrategyView(forms.ModalFormView):
form_class = CreateCloudPatchStrategyForm
template_name = 'dc_admin/dc_software_management/' \
'create_cloud_patch_strategy.html'
context_object_name = 'strategy'
success_url = reverse_lazy("horizon:dc_admin:dc_software_management:index")
class CreateCloudPatchConfigView(forms.ModalFormView):
form_class = CreateCloudPatchConfigForm
template_name = 'dc_admin/dc_software_management/' \
'create_cloud_patch_config.html'
context_object_name = 'config'
success_url = reverse_lazy("horizon:dc_admin:dc_software_management:index")
class EditCloudPatchConfigView(forms.ModalFormView):
form_class = CreateCloudPatchConfigForm
template_name = 'dc_admin/dc_software_management/' \
'edit_cloud_patch_config.html'
context_object_name = 'config'
success_url = reverse_lazy("horizon:dc_admin:dc_software_management:index")
def get_context_data(self, **kwargs):
context = super(EditCloudPatchConfigView, self).get_context_data(
**kwargs)
context['subcloud'] = self.kwargs['subcloud']
return context
def get_initial(self):
try:
config = api.dc_manager.config_get(self.request,
self.kwargs['subcloud'])
except Exception:
exceptions.handle(self.request, _("Unable to retrieve subcloud "
"configuration data."))
return {'subcloud': config.cloud,
'storage_apply_type': config.storage_apply_type,
'compute_apply_type': config.compute_apply_type,
'max_parallel_computes': config.max_parallel_computes,
'default_instance_action': config.default_instance_action,
'alarm_restriction_type': config.alarm_restriction_type}

View File

@ -0,0 +1,10 @@
# The slug of the panel to be added to HORIZON_CONFIG. Required.
PANEL = 'dc_software_management'
# The slug of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'dc_admin'
# The slug of the panel group the PANEL is associated with.
PANEL_GROUP = 'default'
# Python panel class of the PANEL to be added.
ADD_PANEL = 'starlingx_dashboard.dashboards.' \
'dc_admin.dc_software_management.panel.DCSoftwareManagement'

View File

@ -12,6 +12,7 @@
# under the License.
from cgtsclient import exc as cgtsclient
from dcmanagerclient import exceptions as dcmanagerclient
UNAUTHORIZED = (
@ -28,4 +29,5 @@ RECOVERABLE = (
cgtsclient.HTTPBadRequest,
cgtsclient.HTTPConflict,
cgtsclient.CommunicationError,
dcmanagerclient.APIException
)