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:
parent
e7aa10061c
commit
79c184ddfe
@ -1,2 +1,2 @@
|
|||||||
SRC_DIR="starlingx-dashboard"
|
SRC_DIR="starlingx-dashboard"
|
||||||
TIS_PATCH_VER=12
|
TIS_PATCH_VER=13
|
||||||
|
@ -1,79 +1,163 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# 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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
# a copy of the License at
|
# a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
# Copyright (c) 2017 Wind River Systems, Inc.
|
# Copyright (c) 2017-2018 Wind River Systems, Inc.
|
||||||
#
|
#
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from dcmanagerclient.api.v1 import client
|
from dcmanagerclient.api.v1 import client
|
||||||
|
from dcmanagerclient.exceptions import APIException
|
||||||
from horizon.utils.memoized import memoized # noqa
|
|
||||||
|
from horizon.utils.memoized import memoized # noqa
|
||||||
from openstack_dashboard.api import base
|
|
||||||
|
from openstack_dashboard.api import base
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@memoized
|
|
||||||
def dcmanagerclient(request):
|
DEFAULT_CONFIG_NAME = "all clouds default"
|
||||||
endpoint = base.url_for(request, 'dcmanager', 'adminURL')
|
|
||||||
c = client.Client(project_id=request.user.project_id,
|
|
||||||
user_id=request.user.id,
|
@memoized
|
||||||
auth_token=request.user.token.id,
|
def dcmanagerclient(request):
|
||||||
dcmanager_url=endpoint)
|
endpoint = base.url_for(request, 'dcmanager', 'adminURL')
|
||||||
return c
|
c = client.Client(project_id=request.user.project_id,
|
||||||
|
user_id=request.user.id,
|
||||||
|
auth_token=request.user.token.id,
|
||||||
class Summary(base.APIResourceWrapper):
|
dcmanager_url=endpoint)
|
||||||
_attrs = ['name', 'critical', 'major', 'minor', 'warnings', 'status']
|
return c
|
||||||
|
|
||||||
|
|
||||||
def alarm_summary_list(request):
|
class Summary(base.APIResourceWrapper):
|
||||||
summaries = dcmanagerclient(request).alarm_manager.list_alarms()
|
_attrs = ['name', 'critical', 'major', 'minor', 'warnings', 'status']
|
||||||
return [Summary(summary) for summary in summaries]
|
|
||||||
|
|
||||||
|
def alarm_summary_list(request):
|
||||||
class Subcloud(base.APIResourceWrapper):
|
summaries = dcmanagerclient(request).alarm_manager.list_alarms()
|
||||||
_attrs = ['subcloud_id', 'name', 'description', 'location',
|
return [Summary(summary) for summary in summaries]
|
||||||
'software_version', 'management_subnet', 'management_state',
|
|
||||||
'availability_status', 'management_start_ip',
|
|
||||||
'management_end_ip', 'management_gateway_ip',
|
class Subcloud(base.APIResourceWrapper):
|
||||||
'systemcontroller_gateway_ip', 'created_at', 'updated_at',
|
_attrs = ['subcloud_id', 'name', 'description', 'location',
|
||||||
'sync_status', 'endpoint_sync_status', ]
|
'software_version', 'management_subnet', 'management_state',
|
||||||
|
'availability_status', 'management_start_ip',
|
||||||
|
'management_end_ip', 'management_gateway_ip',
|
||||||
def subcloud_list(request):
|
'systemcontroller_gateway_ip', 'created_at', 'updated_at',
|
||||||
subclouds = dcmanagerclient(request).subcloud_manager.list_subclouds()
|
'sync_status', 'endpoint_sync_status', ]
|
||||||
return [Subcloud(subcloud) for subcloud in subclouds]
|
|
||||||
|
|
||||||
|
def subcloud_list(request):
|
||||||
def subcloud_create(request, data):
|
subclouds = dcmanagerclient(request).subcloud_manager.list_subclouds()
|
||||||
return dcmanagerclient(request).subcloud_manager.add_subcloud(
|
return [Subcloud(subcloud) for subcloud in subclouds]
|
||||||
**data.get('data'))
|
|
||||||
|
|
||||||
|
def subcloud_create(request, data):
|
||||||
def subcloud_update(request, subcloud_id, changes):
|
return dcmanagerclient(request).subcloud_manager.add_subcloud(
|
||||||
response = dcmanagerclient(request).subcloud_manager.update_subcloud(
|
**data.get('data'))
|
||||||
subcloud_id, **changes.get('updated'))
|
|
||||||
# Updating returns a list of subclouds for some reason
|
|
||||||
return [Subcloud(subcloud) for subcloud in response]
|
def subcloud_update(request, subcloud_id, changes):
|
||||||
|
response = dcmanagerclient(request).subcloud_manager.update_subcloud(
|
||||||
|
subcloud_id, **changes.get('updated'))
|
||||||
def subcloud_delete(request, subcloud_id):
|
# Updating returns a list of subclouds for some reason
|
||||||
return dcmanagerclient(request).subcloud_manager.delete_subcloud(
|
return [Subcloud(subcloud) for subcloud in response]
|
||||||
subcloud_id)
|
|
||||||
|
|
||||||
|
def subcloud_delete(request, subcloud_id):
|
||||||
def subcloud_generate_config(request, subcloud_id, data):
|
return dcmanagerclient(request).subcloud_manager.delete_subcloud(
|
||||||
return dcmanagerclient(request).subcloud_manager.generate_config_subcloud(
|
subcloud_id)
|
||||||
subcloud_id, **data)
|
|
||||||
|
|
||||||
|
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])
|
||||||
|
@ -19,12 +19,16 @@ class SoftwareManagement(horizon.Panel):
|
|||||||
def allowed(self, context):
|
def allowed(self, context):
|
||||||
if not base.is_service_enabled(context['request'], 'platform'):
|
if not base.is_service_enabled(context['request'], 'platform'):
|
||||||
return False
|
return False
|
||||||
|
elif context['request'].user.services_region == 'SystemController':
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
return super(SoftwareManagement, self).allowed(context)
|
return super(SoftwareManagement, self).allowed(context)
|
||||||
|
|
||||||
def nav(self, context):
|
def nav(self, context):
|
||||||
if not base.is_service_enabled(context['request'], 'platform'):
|
if not base.is_service_enabled(context['request'], 'platform'):
|
||||||
return False
|
return False
|
||||||
|
elif context['request'].user.services_region == 'SystemController':
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ class IndexView(tabs.TabbedTableView):
|
|||||||
|
|
||||||
class DetailPatchView(views.HorizonTemplateView):
|
class DetailPatchView(views.HorizonTemplateView):
|
||||||
template_name = 'admin/software_management/_detail_patches.html'
|
template_name = 'admin/software_management/_detail_patches.html'
|
||||||
|
failure_url = 'horizon:admin:software_management:index'
|
||||||
page_title = 'Patch Detail'
|
page_title = 'Patch Detail'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@ -59,7 +60,7 @@ class DetailPatchView(views.HorizonTemplateView):
|
|||||||
patch.requires_display = "%s" % "\n".join(
|
patch.requires_display = "%s" % "\n".join(
|
||||||
filter(None, patch.requires))
|
filter(None, patch.requires))
|
||||||
except Exception:
|
except Exception:
|
||||||
redirect = reverse('horizon:admin:software_management:index')
|
redirect = reverse(self.failure_url)
|
||||||
exceptions.handle(self.request,
|
exceptions.handle(self.request,
|
||||||
_('Unable to retrieve details for '
|
_('Unable to retrieve details for '
|
||||||
'patch "%s".') % patch_id,
|
'patch "%s".') % patch_id,
|
||||||
|
@ -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
|
@ -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)
|
@ -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
|
@ -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
|
@ -0,0 +1,5 @@
|
|||||||
|
{% load i18n sizeformat %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{{ cloudpatchconfig_table.render }}
|
||||||
|
{% endblock %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
||||||
|
|
||||||
|
|
@ -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 %}
|
@ -0,0 +1,10 @@
|
|||||||
|
{% load i18n sizeformat %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<div id="patches">
|
||||||
|
{{ dc_patches_table.render }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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'),
|
||||||
|
]
|
@ -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}
|
@ -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'
|
@ -12,6 +12,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from cgtsclient import exc as cgtsclient
|
from cgtsclient import exc as cgtsclient
|
||||||
|
from dcmanagerclient import exceptions as dcmanagerclient
|
||||||
|
|
||||||
|
|
||||||
UNAUTHORIZED = (
|
UNAUTHORIZED = (
|
||||||
@ -28,4 +29,5 @@ RECOVERABLE = (
|
|||||||
cgtsclient.HTTPBadRequest,
|
cgtsclient.HTTPBadRequest,
|
||||||
cgtsclient.HTTPConflict,
|
cgtsclient.HTTPConflict,
|
||||||
cgtsclient.CommunicationError,
|
cgtsclient.CommunicationError,
|
||||||
|
dcmanagerclient.APIException
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user