StarlingX open source release updates
Signed-off-by: Dean Troyer <dtroyer@gmail.com>
This commit is contained in:
parent
d1d7322a82
commit
5a4b802dfc
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
*.egg*
|
||||
*.mo
|
||||
*.pyc
|
||||
*.sw?
|
||||
*.sqlite3
|
||||
*.lock
|
||||
.environment_version
|
||||
.selenium_log
|
||||
.coverage*
|
||||
.noseids
|
||||
.DS_STORE
|
||||
.DS_Store
|
||||
/cover
|
||||
coverage.xml
|
||||
coverage-karma
|
||||
nosetests.xml
|
||||
pep8.txt
|
||||
pylint.txt
|
||||
# Files created by releasenotes build
|
||||
releasenotes/build
|
||||
reports
|
||||
openstack_dashboard/local/*
|
||||
!openstack_dashboard/local/local_settings.py.example
|
||||
!openstack_dashboard/local/enabled/_50__settings.py.example
|
||||
!openstack_dashboard/local/local_settings.d
|
||||
openstack_dashboard/local/local_settings.d/*
|
||||
!openstack_dashboard/local/local_settings.d/*.example
|
||||
openstack_dashboard/test/.secret_key_store
|
||||
openstack_dashboard/test/integration_tests/local-horizon.conf
|
||||
openstack_dashboard/test/integration_tests/test_reports/
|
||||
openstack_dashboard/wsgi/horizon.wsgi
|
||||
doc/build/
|
||||
doc/source/sourcecode
|
||||
/static/
|
||||
integration_tests_screenshots/
|
||||
.venv
|
||||
.tox
|
||||
node_modules
|
||||
npm-debug.log
|
||||
build
|
||||
dist
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
tags
|
||||
ghostdriver.log
|
||||
.testrepository
|
||||
.idea
|
18
CONTRIBUTING.rst
Normal file
18
CONTRIBUTING.rst
Normal file
@ -0,0 +1,18 @@
|
||||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps documented at:
|
||||
|
||||
http://docs.openstack.org/developer/horizon/contributing.html
|
||||
|
||||
or http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://bugs.launchpad.net/horizon
|
15
HACKING.rst
Normal file
15
HACKING.rst
Normal file
@ -0,0 +1,15 @@
|
||||
Horizon Style Commandments
|
||||
==========================
|
||||
|
||||
- Step 1: Read the OpenStack Style Commandments
|
||||
http://docs.openstack.org/developer/hacking/
|
||||
- Step 2: Read [hacking] section in tox.ini to find the list of names which
|
||||
can be imported directly without triggering the "H302: import only modules"
|
||||
flake8 warning
|
||||
- Step 3: Read on
|
||||
|
||||
Horizon Specific Commandments
|
||||
-----------------------------
|
||||
|
||||
- Read the Horizon contributing documentation at http://docs.openstack.org/developer/horizon/contributing.html
|
||||
- [M322] Method's default argument shouldn't be mutable.
|
176
LICENSE
Normal file
176
LICENSE
Normal file
@ -0,0 +1,176 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
0
cgcs_dashboard/__init__.py
Normal file
0
cgcs_dashboard/__init__.py
Normal file
31
cgcs_dashboard/api/__init__.py
Executable file
31
cgcs_dashboard/api/__init__.py
Executable file
@ -0,0 +1,31 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
from cgcs_dashboard.api import ceilometer
|
||||
from cgcs_dashboard.api import ceph
|
||||
from cgcs_dashboard.api import dc_manager
|
||||
from cgcs_dashboard.api import iservice
|
||||
from cgcs_dashboard.api import patch
|
||||
from cgcs_dashboard.api import sysinv
|
||||
from cgcs_dashboard.api import vim
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ceilometer",
|
||||
"ceph",
|
||||
"dc_manager",
|
||||
"iservice",
|
||||
"patch",
|
||||
"sysinv",
|
||||
"vim",
|
||||
]
|
52
cgcs_dashboard/api/ceilometer.py
Normal file
52
cgcs_dashboard/api/ceilometer.py
Normal file
@ -0,0 +1,52 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
from ceilometerclient import client as ceilometer_client
|
||||
from django.conf import settings
|
||||
from openstack_dashboard.api import base
|
||||
|
||||
from horizon.utils.memoized import memoized # noqa
|
||||
|
||||
|
||||
class Pipeline(base.APIResourceWrapper):
|
||||
"""Represents one Ceilometer pipeline entry."""
|
||||
|
||||
_attrs = ['name', 'enabled', 'meters', 'location', 'max_bytes',
|
||||
'backup_count', 'compress']
|
||||
|
||||
def __init__(self, apipipeline):
|
||||
super(Pipeline, self).__init__(apipipeline)
|
||||
|
||||
|
||||
@memoized
|
||||
def ceilometerclient(request):
|
||||
"""Initialization of Ceilometer client."""
|
||||
|
||||
endpoint = base.url_for(request, 'metering')
|
||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
|
||||
return ceilometer_client.Client('2', endpoint,
|
||||
token=(lambda: request.user.token.id),
|
||||
insecure=insecure,
|
||||
cacert=cacert)
|
||||
|
||||
|
||||
def pipeline_list(request):
|
||||
"""List the configured pipeline."""
|
||||
pipeline_entries = ceilometerclient(request).pipelines.list()
|
||||
pipelines = [Pipeline(p) for p in pipeline_entries]
|
||||
return pipelines
|
||||
|
||||
|
||||
def pipeline_update(request, pipeline_name, some_dict):
|
||||
pipeline = ceilometerclient(request).pipelines.update(pipeline_name,
|
||||
**some_dict)
|
||||
if not pipeline:
|
||||
raise ValueError(
|
||||
'No match found for pipeline_name "%s".' % pipeline_name)
|
||||
return Pipeline(pipeline)
|
187
cgcs_dashboard/api/ceph.py
Executable file
187
cgcs_dashboard/api/ceph.py
Executable file
@ -0,0 +1,187 @@
|
||||
# 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) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from cephclient import wrapper
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# TODO(wrs) this can be instancized once, or will need to pass request per
|
||||
# user?
|
||||
def cephwrapper():
|
||||
return wrapper.CephWrapper()
|
||||
|
||||
|
||||
class Monitor(base.APIDictWrapper):
|
||||
__attrs = ['host', 'rank']
|
||||
|
||||
def __init__(self, apidict):
|
||||
super(Monitor, self).__init__(apidict)
|
||||
|
||||
|
||||
class OSD(base.APIDictWrapper):
|
||||
__attrs = ['id', 'name', 'host', 'status']
|
||||
|
||||
def __init__(self, apidict):
|
||||
super(OSD, self).__init__(apidict)
|
||||
|
||||
|
||||
class Cluster(base.APIDictWrapper):
|
||||
_attrs = ['fsid', 'status', 'health', 'detail']
|
||||
|
||||
def __init__(self, apidict):
|
||||
super(Cluster, self).__init__(apidict)
|
||||
|
||||
|
||||
class Storage(base.APIDictWrapper):
|
||||
_attrs = ['total', 'used', 'available',
|
||||
'writes_per_sec', 'operations_per_sec']
|
||||
|
||||
def __init__(self, apidict):
|
||||
super(Storage, self).__init__(apidict)
|
||||
|
||||
|
||||
def _Bytes_to_MiB(value_B):
|
||||
return (value_B / (1024 * 1024))
|
||||
|
||||
|
||||
def _Bytes_to_GiB(value_B):
|
||||
return (value_B / (1024 * 1024 * 1024))
|
||||
|
||||
|
||||
def cluster_get():
|
||||
# the json response doesn't give all the information
|
||||
response, text_body = cephwrapper().health(body='text')
|
||||
# ceph is not up, raise exception
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
health_info = text_body.split(' ', 1)
|
||||
|
||||
# if health is ok, there will be no details so just show HEALTH_OK
|
||||
if len(health_info) > 1:
|
||||
detail = health_info[1]
|
||||
else:
|
||||
detail = health_info[0]
|
||||
|
||||
response, cluster_uuid = cephwrapper().fsid(body='text')
|
||||
if not response.ok:
|
||||
cluster_uuid = None
|
||||
|
||||
cluster = {
|
||||
'fsid': cluster_uuid,
|
||||
'health': health_info[0],
|
||||
'detail': detail,
|
||||
}
|
||||
|
||||
return Cluster(cluster)
|
||||
|
||||
|
||||
def storage_get():
|
||||
# # Space info
|
||||
response, body = cephwrapper().df(body='json')
|
||||
# return no space info
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
stats = body['output']['stats']
|
||||
space = {
|
||||
'total': _Bytes_to_GiB(stats['total_bytes']),
|
||||
'used': _Bytes_to_MiB(stats['total_used_bytes']),
|
||||
'available': _Bytes_to_GiB(stats['total_avail_bytes']),
|
||||
}
|
||||
|
||||
# # I/O info
|
||||
response, body = cephwrapper().osd_pool_stats(body='json',
|
||||
name='cinder-volumes')
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
stats = body['output'][0]['client_io_rate']
|
||||
# not showing reads/sec at the moment
|
||||
# reads_per_sec = stats['read_bytes_sec'] if (
|
||||
# 'read_bytes_sec' in stats) else 0
|
||||
writes_per_sec = stats['write_bytes_sec'] if (
|
||||
'write_bytes_sec' in stats) else 0
|
||||
operations_per_sec = stats['op_per_sec'] if ('op_per_sec' in stats) else 0
|
||||
io = {
|
||||
'writes_per_sec': writes_per_sec / 1024,
|
||||
'operations_per_sec': operations_per_sec
|
||||
}
|
||||
|
||||
storage = {}
|
||||
storage.update(space)
|
||||
storage.update(io)
|
||||
|
||||
return Storage(storage)
|
||||
|
||||
|
||||
def _get_quorum_status(mon, quorums):
|
||||
if mon['rank'] in quorums:
|
||||
status = 'up'
|
||||
else:
|
||||
status = 'down'
|
||||
return status
|
||||
|
||||
|
||||
def monitor_list():
|
||||
response, body = cephwrapper().mon_dump(body='json')
|
||||
# return no monitors info
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
|
||||
quorums = body['output']['quorum']
|
||||
|
||||
mons = []
|
||||
for mon in body['output']['mons']:
|
||||
status = _get_quorum_status(mon, quorums)
|
||||
mons.append(
|
||||
{'host': mon['name'], 'rank': mon['rank'], 'status': status})
|
||||
return [Monitor(m) for m in mons]
|
||||
|
||||
|
||||
def osd_list():
|
||||
# would use osd_find, but it doesn't give osd's name
|
||||
response, tree = cephwrapper().osd_tree(body='json')
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
|
||||
osds = []
|
||||
for node in tree['output']['nodes']:
|
||||
# found osd
|
||||
if node['type'] == 'osd':
|
||||
osd = {}
|
||||
osd['id'] = node['id']
|
||||
osd['name'] = node['name']
|
||||
osd['status'] = node['status']
|
||||
|
||||
# check if osd belongs to host
|
||||
response, body = cephwrapper().osd_find(body='json', id=osd['id'])
|
||||
if response.ok and 'host' in body['output']['crush_location']:
|
||||
osd['host'] = body['output']['crush_location']['host']
|
||||
# else dont set hostname
|
||||
|
||||
osds.append(osd)
|
||||
|
||||
return [OSD(o) for o in osds]
|
73
cgcs_dashboard/api/dc_manager.py
Executable file
73
cgcs_dashboard/api/dc_manager.py
Executable file
@ -0,0 +1,73 @@
|
||||
#
|
||||
# Copyright (c) 2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
|
||||
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)
|
61
cgcs_dashboard/api/iservice.py
Executable file
61
cgcs_dashboard/api/iservice.py
Executable file
@ -0,0 +1,61 @@
|
||||
# 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) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
import sm_client as smc
|
||||
|
||||
# Swap out with SM API
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def sm_client(request):
|
||||
# o = urlparse.urlparse(url_for(request, 'inventory'))
|
||||
# url = "://".join((o.scheme, o.netloc))
|
||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||
# LOG.debug('sysinv client conn using token "%s" and url "%s"'
|
||||
# % (request.user.token.id, url))
|
||||
# return smc.Client('1', url, token=request.user.token.id,
|
||||
# insecure=insecure)
|
||||
|
||||
return smc.Client('1', 'http://localhost:7777',
|
||||
token=request.user.token.id,
|
||||
insecure=insecure)
|
||||
|
||||
|
||||
def sm_sda_list(request):
|
||||
sdas = sm_client(request).sm_sda.list()
|
||||
LOG.debug("SM sdas list %s", sdas)
|
||||
|
||||
# fields = ['uuid', 'service_group_name', 'node_name', 'state', 'status',
|
||||
# 'condition']
|
||||
return sdas
|
||||
|
||||
|
||||
def sm_nodes_list(request):
|
||||
nodes = sm_client(request).sm_nodes.list()
|
||||
LOG.debug("SM nodes list %s", nodes)
|
||||
|
||||
# fields = ['id', 'name', 'state', 'online']
|
||||
return nodes
|
239
cgcs_dashboard/api/patch.py
Executable file
239
cgcs_dashboard/api/patch.py
Executable file
@ -0,0 +1,239 @@
|
||||
# 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) 2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
import urlparse
|
||||
|
||||
import requests
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
from requests_toolbelt import MultipartEncoder
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, version, url, token_id):
|
||||
self.version = version
|
||||
self.url = url
|
||||
self.token_id = token_id
|
||||
|
||||
def _make_request(self, token_id, method, api_version, api_cmd,
|
||||
encoder=None):
|
||||
url = self.url
|
||||
url += "/%s/%s" % (api_version, api_cmd)
|
||||
# url += "/patch/%s" % (api_cmd)
|
||||
|
||||
headers = {"X-Auth-Token": token_id,
|
||||
"Accept": "application/json"}
|
||||
|
||||
if method == 'GET':
|
||||
req = requests.get(url, headers=headers)
|
||||
elif method == 'POST':
|
||||
if encoder is not None:
|
||||
headers['Content-Type'] = encoder.content_type
|
||||
req = requests.post(url, headers=headers, data=encoder)
|
||||
|
||||
resp = req.json()
|
||||
|
||||
return resp
|
||||
|
||||
def get_patches(self):
|
||||
return self._make_request(self.token_id, "GET", self.version,
|
||||
"query?show=all")
|
||||
|
||||
def show_patch(self, patch_id):
|
||||
return self._make_request(self.token_id, "GET", self.version,
|
||||
"show/%s" % patch_id)
|
||||
|
||||
def get_hosts(self):
|
||||
return self._make_request(self.token_id, "GET", self.version,
|
||||
"query_hosts")
|
||||
|
||||
def upload(self, file):
|
||||
encoder = MultipartEncoder(fields=file)
|
||||
return self._make_request(self.token_id, "POST", self.version,
|
||||
"upload", encoder=encoder)
|
||||
|
||||
def apply(self, patch_ids):
|
||||
patches = "/".join(patch_ids)
|
||||
return self._make_request(self.token_id, "POST", self.version,
|
||||
"apply/%s" % patches)
|
||||
|
||||
def remove(self, patch_ids):
|
||||
patches = "/".join(patch_ids)
|
||||
return self._make_request(self.token_id, "POST", self.version,
|
||||
"remove/%s" % patches)
|
||||
|
||||
def delete(self, patch_ids):
|
||||
patches = "/".join(patch_ids)
|
||||
return self._make_request(self.token_id, "POST", self.version,
|
||||
"delete/%s" % patches)
|
||||
|
||||
def host_install(self, host):
|
||||
return self._make_request(self.token_id, "POST", self.version,
|
||||
"host_install/%s" % host)
|
||||
|
||||
def host_install_async(self, host):
|
||||
return self._make_request(self.token_id, "POST", self.version,
|
||||
"host_install_async/%s" % host)
|
||||
|
||||
|
||||
def _patching_client(request):
|
||||
o = urlparse.urlparse(base.url_for(request, 'patching'))
|
||||
url = "://".join((o.scheme, o.netloc))
|
||||
return Client("v1", url, token_id=request.user.token.id)
|
||||
|
||||
|
||||
class Patch(object):
|
||||
_attrs = ['status',
|
||||
'sw_version',
|
||||
'install_instructions',
|
||||
'description',
|
||||
'warnings',
|
||||
'summary',
|
||||
'repostate',
|
||||
'patchstate',
|
||||
'requires',
|
||||
'unremovable',
|
||||
'reboot_required']
|
||||
|
||||
|
||||
class Host(object):
|
||||
_attrs = ['hostname',
|
||||
'installed',
|
||||
'ip',
|
||||
'missing_pkgs',
|
||||
'nodetype',
|
||||
'patch_current',
|
||||
'patch_failed',
|
||||
'requires_reboot',
|
||||
'secs_since_ack',
|
||||
'stale_details',
|
||||
'to_remove',
|
||||
'sw_version',
|
||||
'state',
|
||||
'allow_insvc_patching',
|
||||
'interim_state']
|
||||
|
||||
|
||||
def get_patches(request):
|
||||
patches = []
|
||||
try:
|
||||
info = _patching_client(request).get_patches()
|
||||
except Exception:
|
||||
return patches
|
||||
|
||||
if info:
|
||||
for p in info['pd'].iteritems():
|
||||
patch = Patch()
|
||||
|
||||
for a in patch._attrs:
|
||||
if a == 'requires':
|
||||
patch.requires = [str(rp) for rp in p[1][a]]
|
||||
continue
|
||||
|
||||
if a == 'reboot_required':
|
||||
# Default to "N"
|
||||
setattr(patch, a, str(p[1].get(a, "N")))
|
||||
continue
|
||||
|
||||
# Must handle older patches that have metadata that is missing
|
||||
# newer attributes. Default missing attributes to "".
|
||||
setattr(patch, a, str(p[1].get(a, "")))
|
||||
patch.patch_id = str(p[0])
|
||||
|
||||
patches.append(patch)
|
||||
return patches
|
||||
|
||||
|
||||
def get_patch(request, patch_id):
|
||||
patches = get_patches(request)
|
||||
patch = next((p for p in patches if p.patch_id == patch_id), None)
|
||||
|
||||
# add on patch contents
|
||||
data = _patching_client(request).show_patch(patch_id)
|
||||
patch.contents = [str(pkg) for pkg in data['contents'][patch_id]]
|
||||
|
||||
return patch
|
||||
|
||||
|
||||
def get_hosts(request):
|
||||
hosts = []
|
||||
try:
|
||||
info = _patching_client(request).get_hosts()
|
||||
except Exception:
|
||||
return hosts
|
||||
|
||||
if info:
|
||||
for h in info['data']:
|
||||
host = Host()
|
||||
for a in host._attrs:
|
||||
setattr(host, a, h[a])
|
||||
hosts.append(host)
|
||||
return hosts
|
||||
|
||||
|
||||
def get_host(request, hostname):
|
||||
phosts = get_hosts(request)
|
||||
return next((phost for phost in phosts if phost.hostname == hostname),
|
||||
None)
|
||||
|
||||
|
||||
def get_message(data):
|
||||
LOG.info("RESPONSE: %s", data)
|
||||
if not data or ('error' in data and data["error"] != ""):
|
||||
raise ValueError(data["error"] or "Invalid patch file")
|
||||
if 'warning' in data and data["warning"] != "":
|
||||
return data["warning"]
|
||||
if 'info' in data and data["info"] != "":
|
||||
return data["info"]
|
||||
return ""
|
||||
|
||||
|
||||
def upload_patch(request, patchfile, name):
|
||||
file = {'file': (name, patchfile,)}
|
||||
resp = _patching_client(request).upload(file)
|
||||
return get_message(resp)
|
||||
|
||||
|
||||
def patch_apply_req(request, patch_id):
|
||||
resp = _patching_client(request).apply(patch_id)
|
||||
return get_message(resp)
|
||||
|
||||
|
||||
def patch_remove_req(request, patch_id):
|
||||
resp = _patching_client(request).remove(patch_id)
|
||||
return get_message(resp)
|
||||
|
||||
|
||||
def patch_delete_req(request, patch_id):
|
||||
resp = _patching_client(request).delete(patch_id)
|
||||
return get_message(resp)
|
||||
|
||||
|
||||
def host_install(request, hostname):
|
||||
resp = _patching_client(request).host_install(hostname)
|
||||
return get_message(resp)
|
||||
|
||||
|
||||
def host_install_async(request, hostname):
|
||||
resp = _patching_client(request).host_install_async(hostname)
|
||||
return get_message(resp)
|
31
cgcs_dashboard/api/rest/__init__.py
Executable file
31
cgcs_dashboard/api/rest/__init__.py
Executable file
@ -0,0 +1,31 @@
|
||||
# Copyright 2014, Rackspace, US, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""This package holds the REST API that supports the Horizon dashboard
|
||||
Javascript code.
|
||||
|
||||
It is not intended to be used outside of Horizon, and makes no promises of
|
||||
stability or fitness for purpose outside of that scope.
|
||||
|
||||
It does not promise to adhere to the general OpenStack API Guidelines set out
|
||||
in https://wiki.openstack.org/wiki/APIChangeGuidelines.
|
||||
"""
|
||||
|
||||
from openstack_dashboard.api.rest import dc_manager
|
||||
from openstack_dashboard.api.rest import sysinv
|
||||
|
||||
|
||||
__all__ = [
|
||||
'dc_manager',
|
||||
'sysinv',
|
||||
]
|
90
cgcs_dashboard/api/rest/dc_manager.py
Executable file
90
cgcs_dashboard/api/rest/dc_manager.py
Executable file
@ -0,0 +1,90 @@
|
||||
#
|
||||
# Copyright (c) 2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.views import generic
|
||||
|
||||
from openstack_dashboard.api import dc_manager
|
||||
from openstack_dashboard.api.rest import urls
|
||||
from openstack_dashboard.api.rest import utils as rest_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Subcloud(generic.View):
|
||||
"""API for manipulating a single subcloud"""
|
||||
|
||||
url_regex = r'dc_manager/subclouds/(?P<subcloud_id>[^/]+|default)/$'
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def patch(self, request, subcloud_id):
|
||||
"""Update a specific subcloud
|
||||
|
||||
PATCH http://localhost/api/dc_manager/subclouds/2
|
||||
"""
|
||||
dc_manager.subcloud_update(request, subcloud_id, request.DATA)
|
||||
|
||||
@rest_utils.ajax()
|
||||
def delete(self, request, subcloud_id):
|
||||
"""Delete a specific subcloud
|
||||
|
||||
DELETE http://localhost/api/dc_manager/subclouds/3
|
||||
"""
|
||||
dc_manager.subcloud_delete(request, subcloud_id)
|
||||
|
||||
|
||||
@urls.register
|
||||
class SubcloudConfig(generic.View):
|
||||
"""API for Subcloud configurations."""
|
||||
url_regex = \
|
||||
r'dc_manager/subclouds/(?P<subcloud_id>[^/]+)/generate-config/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, subcloud_id):
|
||||
"""Generate a config for a specific subcloud."""
|
||||
|
||||
response = dc_manager.subcloud_generate_config(request, subcloud_id,
|
||||
request.GET.dict())
|
||||
response = {'config': response}
|
||||
return rest_utils.CreatedResponse('/api/dc_manager/subclouds/',
|
||||
response)
|
||||
|
||||
|
||||
@urls.register
|
||||
class SubClouds(generic.View):
|
||||
"""API for Distributed Cloud Subclouds"""
|
||||
url_regex = r'dc_manager/subclouds/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get a list of subclouds"""
|
||||
result = dc_manager.subcloud_list(request)
|
||||
return {'items': [sc.to_dict() for sc in result]}
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def put(self, request):
|
||||
"""Create a Subcloud.
|
||||
|
||||
Create a subcloud using the parameters supplied in the POST
|
||||
application/json object.
|
||||
"""
|
||||
dc_manager.subcloud_create(request, request.DATA)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Summaries(generic.View):
|
||||
"""API for Distributed Cloud Alarm Summaries"""
|
||||
url_regex = r'dc_manager/alarm_summaries/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get a list of summaries"""
|
||||
result = dc_manager.alarm_summary_list(request)
|
||||
return {'items': [s.to_dict() for s in result]}
|
38
cgcs_dashboard/api/rest/sysinv.py
Executable file
38
cgcs_dashboard/api/rest/sysinv.py
Executable file
@ -0,0 +1,38 @@
|
||||
#
|
||||
# Copyright (c) 2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
from django.views import generic
|
||||
|
||||
from openstack_dashboard.api.rest import urls
|
||||
from openstack_dashboard.api.rest import utils as rest_utils
|
||||
from openstack_dashboard.api import sysinv
|
||||
|
||||
|
||||
@urls.register
|
||||
class AlarmSummary(generic.View):
|
||||
"""API for retrieving alarm summaries."""
|
||||
url_regex = r'sysinv/alarm_summary/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get an alarm summary for the system"""
|
||||
include_suppress = request.GET.get('include_suppress', False)
|
||||
result = sysinv.alarm_summary_get(request, include_suppress)
|
||||
return result.to_dict()
|
||||
|
||||
|
||||
@urls.register
|
||||
class System(generic.View):
|
||||
"""API for retrieving the system."""
|
||||
url_regex = r'sysinv/system/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get the system entity"""
|
||||
result = sysinv.system_get(request)
|
||||
return result.to_dict()
|
2601
cgcs_dashboard/api/sysinv.py
Executable file
2601
cgcs_dashboard/api/sysinv.py
Executable file
File diff suppressed because it is too large
Load Diff
129
cgcs_dashboard/api/vim.py
Executable file
129
cgcs_dashboard/api/vim.py
Executable file
@ -0,0 +1,129 @@
|
||||
#
|
||||
# Copyright (c) 2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
import urlparse
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
|
||||
from nfv_client.openstack import sw_update
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
STRATEGY_SW_PATCH = 'sw-patch'
|
||||
STRATEGY_SW_UPGRADE = 'sw-upgrade'
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, url, token_id):
|
||||
self.url = url
|
||||
self.token_id = token_id
|
||||
|
||||
def get_strategy(self, strategy_name):
|
||||
return sw_update.get_strategies(self.token_id, self.url, strategy_name)
|
||||
|
||||
def create_strategy(
|
||||
self, strategy_name, controller_apply_type, storage_apply_type,
|
||||
swift_apply_type, compute_apply_type, max_parallel_compute_hosts,
|
||||
default_instance_action, alarm_restrictions):
|
||||
return sw_update.create_strategy(
|
||||
self.token_id, self.url, strategy_name, controller_apply_type,
|
||||
storage_apply_type,
|
||||
swift_apply_type, compute_apply_type, max_parallel_compute_hosts,
|
||||
default_instance_action, alarm_restrictions)
|
||||
|
||||
def delete_strategy(self, strategy_name, force):
|
||||
return sw_update.delete_strategy(self.token_id, self.url,
|
||||
strategy_name, force)
|
||||
|
||||
def apply_strategy(self, strategy_name, stage_id):
|
||||
return sw_update.apply_strategy(self.token_id, self.url, strategy_name,
|
||||
stage_id)
|
||||
|
||||
def abort_strategy(self, strategy_name, stage_id):
|
||||
return sw_update.abort_strategy(self.token_id, self.url, strategy_name,
|
||||
stage_id)
|
||||
|
||||
|
||||
def _sw_update_client(request):
|
||||
o = urlparse.urlparse(base.url_for(request, 'nfv'))
|
||||
url = "://".join((o.scheme, o.netloc))
|
||||
return Client(url, token_id=request.user.token.id)
|
||||
|
||||
|
||||
def get_strategy(request, strategy_name):
|
||||
strategy = _sw_update_client(request).get_strategy(strategy_name)
|
||||
return strategy
|
||||
|
||||
|
||||
def create_strategy(
|
||||
request, strategy_name, controller_apply_type, storage_apply_type,
|
||||
swift_apply_type, compute_apply_type, max_parallel_compute_hosts,
|
||||
default_instance_action, alarm_restrictions):
|
||||
strategy = _sw_update_client(request).create_strategy(
|
||||
strategy_name, controller_apply_type, storage_apply_type,
|
||||
swift_apply_type, compute_apply_type, max_parallel_compute_hosts,
|
||||
default_instance_action, alarm_restrictions)
|
||||
return strategy
|
||||
|
||||
|
||||
def delete_strategy(request, strategy_name, force=False):
|
||||
response = _sw_update_client(request).delete_strategy(strategy_name, force)
|
||||
return response
|
||||
|
||||
|
||||
def apply_strategy(request, strategy_name, stage_id=None):
|
||||
response = _sw_update_client(request).apply_strategy(strategy_name,
|
||||
stage_id)
|
||||
return response
|
||||
|
||||
|
||||
def abort_strategy(request, strategy_name, stage_id=None):
|
||||
response = _sw_update_client(request).abort_strategy(strategy_name,
|
||||
stage_id)
|
||||
return response
|
||||
|
||||
|
||||
def get_stages(request, strategy_name):
|
||||
strategy = _sw_update_client(request).get_strategy(strategy_name)
|
||||
if not strategy:
|
||||
return []
|
||||
stages = []
|
||||
for stage in strategy.build_phase.stages:
|
||||
phase = strategy.build_phase
|
||||
phase.stages = None
|
||||
stage.phase = phase
|
||||
stages.append(stage)
|
||||
for stage in strategy.apply_phase.stages:
|
||||
phase = strategy.apply_phase
|
||||
phase.stages = None
|
||||
stage.phase = phase
|
||||
stages.append(stage)
|
||||
for stage in strategy.abort_phase.stages:
|
||||
phase = strategy.abort_phase
|
||||
phase.stages = None
|
||||
stage.phase = phase
|
||||
stages.append(stage)
|
||||
|
||||
return stages
|
||||
|
||||
|
||||
def get_stage(request, strategy_name, phase_name, stage_id):
|
||||
stages = get_stages(request, strategy_name)
|
||||
for stage in stages:
|
||||
if stage.phase.phase_name == phase_name and \
|
||||
str(stage.stage_id) == str(stage_id):
|
||||
return stage
|
||||
|
||||
|
||||
def get_step(request, strategy_name, phase_name, stage_id, step_id):
|
||||
stage = get_stage(request, strategy_name, phase_name, stage_id)
|
||||
for step in stage.steps:
|
||||
if str(step.step_id) == str(step_id):
|
||||
return step
|
0
cgcs_dashboard/dashboards/__init__.py
Normal file
0
cgcs_dashboard/dashboards/__init__.py
Normal file
0
cgcs_dashboard/dashboards/admin/__init__.py
Normal file
0
cgcs_dashboard/dashboards/admin/__init__.py
Normal file
0
cgcs_dashboard/dashboards/admin/fault_management/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/fault_management/__init__.py
Executable file
50
cgcs_dashboard/dashboards/admin/fault_management/panel.py
Executable file
50
cgcs_dashboard/dashboards/admin/fault_management/panel.py
Executable file
@ -0,0 +1,50 @@
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2013-2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
import horizon
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.dashboards.admin import dashboard
|
||||
|
||||
|
||||
class FaultManagement(horizon.Panel):
|
||||
name = _("Fault Management")
|
||||
slug = 'fault_management'
|
||||
permissions = ('openstack.services.platform',)
|
||||
|
||||
def allowed(self, context):
|
||||
if not base.is_service_enabled(context['request'], 'platform'):
|
||||
return False
|
||||
else:
|
||||
return super(FaultManagement, self).allowed(context)
|
||||
|
||||
def nav(self, context):
|
||||
if not base.is_service_enabled(context['request'], 'platform'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
dashboard.Admin.register(FaultManagement)
|
284
cgcs_dashboard/dashboards/admin/fault_management/tables.py
Executable file
284
cgcs_dashboard/dashboards/admin/fault_management/tables.py
Executable file
@ -0,0 +1,284 @@
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
|
||||
|
||||
from django.utils.html import escape as escape_html
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from horizon.utils import filters as utils_filters
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import sysinv
|
||||
|
||||
SUPPRESSION_STATUS_CHOICES = (
|
||||
("suppressed", False),
|
||||
("unsuppressed", True),
|
||||
("None", True),
|
||||
)
|
||||
SUPPRESSION_STATUS_DISPLAY_CHOICES = (
|
||||
("suppressed", pgettext_lazy("Indicates this type of alarm \
|
||||
is suppressed", u"suppressed")),
|
||||
("unsuppressed", pgettext_lazy("Indicates this type of alarm \
|
||||
is unsuppressed", u"unsuppressed")),
|
||||
("None", pgettext_lazy("Indicates an event type", u"None")),
|
||||
)
|
||||
|
||||
|
||||
class AlarmsLimitAction(tables.LimitAction):
|
||||
verbose_name = _("Alarms")
|
||||
|
||||
|
||||
class AlarmFilterAction(tables.FixedWithQueryFilter):
|
||||
def __init__(self, **kwargs):
|
||||
super(AlarmFilterAction, self).__init__(**kwargs)
|
||||
|
||||
self.filter_choices = [
|
||||
(
|
||||
(sysinv.FM_SUPPRESS_SHOW, _("Show Suppressed"), True),
|
||||
(sysinv.FM_SUPPRESS_HIDE, _('Hide Suppressed'), True)
|
||||
)
|
||||
]
|
||||
self.default_value = sysinv.FM_SUPPRESS_HIDE
|
||||
|
||||
self.disabled_choices = ['enabled']
|
||||
|
||||
|
||||
class AlarmsTable(tables.DataTable):
|
||||
alarm_id = tables.Column('alarm_id',
|
||||
link="horizon:admin:fault_management:detail",
|
||||
verbose_name=_('Alarm ID'))
|
||||
reason_text = tables.Column('reason_text',
|
||||
verbose_name=_('Reason Text'))
|
||||
entity_instance_id = tables.Column('entity_instance_id',
|
||||
verbose_name=_('Entity Instance ID'))
|
||||
suppression_status = \
|
||||
tables.Column('suppression_status',
|
||||
verbose_name=_('Suppression Status'),
|
||||
status=True,
|
||||
status_choices=SUPPRESSION_STATUS_CHOICES,
|
||||
display_choices=SUPPRESSION_STATUS_DISPLAY_CHOICES)
|
||||
severity = tables.Column('severity',
|
||||
verbose_name=_('Severity'))
|
||||
timestamp = tables.Column('timestamp',
|
||||
attrs={'data-type': 'timestamp'},
|
||||
filters=(utils_filters.parse_isotime,),
|
||||
verbose_name=_('Timestamp'))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj.uuid
|
||||
|
||||
class Meta(object):
|
||||
name = "alarms"
|
||||
verbose_name = _("Active Alarms")
|
||||
status_columns = ["suppression_status"]
|
||||
limit_param = "alarm_limit"
|
||||
pagination_param = "alarm_marker"
|
||||
prev_pagination_param = 'prev_alarm_marker'
|
||||
table_actions = (AlarmFilterAction, AlarmsLimitAction)
|
||||
multi_select = False
|
||||
hidden_title = False
|
||||
|
||||
|
||||
class EventLogsLimitAction(tables.LimitAction):
|
||||
verbose_name = _("Events")
|
||||
|
||||
|
||||
class EventLogsFilterAction(tables.FixedWithQueryFilter):
|
||||
def __init__(self, **kwargs):
|
||||
super(EventLogsFilterAction, self).__init__(**kwargs)
|
||||
|
||||
self.filter_choices = [
|
||||
(
|
||||
(sysinv.FM_ALL, _("All Events"), True),
|
||||
(sysinv.FM_ALARM, _('Alarm Events'), True),
|
||||
(sysinv.FM_LOG, _('Log Events'), True),
|
||||
),
|
||||
(
|
||||
(sysinv.FM_SUPPRESS_SHOW, _("Show Suppressed"), True),
|
||||
(sysinv.FM_SUPPRESS_HIDE, _('Hide Suppressed'), True)
|
||||
)
|
||||
]
|
||||
self.default_value = sysinv.FM_ALL_SUPPRESS_HIDE
|
||||
|
||||
self.disabled_choices = ['enabled', 'enabled']
|
||||
|
||||
|
||||
class EventLogsTable(tables.DataTable):
|
||||
timestamp = tables.Column('timestamp',
|
||||
attrs={'data-type': 'timestamp'},
|
||||
filters=(utils_filters.parse_isotime,),
|
||||
verbose_name=_('Timestamp'))
|
||||
|
||||
state = tables.Column('state', verbose_name=_('State'))
|
||||
event_log_id = tables.Column('event_log_id',
|
||||
link="horizon:admin:fault_management:"
|
||||
"eventlogdetail",
|
||||
verbose_name=_('ID'))
|
||||
reason_text = tables.Column('reason_text', verbose_name=_('Reason Text'))
|
||||
entity_instance_id = tables.Column('entity_instance_id',
|
||||
verbose_name=_('Entity Instance ID'))
|
||||
suppression_status = \
|
||||
tables.Column('suppression_status',
|
||||
verbose_name=_('Suppression Status'),
|
||||
status=True,
|
||||
status_choices=SUPPRESSION_STATUS_CHOICES,
|
||||
display_choices=SUPPRESSION_STATUS_DISPLAY_CHOICES)
|
||||
severity = tables.Column('severity', verbose_name=_('Severity'))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj.uuid
|
||||
|
||||
class Meta(object):
|
||||
name = "eventLogs"
|
||||
verbose_name = _("Events")
|
||||
status_columns = ["suppression_status"]
|
||||
table_actions = (EventLogsFilterAction,
|
||||
EventLogsLimitAction,)
|
||||
limit_param = "event_limit"
|
||||
pagination_param = "event_marker"
|
||||
prev_pagination_param = 'prev_event_marker'
|
||||
multi_select = False
|
||||
|
||||
|
||||
class SuppressEvent(tables.BatchAction):
|
||||
name = "suppress"
|
||||
action_type = 'danger'
|
||||
icon = "remove"
|
||||
help_text = _("Events with selected Alarm ID will be suppressed.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Suppress Event",
|
||||
u"Suppress Event",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Events suppressed for Alarm ID",
|
||||
u"Events suppressed for Alarm ID",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, datum):
|
||||
"""Allow suppress action if Alarm ID is unsuppressed."""
|
||||
if datum.suppression_status == sysinv.FM_SUPPRESSED:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def action(self, request, obj_id):
|
||||
kwargs = {"suppression_status": sysinv.FM_SUPPRESSED}
|
||||
|
||||
try:
|
||||
api.sysinv.event_suppression_update(request, obj_id, **kwargs)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to set specified alarm type to \
|
||||
suppressed\'s.'))
|
||||
|
||||
|
||||
class UnsuppressEvent(tables.BatchAction):
|
||||
name = "unsuppress"
|
||||
action_type = 'danger'
|
||||
icon = "remove"
|
||||
help_text = _("Events with selected Alarm ID will be unsuppressed.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Unsuppress Event",
|
||||
u"Unsuppress Event",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Events unsuppressed for Alarm ID",
|
||||
u"Events unsuppressed for Alarm ID",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, datum):
|
||||
"""Allow unsuppress action if Alarm ID is suppressed."""
|
||||
if datum.suppression_status == sysinv.FM_UNSUPPRESSED:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def action(self, request, obj_id):
|
||||
kwargs = {"suppression_status": sysinv.FM_UNSUPPRESSED}
|
||||
|
||||
try:
|
||||
api.sysinv.event_suppression_update(request, obj_id, **kwargs)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to set specified alarm type to \
|
||||
unsuppressed\'s.'))
|
||||
|
||||
|
||||
class EventsSuppressionTable(tables.DataTable):
|
||||
# noinspection PyMethodParameters
|
||||
def description_inject(row_data):
|
||||
description = \
|
||||
escape_html(str(row_data.description)).replace("\n", "<br/>")
|
||||
description = description.replace("\t", " ")
|
||||
description = description.replace(" " * 4, " " * 4)
|
||||
description = description.replace(" " * 3, " " * 3)
|
||||
description = description.replace(" " * 2, " " * 2)
|
||||
return mark_safe(description)
|
||||
|
||||
alarm_id = tables.Column('alarm_id',
|
||||
verbose_name=_('Event ID'))
|
||||
description = tables.Column(description_inject,
|
||||
verbose_name=_('Description'))
|
||||
status = tables.Column('suppression_status',
|
||||
verbose_name=_('Status'))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
# return obj.alarm_id
|
||||
return obj.uuid
|
||||
|
||||
def get_object_display(self, datum):
|
||||
"""Returns a display name that identifies this object."""
|
||||
if hasattr(datum, 'alarm_id'):
|
||||
return datum.alarm_id
|
||||
return None
|
||||
|
||||
class Meta(object):
|
||||
name = "eventsSuppression"
|
||||
limit_param = "events_suppression_limit"
|
||||
pagination_param = "events_suppression_marker"
|
||||
prev_pagination_param = 'prev_event_ids_marker'
|
||||
verbose_name = _("Events Suppression")
|
||||
row_actions = (SuppressEvent, UnsuppressEvent,)
|
||||
multi_select = False
|
318
cgcs_dashboard/dashboards/admin/fault_management/tabs.py
Executable file
318
cgcs_dashboard/dashboards/admin/fault_management/tabs.py
Executable file
@ -0,0 +1,318 @@
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import sysinv
|
||||
from openstack_dashboard.dashboards.admin.fault_management import tables
|
||||
|
||||
ALARMS_SUPPRESSION_FILTER_GROUP = 0
|
||||
EVENT_SUPPRESSION_FILTER_GROUP = 1
|
||||
|
||||
|
||||
class ActiveAlarmsTab(tabs.TableTab):
|
||||
table_classes = (tables.AlarmsTable,)
|
||||
name = _("Active Alarms")
|
||||
slug = "alarms"
|
||||
template_name = 'admin/fault_management/_active_alarms.html'
|
||||
|
||||
def has_more_data(self, table):
|
||||
return self._more
|
||||
|
||||
def get_limit_count(self, table):
|
||||
return self._limit
|
||||
|
||||
def getTableFromName(self, tableName):
|
||||
table = self._tables[tableName]
|
||||
return table
|
||||
|
||||
def set_suppression_filter(self, disabled_status):
|
||||
alarmsTable = self.getTableFromName('alarms')
|
||||
filter_action = alarmsTable._meta._filter_action
|
||||
filter_action.set_disabled_filter_field_for_group(
|
||||
ALARMS_SUPPRESSION_FILTER_GROUP, disabled_status)
|
||||
filter_action.updateFromRequestDataToSession(self.request)
|
||||
|
||||
def get_context_data(self, request):
|
||||
context = super(ActiveAlarmsTab, self).get_context_data(request)
|
||||
|
||||
summary = api.sysinv.alarm_summary_get(
|
||||
self.request, include_suppress=False)
|
||||
context["total"] = summary.critical + summary.major + summary.minor \
|
||||
+ summary.warnings
|
||||
context["summary"] = summary
|
||||
|
||||
events_types = self.get_event_suppression_data()
|
||||
suppressed_events_types = len([etype for etype
|
||||
in events_types
|
||||
if etype.suppression_status ==
|
||||
'suppressed'])
|
||||
|
||||
alarms_table = self.getTableFromName('alarms')
|
||||
|
||||
suppress_filter = self.get_filters()
|
||||
suppress_filter_state = suppress_filter.get('suppression')
|
||||
|
||||
hidden_found = 'hidden' in alarms_table.columns["suppression_status"].\
|
||||
classes
|
||||
|
||||
if not hidden_found:
|
||||
if suppressed_events_types == 0:
|
||||
self.set_suppression_filter('disabled')
|
||||
alarms_table.columns["suppression_status"]\
|
||||
.classes.append('hidden')
|
||||
elif suppress_filter_state == sysinv.FM_SUPPRESS_HIDE:
|
||||
self.set_suppression_filter('enabled')
|
||||
alarms_table.columns["suppression_status"].classes\
|
||||
.append('hidden')
|
||||
else:
|
||||
if suppressed_events_types == 0:
|
||||
self.set_suppression_filter('disabled')
|
||||
else:
|
||||
self.set_suppression_filter('enabled')
|
||||
if suppress_filter_state == sysinv.FM_SUPPRESS_SHOW:
|
||||
alarms_table.columns["suppression_status"].classes\
|
||||
.remove('hidden')
|
||||
|
||||
return context
|
||||
|
||||
def get_filters(self, filters=None):
|
||||
|
||||
filters = filters or {}
|
||||
alarmsTable = self.getTableFromName('alarms')
|
||||
filter_action = alarmsTable._meta._filter_action
|
||||
filter_action.updateFromRequestDataToSession(self.request)
|
||||
filter_field = filter_action.get_filter_field(self.request)
|
||||
|
||||
if filter_field:
|
||||
suppression = filter_action.get_filter_field_for_group(0)
|
||||
filters["suppression"] = suppression
|
||||
|
||||
return filters
|
||||
|
||||
def get_alarms_data(self):
|
||||
search_opts = {}
|
||||
|
||||
# get retrieve parameters from request/session env
|
||||
marker = \
|
||||
self.request.GET.get(tables.AlarmsTable._meta.pagination_param,
|
||||
None)
|
||||
limit = \
|
||||
self.request.GET.get(tables.AlarmsTable._meta.limit_param,
|
||||
None)
|
||||
|
||||
search_opts = self.get_filters()
|
||||
search_opts.update({'marker': marker,
|
||||
'limit': limit,
|
||||
'paginate': True,
|
||||
'sort_key': 'severity,entity_instance_id',
|
||||
'sort_dir': 'asc'})
|
||||
|
||||
alarms = []
|
||||
try:
|
||||
if 'paginate' in search_opts:
|
||||
alarms, self._more = api.sysinv.alarm_list(
|
||||
self.request, search_opts=search_opts)
|
||||
else:
|
||||
alarms = api.sysinv.alarm_list(
|
||||
self.request, search_opts=search_opts)
|
||||
self._limit = limit
|
||||
except Exception:
|
||||
self._more = False
|
||||
self._limit = None
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve alarms list.'))
|
||||
return alarms
|
||||
|
||||
def get_event_suppression_data(self):
|
||||
event_types = []
|
||||
|
||||
try:
|
||||
if 'suppression_list' not in self.tab_group.kwargs:
|
||||
self.tab_group.kwargs['suppression_list'] = \
|
||||
api.sysinv.event_suppression_list(self.request)
|
||||
event_types = self.tab_group.kwargs['suppression_list']
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve event suppression table \ '
|
||||
'list.'))
|
||||
return event_types
|
||||
|
||||
|
||||
class EventLogTab(tabs.TableTab):
|
||||
table_classes = (tables.EventLogsTable,)
|
||||
name = _("Events")
|
||||
slug = "eventLogs"
|
||||
template_name = 'admin/fault_management/_summary.html'
|
||||
preload = False
|
||||
|
||||
def has_more_data(self, table):
|
||||
return self._more
|
||||
|
||||
def get_limit_count(self, table):
|
||||
return self._limit
|
||||
|
||||
def getTableFromName(self, tableName):
|
||||
table = self._tables[tableName]
|
||||
return table
|
||||
|
||||
def set_suppression_filter(self, disabled_status):
|
||||
alarmsTable = self.getTableFromName('eventLogs')
|
||||
filter_action = alarmsTable._meta._filter_action
|
||||
filter_action.set_disabled_filter_field_for_group(
|
||||
EVENT_SUPPRESSION_FILTER_GROUP, disabled_status)
|
||||
filter_action.updateFromRequestDataToSession(self.request)
|
||||
|
||||
def get_context_data(self, request):
|
||||
context = super(EventLogTab, self).get_context_data(request)
|
||||
|
||||
events_types = self.get_event_suppression_data()
|
||||
suppressed_events_types = len([etype for etype in events_types
|
||||
if etype.suppression_status ==
|
||||
'suppressed'])
|
||||
|
||||
event_log_table = self.getTableFromName('eventLogs')
|
||||
|
||||
filters = self.get_filters({'marker': None,
|
||||
'limit': None,
|
||||
'paginate': True})
|
||||
|
||||
suppress_filter_state = filters.get('suppression')
|
||||
|
||||
hidden_found = 'hidden' in event_log_table\
|
||||
.columns["suppression_status"].classes
|
||||
|
||||
if not hidden_found:
|
||||
if suppressed_events_types == 0:
|
||||
self.set_suppression_filter('disabled')
|
||||
event_log_table.columns["suppression_status"]\
|
||||
.classes.append('hidden')
|
||||
elif suppress_filter_state == sysinv.FM_SUPPRESS_HIDE:
|
||||
self.set_suppression_filter('enabled')
|
||||
event_log_table.columns["suppression_status"].\
|
||||
classes.append('hidden')
|
||||
else:
|
||||
if suppressed_events_types == 0:
|
||||
self.set_suppression_filter('disabled')
|
||||
else:
|
||||
self.set_suppression_filter('enabled')
|
||||
if suppress_filter_state == sysinv.FM_SUPPRESS_SHOW:
|
||||
event_log_table.columns["suppression_status"]\
|
||||
.classes.remove('hidden')
|
||||
|
||||
return context
|
||||
|
||||
def get_filters(self, filters):
|
||||
|
||||
eventLogsTable = self.getTableFromName('eventLogs')
|
||||
filter_action = eventLogsTable._meta._filter_action
|
||||
filter_action.updateFromRequestDataToSession(self.request)
|
||||
filter_field = filter_action.get_filter_field(self.request)
|
||||
|
||||
if filter_field:
|
||||
filters["evtType"] = filter_action.get_filter_field_for_group(0)
|
||||
filters["suppression"] = filter_action\
|
||||
.get_filter_field_for_group(1)
|
||||
|
||||
return filters
|
||||
|
||||
def get_eventLogs_data(self):
|
||||
|
||||
# get retrieve parameters from request/session env
|
||||
marker = \
|
||||
self.request.GET.get(tables.EventLogsTable._meta.pagination_param,
|
||||
None)
|
||||
limit = \
|
||||
self.request.GET.get(tables.EventLogsTable._meta.limit_param,
|
||||
None)
|
||||
search_opts = self.get_filters({'marker': marker,
|
||||
'limit': limit,
|
||||
'paginate': True})
|
||||
events = []
|
||||
|
||||
try:
|
||||
# now retrieve data from rest API
|
||||
events, self._more = \
|
||||
api.sysinv.event_log_list(self.request,
|
||||
search_opts=search_opts)
|
||||
self._limit = limit
|
||||
return events
|
||||
|
||||
except Exception:
|
||||
events = []
|
||||
self._more = False
|
||||
self._limit = None
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve Event Log list.'))
|
||||
|
||||
return events
|
||||
|
||||
def get_event_suppression_data(self):
|
||||
event_types = []
|
||||
|
||||
try:
|
||||
if 'suppression_list' not in self.tab_group.kwargs:
|
||||
self.tab_group.kwargs['suppression_list'] = \
|
||||
api.sysinv.event_suppression_list(self.request)
|
||||
event_types = self.tab_group.kwargs['suppression_list']
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve event suppression \
|
||||
table list.'))
|
||||
return event_types
|
||||
|
||||
|
||||
class EventsSuppressionTab(tabs.TableTab):
|
||||
table_classes = (tables.EventsSuppressionTable,)
|
||||
name = _("Events Suppression")
|
||||
slug = "eventsSuppression"
|
||||
template_name = 'admin/fault_management/_summary.html'
|
||||
preload = False
|
||||
|
||||
def get_eventsSuppression_data(self):
|
||||
event_suppression_list = []
|
||||
|
||||
try:
|
||||
if 'suppression_list' not in self.tab_group.kwargs:
|
||||
self.tab_group.kwargs['suppression_list'] = \
|
||||
api.sysinv.event_suppression_list(self.request)
|
||||
event_suppression_list = self.tab_group.kwargs['suppression_list']
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve event suppression \
|
||||
list\'s.'))
|
||||
|
||||
event_suppression_list.sort(key=lambda a: (a.alarm_id))
|
||||
|
||||
return event_suppression_list
|
||||
|
||||
|
||||
class AlarmsTabs(tabs.TabGroup):
|
||||
slug = "alarms_tabs"
|
||||
tabs = (ActiveAlarmsTab, EventLogTab, EventsSuppressionTab)
|
||||
sticky = True
|
@ -0,0 +1,26 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div id="active-alarm-stats" class="info details">
|
||||
<span>
|
||||
<strong>{% trans "Active Alarms" %}:</strong>
|
||||
<span class="badge {% if total != 0 %} badge-success{% endif %}">{{ total }}</span>
|
||||
</span>
|
||||
<span>
|
||||
<strong>{% trans "Critical" %}:</strong>
|
||||
<span class="badge{% if summary.critical != 0 %} badge-danger{% endif %}">{{ summary.critical }}</span>
|
||||
</span>
|
||||
<span>
|
||||
<strong>{% trans "Major" %}:</strong>
|
||||
<span class="badge{% if summary.major != 0 %} badge-danger{% endif %}">{{ summary.major }}</span>
|
||||
</span>
|
||||
<span>
|
||||
<strong>{% trans "Minor" %}:</strong>
|
||||
<span class="badge{% if summary.minor != 0 %} badge-warning{% endif %}">{{ summary.minor }}</span>
|
||||
</span>
|
||||
<span>
|
||||
<strong>{% trans "Warning" %}:</strong>
|
||||
<span class="badge{% if summary.warnings != 0 %} badge-success{% endif %}">{{ summary.warnings }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{ table.render }}
|
@ -0,0 +1,58 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% block title %}{% trans "Historical Alarm Details" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if history.event_log_id == '' or history.event_log_id == ' ' %}
|
||||
<h3> {{history.reason_text }} </h3>
|
||||
{% else %}
|
||||
<h3> {{history.state }} - {{history.event_log_id }} - {{history.reason_text }} </h3>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="info row-fluid detail">
|
||||
<h4>{% trans "Info" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
<dt>{% trans "Alarm UUID" %}</dt>
|
||||
<dd>{{ history.uuid }}</dd>
|
||||
{% if history.event_log_id != '' and history.event_log_id != ' ' %}
|
||||
<dt>{% trans "Alarm ID" %}</dt>
|
||||
<dd>{{ history.event_log_id }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Severity" %}</dt>
|
||||
<dd>{{ history.severity }}</dd>
|
||||
<dt>{% trans "Alarm State" %}</dt>
|
||||
<dd>{{ history.state }}</dd>
|
||||
<dt>{% trans "Alarm Type" %}</dt>
|
||||
<dd>{{ history.event_log_type }}</dd>
|
||||
<dt>{% trans "Timestamp" %}</dt>
|
||||
<dd>{{ history.timestamp|parse_isotime }}</dd>
|
||||
<dt>{% trans "Suppression" %}</dt>
|
||||
<dd>{{ history.suppression }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans "Entity Instance ID" %}</dt>
|
||||
<dd>{{ history.entity_instance_id }}</dd>
|
||||
{% if history.entity_type_id != '' and history.entity_type_id != ' ' %}
|
||||
<dt>{% trans "Entity Type ID" %}</dt>
|
||||
<dd>{{ history.entity_type_id }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Probable Cause" %}</dt>
|
||||
<dd>{{ history.probable_cause }}</dd>
|
||||
{% if history.proposed_repair_action != '' and history.proposed_repair_action != ' ' %}
|
||||
<dt>{% trans "Proposed Repair Action" %}</dt>
|
||||
<dd>{{ history.proposed_repair_action }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Service Affecting" %}</dt>
|
||||
<dd>{{ history.service_affecting }}</dd>
|
||||
{% if history.reason_text != '' and history.reason_text != ' ' %}
|
||||
<dt>{% trans "Reason" %}</dt>
|
||||
<dd>{{ history.reason_text }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,50 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% block title %}{% trans "Customer Log Details" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if log.event_log_id == '' or log.event_log_id == ' ' %}
|
||||
<h3> {{log.reason_text }} </h3>
|
||||
{% else %}
|
||||
<h3> {{log.event_log_id }} - {{log.reason_text }} </h3>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="info row-fluid detail">
|
||||
<h4>{% trans "Info" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
<dt>{% trans "Log UUID" %}</dt>
|
||||
<dd>{{ log.uuid }}</dd>
|
||||
{% if log.event_log_id != '' and log.event_log_id != ' ' %}
|
||||
<dt>{% trans "Log ID" %}</dt>
|
||||
<dd>{{ log.event_log_id }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Severity" %}</dt>
|
||||
<dd>{{ log.severity }}</dd>
|
||||
<dt>{% trans "Log Type" %}</dt>
|
||||
<dd>{{ log.event_log_type }}</dd>
|
||||
<dt>{% trans "Timestamp" %}</dt>
|
||||
<dd>{{ log.timestamp|parse_isotime }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans "Entity Instance ID" %}</dt>
|
||||
<dd>{{ log.entity_instance_id }}</dd>
|
||||
{% if log.entity_type_id != '' and log.entity_type_id != ' ' %}
|
||||
<dt>{% trans "Entity Type ID" %}</dt>
|
||||
<dd>{{ log.entity_type_id }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Probable Cause" %}</dt>
|
||||
<dd>{{ log.probable_cause }}</dd>
|
||||
<dt>{% trans "Service Affecting" %}</dt>
|
||||
<dd>{{ log.service_affecting }}</dd>
|
||||
{% if log.reason_text != '' and log.reason_text != ' ' %}
|
||||
<dt>{% trans "Reason" %}</dt>
|
||||
<dd>{{ log.reason_text }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,52 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% block title %}{% trans "Alarm Details" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h3> {{alarm.alarm_id }} - {{alarm.reason_text }}</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="info row-fluid detail">
|
||||
<h4>{% trans "Info" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
<dt>{% trans "Alarm UUID" %}</dt>
|
||||
<dd>{{ alarm.uuid }}</dd>
|
||||
<dt>{% trans "Alarm ID" %}</dt>
|
||||
<dd>{{ alarm.alarm_id }}</dd>
|
||||
<dt>{% trans "Severity" %}</dt>
|
||||
<dd>{{ alarm.severity }}</dd>
|
||||
<dt>{% trans "Alarm State" %}</dt>
|
||||
<dd>{{ alarm.alarm_state }}</dd>
|
||||
<dt>{% trans "Alarm Type" %}</dt>
|
||||
<dd>{{ alarm.alarm_type }}</dd>
|
||||
<dt>{% trans "Timestamp" %}</dt>
|
||||
<dd>{{ alarm.timestamp|parse_isotime }}</dd>
|
||||
<dt>{% trans "Suppression" %}</dt>
|
||||
<dd>{{ alarm.suppression }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans "Entity Instance ID" %}</dt>
|
||||
<dd>{{ alarm.entity_instance_id }}</dd>
|
||||
<dt>{% trans "Entity Type ID" %}</dt>
|
||||
<dd>{{ alarm.entity_type_id }}</dd>
|
||||
<dt>{% trans "Probable Cause" %}</dt>
|
||||
<dd>{{ alarm.probable_cause }}</dd>
|
||||
<dt>{% trans "Proposed Repair Action" %}</dt>
|
||||
<dd>{{ alarm.proposed_repair_action }}</dd>
|
||||
<dt>{% trans "Service Affecting" %}</dt>
|
||||
<dd>{{ alarm.service_affecting }}</dd>
|
||||
<dt>{% trans "Management Affecting" %}</dt>
|
||||
<dd>{{ alarm.mgmt_affecting }}</dd>
|
||||
{% if alarm.reason_text != '' and alarm.reason_text != ' ' %}
|
||||
<dt>{% trans "Reason" %}</dt>
|
||||
<dd>{{ alarm.reason_text }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -0,0 +1,29 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Fault Management" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Fault Management")%}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<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_stats = $('#active-alarm-stats');
|
||||
var $new_stats = $(html).find('#active-alarm-stats');
|
||||
if ($new_stats.html() != $old_stats.html()) {
|
||||
$old_stats.replaceWith($new_stats);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
38
cgcs_dashboard/dashboards/admin/fault_management/urls.py
Executable file
38
cgcs_dashboard/dashboards/admin/fault_management/urls.py
Executable file
@ -0,0 +1,38 @@
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
|
||||
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.dashboards.admin.fault_management import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^(?P<id>[^/]+)/detail/$',
|
||||
views.DetailView.as_view(), name='detail'),
|
||||
url(r'^(?P<id>[^/]+)/eventlogdetail/$',
|
||||
views.EventLogDetailView.as_view(), name='eventlogdetail'),
|
||||
url(r'^banner/$', views.BannerView.as_view(),
|
||||
name='banner')
|
||||
]
|
175
cgcs_dashboard/dashboards/admin/fault_management/views.py
Executable file
175
cgcs_dashboard/dashboards/admin/fault_management/views.py
Executable file
@ -0,0 +1,175 @@
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from horizon import views
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.fault_management import \
|
||||
tabs as project_tabs
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(tabs.TabbedTableView):
|
||||
tab_group_class = project_tabs.AlarmsTabs
|
||||
template_name = 'admin/fault_management/index.html'
|
||||
page_title = _("Fault Management")
|
||||
|
||||
|
||||
class DetailView(views.HorizonTemplateView):
|
||||
template_name = 'admin/fault_management/_detail_overview.html'
|
||||
page_title = 'Alarm Details'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
context["alarm"] = self.get_data()
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
if not hasattr(self, "_alarm"):
|
||||
alarm_uuid = self.kwargs['id']
|
||||
try:
|
||||
alarm = api.sysinv.alarm_get(self.request, alarm_uuid)
|
||||
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:fault_management:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'alarm "%s".') % alarm_uuid,
|
||||
redirect=redirect)
|
||||
|
||||
self._alarm = alarm
|
||||
return self._alarm
|
||||
|
||||
|
||||
class EventLogDetailView(views.HorizonTemplateView):
|
||||
# Strategy behind this event log detail view is to
|
||||
# first retrieve the event_log data, then examine the event's
|
||||
# state property, and from that, determine if it should use
|
||||
# the _detail_history.html (alarmhistory) template or
|
||||
# or use the _detail_log.html (customer log) template
|
||||
|
||||
def get_template_names(self):
|
||||
if self.type == "alarmhistory":
|
||||
template_name = 'admin/fault_management/_detail_history.html'
|
||||
else:
|
||||
template_name = 'admin/fault_management/_detail_log.html'
|
||||
return template_name
|
||||
|
||||
def _detectEventLogType(self):
|
||||
if hasattr(self, "type"):
|
||||
return self.type
|
||||
if not self._eventlog:
|
||||
raise Exception("Cannot determine Event Log type for "
|
||||
"EventLogDetailView. First retrieve "
|
||||
"Eventlog data")
|
||||
if self._eventlog.state == "log":
|
||||
self.type = "log"
|
||||
elif self._eventlog.state in ["set", "clear"]:
|
||||
self.type = "alarmhistory"
|
||||
else:
|
||||
raise Exception("Invalid state = '{}'. Cannot "
|
||||
"determine Event log type for "
|
||||
"event log".format(self._eventlog.state))
|
||||
return self.type
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventLogDetailView, self).get_context_data(**kwargs)
|
||||
data = self.get_data()
|
||||
if self.type == "alarmhistory":
|
||||
self.page_title = 'Historical Alarm Details'
|
||||
self.template_name = 'admin/fault_management/_detail_history.html'
|
||||
context["history"] = data
|
||||
else:
|
||||
self.page_title = 'Customer Log Detail'
|
||||
self.template_name = 'admin/fault_management/_detail_log.html'
|
||||
context["log"] = data
|
||||
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
if not hasattr(self, "_eventlog"):
|
||||
uuid = self.kwargs['id']
|
||||
try:
|
||||
self._eventlog = api.sysinv.event_log_get(self.request, uuid)
|
||||
self._detectEventLogType()
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:fault_management:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'event log "%s".') % uuid,
|
||||
redirect=redirect)
|
||||
return self._eventlog
|
||||
|
||||
|
||||
class BannerView(TemplateView):
|
||||
template_name = 'header/_alarm_banner.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TemplateView, self).get_context_data(**kwargs)
|
||||
|
||||
if not self.request.is_ajax():
|
||||
raise exceptions.NotFound()
|
||||
|
||||
if (not self.request.user.is_authenticated() or
|
||||
not self.request.user.is_superuser):
|
||||
context["alarmbanner"] = False
|
||||
elif 'dc_admin' in self.request.META.get('HTTP_REFERER'):
|
||||
summaries = self.get_subcloud_data()
|
||||
central_summary = self.get_data()
|
||||
summaries.append(central_summary)
|
||||
context["dc_admin"] = True
|
||||
context["alarmbanner"] = True
|
||||
context["OK"] = len(
|
||||
[s for s in summaries if s.status == 'OK'])
|
||||
context["degraded"] = len(
|
||||
[s for s in summaries if s.status == 'degraded'])
|
||||
context["critical"] = len(
|
||||
[s for s in summaries if s.status == 'critical'])
|
||||
context["disabled"] = len(
|
||||
[s for s in summaries if s.status == 'disabled'])
|
||||
elif api.base.is_TiS_region(self.request):
|
||||
context["summary"] = self.get_data()
|
||||
context["alarmbanner"] = True
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
summary = None
|
||||
try:
|
||||
summary = api.sysinv.alarm_summary_get(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve alarm summary.'))
|
||||
return summary
|
||||
|
||||
def get_subcloud_data(self):
|
||||
return api.dc_manager.alarm_summary_list(self.request)
|
0
cgcs_dashboard/dashboards/admin/host_topology/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/host_topology/__init__.py
Executable file
34
cgcs_dashboard/dashboards/admin/host_topology/panel.py
Executable file
34
cgcs_dashboard/dashboards/admin/host_topology/panel.py
Executable file
@ -0,0 +1,34 @@
|
||||
#
|
||||
# Copyright (c) 2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.dashboards.admin import dashboard
|
||||
|
||||
|
||||
class HostTopology(horizon.Panel):
|
||||
name = _("Provider Network Topology")
|
||||
slug = 'host_topology'
|
||||
permissions = ('openstack.services.platform',)
|
||||
|
||||
def allowed(self, context):
|
||||
if not base.is_service_enabled(context['request'], 'platform'):
|
||||
return False
|
||||
else:
|
||||
return super(HostTopology, self).allowed(context)
|
||||
|
||||
def nav(self, context):
|
||||
if not base.is_service_enabled(context['request'], 'platform'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
dashboard.Admin.register(HostTopology)
|
46
cgcs_dashboard/dashboards/admin/host_topology/tables.py
Executable file
46
cgcs_dashboard/dashboards/admin/host_topology/tables.py
Executable file
@ -0,0 +1,46 @@
|
||||
#
|
||||
# Copyright (c) 2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from openstack_dashboard.dashboards.admin.fault_management import \
|
||||
tables as fm_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.interfaces import \
|
||||
tables as if_tables
|
||||
from openstack_dashboard.dashboards.admin.providernets.providernets.ranges import \
|
||||
tables as sr_tables
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProviderNetworkRangeTable(sr_tables.ProviderNetworkRangeTable):
|
||||
class Meta(object):
|
||||
name = "provider_network_ranges"
|
||||
verbose_name = _("Segmentation Ranges")
|
||||
table_actions = ()
|
||||
row_actions = ()
|
||||
|
||||
|
||||
class AlarmsTable(fm_tables.AlarmsTable):
|
||||
class Meta(object):
|
||||
name = "alarms"
|
||||
verbose_name = _("Related Alarms")
|
||||
multi_select = False
|
||||
table_actions = []
|
||||
row_actions = []
|
||||
|
||||
|
||||
class InterfacesTable(if_tables.InterfacesTable):
|
||||
class Meta(object):
|
||||
name = "interfaces"
|
||||
verbose_name = _("Interfaces")
|
||||
multi_select = False
|
||||
table_actions = []
|
||||
row_actions = []
|
128
cgcs_dashboard/dashboards/admin/host_topology/tabs.py
Executable file
128
cgcs_dashboard/dashboards/admin/host_topology/tabs.py
Executable file
@ -0,0 +1,128 @@
|
||||
# Copyright (c) 2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy # noqa
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.host_topology import \
|
||||
tables as tables
|
||||
from openstack_dashboard.dashboards.admin.inventory import \
|
||||
tabs as i_tabs
|
||||
from openstack_dashboard.dashboards.admin.providernets.providernets import \
|
||||
tables as pn_tables
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_alarms_for_entity(alarms, entity_str):
|
||||
matched = []
|
||||
for alarm in alarms:
|
||||
for id in alarm.entity_instance_id.split('.'):
|
||||
try:
|
||||
if entity_str == id.split('=')[1]:
|
||||
matched.append(alarm)
|
||||
except Exception:
|
||||
# malformed entity_instance_id
|
||||
pass
|
||||
return matched
|
||||
|
||||
|
||||
class AlarmsTab(tabs.TableTab):
|
||||
table_classes = (tables.AlarmsTable,)
|
||||
name = _("Related Alarms")
|
||||
slug = "alarm_tab"
|
||||
template_name = ("admin/host_topology/detail/_detail_alarms.html")
|
||||
|
||||
def get_alarms_data(self):
|
||||
entity = self.tab_group.kwargs.get('host')
|
||||
if not entity:
|
||||
entity = self.tab_group.kwargs.get('providernet')
|
||||
return entity.alarms
|
||||
|
||||
|
||||
class InterfacesTab(i_tabs.InterfacesTab):
|
||||
table_classes = (tables.InterfacesTable, )
|
||||
|
||||
|
||||
class HostDetailTabs(tabs.TabGroup):
|
||||
slug = "host_details"
|
||||
tabs = (i_tabs.OverviewTab, AlarmsTab, InterfacesTab,
|
||||
i_tabs.LldpTab)
|
||||
sticky = True
|
||||
|
||||
|
||||
class OverviewTab(tabs.TableTab):
|
||||
table_classes = (tables.ProviderNetworkRangeTable,
|
||||
pn_tables.ProviderNetworkTenantNetworkTable)
|
||||
template_name = 'admin/host_topology/detail/providernet.html'
|
||||
name = "Provider Network Detail"
|
||||
slug = 'providernet_details_overview'
|
||||
failure_url = reverse_lazy('horizon:admin:host_topology:index')
|
||||
|
||||
def _get_tenant_list(self):
|
||||
if not hasattr(self, "_tenants"):
|
||||
try:
|
||||
tenants, has_more = api.keystone.tenant_list(self.request)
|
||||
except Exception:
|
||||
tenants = []
|
||||
msg = _('Unable to retrieve instance project information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
tenant_dict = OrderedDict([(t.id, t) for t in tenants])
|
||||
self._tenants = tenant_dict
|
||||
return self._tenants
|
||||
|
||||
def get_tenant_networks_data(self):
|
||||
try:
|
||||
providernet_id = self.tab_group.kwargs['providernet_id']
|
||||
networks = api.neutron.provider_network_list_tenant_networks(
|
||||
self.request, providernet_id=providernet_id)
|
||||
except Exception:
|
||||
networks = []
|
||||
msg = _('Tenant network list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return networks
|
||||
|
||||
def get_provider_network_ranges_data(self):
|
||||
try:
|
||||
providernet_id = self.tab_group.kwargs['providernet_id']
|
||||
ranges = api.neutron.provider_network_range_list(
|
||||
self.request, providernet_id=providernet_id)
|
||||
except Exception:
|
||||
ranges = []
|
||||
msg = _('Segmentation id range list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
tenant_dict = self._get_tenant_list()
|
||||
for r in ranges:
|
||||
r.set_id_as_name_if_empty()
|
||||
# Set tenant name
|
||||
tenant = tenant_dict.get(r.tenant_id, None)
|
||||
r.tenant_name = getattr(tenant, 'name', None)
|
||||
return ranges
|
||||
|
||||
def get_context_data(self, request):
|
||||
context = super(OverviewTab, self).get_context_data(request)
|
||||
try:
|
||||
context['providernet'] = self.tab_group.kwargs['providernet']
|
||||
context['nova_providernet'] = \
|
||||
self.tab_group.kwargs['nova_providernet']
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve providernet details.'))
|
||||
return context
|
||||
|
||||
|
||||
class ProvidernetDetailTabs(tabs.TabGroup):
|
||||
slug = "pnet_details"
|
||||
tabs = (OverviewTab, AlarmsTab)
|
||||
sticky = True
|
@ -0,0 +1,261 @@
|
||||
{% load i18n %}
|
||||
<style type="text/css">
|
||||
svg#topology_canvas {
|
||||
font-family: sans-serif;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
svg#topology_canvas .selected {
|
||||
filter: url(#select-highlight);
|
||||
}
|
||||
svg#topology_canvas .network_alarmed {
|
||||
fill: url(#diagonalHatch2);
|
||||
}
|
||||
svg#topology_canvas .network-rect {
|
||||
cursor: pointer;
|
||||
}
|
||||
svg#topology_canvas .network-rect.nourl {
|
||||
cursor: auto;
|
||||
}
|
||||
svg#topology_canvas .network-name {
|
||||
font-size: 14px;
|
||||
text-anchor: middle;
|
||||
}
|
||||
svg#topology_canvas .network-cidr {
|
||||
font-size: 11px;
|
||||
text-anchor: end;
|
||||
}
|
||||
svg#topology_canvas text.network-type {
|
||||
font-family: FontAwesome;
|
||||
text-anchor: end;
|
||||
}
|
||||
svg#topology_canvas .port_alarmed {
|
||||
stroke-dasharray: 13,5;
|
||||
}
|
||||
svg#topology_canvas .port_text {
|
||||
font-size: 9px;
|
||||
fill: #666;
|
||||
}
|
||||
svg#topology_canvas .port_text.left {
|
||||
text-anchor: end;
|
||||
}
|
||||
svg#topology_canvas .lldp_text {
|
||||
font-size: 9px;
|
||||
fill: #666;
|
||||
}
|
||||
svg#topology_canvas .lldp_text.right {
|
||||
text-anchor: end;
|
||||
}
|
||||
svg#topology_canvas .base_bg_normal {
|
||||
fill: #333;
|
||||
}
|
||||
svg#topology_canvas .loading_bg_normal {
|
||||
fill: #555;
|
||||
}
|
||||
svg#topology_canvas .active {
|
||||
fill: #45B035;
|
||||
}
|
||||
svg#topology_canvas .icon polygon {
|
||||
fill: #333;
|
||||
}
|
||||
svg#topology_canvas .router_normal .frame,
|
||||
svg#topology_canvas .host .frame {
|
||||
fill: #f3f3f3;
|
||||
stroke: #333;
|
||||
stroke-width: 4;
|
||||
cursor: pointer;
|
||||
}
|
||||
svg#topology_canvas .router_normal .icon_bg,
|
||||
svg#topology_canvas .host .icon_bg {
|
||||
fill: #fff;
|
||||
stroke: #333;
|
||||
stroke-width: 4;
|
||||
}
|
||||
svg#topology_canvas .host .unlocked {
|
||||
visibility: hidden;
|
||||
stroke-width: 0;
|
||||
}
|
||||
svg#topology_canvas .host .status {
|
||||
fill: #222;
|
||||
stroke-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
svg#topology_canvas .host .status circle {
|
||||
fill: #F3F3F3;
|
||||
}
|
||||
svg .alarm {
|
||||
visibility: hidden;
|
||||
stroke: #333;
|
||||
stroke-width: 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
svg .level_4 {
|
||||
visibility: visible;
|
||||
fill: #CC0000;
|
||||
stroke-width: 2;
|
||||
}
|
||||
svg .level_3 {
|
||||
visibility: visible;
|
||||
fill: #CC0000;
|
||||
stroke-width: 2;
|
||||
}
|
||||
svg .level_2 {
|
||||
visibility: visible;
|
||||
fill: #FFCC33;
|
||||
stroke-width: 2;
|
||||
}
|
||||
svg .level_1 {
|
||||
visibility: visible;
|
||||
fill: #336699;
|
||||
stroke-width: 2;
|
||||
}
|
||||
svg#topology_canvas .host .texts_bg {
|
||||
fill: #222;
|
||||
cursor: pointer;
|
||||
}
|
||||
svg#topology_canvas .host .texts .name {
|
||||
text-anchor: middle;
|
||||
fill: #333;
|
||||
font-size: 12px;
|
||||
}
|
||||
svg#topology_canvas .host .texts .type {
|
||||
text-anchor: middle;
|
||||
fill: #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
svg#topology_canvas .host .instance_bg {
|
||||
fill: #333;
|
||||
}
|
||||
svg#topology_canvas g.loading .active {
|
||||
fill: #555;
|
||||
}
|
||||
svg#topology_canvas g.loading .icon polygon {
|
||||
fill: #555;
|
||||
}
|
||||
svg#topology_canvas g.loading .instance_bg {
|
||||
fill: #555;
|
||||
}
|
||||
svg#topology_canvas g.loading .host .frame {
|
||||
stroke: #555;
|
||||
}
|
||||
svg#topology_canvas g.loading .host .name {
|
||||
fill: #999;
|
||||
}
|
||||
svg#topology_canvas g.loading .host .texts_bg {
|
||||
fill: #222;
|
||||
}
|
||||
svg#topology_canvas g.loading .host .icon_bg {
|
||||
stroke: #555;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="top_row">
|
||||
<div id="lists_container">
|
||||
<h4>Compute Hosts</h4>
|
||||
<input id="host_list_search" class="form-control" type="text" placeholder="Search Compute Hosts" />
|
||||
<div id="host_list" class="list-group">
|
||||
</div>
|
||||
<h4>Provider Networks</h4>
|
||||
<input id="network_list_search" class="form-control" type="text" placeholder="Search Provider Networks" />
|
||||
<div id="network_list" class="list-group">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="canvas_container">
|
||||
<svg id="topology_canvas">
|
||||
<g id="zoom_container" transform="translate(50,25)scale(1)" >
|
||||
<defs>
|
||||
|
||||
<pattern id="diagonalHatch2" patternUnits="userSpaceOnUse" width="4" height="4">
|
||||
<path d="M-1,1 l2,-2
|
||||
M0,4 l4,-4
|
||||
M3,5 l2,-2" style="stroke:white; stroke-width:1" />
|
||||
</pattern>
|
||||
|
||||
<pattern id="device_normal_bg" patternUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||
<g>
|
||||
<rect width="20" height="20" class="base_bg_normal"></rect>
|
||||
</g>
|
||||
</pattern>
|
||||
<pattern id="device_normal_bg_loading" patternUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||
<g>
|
||||
<rect width="20" height="20" class="loading_bg_normal"></rect>
|
||||
<path d='M0 20L20 0ZM22 18L18 22ZM-2 2L2 -2Z' stroke-linecap="square" stroke='rgba(0, 0, 0, 0.3)' stroke-width="7"></path>
|
||||
</g>
|
||||
<animate attributeName="x" attributeType="XML" begin="0s" dur="0.5s" from="0" to="-20" repeatCount="indefinite"></animate>
|
||||
</pattern>
|
||||
|
||||
<filter x="0" y="0" width="1" height="1" id="text_bg">
|
||||
<feFlood flood-color="white"/>
|
||||
<feComposite in="SourceGraphic"/>
|
||||
</filter>
|
||||
|
||||
<filter id="select-highlight" width="200%" height="200%" x="-50%" y="-50%" filterRes="1000">
|
||||
<feOffset in="SourceGraphic" dx="0" dy="0" result="offset"></feOffset>
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComponentTransfer result="offsetmorph">
|
||||
<feFuncA type="table" tableValues="0 .05 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1"/>
|
||||
</feComponentTransfer>
|
||||
<feFlood flood-color="#51a1e7"></feFlood>
|
||||
<feComposite operator="in" in2="offsetmorph" result="stroke"></feComposite>
|
||||
<feGaussianBlur stdDeviation="4" result="offsetblur"></feGaussianBlur>
|
||||
<feFlood flood-color="#51a1e7"></feFlood>
|
||||
<feComposite operator="in" in2="offsetblur" result="blur"></feComposite>
|
||||
<feMerge>
|
||||
<feMergeNode in="blur"></feMergeNode>
|
||||
<feMergeNode in="stroke"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
|
||||
</defs>
|
||||
</g>
|
||||
</svg>
|
||||
<svg id="topology_template" display="none">
|
||||
<g class="host_body">
|
||||
<g class="connections"></g>
|
||||
<rect class="frame" x="0" y="0" rx="5" ry="5" width="70" height="50"></rect>
|
||||
<g class="texts">
|
||||
<rect class="texts_bg" x="1.5" y="32" width="67" height="17"></rect>
|
||||
<text x="35" y="22" class="name">instance</text>
|
||||
</g>
|
||||
<g class="alarm" transform="translate(20,0)">
|
||||
<circle cx="0" cy="0" r="12"></circle>
|
||||
<g transform="translate(-5,-6.5)">
|
||||
<rect x="4" y="-2" fill="#000000" width="2" height="10"></rect>
|
||||
<circle fill="#000000" cx="5" cy="14" r="1.3"></circle>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(14,-13)">
|
||||
<g class="status" transform="translate(0,0)">
|
||||
<circle fill="#FFFFFF" stroke-width="5" cx="22" cy="4" r="10" stroke="#222" fill="#F3F3F3"></circle>
|
||||
<rect height="22" width="33" y="5" x="5" stroke-width="0" fill="#222"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g class="network_container_normal">
|
||||
<rect rx="9" ry="9" width="17" height="500" style="fill: #8541B5;" class="network-rect"></rect>
|
||||
<rect rx="9" ry="9" width="17" height="500" pointer-events="none" style="fill: url(#diagonalHatch2);" class="network-rect-hash"></rect>
|
||||
<text x="4" y="-5" class="network-name" transform="rotate(0 0 0)" pointer-events="none">Network</text>
|
||||
<g class="alarm" transform="translate(8.5,13)">
|
||||
<circle cx="0" cy="0" r="12"></circle>
|
||||
<g transform="translate(-5,-6.5)">
|
||||
<rect x="4" y="-2" fill="#000000" width="2" height="10"></rect>
|
||||
<circle fill="#000000" cx="5" cy="14" r="1.3"></circle>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<svg class="list_alarm">
|
||||
<g class="alarm" transform="translate(13,13)">
|
||||
<circle cx="0" cy="0" r="12"></circle>
|
||||
<g transform="translate(-5,-6.5)">
|
||||
<rect x="4" y="-2" fill="#000000" width="2" height="10"></rect>
|
||||
<circle fill="#000000" cx="5" cy="14" r="1.3"></circle>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,9 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block main %}
|
||||
{% autoescape off %}
|
||||
<div id="alarmss">
|
||||
{{ alarms_table.render }}
|
||||
</div>
|
||||
{% endautoescape %}
|
||||
{% endblock %}
|
@ -0,0 +1,13 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<div class="info row-fluid detail">
|
||||
{% include "admin/providernets/providernets/_detail_overview.html" %}
|
||||
<hr>
|
||||
<div id="ranges">
|
||||
{{ provider_network_ranges_table.render }}
|
||||
</div>
|
||||
<div id="tenant_networks">
|
||||
{{ tenant_networks_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,16 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
|
||||
{% if host %}
|
||||
<h4>Selected Entity: <a href="{% url 'horizon:admin:inventory:detail' host.id %}" >{{host.hostname}}</a></h4>
|
||||
{% else %}
|
||||
<h4>Selected Entity: <a href="{% url 'horizon:admin:providernets:providernets:detail' providernet.id %}" >{{providernet.name}}</a></h4>
|
||||
{% endif %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,40 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Provider Network Topology" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<noscript>
|
||||
{% trans "This pane needs javascript support." %}
|
||||
</noscript>
|
||||
|
||||
<div id="hostTopologyNavi">
|
||||
<div id="toggleLabels">
|
||||
<label class="btn btn-default">
|
||||
<input type="checkbox" name="options" id="label_toggle">
|
||||
<span class="fa fa-th-large"></span>
|
||||
{% trans "Hide Labels" %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="hostTopologyContainer">
|
||||
<div id="topologyCanvasContainer">
|
||||
<div class="nodata">{% blocktrans %}There are no hosts or provider networks to display.{% endblocktrans %}</div>
|
||||
{% include "admin/host_topology/_svg_element.html" %}
|
||||
<div id="detail_view">{% blocktrans %}Select a host or providernet to view its details. The current view can be moved by clicking and dragging.{% endblocktrans %}</div>
|
||||
</div>
|
||||
<span data-hosttopology="{% url 'horizon:admin:host_topology:json' %}" id="hosttopology"></span>
|
||||
<div id="topologyMessages"></div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
if (typeof horizon.host_topology !== 'undefined') {
|
||||
horizon.host_topology.init();
|
||||
} else {
|
||||
addHorizonLoadEvent(function () {
|
||||
horizon.host_topology.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
23
cgcs_dashboard/dashboards/admin/host_topology/urls.py
Executable file
23
cgcs_dashboard/dashboards/admin/host_topology/urls.py
Executable file
@ -0,0 +1,23 @@
|
||||
#
|
||||
# Copyright (c) 2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from openstack_dashboard.dashboards.admin.host_topology import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.HostTopologyView.as_view(), name='index'),
|
||||
url(r'^json$', views.JSONView.as_view(), name='json'),
|
||||
|
||||
url(r'^(?P<host_id>[^/]+)/host/$',
|
||||
views.HostDetailView.as_view(), name='host'),
|
||||
url(r'^(?P<providernet_id>[^/]+)/providernet/$',
|
||||
views.ProvidernetDetailView.as_view(), name='providernet')
|
||||
]
|
235
cgcs_dashboard/dashboards/admin/host_topology/views.py
Executable file
235
cgcs_dashboard/dashboards/admin/host_topology/views.py
Executable file
@ -0,0 +1,235 @@
|
||||
#
|
||||
# Copyright (c) 2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.http import HttpResponse # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import View # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from horizon.utils import settings as utils_settings
|
||||
from horizon import views
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.host_topology import\
|
||||
tabs as topology_tabs
|
||||
from openstack_dashboard.dashboards.admin.inventory import\
|
||||
views as i_views
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HostDetailView(i_views.DetailView):
|
||||
tab_group_class = topology_tabs.HostDetailTabs
|
||||
template_name = 'admin/host_topology/detail/tabbed_detail.html'
|
||||
|
||||
def get_data(self):
|
||||
if not hasattr(self, "_host"):
|
||||
try:
|
||||
host = super(HostDetailView, self).get_data()
|
||||
|
||||
alarms = []
|
||||
try:
|
||||
alarms = api.sysinv.alarm_list(self.request)
|
||||
except Exception as ex:
|
||||
exceptions.handle(ex)
|
||||
# Filter out unrelated alarms
|
||||
host.alarms = topology_tabs.get_alarms_for_entity(
|
||||
alarms, host.hostname)
|
||||
# Sort alarms by severity
|
||||
host.alarms.sort(key=lambda a: (a.severity))
|
||||
|
||||
except Exception as ex:
|
||||
LOG.exception(ex)
|
||||
raise
|
||||
self._host = host
|
||||
return self._host
|
||||
|
||||
|
||||
class ProvidernetDetailView(tabs.TabbedTableView):
|
||||
tab_group_class = topology_tabs.ProvidernetDetailTabs
|
||||
template_name = 'admin/host_topology/detail/tabbed_detail.html'
|
||||
failure_url = reverse_lazy('horizon:admin:host_topology:index')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ProvidernetDetailView, self).get_context_data(**kwargs)
|
||||
context["providernet"] = self.get_data()
|
||||
context["nova_providernet"] = self.get_nova_data()
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
if not hasattr(self, "_providernet"):
|
||||
try:
|
||||
providernet_id = self.kwargs['providernet_id']
|
||||
providernet = api.neutron.provider_network_get(
|
||||
self.request, providernet_id)
|
||||
providernet.set_id_as_name_if_empty(length=0)
|
||||
|
||||
alarms = api.sysinv.alarm_list(self.request)
|
||||
# Filter out unrelated alarms
|
||||
providernet.alarms = \
|
||||
topology_tabs.get_alarms_for_entity(alarms,
|
||||
providernet.id) + \
|
||||
topology_tabs.get_alarms_for_entity(alarms,
|
||||
providernet.name)
|
||||
# Sort alarms by severity
|
||||
providernet.alarms.sort(key=lambda a: (a.severity))
|
||||
|
||||
except Exception:
|
||||
redirect = self.failure_url
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'provider network "%s".') % providernet_id,
|
||||
redirect=redirect)
|
||||
self._providernet = providernet
|
||||
return self._providernet
|
||||
|
||||
def get_nova_data(self):
|
||||
if not hasattr(self, "_providernet_nova"):
|
||||
try:
|
||||
providernet_id = self.kwargs['providernet_id']
|
||||
providernet_nova = api.nova.provider_network_get(
|
||||
self.request, providernet_id)
|
||||
except Exception:
|
||||
redirect = self.failure_url
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'provider network "%s".') % providernet_id,
|
||||
redirect=redirect)
|
||||
|
||||
self._providernet_nova = providernet_nova
|
||||
return self._providernet_nova
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
providernet = self.get_data()
|
||||
nova_providernet = self.get_nova_data()
|
||||
return self.tab_group_class(
|
||||
request, providernet=providernet,
|
||||
nova_providernet=nova_providernet, **kwargs)
|
||||
|
||||
|
||||
class HostTopologyView(views.HorizonTemplateView):
|
||||
template_name = 'admin/host_topology/index.html'
|
||||
page_title = _("Provider Network Topology")
|
||||
|
||||
def _has_permission(self, policy):
|
||||
has_permission = True
|
||||
# policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None)
|
||||
policy_check = utils_settings.import_setting("POLICY_CHECK_FUNCTION")
|
||||
|
||||
if policy_check:
|
||||
has_permission = policy_check(policy, self.request)
|
||||
|
||||
return has_permission
|
||||
|
||||
def _quota_exceeded(self, quota):
|
||||
usages = quotas.tenant_quota_usages(self.request)
|
||||
available = usages[quota]['available']
|
||||
return available <= 0
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(HostTopologyView, self).get_context_data(**kwargs)
|
||||
|
||||
context['launch_instance_allowed'] = self._has_permission(
|
||||
(("compute", "compute:create"),))
|
||||
context['instance_quota_exceeded'] = self._quota_exceeded('instances')
|
||||
return context
|
||||
|
||||
|
||||
class JSONView(View):
|
||||
|
||||
@property
|
||||
def is_router_enabled(self):
|
||||
network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
|
||||
return network_config.get('enable_router', True)
|
||||
|
||||
def add_resource_url(self, view, resources):
|
||||
tenant_id = self.request.user.tenant_id
|
||||
for resource in resources:
|
||||
if (resource.get('tenant_id')
|
||||
and tenant_id != resource.get('tenant_id')):
|
||||
continue
|
||||
resource['url'] = reverse(view, None, [str(resource['id'])])
|
||||
|
||||
def _check_router_external_port(self, ports, router_id, network_id):
|
||||
for port in ports:
|
||||
if (port['network_id'] == network_id
|
||||
and port['device_id'] == router_id):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_alarms(self, request):
|
||||
alarms = []
|
||||
try:
|
||||
alarms = api.sysinv.alarm_list(request)
|
||||
except Exception as ex:
|
||||
exceptions.handle(ex)
|
||||
|
||||
data = [a.to_dict() for a in alarms]
|
||||
return data
|
||||
|
||||
def _get_hosts(self, request):
|
||||
hosts = []
|
||||
try:
|
||||
hosts = api.sysinv.host_list(request)
|
||||
except Exception as ex:
|
||||
exceptions.handle(ex)
|
||||
|
||||
data = []
|
||||
for host in hosts:
|
||||
host_data = host.to_dict()
|
||||
try:
|
||||
host_data['ports'] = [
|
||||
p.to_dict() for p in
|
||||
api.sysinv.host_port_list(request, host.uuid)]
|
||||
|
||||
host_data['interfaces'] = [
|
||||
i.to_dict() for i in
|
||||
api.sysinv.host_interface_list(request, host.uuid)]
|
||||
|
||||
host_data['lldpneighbours'] = [
|
||||
n.to_dict() for n in
|
||||
api.sysinv.host_lldpneighbour_list(request, host.uuid)]
|
||||
|
||||
# Set the value for neighbours field for each port in the host.
|
||||
# This will be referenced in Interfaces table
|
||||
for p in host_data['ports']:
|
||||
p['neighbours'] = \
|
||||
[n['port_identifier'] for n in
|
||||
host_data['lldpneighbours']
|
||||
if n['port_uuid'] == p['uuid']]
|
||||
|
||||
except Exception as ex:
|
||||
exceptions.handle(ex)
|
||||
|
||||
data.append(host_data)
|
||||
return data
|
||||
|
||||
def _get_pnets(self, request):
|
||||
pnets = []
|
||||
try:
|
||||
pnets = api.neutron.provider_network_list(request)
|
||||
except Exception as ex:
|
||||
exceptions.handle(ex)
|
||||
data = [p.to_dict() for p in pnets]
|
||||
return data
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
data = {'hosts': self._get_hosts(request),
|
||||
'networks': self._get_pnets(request),
|
||||
'alarms': self._get_alarms(request), }
|
||||
json_string = json.dumps(data, ensure_ascii=False)
|
||||
return HttpResponse(json_string, content_type='text/json')
|
0
cgcs_dashboard/dashboards/admin/inventory/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/cpu_functions/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/cpu_functions/__init__.py
Executable file
400
cgcs_dashboard/dashboards/admin/inventory/cpu_functions/forms.py
Normal file
400
cgcs_dashboard/dashboards/admin/inventory/cpu_functions/forms.py
Normal file
@ -0,0 +1,400 @@
|
||||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from cgtsclient import exc
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import shortcuts
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateCpuFunctions(forms.SelfHandlingForm):
|
||||
host = forms.CharField(label=_("host"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
host_id = forms.CharField(label=_("host_id"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
platform = forms.CharField(
|
||||
label=_("------------------------ Function ------------------------"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
|
||||
platform_processor0 = forms.DynamicIntegerField(
|
||||
label=_("# of Platform Physical Cores on Processor 0:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
platform_processor1 = forms.DynamicIntegerField(
|
||||
label=_("# of Platform Physical Cores on Processor 1:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
platform_processor2 = forms.DynamicIntegerField(
|
||||
label=_("# of Platform Physical Cores on Processor 2:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
platform_processor3 = forms.DynamicIntegerField(
|
||||
label=_("# of Platform Physical Cores on Processor 3:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
|
||||
vswitch = forms.CharField(
|
||||
label=_("------------------------ Function ------------------------"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
num_cores_on_processor0 = forms.DynamicIntegerField(
|
||||
label=_("# of vSwitch Physical Cores on Processor 0:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_cores_on_processor1 = forms.DynamicIntegerField(
|
||||
label=_("# of vSwitch Physical Cores on Processor 1:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_cores_on_processor2 = forms.DynamicIntegerField(
|
||||
label=_("# of vSwitch Physical Cores on Processor 2:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_cores_on_processor3 = forms.DynamicIntegerField(
|
||||
label=_("# of vSwitch Physical Cores on Processor 3:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
|
||||
shared_vcpu = forms.CharField(
|
||||
label=_("------------------------ Function ------------------------"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
num_shared_on_processor0 = forms.DynamicIntegerField(
|
||||
label=_("# of Shared Physical Cores on Processor 0:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_shared_on_processor1 = forms.DynamicIntegerField(
|
||||
label=_("# of Shared Physical Cores on Processor 1:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_shared_on_processor2 = forms.DynamicIntegerField(
|
||||
label=_("# of Shared Physical Cores on Processor 2:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_shared_on_processor3 = forms.DynamicIntegerField(
|
||||
label=_("# of Shared Physical Cores on Processor 3:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UpdateCpuFunctions, self).__init__(*args, **kwargs)
|
||||
|
||||
self.host = kwargs['initial']['host']
|
||||
|
||||
if kwargs['initial']['platform_processor0'] == 99: # No Processor
|
||||
self.fields[
|
||||
'platform_processor0'].widget = forms.widgets.HiddenInput()
|
||||
else:
|
||||
avail_socket_cores = self.host.physical_cores.get(0, 0)
|
||||
self.fields['platform_processor0'].set_max_value(
|
||||
avail_socket_cores)
|
||||
self.fields[
|
||||
'platform_processor0'].help_text = \
|
||||
"Processor 0 has %s physical cores." % avail_socket_cores
|
||||
|
||||
if kwargs['initial']['platform_processor1'] == 99: # No Processor
|
||||
self.fields[
|
||||
'platform_processor1'].widget = forms.widgets.HiddenInput()
|
||||
else:
|
||||
avail_socket_cores = self.host.physical_cores.get(1, 0)
|
||||
self.fields['platform_processor1'].set_max_value(
|
||||
avail_socket_cores)
|
||||
self.fields[
|
||||
'platform_processor1'].help_text =\
|
||||
"Processor 1 has %s physical cores." % avail_socket_cores
|
||||
|
||||
if kwargs['initial']['platform_processor2'] == 99: # No Processor
|
||||
self.fields[
|
||||
'platform_processor2'].widget = forms.widgets.HiddenInput()
|
||||
else:
|
||||
avail_socket_cores = self.host.physical_cores.get(2, 0)
|
||||
self.fields['platform_processor2'].set_max_value(
|
||||
avail_socket_cores)
|
||||
self.fields[
|
||||
'platform_processor2'].help_text = \
|
||||
"Processor 2 has %s physical cores." % avail_socket_cores
|
||||
|
||||
if kwargs['initial']['platform_processor3'] == 99: # No Processor
|
||||
self.fields[
|
||||
'platform_processor3'].widget = forms.widgets.HiddenInput()
|
||||
else:
|
||||
avail_socket_cores = self.host.physical_cores.get(3, 0)
|
||||
self.fields['platform_processor3'].set_max_value(
|
||||
avail_socket_cores)
|
||||
self.fields[
|
||||
'platform_processor3'].help_text = \
|
||||
"Processor 3 has %s physical cores." % avail_socket_cores
|
||||
|
||||
if 'compute' not in self.host.subfunctions:
|
||||
self.fields['vswitch'].widget = forms.widgets.HiddenInput()
|
||||
self.fields[
|
||||
'num_cores_on_processor0'].widget = forms.widgets.HiddenInput()
|
||||
self.fields[
|
||||
'num_cores_on_processor1'].widget = forms.widgets.HiddenInput()
|
||||
self.fields[
|
||||
'num_cores_on_processor2'].widget = forms.widgets.HiddenInput()
|
||||
self.fields[
|
||||
'num_cores_on_processor3'].widget = forms.widgets.HiddenInput()
|
||||
else:
|
||||
if kwargs['initial'][
|
||||
'num_cores_on_processor0'] == 99: # No Processor
|
||||
self.fields[
|
||||
'num_cores_on_processor0'].widget =\
|
||||
forms.widgets.HiddenInput()
|
||||
else:
|
||||
avail_socket_cores = self.host.physical_cores.get(0, 0)
|
||||
self.fields[
|
||||
'num_cores_on_processor0'].set_max_value(
|
||||
avail_socket_cores)
|
||||
self.fields[
|
||||
'num_cores_on_processor0'].help_text = \
|
||||
"Processor 0 has %s physical cores." % avail_socket_cores
|
||||
|
||||
if kwargs['initial'][
|
||||
'num_cores_on_processor1'] == 99: # No Processor
|
||||
self.fields[
|
||||
'num_cores_on_processor1'].widget =\
|
||||
forms.widgets.HiddenInput()
|
||||
else:
|
||||
avail_socket_cores = self.host.physical_cores.get(1, 0)
|
||||
self.fields[
|
||||
'num_cores_on_processor1'].set_max_value(
|
||||
avail_socket_cores)
|
||||
self.fields[
|
||||
'num_cores_on_processor1'].help_text =\
|
||||
"Processor 1 has %s physical cores." % avail_socket_cores
|
||||
|
||||
if kwargs['initial'][
|
||||
'num_cores_on_processor2'] == 99: # No Processor
|
||||
self.fields[
|
||||
'num_cores_on_processor2'].widget =\
|
||||
forms.widgets.HiddenInput()
|
||||
else:
|
||||
avail_socket_cores = self.host.physical_cores.get(2, 0)
|
||||
self.fields[
|
||||
'num_cores_on_processor2'].set_max_value(
|
||||
avail_socket_cores)
|
||||
self.fields[
|
||||
'num_cores_on_processor2'].help_text =\
|
||||
"Processor 2 has %s physical cores." % avail_socket_cores
|
||||
|
||||
if kwargs['initial'][
|
||||
'num_cores_on_processor3'] == 99: # No Processor
|
||||
self.fields[
|
||||
'num_cores_on_processor3'].widget =\
|
||||
forms.widgets.HiddenInput()
|
||||
else:
|
||||
avail_socket_cores = self.host.physical_cores.get(3, 0)
|
||||
self.fields[
|
||||
'num_cores_on_processor3'].set_max_value(
|
||||
avail_socket_cores)
|
||||
self.fields[
|
||||
'num_cores_on_processor3'].help_text =\
|
||||
"Processor 3 has %s physical cores." % avail_socket_cores
|
||||
|
||||
for s in range(0, 4):
|
||||
processor = 'num_shared_on_processor{0}'.format(s)
|
||||
if ('compute' not in self.host.subfunctions or
|
||||
kwargs['initial'][processor] == 99): # No Processor
|
||||
self.fields[processor].widget = forms.widgets.HiddenInput()
|
||||
else:
|
||||
self.fields[processor].set_max_value(1)
|
||||
self.fields[processor].help_text =\
|
||||
"Each processor can have at most one shared core."
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(UpdateCpuFunctions, self).clean()
|
||||
|
||||
# host_id = cleaned_data.get('host_id')
|
||||
|
||||
try:
|
||||
cleaned_data['platform_processor0'] = str(
|
||||
cleaned_data['platform_processor0'])
|
||||
cleaned_data['platform_processor1'] = str(
|
||||
cleaned_data['platform_processor1'])
|
||||
cleaned_data['platform_processor2'] = str(
|
||||
cleaned_data['platform_processor2'])
|
||||
cleaned_data['platform_processor3'] = str(
|
||||
cleaned_data['platform_processor3'])
|
||||
|
||||
cleaned_data['num_cores_on_processor0'] = str(
|
||||
cleaned_data['num_cores_on_processor0'])
|
||||
cleaned_data['num_cores_on_processor1'] = str(
|
||||
cleaned_data['num_cores_on_processor1'])
|
||||
cleaned_data['num_cores_on_processor2'] = str(
|
||||
cleaned_data['num_cores_on_processor2'])
|
||||
cleaned_data['num_cores_on_processor3'] = str(
|
||||
cleaned_data['num_cores_on_processor3'])
|
||||
|
||||
cleaned_data['num_shared_on_processor0'] = str(
|
||||
cleaned_data['num_shared_on_processor0'])
|
||||
cleaned_data['num_shared_on_processor1'] = str(
|
||||
cleaned_data['num_shared_on_processor1'])
|
||||
cleaned_data['num_shared_on_processor2'] = str(
|
||||
cleaned_data['num_shared_on_processor2'])
|
||||
cleaned_data['num_shared_on_processor3'] = str(
|
||||
cleaned_data['num_shared_on_processor3'])
|
||||
|
||||
num_platform_cores = {}
|
||||
num_platform_cores[0] = cleaned_data.get('platform_processor0',
|
||||
'None')
|
||||
num_platform_cores[1] = cleaned_data.get('platform_processor1',
|
||||
'None')
|
||||
num_platform_cores[2] = cleaned_data.get('platform_processor2',
|
||||
'None')
|
||||
num_platform_cores[3] = cleaned_data.get('platform_processor3',
|
||||
'None')
|
||||
|
||||
num_vswitch_cores = {}
|
||||
num_vswitch_cores[0] = cleaned_data.get('num_cores_on_processor0',
|
||||
'None')
|
||||
num_vswitch_cores[1] = cleaned_data.get('num_cores_on_processor1',
|
||||
'None')
|
||||
num_vswitch_cores[2] = cleaned_data.get('num_cores_on_processor2',
|
||||
'None')
|
||||
num_vswitch_cores[3] = cleaned_data.get('num_cores_on_processor3',
|
||||
'None')
|
||||
|
||||
num_shared_on_map = {}
|
||||
num_shared_on_map[0] = cleaned_data.get('num_shared_on_processor0',
|
||||
'None')
|
||||
num_shared_on_map[1] = cleaned_data.get('num_shared_on_processor1',
|
||||
'None')
|
||||
num_shared_on_map[2] = cleaned_data.get('num_shared_on_processor2',
|
||||
'None')
|
||||
num_shared_on_map[3] = cleaned_data.get('num_shared_on_processor3',
|
||||
'None')
|
||||
|
||||
if ('None' in num_platform_cores.values() or
|
||||
'None' in num_vswitch_cores.values() or
|
||||
'None' in num_shared_on_map.values()):
|
||||
raise forms.ValidationError(_("Invalid entry."))
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
raise forms.ValidationError(_("Invalid entry."))
|
||||
|
||||
# Since only vswitch is allowed to be modified
|
||||
cleaned_data['function'] = 'vswitch'
|
||||
# NOTE: shared_vcpu can be changed
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
host_id = data['host_id']
|
||||
del data['host_id']
|
||||
del data['host']
|
||||
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_id)
|
||||
cpudata = {}
|
||||
sharedcpudata = {}
|
||||
platformcpudata = {}
|
||||
for key, val in data.iteritems():
|
||||
if 'num_cores_on_processor' in key or 'function' in key:
|
||||
if key not in self.fields:
|
||||
cpudata[key] = val
|
||||
elif not type(self.fields[key].widget) is\
|
||||
forms.widgets.HiddenInput:
|
||||
cpudata[key] = val
|
||||
if 'platform_processor' in key:
|
||||
update_key = 'num_cores_on_processor' + key[-1:]
|
||||
if key not in self.fields:
|
||||
platformcpudata[update_key] = val
|
||||
elif not type(self.fields[key].widget) is\
|
||||
forms.widgets.HiddenInput:
|
||||
platformcpudata[update_key] = val
|
||||
if 'num_shared_on_processor' in key:
|
||||
key2 = key.replace('shared', 'cores')
|
||||
if key not in self.fields:
|
||||
sharedcpudata[key2] = val
|
||||
elif not type(self.fields[key].widget) is\
|
||||
forms.widgets.HiddenInput:
|
||||
sharedcpudata[key2] = val
|
||||
|
||||
sharedcpudata['function'] = 'shared'
|
||||
platformcpudata['function'] = 'platform'
|
||||
|
||||
api.sysinv.host_cpus_modify(request, host.uuid,
|
||||
platformcpudata,
|
||||
cpudata,
|
||||
sharedcpudata)
|
||||
msg = _('CPU Assignments were successfully updated.')
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return self.host.cpus
|
||||
except exc.ClientException as ce:
|
||||
# Display REST API error message on UI
|
||||
messages.error(request, ce)
|
||||
LOG.error(ce)
|
||||
|
||||
# Redirect to failure pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
msg = _('Failed to update CPU Assignments.')
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class AddCpuProfile(forms.SelfHandlingForm):
|
||||
host_id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
profilename = forms.CharField(label=_("Cpu Profile Name"),
|
||||
required=True)
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddCpuProfile, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddCpuProfile, self).clean()
|
||||
# host_id = cleaned_data.get('host_id')
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
|
||||
cpuProfileName = data['profilename']
|
||||
try:
|
||||
cpuProfile = api.sysinv.host_cpuprofile_create(request, **data)
|
||||
msg = _(
|
||||
'Cpu Profile "%s" was successfully created.') % cpuProfileName
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return cpuProfile
|
||||
except exc.ClientException as ce:
|
||||
# Display REST API error message on UI
|
||||
messages.error(request, ce)
|
||||
LOG.error(ce)
|
||||
|
||||
# Redirect to failure pg
|
||||
redirect = reverse(self.failure_url, args=[data['host_id']])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception:
|
||||
msg = _('Failed to create cpu profile "%s".') % cpuProfileName
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url,
|
||||
args=[data['host_id']])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
@ -0,0 +1,81 @@
|
||||
#
|
||||
# Copyright (c) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.cpu_functions \
|
||||
import utils as cpufunctions_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EditCpuFunctions(tables.LinkAction):
|
||||
name = "editCpuFunctions"
|
||||
verbose_name = _("Edit CPU Assignments")
|
||||
url = "horizon:admin:inventory:editcpufunctions"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, cpufunction=None):
|
||||
host = self.table.kwargs['host']
|
||||
return host._administrative == 'locked'
|
||||
|
||||
|
||||
class CreateCpuProfile(tables.LinkAction):
|
||||
name = "createCpuProfile"
|
||||
verbose_name = _("Create Cpu Profile")
|
||||
url = "horizon:admin:inventory:addcpuprofile"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, cpufunction=None):
|
||||
return not api.sysinv.is_system_mode_simplex(request)
|
||||
|
||||
|
||||
def get_function_name(datum):
|
||||
if datum.allocated_function in cpufunctions_utils.CPU_TYPE_FORMATS:
|
||||
return cpufunctions_utils.CPU_TYPE_FORMATS[datum.allocated_function]
|
||||
return "unknown({})".format(datum.allocated_function)
|
||||
|
||||
|
||||
def get_socket_cores(datum):
|
||||
template_name = 'admin/inventory/cpu_functions/' \
|
||||
'_cpufunction_processorcores.html'
|
||||
context = {"cpufunction": datum}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
class CpuFunctionsTable(tables.DataTable):
|
||||
allocated_function = tables.Column(get_function_name,
|
||||
verbose_name=_('Function'))
|
||||
|
||||
socket_cores = tables.Column(get_socket_cores,
|
||||
verbose_name=_('Processor Logical Cores'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.allocated_function)
|
||||
|
||||
class Meta(object):
|
||||
name = "cpufunctions"
|
||||
verbose_name = _("CPU Assignments")
|
||||
multi_select = False
|
||||
table_actions = (CreateCpuProfile, EditCpuFunctions,)
|
||||
row_actions = ()
|
238
cgcs_dashboard/dashboards/admin/inventory/cpu_functions/utils.py
Executable file
238
cgcs_dashboard/dashboards/admin/inventory/cpu_functions/utils.py
Executable file
@ -0,0 +1,238 @@
|
||||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
PLATFORM_CPU_TYPE = "Platform"
|
||||
VSWITCH_CPU_TYPE = "Vswitch"
|
||||
SHARED_CPU_TYPE = "Shared"
|
||||
VMS_CPU_TYPE = "VMs"
|
||||
NONE_CPU_TYPE = "None"
|
||||
|
||||
CPU_TYPE_LIST = [PLATFORM_CPU_TYPE, VSWITCH_CPU_TYPE,
|
||||
SHARED_CPU_TYPE, VMS_CPU_TYPE,
|
||||
NONE_CPU_TYPE]
|
||||
|
||||
|
||||
PLATFORM_CPU_TYPE_FORMAT = _("Platform")
|
||||
VSWITCH_CPU_TYPE_FORMAT = _("vSwitch")
|
||||
SHARED_CPU_TYPE_FORMAT = _("Shared")
|
||||
VMS_CPU_TYPE_FORMAT = _("VMs")
|
||||
NONE_CPU_TYPE_FORMAT = _("None")
|
||||
|
||||
CPU_TYPE_FORMATS = {PLATFORM_CPU_TYPE: PLATFORM_CPU_TYPE_FORMAT,
|
||||
VSWITCH_CPU_TYPE: VSWITCH_CPU_TYPE_FORMAT,
|
||||
SHARED_CPU_TYPE: SHARED_CPU_TYPE_FORMAT,
|
||||
VMS_CPU_TYPE: VMS_CPU_TYPE_FORMAT,
|
||||
NONE_CPU_TYPE: NONE_CPU_TYPE_FORMAT}
|
||||
|
||||
|
||||
class CpuFunction(object):
|
||||
def __init__(self, function):
|
||||
self.allocated_function = function
|
||||
self.socket_cores = {}
|
||||
self.socket_cores_number = {}
|
||||
|
||||
|
||||
class CpuProfile(object):
|
||||
class CpuConfigure(object):
|
||||
def __init__(self):
|
||||
self.platform = 0
|
||||
self.vswitch = 0
|
||||
self.shared = 0
|
||||
self.vms = 0
|
||||
self.numa_node = 0
|
||||
|
||||
# cpus is a list of icpu sorted by numa_node, core and thread
|
||||
# if not sorted, provide nodes list so it can be sorted here
|
||||
def __init__(self, cpus, nodes=None):
|
||||
if nodes:
|
||||
cpus = CpuProfile.sort_cpu_by_numa_node(cpus, nodes)
|
||||
|
||||
cores = []
|
||||
|
||||
self.number_of_cpu = 0
|
||||
self.cores_per_cpu = 0
|
||||
self.hyper_thread = False
|
||||
self.processors = []
|
||||
cur_processor = None
|
||||
|
||||
for cpu in cpus:
|
||||
key = '{0}-{1}'.format(cpu.numa_node, cpu.core)
|
||||
if key not in cores:
|
||||
cores.append(key)
|
||||
else:
|
||||
self.hyper_thread = True
|
||||
continue
|
||||
|
||||
if cur_processor is None \
|
||||
or cur_processor.numa_node != cpu.numa_node:
|
||||
cur_processor = CpuProfile.CpuConfigure()
|
||||
cur_processor.numa_node = cpu.numa_node
|
||||
self.processors.append(cur_processor)
|
||||
|
||||
if cpu.allocated_function == PLATFORM_CPU_TYPE:
|
||||
cur_processor.platform += 1
|
||||
elif cpu.allocated_function == VSWITCH_CPU_TYPE:
|
||||
cur_processor.vswitch += 1
|
||||
elif cpu.allocated_function == SHARED_CPU_TYPE:
|
||||
cur_processor.shared += 1
|
||||
elif cpu.allocated_function == VMS_CPU_TYPE:
|
||||
cur_processor.vms += 1
|
||||
|
||||
self.cores_per_cpu = len(cores)
|
||||
self.number_of_cpu = len(self.processors)
|
||||
|
||||
@staticmethod
|
||||
def sort_cpu_by_numa_node(cpus, nodes):
|
||||
newlist = []
|
||||
for node in nodes:
|
||||
for cpu in cpus:
|
||||
if cpu.numa_node == node.numa_node:
|
||||
newlist.append(cpu)
|
||||
return newlist
|
||||
|
||||
|
||||
class HostCpuProfile(CpuProfile):
|
||||
def __init__(self, personality, cpus, nodes=None):
|
||||
super(HostCpuProfile, self).__init__(cpus, nodes)
|
||||
self.personality = personality
|
||||
|
||||
# see if a cpu profile is applicable to this host
|
||||
def profile_applicable(self, profile):
|
||||
if self.number_of_cpu == profile.number_of_cpu and \
|
||||
self.cores_per_cpu == profile.cores_per_cpu:
|
||||
return self.check_profile_core_functions(profile)
|
||||
else:
|
||||
return False
|
||||
|
||||
return not True
|
||||
|
||||
def check_profile_core_functions(self, profile):
|
||||
platform_cores = 0
|
||||
vswitch_cores = 0
|
||||
shared_cores = 0
|
||||
vm_cores = 0
|
||||
for cpu in profile.processors:
|
||||
platform_cores += cpu.platform
|
||||
vswitch_cores += cpu.vswitch
|
||||
shared_cores += cpu.shared
|
||||
vm_cores += cpu.vms
|
||||
|
||||
result = True
|
||||
if platform_cores == 0:
|
||||
result = False
|
||||
elif 'compute' in self.personality and vswitch_cores == 0:
|
||||
result = False
|
||||
elif 'compute' in self.personality and vm_cores == 0:
|
||||
result = False
|
||||
return result
|
||||
|
||||
|
||||
def compress_range(c_list):
|
||||
c_list.append(999)
|
||||
c_list.sort()
|
||||
c_sep = ""
|
||||
c_item = ""
|
||||
c_str = ""
|
||||
pn = 0
|
||||
for n in c_list:
|
||||
if not c_item:
|
||||
c_item = "%s" % n
|
||||
else:
|
||||
if n > (pn + 1):
|
||||
if int(pn) == int(c_item):
|
||||
c_str = "%s%s%s" % (c_str, c_sep, c_item)
|
||||
else:
|
||||
c_str = "%s%s%s-%s" % (c_str, c_sep, c_item, pn)
|
||||
c_sep = ","
|
||||
c_item = "%s" % n
|
||||
pn = n
|
||||
return c_str
|
||||
|
||||
|
||||
def restructure_host_cpu_data(host):
|
||||
host.core_assignment = []
|
||||
if host.cpus:
|
||||
host.cpu_model = host.cpus[0].cpu_model
|
||||
host.sockets = len(host.nodes)
|
||||
host.hyperthreading = "No"
|
||||
host.physical_cores = {}
|
||||
|
||||
core_assignment = {}
|
||||
number_of_cores = {}
|
||||
|
||||
for cpu in host.cpus:
|
||||
if cpu.numa_node not in host.physical_cores:
|
||||
host.physical_cores[cpu.numa_node] = 0
|
||||
if cpu.thread == 0:
|
||||
host.physical_cores[cpu.numa_node] += 1
|
||||
elif cpu.thread > 0:
|
||||
host.hyperthreading = "Yes"
|
||||
|
||||
if cpu.allocated_function is None:
|
||||
cpu.allocated_function = NONE_CPU_TYPE
|
||||
|
||||
if cpu.allocated_function not in core_assignment:
|
||||
core_assignment[cpu.allocated_function] = {}
|
||||
number_of_cores[cpu.allocated_function] = {}
|
||||
if cpu.numa_node not in core_assignment[cpu.allocated_function]:
|
||||
core_assignment[cpu.allocated_function][cpu.numa_node] = [
|
||||
int(cpu.cpu)]
|
||||
number_of_cores[cpu.allocated_function][cpu.numa_node] = 1
|
||||
else:
|
||||
core_assignment[cpu.allocated_function][cpu.numa_node].append(
|
||||
int(cpu.cpu))
|
||||
number_of_cores[cpu.allocated_function][cpu.numa_node] += 1
|
||||
|
||||
for f in CPU_TYPE_LIST:
|
||||
cpufunction = CpuFunction(f)
|
||||
if f in core_assignment:
|
||||
host.core_assignment.append(cpufunction)
|
||||
for s, cores in core_assignment[f].items():
|
||||
cpufunction.socket_cores[s] = compress_range(cores)
|
||||
cpufunction.socket_cores_number[s] = number_of_cores[f][s]
|
||||
else:
|
||||
if (f == PLATFORM_CPU_TYPE or
|
||||
(hasattr(host, 'subfunctions') and
|
||||
'compute' in host.subfunctions)):
|
||||
if f != NONE_CPU_TYPE:
|
||||
host.core_assignment.append(cpufunction)
|
||||
for s in range(0, len(host.nodes)):
|
||||
cpufunction.socket_cores[s] = ""
|
||||
cpufunction.socket_cores_number[s] = 0
|
||||
|
||||
|
||||
def check_core_functions(personality, icpus):
|
||||
platform_cores = 0
|
||||
vswitch_cores = 0
|
||||
shared_vcpu_cores = 0
|
||||
vm_cores = 0
|
||||
for cpu in icpus:
|
||||
allocated_function = cpu.allocated_function
|
||||
if allocated_function == PLATFORM_CPU_TYPE:
|
||||
platform_cores += 1
|
||||
elif allocated_function == VSWITCH_CPU_TYPE:
|
||||
vswitch_cores += 1
|
||||
elif allocated_function == SHARED_CPU_TYPE:
|
||||
shared_vcpu_cores += 1
|
||||
elif allocated_function == VMS_CPU_TYPE:
|
||||
vm_cores += 1
|
||||
|
||||
# No limiations for shared_vcpu cores
|
||||
error_string = ""
|
||||
if platform_cores == 0:
|
||||
error_string = "There must be at least one" \
|
||||
" core for %s." % PLATFORM_CPU_TYPE_FORMAT
|
||||
elif 'compute' in personality and vswitch_cores == 0:
|
||||
error_string = "There must be at least one" \
|
||||
" core for %s." % VSWITCH_CPU_TYPE_FORMAT
|
||||
elif 'compute' in personality and vm_cores == 0:
|
||||
error_string = "There must be at least one" \
|
||||
" core for %s." % VMS_CPU_TYPE_FORMAT
|
||||
return error_string
|
193
cgcs_dashboard/dashboards/admin/inventory/cpu_functions/views.py
Executable file
193
cgcs_dashboard/dashboards/admin/inventory/cpu_functions/views.py
Executable file
@ -0,0 +1,193 @@
|
||||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
import utils as icpu_utils
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.cpu_functions.forms \
|
||||
import AddCpuProfile
|
||||
from openstack_dashboard.dashboards.admin.inventory.cpu_functions.forms \
|
||||
import UpdateCpuFunctions
|
||||
from openstack_dashboard.dashboards.admin.inventory.cpu_functions \
|
||||
import utils as cpufunctions_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateCpuFunctionsView(forms.ModalFormView):
|
||||
form_class = UpdateCpuFunctions
|
||||
template_name = 'admin/inventory/cpu_functions/update.html'
|
||||
context_object_name = 'cpufunctions'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_id)
|
||||
host.nodes = api.sysinv.host_node_list(self.request, host.uuid)
|
||||
host.cpus = api.sysinv.host_cpu_list(self.request, host.uuid)
|
||||
icpu_utils.restructure_host_cpu_data(host)
|
||||
self._object = host
|
||||
self._object.host_id = host_id
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
redirect = reverse("horizon:project:networks:detail",
|
||||
args=(host_id))
|
||||
msg = _('Unable to retrieve port details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = \
|
||||
super(UpdateCpuFunctionsView, self).get_context_data(**kwargs)
|
||||
host = self._get_object()
|
||||
context['host_id'] = host.host_id
|
||||
return context
|
||||
|
||||
def get_physical_core_count(self, host, cpufunc, socket):
|
||||
value = cpufunc.socket_cores_number.get(socket, 0)
|
||||
if host.hyperthreading.lower() == "yes":
|
||||
value = value / 2
|
||||
return value
|
||||
|
||||
def get_initial(self):
|
||||
host = self._get_object()
|
||||
|
||||
platform_processor0 = 99 # NO_PROCESSOR
|
||||
platform_processor1 = 99 # NO_PROCESSOR
|
||||
platform_processor2 = 99 # NO_PROCESSOR
|
||||
platform_processor3 = 99 # NO_PROCESSOR
|
||||
|
||||
num_cores_on_processor0 = 99 # NO_PROCESSOR
|
||||
num_cores_on_processor1 = 99 # NO_PROCESSOR
|
||||
num_cores_on_processor2 = 99 # NO_PROCESSOR
|
||||
num_cores_on_processor3 = 99 # NO_PROCESSOR
|
||||
|
||||
num_shared_on_processor0 = 99 # NO_PROCESSOR
|
||||
num_shared_on_processor1 = 99 # NO_PROCESSOR
|
||||
num_shared_on_processor2 = 99 # NO_PROCESSOR
|
||||
num_shared_on_processor3 = 99 # NO_PROCESSOR
|
||||
|
||||
for cpufunc in host.core_assignment:
|
||||
if cpufunc.allocated_function == icpu_utils.PLATFORM_CPU_TYPE:
|
||||
if host.sockets > 0:
|
||||
platform_processor0 = self.get_physical_core_count(
|
||||
host, cpufunc, 0)
|
||||
if host.sockets > 1:
|
||||
platform_processor1 = self.get_physical_core_count(
|
||||
host, cpufunc, 1)
|
||||
if host.sockets > 2:
|
||||
platform_processor2 = self.get_physical_core_count(
|
||||
host, cpufunc, 2)
|
||||
if host.sockets > 3:
|
||||
platform_processor3 = self.get_physical_core_count(
|
||||
host, cpufunc, 3)
|
||||
|
||||
elif cpufunc.allocated_function == icpu_utils.VSWITCH_CPU_TYPE:
|
||||
if host.sockets > 0:
|
||||
num_cores_on_processor0 = \
|
||||
self.get_physical_core_count(host, cpufunc, 0)
|
||||
if host.sockets > 1:
|
||||
num_cores_on_processor1 = \
|
||||
self.get_physical_core_count(host, cpufunc, 1)
|
||||
if host.sockets > 2:
|
||||
num_cores_on_processor2 = \
|
||||
self.get_physical_core_count(host, cpufunc, 2)
|
||||
if host.sockets > 3:
|
||||
num_cores_on_processor3 = \
|
||||
self.get_physical_core_count(host, cpufunc, 3)
|
||||
|
||||
elif cpufunc.allocated_function == icpu_utils.SHARED_CPU_TYPE:
|
||||
if host.sockets > 0:
|
||||
num_shared_on_processor0 = \
|
||||
self.get_physical_core_count(host, cpufunc, 0)
|
||||
if host.sockets > 1:
|
||||
num_shared_on_processor1 = \
|
||||
self.get_physical_core_count(host, cpufunc, 1)
|
||||
if host.sockets > 2:
|
||||
num_shared_on_processor2 = \
|
||||
self.get_physical_core_count(host, cpufunc, 2)
|
||||
if host.sockets > 3:
|
||||
num_shared_on_processor3 = \
|
||||
self.get_physical_core_count(host, cpufunc, 3)
|
||||
|
||||
return {'host': host,
|
||||
'host_id': host.host_id,
|
||||
'platform': icpu_utils.PLATFORM_CPU_TYPE_FORMAT,
|
||||
'platform_processor0': platform_processor0,
|
||||
'platform_processor1': platform_processor1,
|
||||
'platform_processor2': platform_processor2,
|
||||
'platform_processor3': platform_processor3,
|
||||
'vswitch': icpu_utils.VSWITCH_CPU_TYPE_FORMAT,
|
||||
'num_cores_on_processor0': num_cores_on_processor0,
|
||||
'num_cores_on_processor1': num_cores_on_processor1,
|
||||
'num_cores_on_processor2': num_cores_on_processor2,
|
||||
'num_cores_on_processor3': num_cores_on_processor3,
|
||||
'shared_vcpu': icpu_utils.SHARED_CPU_TYPE_FORMAT,
|
||||
'num_shared_on_processor0': num_shared_on_processor0,
|
||||
'num_shared_on_processor1': num_shared_on_processor1,
|
||||
'num_shared_on_processor2': num_shared_on_processor2,
|
||||
'num_shared_on_processor3': num_shared_on_processor3}
|
||||
|
||||
|
||||
class AddCpuProfileView(forms.ModalFormView):
|
||||
form_class = AddCpuProfile
|
||||
template_name = 'admin/inventory/cpu_functions/createprofile.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_myhost_data(self):
|
||||
if not hasattr(self, "_host"):
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_id)
|
||||
host.nodes = api.sysinv.host_node_list(self.request, host.uuid)
|
||||
host.cpus = api.sysinv.host_cpu_list(self.request, host.uuid)
|
||||
icpu_utils.restructure_host_cpu_data(host)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'host "%s".') % host_id,
|
||||
redirect=redirect)
|
||||
self._host = host
|
||||
return self._host
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddCpuProfileView, self).get_context_data(**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
context['host'] = self.get_myhost_data()
|
||||
context['cpu_formats'] = cpufunctions_utils.CPU_TYPE_FORMATS
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddCpuProfileView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
return initial
|
0
cgcs_dashboard/dashboards/admin/inventory/devices/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/devices/__init__.py
Executable file
78
cgcs_dashboard/dashboards/admin/inventory/devices/forms.py
Executable file
78
cgcs_dashboard/dashboards/admin/inventory/devices/forms.py
Executable file
@ -0,0 +1,78 @@
|
||||
#
|
||||
# Copyright (c) 2014-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
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 openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateDevice(forms.SelfHandlingForm):
|
||||
|
||||
host_id = forms.CharField(label=_("host_id"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
uuid = forms.CharField(label=_("uuid"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
device_name = forms.CharField(label=_("Device Name"),
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
|
||||
pciaddr = forms.CharField(label=_("Device Address"),
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
|
||||
name = forms.CharField(label=_("Name"),
|
||||
required=False,
|
||||
widget=forms.TextInput())
|
||||
|
||||
enabled = forms.BooleanField(label=_("Enabled"),
|
||||
required=False,
|
||||
widget=forms.CheckboxInput())
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def clean(self):
|
||||
data = super(UpdateDevice, self).clean()
|
||||
if isinstance(data['enabled'], bool):
|
||||
data['enabled'] = 'True' if data['enabled'] else 'False'
|
||||
return data
|
||||
|
||||
def handle(self, request, data):
|
||||
name = data['name']
|
||||
uuid = data['uuid']
|
||||
|
||||
try:
|
||||
p = {}
|
||||
p['name'] = name
|
||||
p['enabled'] = str(data['enabled'])
|
||||
device = api.sysinv.host_device_update(request, uuid, **p)
|
||||
msg = _('device "%s" was successfully updated.') % name
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return device
|
||||
except Exception as exc:
|
||||
msg = _('Failed to update device "%(n)s" (%(e)s).') % ({'n': name,
|
||||
'e': exc})
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[data['host_id']])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
144
cgcs_dashboard/dashboards/admin/inventory/devices/tables.py
Executable file
144
cgcs_dashboard/dashboards/admin/inventory/devices/tables.py
Executable file
@ -0,0 +1,144 @@
|
||||
#
|
||||
# Copyright (c) 2014-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EditDevice(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit Device")
|
||||
url = "horizon:admin:inventory:editdevice"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, device=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id, device.uuid))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
host = self.table.kwargs['host']
|
||||
return (host._administrative == 'locked' and
|
||||
api.sysinv.SUBFUNCTIONS_COMPUTE in host.subfunctions)
|
||||
|
||||
|
||||
def get_viewdevice_link_url(device):
|
||||
return reverse("horizon:admin:inventory:viewdevice",
|
||||
args=(device.host_id, device.uuid))
|
||||
|
||||
|
||||
class DevicesTable(tables.DataTable):
|
||||
"""Devices Table per host under Host Tab"""
|
||||
|
||||
name = tables.Column('name',
|
||||
verbose_name=_('Name'),
|
||||
link=get_viewdevice_link_url)
|
||||
address = tables.Column('pciaddr',
|
||||
verbose_name=_('Address'))
|
||||
device_id = tables.Column('pdevice_id',
|
||||
verbose_name=_('Device Id'))
|
||||
device_name = tables.Column('pdevice',
|
||||
verbose_name=_('Device Name'))
|
||||
numa_node = tables.Column('numa_node',
|
||||
verbose_name=_('Numa Node'))
|
||||
enabled = tables.Column('enabled',
|
||||
verbose_name=_('Enabled'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.name
|
||||
|
||||
class Meta(object):
|
||||
name = "devices"
|
||||
verbose_name = _("Devices")
|
||||
multi_select = False
|
||||
row_actions = (EditDevice,)
|
||||
|
||||
|
||||
class UsageTable(tables.DataTable):
|
||||
"""Detail usage table for a device under Device Usage tab"""
|
||||
|
||||
host = tables.Column('host',
|
||||
verbose_name=_('Host'))
|
||||
pci_pfs_configured = tables.Column('pci_pfs_configured',
|
||||
verbose_name=_('PFs configured'))
|
||||
pci_pfs_used = tables.Column('pci_pfs_configured',
|
||||
verbose_name=_('PFs used'))
|
||||
pci_vfs_configured = tables.Column('pci_vfs_configured',
|
||||
verbose_name=_('VFs configured'))
|
||||
pci_vfs_used = tables.Column('pci_vfs_used',
|
||||
verbose_name=_('VFs used'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.id)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.host
|
||||
|
||||
class Meta(object):
|
||||
name = "usage"
|
||||
verbose_name = _("Usage")
|
||||
multi_select = False
|
||||
|
||||
|
||||
def get_viewusage_link_url(usage):
|
||||
return reverse("horizon:admin:inventory:viewusage",
|
||||
args=(usage.device_id,))
|
||||
|
||||
|
||||
class DeviceUsageTable(tables.DataTable):
|
||||
"""Device Usage table for all devices (i.e Device Usage tab)"""
|
||||
|
||||
device_name = tables.Column('device_name',
|
||||
link=get_viewusage_link_url,
|
||||
verbose_name=_('PCI Alias'))
|
||||
|
||||
description = tables.Column('description', verbose_name=_('Description'))
|
||||
|
||||
device_id = tables.Column('device_id',
|
||||
verbose_name=_('Device Id'))
|
||||
|
||||
vendor_id = tables.Column('vendor_id',
|
||||
verbose_name=_('Vendor Id'))
|
||||
|
||||
class_id = tables.Column('class_id',
|
||||
verbose_name=_('Class Id'))
|
||||
|
||||
pci_pfs_configured = tables.Column('pci_pfs_configured',
|
||||
verbose_name=_("PFs configured"))
|
||||
|
||||
pci_pfs_used = tables.Column('pci_pfs_used',
|
||||
verbose_name=_("PFs used"))
|
||||
|
||||
pci_vfs_configured = tables.Column('pci_vfs_configured',
|
||||
verbose_name=_("VFs configured"))
|
||||
|
||||
pci_vfs_used = tables.Column('pci_vfs_used',
|
||||
verbose_name=_("VFs used"))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.device_id)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.device_name
|
||||
|
||||
class Meta(object):
|
||||
name = "deviceusage"
|
||||
verbose_name = _("Device Usage")
|
169
cgcs_dashboard/dashboards/admin/inventory/devices/views.py
Executable file
169
cgcs_dashboard/dashboards/admin/inventory/devices/views.py
Executable file
@ -0,0 +1,169 @@
|
||||
#
|
||||
# Copyright (c) 2014-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.devices.forms import \
|
||||
UpdateDevice
|
||||
from openstack_dashboard.dashboards.admin.inventory.devices.tables import \
|
||||
UsageTable
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdateDevice
|
||||
template_name = 'admin/inventory/devices/update.html'
|
||||
context_object_name = 'device'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
device_uuid = self.kwargs['device_uuid']
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
self._object = api.sysinv.host_device_get(self.request,
|
||||
device_uuid)
|
||||
self._object.host_id = host_id
|
||||
except Exception:
|
||||
redirect = reverse("horizon:admin:inventory:detail",
|
||||
args=(host_id))
|
||||
msg = _('Unable to retrieve device details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
device = self._get_object()
|
||||
context['device_uuid'] = device.uuid
|
||||
context['host_id'] = device.host_id
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
device = self._get_object()
|
||||
enabled = device.enabled
|
||||
if isinstance(enabled, basestring):
|
||||
if enabled.lower() == 'false':
|
||||
enabled = False
|
||||
elif enabled.lower() == 'true':
|
||||
enabled = True
|
||||
return {'name': device.name,
|
||||
'enabled': enabled,
|
||||
'pciaddr': device.pciaddr,
|
||||
'device_id': device.pdevice_id,
|
||||
'device_name': device.pdevice,
|
||||
'host_id': device.host_id,
|
||||
'uuid': device.uuid}
|
||||
|
||||
|
||||
class DetailView(views.HorizonTemplateView):
|
||||
template_name = 'admin/inventory/devices/detail.html'
|
||||
page_title = '{{ device.name }}'
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
device_uuid = self.kwargs['device_uuid']
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
self._object = api.sysinv.host_device_get(self.request,
|
||||
device_uuid)
|
||||
self._object.host_id = host_id
|
||||
|
||||
except Exception:
|
||||
redirect = reverse("horizon:admin:inventory:detail",
|
||||
args=(self.kwargs['host_id'],))
|
||||
msg = _('Unable to retrieve device details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
return self._object
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_hostname(self, host_uuid):
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_uuid)
|
||||
except Exception:
|
||||
host = {}
|
||||
msg = _('Unable to retrieve hostname details.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return host.hostname
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
device = self._get_object()
|
||||
|
||||
hostname = self.get_hostname(device.host_id)
|
||||
host_nav = hostname or "Unprovisioned Node"
|
||||
breadcrumb = [
|
||||
(host_nav, reverse('horizon:admin:inventory:detail',
|
||||
args=(device.host_id,))),
|
||||
(_("Devices"), None)
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
||||
context['device_uuid'] = device.uuid
|
||||
context['host_id'] = device.host_id
|
||||
context['device'] = device
|
||||
return context
|
||||
|
||||
|
||||
class UsageView(tables.MultiTableView):
|
||||
table_classes = (UsageTable, )
|
||||
template_name = 'admin/inventory/devices/usage.html'
|
||||
|
||||
def _handle_exception(self, device_id):
|
||||
redirect = reverse("horizon:admin:inventory:index")
|
||||
msg = _('Unable to retrieve device usage for %s') % device_id
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_usage_data(self, *args, **kwargs):
|
||||
if not hasattr(self, "_detail_object"):
|
||||
dev_id = self.kwargs['device_id']
|
||||
try:
|
||||
_object = api.nova.get_detail_usage(self.request, dev_id)
|
||||
_object.sort(key=lambda f: (f.host))
|
||||
id = 0
|
||||
for u in _object:
|
||||
id += 1
|
||||
u.id = id
|
||||
self._detail_object = _object
|
||||
|
||||
except Exception:
|
||||
self._handle_exception(dev_id)
|
||||
|
||||
return self._detail_object
|
||||
|
||||
def _get_device_usage(self, *args, **kwargs):
|
||||
if not hasattr(self, "_usage_object"):
|
||||
dev_id = self.kwargs['device_id']
|
||||
try:
|
||||
_object = api.nova.get_device_usage(self.request, dev_id)
|
||||
self._usage_object = _object
|
||||
except Exception:
|
||||
self._handle_exception(dev_id)
|
||||
|
||||
return self._usage_object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UsageView, self).get_context_data(**kwargs)
|
||||
context['device_usage'] = self._get_device_usage()
|
||||
return context
|
0
cgcs_dashboard/dashboards/admin/inventory/interfaces/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/interfaces/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/interfaces/address/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/interfaces/address/__init__.py
Executable file
69
cgcs_dashboard/dashboards/admin/inventory/interfaces/address/forms.py
Executable file
69
cgcs_dashboard/dashboards/admin/inventory/interfaces/address/forms.py
Executable file
@ -0,0 +1,69 @@
|
||||
# Copyright 2015 Wind River Systems, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import shortcuts
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import netaddr
|
||||
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateAddress(forms.SelfHandlingForm):
|
||||
host_id = forms.CharField(widget=forms.HiddenInput())
|
||||
interface_id = forms.CharField(widget=forms.HiddenInput())
|
||||
success_url = 'horizon:admin:inventory:viewinterface'
|
||||
failure_url = 'horizon:admin:inventory:viewinterface'
|
||||
|
||||
ip_address = forms.IPField(
|
||||
label=_("IP Address"),
|
||||
required=True,
|
||||
initial="",
|
||||
help_text=_("IP interface address in CIDR format "
|
||||
"(e.g. 192.168.0.2/24, 2001:DB8::/48"),
|
||||
version=forms.IPv4 | forms.IPv6,
|
||||
mask=True)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
ip_address = netaddr.IPNetwork(data['ip_address'])
|
||||
body = {'interface_uuid': data['interface_id'],
|
||||
'address': str(ip_address.ip),
|
||||
'prefix': ip_address.prefixlen}
|
||||
address = api.sysinv.address_create(request, **body)
|
||||
msg = (_('Address %(address)s/%(prefix)s was '
|
||||
'successfully created') % body)
|
||||
messages.success(request, msg)
|
||||
return address
|
||||
except Exception as e:
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, e)
|
||||
LOG.error(e)
|
||||
# Redirect to failure page
|
||||
redirect = reverse(self.failure_url,
|
||||
args=(data['host_id'], data['interface_id']))
|
||||
return shortcuts.redirect(redirect)
|
130
cgcs_dashboard/dashboards/admin/inventory/interfaces/address/tables.py
Executable file
130
cgcs_dashboard/dashboards/admin/inventory/interfaces/address/tables.py
Executable file
@ -0,0 +1,130 @@
|
||||
# Copyright 2015 Wind River Systems, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
ALLOWED_INTERFACE_TYPES = ['infra', 'data', 'control']
|
||||
|
||||
|
||||
class DeleteAddress(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Address",
|
||||
u"Delete Addresses",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Address",
|
||||
u"Deleted Addresses",
|
||||
count
|
||||
)
|
||||
|
||||
def get_redirect_url(self):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
interface_id = self.table.kwargs['interface_id']
|
||||
return reverse('horizon:admin:inventory:viewinterface',
|
||||
args=[host_id, interface_id])
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.sysinv.address_delete(request, obj_id)
|
||||
except Exception:
|
||||
exceptions.handle(request, redirect=self.get_redirect_url())
|
||||
|
||||
|
||||
class CreateAddress(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Address")
|
||||
url = "horizon:admin:inventory:addaddress"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
interface_id = self.table.kwargs['interface_id']
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id, interface_id))
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
interface = self.table.get_interface()
|
||||
supported = interface.networktype.split(',')
|
||||
if not interface:
|
||||
return False
|
||||
if any(t in supported for t in ALLOWED_INTERFACE_TYPES):
|
||||
return True
|
||||
if interface.ipv4_mode in ['static']:
|
||||
return True
|
||||
if interface.ipv6_mode in ['static']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_address_column(address):
|
||||
ip_address = getattr(address, 'address')
|
||||
prefix = getattr(address, 'prefix')
|
||||
return ip_address + '/' + str(prefix)
|
||||
|
||||
|
||||
class AddressTable(tables.DataTable):
|
||||
address = tables.Column(get_address_column,
|
||||
verbose_name=_("Address"))
|
||||
enable_dad = tables.Column("enable_dad",
|
||||
verbose_name=_("DAD"))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return ("%(address)s/%(prefix)s" %
|
||||
{'address': datum.address,
|
||||
'prefix': datum.prefix})
|
||||
|
||||
class Meta(object):
|
||||
name = "addresses"
|
||||
verbose_name = _("Address List")
|
||||
table_actions = (CreateAddress, DeleteAddress)
|
||||
row_actions = (DeleteAddress,)
|
||||
|
||||
def get_interface(self):
|
||||
if not hasattr(self, "_interface"):
|
||||
try:
|
||||
interface_id = self.kwargs["interface_id"]
|
||||
self._interface = api.sysinv.host_interface_get(
|
||||
self.request, interface_id)
|
||||
except Exception:
|
||||
redirect = reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],
|
||||
self.kwargs['interface_id'],))
|
||||
msg = _("Unable to retrieve interface details.")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return
|
||||
return self._interface
|
||||
|
||||
def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
|
||||
super(AddressTable, self).__init__(
|
||||
request, data=data, needs_form_wrapper=needs_form_wrapper,
|
||||
**kwargs)
|
51
cgcs_dashboard/dashboards/admin/inventory/interfaces/address/views.py
Executable file
51
cgcs_dashboard/dashboards/admin/inventory/interfaces/address/views.py
Executable file
@ -0,0 +1,51 @@
|
||||
# Copyright 2015 Wind River Systems, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
|
||||
from horizon import forms
|
||||
from openstack_dashboard.dashboards.admin.inventory.interfaces.address import \
|
||||
forms as address_forms
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = address_forms.CreateAddress
|
||||
template_name = 'admin/inventory/interfaces/address/create.html'
|
||||
success_url = 'horizon:admin:inventory:viewinterface'
|
||||
failure_url = 'horizon:admin:inventory:viewinterface'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],
|
||||
self.kwargs['interface_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],
|
||||
self.kwargs['interface_id'],))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
context['interface_id'] = self.kwargs['interface_id']
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
return {'interface_id': self.kwargs['interface_id'],
|
||||
'host_id': self.kwargs['host_id']}
|
886
cgcs_dashboard/dashboards/admin/inventory/interfaces/forms.py
Executable file
886
cgcs_dashboard/dashboards/admin/inventory/interfaces/forms.py
Executable file
@ -0,0 +1,886 @@
|
||||
#
|
||||
# Copyright (c) 2013-2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from compiler.ast import flatten
|
||||
import netaddr
|
||||
|
||||
from cgtsclient import exc
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import shortcuts
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_ipv4_pool_choices(pools):
|
||||
choices = []
|
||||
for p in pools:
|
||||
address = netaddr.IPAddress(p.network)
|
||||
if address.version == 4:
|
||||
choices.append((p.uuid, p.name))
|
||||
return choices
|
||||
|
||||
|
||||
def _get_ipv6_pool_choices(pools):
|
||||
choices = []
|
||||
for p in pools:
|
||||
address = netaddr.IPAddress(p.network)
|
||||
if address.version == 6:
|
||||
choices.append((p.uuid, p.name))
|
||||
return choices
|
||||
|
||||
|
||||
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
|
||||
"""Custom checkbox select widget that will render a text string
|
||||
|
||||
with an hidden input if there are no choices.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, attrs=None, choices=(), empty_value=''):
|
||||
super(CheckboxSelectMultiple, self).__init__(attrs, choices)
|
||||
self.empty_value = empty_value
|
||||
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
if self.choices:
|
||||
return super(CheckboxSelectMultiple, self).render(name, value,
|
||||
attrs, choices)
|
||||
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))
|
||||
|
||||
|
||||
class MultipleChoiceField(forms.MultipleChoiceField):
|
||||
"""Custom multiple choice field that only validates
|
||||
|
||||
if a value was provided.
|
||||
|
||||
"""
|
||||
|
||||
def valid_value(self, value):
|
||||
if not self.required and not value:
|
||||
return True
|
||||
return super(MultipleChoiceField, self).valid_value(value)
|
||||
|
||||
|
||||
class AddInterfaceProfile(forms.SelfHandlingForm):
|
||||
host_id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
profilename = forms.CharField(label=_("Interface Profile Name"),
|
||||
required=True)
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddInterfaceProfile, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddInterfaceProfile, self).clean()
|
||||
# host_id = cleaned_data.get('host_id')
|
||||
# interfaceProfileName = cleaned_data.get('hostname')
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
host_id = data['host_id']
|
||||
interfaceProfileName = data['profilename']
|
||||
try:
|
||||
interfaceProfile = api.sysinv.host_interfaceprofile_create(request,
|
||||
**data)
|
||||
msg = _(
|
||||
'Interface Profile "%s" was '
|
||||
'successfully created.') % interfaceProfileName
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return interfaceProfile
|
||||
except exc.ClientException as ce:
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, ce)
|
||||
LOG.error(ce)
|
||||
|
||||
# Redirect to failure pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception:
|
||||
msg = _(
|
||||
'Failed to create interface'
|
||||
' profile "%s".') % interfaceProfileName
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url,
|
||||
args=[data['host_id']])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class AddInterface(forms.SelfHandlingForm):
|
||||
NETWORK_TYPE_CHOICES = (
|
||||
('none', _("none")),
|
||||
('mgmt', _("mgmt")),
|
||||
('oam', _("oam")),
|
||||
('data', _("data")),
|
||||
('data-external', _("data-external")),
|
||||
('control', _("control")),
|
||||
('infra', _("infra")),
|
||||
('pxeboot', _("pxeboot")),
|
||||
)
|
||||
|
||||
INTERFACE_TYPE_CHOICES = (
|
||||
(None, _("<Select interface type>")),
|
||||
('ae', _("aggregated ethernet")),
|
||||
('vlan', _("vlan")),
|
||||
)
|
||||
|
||||
AE_MODE_CHOICES = (
|
||||
('active_standby', _("active/standby")),
|
||||
('balanced', _("balanced")),
|
||||
('802.3ad', _("802.3ad")),
|
||||
)
|
||||
|
||||
AE_XMIT_HASH_POLICY_CHOICES = (
|
||||
('layer3+4', _("layer3+4")),
|
||||
('layer2+3', _("layer2+3")),
|
||||
('layer2', _("layer2")),
|
||||
)
|
||||
|
||||
IPV4_MODE_CHOICES = (
|
||||
('disabled', _("Disabled")),
|
||||
('static', _("Static")),
|
||||
('pool', _("Pool")),
|
||||
)
|
||||
|
||||
IPV6_MODE_CHOICES = (
|
||||
('disabled', _("Disabled")),
|
||||
('static', _("Static")),
|
||||
('pool', _("Pool")),
|
||||
('auto', _("Automatic Assignment")),
|
||||
('link-local', _("Link Local")),
|
||||
)
|
||||
|
||||
ihost_uuid = forms.CharField(
|
||||
label=_("ihost_uuid"),
|
||||
initial='ihost_uuid',
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
host_id = forms.CharField(
|
||||
label=_("host_id"),
|
||||
initial='host_id',
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
# don't enforce a max length in ifname form field as
|
||||
# this will be validated by the SysInv REST call.
|
||||
# This ensures that both cgsclient and Dashboard
|
||||
# have the same length constraints.
|
||||
ifname = forms.RegexField(
|
||||
label=_("Interface Name"),
|
||||
required=True,
|
||||
regex=r'^[\w\.\-]+$',
|
||||
error_messages={
|
||||
'invalid':
|
||||
_('Name may only contain letters, numbers, underscores, '
|
||||
'periods and hyphens.')})
|
||||
|
||||
networktype = forms.MultipleChoiceField(
|
||||
label=_("Network Type"),
|
||||
required=True,
|
||||
choices=NETWORK_TYPE_CHOICES,
|
||||
widget=forms.CheckboxSelectMultiple(
|
||||
attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'network_type'}))
|
||||
|
||||
iftype = forms.ChoiceField(
|
||||
label=_("Interface Type"),
|
||||
required=True,
|
||||
choices=INTERFACE_TYPE_CHOICES,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'switchable switched',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-network_type-none': 'Interface Type',
|
||||
'data-network_type-infra': 'Interface Type',
|
||||
'data-network_type-data': 'Interface Type',
|
||||
'data-network_type-data-external': 'Interface Type',
|
||||
'data-network_type-control': 'Interface Type',
|
||||
'data-network_type-mgmt': 'Interface Type',
|
||||
'data-network_type-oam': 'Interface Type',
|
||||
'data-network_type-pxeboot': 'Interface Type',
|
||||
'data-slug': 'interface_type'}))
|
||||
|
||||
aemode = forms.ChoiceField(
|
||||
label=_("Aggregated Ethernet - Mode"),
|
||||
required=False,
|
||||
choices=AE_MODE_CHOICES,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'switchable switched',
|
||||
'data-slug': 'ae_mode',
|
||||
'data-switch-on': 'interface_type',
|
||||
'data-interface_type-ae': 'Aggregated Ethernet - Mode'}))
|
||||
|
||||
txhashpolicy = forms.ChoiceField(
|
||||
label=_("Aggregated Ethernet - Tx Policy"),
|
||||
required=False,
|
||||
choices=AE_XMIT_HASH_POLICY_CHOICES,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'ae_mode',
|
||||
'data-ae_mode-balanced': 'Aggregated Ethernet - Tx Policy',
|
||||
'data-ae_mode-802.3ad': 'Aggregated Ethernet - Tx Policy'}))
|
||||
|
||||
vlan_id = forms.IntegerField(
|
||||
label=_("Vlan ID"),
|
||||
initial=1,
|
||||
min_value=1,
|
||||
max_value=4094,
|
||||
required=False,
|
||||
help_text=_("Virtual LAN tag."),
|
||||
error_messages={'invalid': _('Vlan ID must be '
|
||||
'between 1 and 4094.')},
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'interface_type',
|
||||
'data-slug': 'vlanid',
|
||||
'data-interface_type-vlan': 'Vlan ID'}))
|
||||
|
||||
uses = forms.MultipleChoiceField(
|
||||
label=_("Interface(s)"),
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
help_text=_("Interface(s) of Interface."))
|
||||
|
||||
ports = forms.CharField(
|
||||
label=_("Port(s)"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
providernetworks_data = MultipleChoiceField(
|
||||
label=_("Provider Network(s)"),
|
||||
required=False,
|
||||
widget=CheckboxSelectMultiple(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-network_type-data': ''},
|
||||
empty_value=_("No provider networks available for network type.")))
|
||||
|
||||
providernetworks_data_external = MultipleChoiceField(
|
||||
label=_("Provider Network(s)"),
|
||||
required=False,
|
||||
widget=CheckboxSelectMultiple(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-network_type-data-external': ''},
|
||||
empty_value=_("No provider networks available for network type.")))
|
||||
|
||||
providernetworks_pci = MultipleChoiceField(
|
||||
label=_("Provider Network(s)"),
|
||||
required=False,
|
||||
widget=CheckboxSelectMultiple(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-network_type-pci-passthrough': ''},
|
||||
empty_value=_("No provider networks available for network type.")))
|
||||
|
||||
providernetworks_sriov = MultipleChoiceField(
|
||||
label=_("Provider Network(s)"),
|
||||
required=False,
|
||||
widget=CheckboxSelectMultiple(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-network_type-pci-sriov': ''},
|
||||
empty_value=_("No provider networks available for network type.")))
|
||||
|
||||
imtu = forms.IntegerField(
|
||||
label=_("MTU"),
|
||||
initial=1500,
|
||||
min_value=576,
|
||||
max_value=9216,
|
||||
required=False,
|
||||
help_text=_("Maximum Transmit Unit."),
|
||||
error_messages={'invalid': _('MTU must be '
|
||||
'between 576 and 9216 bytes.')},
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-network_type-none': _('MTU'),
|
||||
'data-network_type-data': _('MTU'),
|
||||
'data-network_type-data-external': _('MTU'),
|
||||
'data-network_type-control': _('MTU'),
|
||||
'data-network_type-mgmt': _('MTU'),
|
||||
'data-network_type-infra': _('MTU'),
|
||||
'data-network_type-pci-passthrough': _('MTU'),
|
||||
'data-network_type-pci-sriov': _('MTU'),
|
||||
'data-network_type-oam': _('MTU'),
|
||||
'data-network_type-pxeboot': _('MTU')}))
|
||||
|
||||
ipv4_mode = forms.ChoiceField(
|
||||
label=_("IPv4 Addressing Mode"),
|
||||
required=False,
|
||||
initial='disabled',
|
||||
choices=IPV4_MODE_CHOICES,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'switchable switched',
|
||||
'data-slug': 'ipv4_mode',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-network_type-data': 'IPv4 Addressing Mode',
|
||||
'data-network_type-control': 'IPv4 Addressing Mode'}))
|
||||
|
||||
ipv4_pool = forms.ChoiceField(
|
||||
label=_("IPv4 Address Pool"),
|
||||
required=False,
|
||||
initial='',
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'ipv4_mode',
|
||||
'data-ipv4_mode-pool': 'IPv4 Address Pool'}))
|
||||
|
||||
ipv6_mode = forms.ChoiceField(
|
||||
label=_("IPv6 Addressing Mode"),
|
||||
required=False,
|
||||
initial='disabled',
|
||||
choices=IPV6_MODE_CHOICES,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'switchable switched',
|
||||
'data-slug': 'ipv6_mode',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-network_type-data': 'IPv6 Addressing Mode',
|
||||
'data-network_type-control': 'IPv6 Addressing Mode'}))
|
||||
|
||||
ipv6_pool = forms.ChoiceField(
|
||||
label=_("IPv6 Address Pool"),
|
||||
required=False,
|
||||
initial='',
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'ipv6_mode',
|
||||
'data-ipv6_mode-pool': 'IPv6 Address Pool'}))
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddInterface, self).__init__(*args, **kwargs)
|
||||
|
||||
# Populate Available Port Choices
|
||||
# Only include ports that are not already part of other interfaces
|
||||
this_interface_id = 0
|
||||
|
||||
current_interface = None
|
||||
if (type(self) is UpdateInterface):
|
||||
this_interface_id = kwargs['initial']['id']
|
||||
current_interface = api.sysinv.host_interface_get(
|
||||
self.request, this_interface_id)
|
||||
else:
|
||||
self.fields['providernetworks_sriov'].widget = \
|
||||
forms.widgets.HiddenInput()
|
||||
self.fields['providernetworks_pci'].widget = \
|
||||
forms.widgets.HiddenInput()
|
||||
|
||||
host_uuid = kwargs['initial']['ihost_uuid']
|
||||
|
||||
# Retrieve SDN configuration
|
||||
sdn_enabled = kwargs['initial']['sdn_enabled']
|
||||
sdn_l3_mode = kwargs['initial']['sdn_l3_mode_enabled']
|
||||
|
||||
# Populate Address Pool selections
|
||||
pools = api.sysinv.address_pool_list(self.request)
|
||||
self.fields['ipv4_pool'].choices = _get_ipv4_pool_choices(pools)
|
||||
self.fields['ipv6_pool'].choices = _get_ipv6_pool_choices(pools)
|
||||
|
||||
# Populate Provider Network Choices by querying Neutron
|
||||
self.extras = {}
|
||||
interfaces = api.sysinv.host_interface_list(self.request, host_uuid)
|
||||
|
||||
used_providernets = []
|
||||
for i in interfaces:
|
||||
networktypelist = []
|
||||
if i.networktype:
|
||||
networktypelist = [i.networktype.split(",")]
|
||||
if 'data' in networktypelist and \
|
||||
i.providernetworks and \
|
||||
i.uuid != this_interface_id:
|
||||
used_providernets = used_providernets + \
|
||||
i.providernetworks.split(",")
|
||||
|
||||
providernet_choices = []
|
||||
providernet_filtered = []
|
||||
providernet_flat = None
|
||||
providernets = api.neutron.provider_network_list(self.request)
|
||||
for provider in providernets:
|
||||
label = "{} (mtu={})".format(provider.name, provider.mtu)
|
||||
providernet = (str(provider.name), label)
|
||||
providernet_choices.append(providernet)
|
||||
if provider.name not in used_providernets:
|
||||
providernet_filtered.append(providernet)
|
||||
if provider.type == 'flat':
|
||||
providernet_flat = providernet
|
||||
|
||||
self.fields['providernetworks_data'].choices = providernet_filtered
|
||||
if (type(self) is UpdateInterface):
|
||||
self.fields['providernetworks_pci'].choices = providernet_choices
|
||||
self.fields['providernetworks_sriov'].choices = providernet_choices
|
||||
|
||||
if not (sdn_enabled and sdn_l3_mode):
|
||||
self.fields['providernetworks_data_external'].widget = \
|
||||
forms.widgets.HiddenInput()
|
||||
nt_choices = self.fields['networktype'].choices
|
||||
self.fields['networktype'].choices = [i for i in nt_choices
|
||||
if i[0] != 'data-external']
|
||||
else:
|
||||
# Support a 'data-external' network type and allow
|
||||
# its Provider Network configuration (FLAT only)
|
||||
self.fields['providernetworks_data_external'].choices = \
|
||||
[providernet_flat]
|
||||
|
||||
if current_interface:
|
||||
# update operation
|
||||
if not current_interface.uses:
|
||||
# update default interfaces
|
||||
self.fields['uses'].widget = forms.widgets.HiddenInput()
|
||||
avail_port_list = api.sysinv.host_port_list(
|
||||
self.request, host_uuid)
|
||||
for p in avail_port_list:
|
||||
if p.interface_uuid == this_interface_id:
|
||||
self.fields['ports'].initial = p.uuid
|
||||
else:
|
||||
# update non default interfaces
|
||||
avail_interface_list = api.sysinv.host_interface_list(
|
||||
self.request, host_uuid)
|
||||
interface_tuple_list = []
|
||||
for i in avail_interface_list:
|
||||
if i.uuid != current_interface.uuid:
|
||||
interface_tuple_list.append(
|
||||
(i.uuid, "%s (%s, %s)" %
|
||||
(i.ifname, i.imac, i.networktype)))
|
||||
|
||||
uses_initial = [i.uuid for i in avail_interface_list if
|
||||
i.ifname in current_interface.uses]
|
||||
|
||||
self.fields['uses'].initial = uses_initial
|
||||
self.fields['uses'].choices = interface_tuple_list
|
||||
|
||||
if current_interface.vlan_id:
|
||||
self.fields['vlan_id'].initial = current_interface.vlan_id
|
||||
|
||||
else:
|
||||
# add operation
|
||||
avail_interface_list = api.sysinv.host_interface_list(
|
||||
self.request, host_uuid)
|
||||
interface_tuple_list = []
|
||||
for i in avail_interface_list:
|
||||
interface_tuple_list.append(
|
||||
(i.uuid, "%s (%s, %s)" %
|
||||
(i.ifname, i.imac, i.networktype)))
|
||||
self.fields['uses'].choices = interface_tuple_list
|
||||
self.fields['networktype'].initial = ('none', 'none')
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddInterface, self).clean()
|
||||
|
||||
networktype = cleaned_data.get('networktype', 'none')
|
||||
|
||||
if ('data' not in networktype and
|
||||
'control' not in networktype):
|
||||
cleaned_data.pop('ipv4_mode', None)
|
||||
cleaned_data.pop('ipv6_mode', None)
|
||||
|
||||
if cleaned_data.get('ipv4_mode') != 'pool':
|
||||
cleaned_data.pop('ipv4_pool', None)
|
||||
|
||||
if cleaned_data.get('ipv6_mode') != 'pool':
|
||||
cleaned_data.pop('ipv6_pool', None)
|
||||
|
||||
if 'data' in networktype:
|
||||
providernetworks = filter(
|
||||
None, cleaned_data.get('providernetworks_data', []))
|
||||
elif 'data-external' in networktype:
|
||||
# 'data' and 'data-external' nts cannot be consolidated
|
||||
# on same interface
|
||||
providernetworks = filter(
|
||||
None, cleaned_data.get('providernetworks_data_external', []))
|
||||
elif 'pci-passthrough' in networktype:
|
||||
providernetworks = filter(None, cleaned_data.get(
|
||||
'providernetworks_pci', []))
|
||||
elif 'pci-sriov' in networktype:
|
||||
providernetworks = filter(
|
||||
None,
|
||||
cleaned_data.get('providernetworks_sriov', []))
|
||||
else:
|
||||
providernetworks = []
|
||||
|
||||
# providernetwork selection is required for 'data', 'pci-passthrough'
|
||||
# and 'pci-sriov'. It is NOT required for any other network type
|
||||
if not providernetworks:
|
||||
|
||||
# Note that 1 of 3 different controls may be used to select
|
||||
# provider network, make sure to set the error on the appropriate
|
||||
# control
|
||||
if any(network in ['data', 'pci-passthrough', 'pci-sriov']
|
||||
for network in networktype):
|
||||
raise forms.ValidationError(_(
|
||||
"You must specify a Provider Network"))
|
||||
|
||||
cleaned_data['providernetworks'] = ",".join(providernetworks)
|
||||
if 'providernetworks_data' in cleaned_data:
|
||||
del cleaned_data['providernetworks_data']
|
||||
if 'providernetworks_data_external' in cleaned_data:
|
||||
del cleaned_data['providernetworks_data_external']
|
||||
if 'providernetworks_pci' in cleaned_data:
|
||||
del cleaned_data['providernetworks_pci']
|
||||
if 'providernetworks_sriov' in cleaned_data:
|
||||
del cleaned_data['providernetworks_sriov']
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
host_id = data['host_id']
|
||||
|
||||
try:
|
||||
del data['host_id']
|
||||
|
||||
if data['ports']:
|
||||
del data['uses']
|
||||
else:
|
||||
uses = data['uses'][:]
|
||||
data['uses'] = uses
|
||||
del data['ports']
|
||||
|
||||
if not data['providernetworks']:
|
||||
del data['providernetworks']
|
||||
|
||||
if not data['vlan_id'] or data['iftype'] != 'vlan':
|
||||
del data['vlan_id']
|
||||
else:
|
||||
data['vlan_id'] = unicode(data['vlan_id'])
|
||||
|
||||
if any(network in data['networktype'] for network in
|
||||
['mgmt', 'infra', 'oam']):
|
||||
del data['imtu']
|
||||
else:
|
||||
data['imtu'] = unicode(data['imtu'])
|
||||
|
||||
if data['networktype']:
|
||||
data['networktype'] = ",".join(data['networktype'])
|
||||
|
||||
if data['iftype'] != 'ae':
|
||||
del data['txhashpolicy']
|
||||
del data['aemode']
|
||||
elif data['aemode'] == 'active_standby':
|
||||
del data['txhashpolicy']
|
||||
|
||||
interface = api.sysinv.host_interface_create(request, **data)
|
||||
msg = _('Interface "%s" was successfully'
|
||||
' created.') % data['ifname']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
return interface
|
||||
except exc.ClientException as ce:
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, ce)
|
||||
LOG.error(ce)
|
||||
|
||||
# Redirect to failure page
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception:
|
||||
msg = _('Failed to create interface "%s".') % data['ifname']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class UpdateInterface(AddInterface):
|
||||
MGMT_AE_MODE_CHOICES = (
|
||||
('active_standby', _("active/standby")),
|
||||
('802.3ad', _("802.3ad")),
|
||||
)
|
||||
|
||||
INTERFACE_TYPE_CHOICES = (
|
||||
(None, _("<Select interface type>")),
|
||||
('ethernet', _("ethernet")),
|
||||
('ae', _("aggregated ethernet")),
|
||||
('vlan', _("vlan")),
|
||||
)
|
||||
|
||||
id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
|
||||
networktype = forms.MultipleChoiceField(
|
||||
label=_("Network Type"),
|
||||
help_text=_("Note: The network type of an interface cannot be changed "
|
||||
"without first being reset back to 'none'"),
|
||||
required=True,
|
||||
widget=forms.CheckboxSelectMultiple(
|
||||
attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'network_type'}))
|
||||
|
||||
sriov_numvfs = forms.IntegerField(
|
||||
label=_("Virtual Functions"),
|
||||
required=False,
|
||||
min_value=0,
|
||||
help_text=_("Virtual Functions for pci-sriov."),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-slug': 'num_vfs',
|
||||
'data-network_type-pci-sriov': 'Num VFs'}))
|
||||
|
||||
sriov_totalvfs = forms.IntegerField(
|
||||
label=_("Maximum Virtual Functions"),
|
||||
required=False,
|
||||
widget=forms.widgets.TextInput(
|
||||
attrs={
|
||||
'class': 'switched',
|
||||
'readonly': 'readonly',
|
||||
'data-switch-on': 'network_type',
|
||||
'data-network_type-pci-sriov': 'Max VFs'}))
|
||||
|
||||
iftypedata = forms.ChoiceField(
|
||||
label=_("Interface Type"),
|
||||
choices=INTERFACE_TYPE_CHOICES,
|
||||
widget=forms.HiddenInput)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UpdateInterface, self).__init__(*args, **kwargs)
|
||||
|
||||
networktype_val = kwargs['initial']['networktype']
|
||||
host_uuid = kwargs['initial']['ihost_uuid']
|
||||
|
||||
# Get the SDN configuration
|
||||
sdn_enabled = kwargs['initial']['sdn_enabled']
|
||||
sdn_l3_mode = kwargs['initial']['sdn_l3_mode_enabled']
|
||||
|
||||
this_interface_id = kwargs['initial']['id']
|
||||
|
||||
iftype_val = kwargs['initial']['iftype']
|
||||
if 'mgmt' in networktype_val:
|
||||
self.fields['aemode'].choices = self.MGMT_AE_MODE_CHOICES
|
||||
|
||||
else:
|
||||
self.fields['aemode'].choices = self.AE_MODE_CHOICES
|
||||
|
||||
# Populate Address Pool selections
|
||||
pools = api.sysinv.address_pool_list(self.request)
|
||||
self.fields['ipv4_pool'].choices = _get_ipv4_pool_choices(pools)
|
||||
self.fields['ipv6_pool'].choices = _get_ipv6_pool_choices(pools)
|
||||
self.fields['ipv4_pool'].initial = kwargs['initial'].get('ipv4_pool')
|
||||
self.fields['ipv6_pool'].initial = kwargs['initial'].get('ipv6_pool')
|
||||
|
||||
# Setting field to read-only doesn't actually work so we're making
|
||||
# it disabled. This has the effect of not allowing the data through
|
||||
# to the form submission, so we require a hidden field to carry the
|
||||
# actual value through (iftype data)
|
||||
self.fields['iftype'].widget.attrs['disabled'] = 'disabled'
|
||||
self.fields['iftype'].required = False
|
||||
self.fields['iftype'].choices = self.INTERFACE_TYPE_CHOICES
|
||||
self.fields['iftypedata'].initial = kwargs['initial'].get('iftype')
|
||||
self.fields['iftype'].initial = kwargs['initial'].get('iftype')
|
||||
|
||||
# Load the networktype choices
|
||||
networktype_choices = []
|
||||
used_choices = []
|
||||
if networktype_val:
|
||||
for network in networktype_val:
|
||||
label = "{}".format(network)
|
||||
net_type = (str(network), label)
|
||||
used_choices.append(str(network))
|
||||
networktype_choices.append(net_type)
|
||||
else:
|
||||
label = "{}".format("none")
|
||||
net_type = ("none", label)
|
||||
networktype_choices.append(net_type)
|
||||
used_choices.append("none")
|
||||
|
||||
# if SDN L3 mode is enabled, then we may allow
|
||||
# updating an interface network type to 'data-external'
|
||||
data_choices = ['data', 'control']
|
||||
if (sdn_enabled and sdn_l3_mode):
|
||||
data_choices.append('data-external')
|
||||
|
||||
if iftype_val == 'ethernet':
|
||||
choices_list = ['none', 'infra', 'oam', 'mgmt', 'pci-passthrough',
|
||||
data_choices, 'pci-sriov', 'pxeboot']
|
||||
elif iftype_val == 'ae':
|
||||
choices_list = ['none', 'infra', 'oam', 'mgmt',
|
||||
data_choices, 'pxeboot']
|
||||
else:
|
||||
choices_list = ['infra', 'oam', 'mgmt', data_choices]
|
||||
|
||||
choices_list = flatten(choices_list)
|
||||
|
||||
for choice in choices_list:
|
||||
if choice not in used_choices:
|
||||
label = "{}".format(choice)
|
||||
net_type = (str(choice), label)
|
||||
networktype_choices.append(net_type)
|
||||
|
||||
self.fields['networktype'].choices = networktype_choices
|
||||
if not networktype_val:
|
||||
del kwargs['initial']['networktype']
|
||||
self.fields['networktype'].initial = ('none', 'none')
|
||||
|
||||
# Get the total possible number of VFs for SRIOV network type
|
||||
port_list = api.sysinv.host_port_list(self.request,
|
||||
host_uuid)
|
||||
for p in port_list:
|
||||
if p.interface_uuid == this_interface_id:
|
||||
if p.sriov_totalvfs:
|
||||
self.fields['sriov_totalvfs'].initial = p.sriov_totalvfs
|
||||
else:
|
||||
self.fields['sriov_totalvfs'].initial = 0
|
||||
break
|
||||
|
||||
initial_numvfs = kwargs['initial']['sriov_numvfs']
|
||||
if initial_numvfs:
|
||||
self.fields['sriov_numvfs'].initial = initial_numvfs
|
||||
else:
|
||||
self.fields['sriov_numvfs'].initial = 0
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(UpdateInterface, self).clean()
|
||||
|
||||
cleaned_data['iftype'] = cleaned_data.get('iftypedata')
|
||||
cleaned_data.pop('iftypedata', None)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
host_id = data['host_id']
|
||||
interface_id = data['id']
|
||||
host_uuid = data['ihost_uuid']
|
||||
|
||||
try:
|
||||
if data['ports']:
|
||||
del data['uses']
|
||||
else:
|
||||
uses = data['uses'][:]
|
||||
data['usesmodify'] = ','.join(uses)
|
||||
del data['ports']
|
||||
del data['uses']
|
||||
|
||||
del data['id']
|
||||
del data['host_id']
|
||||
del data['ihost_uuid']
|
||||
|
||||
if not data['vlan_id'] or data['iftype'] != 'vlan':
|
||||
del data['vlan_id']
|
||||
else:
|
||||
data['vlan_id'] = unicode(data['vlan_id'])
|
||||
|
||||
data['imtu'] = unicode(data['imtu'])
|
||||
|
||||
if data['iftype'] != 'ae':
|
||||
del data['txhashpolicy']
|
||||
del data['aemode']
|
||||
elif data['aemode'] == 'active_standby':
|
||||
del data['txhashpolicy']
|
||||
|
||||
if 'none' in data['networktype']:
|
||||
avail_port_list = api.sysinv.host_port_list(
|
||||
self.request, host_uuid)
|
||||
current_interface = api.sysinv.host_interface_get(
|
||||
self.request, interface_id)
|
||||
if data['iftype'] != 'ae' or data['iftype'] != 'vlan':
|
||||
for p in avail_port_list:
|
||||
if p.interface_uuid == current_interface.uuid:
|
||||
data['ifname'] = p.get_port_display_name()
|
||||
break
|
||||
|
||||
if any(nt in ['data', 'data-external'] for nt in
|
||||
[str(current_interface.networktype).split(",")]):
|
||||
data['providernetworks'] = 'none'
|
||||
|
||||
if not data['providernetworks']:
|
||||
del data['providernetworks']
|
||||
|
||||
if 'sriov_numvfs' in data:
|
||||
data['sriov_numvfs'] = unicode(data['sriov_numvfs'])
|
||||
|
||||
# Explicitly set iftype when user selects pci-pt or pci-sriov
|
||||
network_type = \
|
||||
flatten(list(nt) for nt in self.fields['networktype'].choices)
|
||||
if 'pci-passthrough' in network_type or \
|
||||
('pci-sriov' in network_type and data['sriov_numvfs']):
|
||||
current_interface = api.sysinv.host_interface_get(
|
||||
self.request, interface_id)
|
||||
if current_interface.iftype != 'ethernet':
|
||||
# Only ethernet interfaces can be pci-sriov
|
||||
msg = _('pci-passthrough or pci-sriov can only'
|
||||
' be set on ethernet interfaces')
|
||||
messages.error(request, msg)
|
||||
LOG.error(msg)
|
||||
# Redirect to failure pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
else:
|
||||
data['iftype'] = current_interface.iftype
|
||||
|
||||
del data['sriov_totalvfs']
|
||||
if 'pci-sriov' not in data['networktype']:
|
||||
del data['sriov_numvfs']
|
||||
|
||||
if data['networktype']:
|
||||
data['networktype'] = ",".join(data['networktype'])
|
||||
|
||||
interface = api.sysinv.host_interface_update(request, interface_id,
|
||||
**data)
|
||||
|
||||
# FIXME: this should be done under
|
||||
# the interface update API of sysinv
|
||||
# Update Ports' iinterface_uuid attribute
|
||||
# port_list = api.sysinv.host_port_list(request, host_uuid)
|
||||
# for p in port_list:
|
||||
# if p.uuid in ports:
|
||||
# pdata = { 'interface_uuid' : interface.uuid }
|
||||
# api.sysinv.host_port_update(request, p.uuid, **pdata)
|
||||
# elif p.interface_uuid == interface.uuid:
|
||||
# pdata = { 'interface_uuid' : '0' }
|
||||
# api.sysinv.host_port_update(request, p.uuid, **pdata)
|
||||
|
||||
msg = _('Interface "%s" was'
|
||||
' successfully updated.') % data['ifname']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return interface
|
||||
|
||||
except exc.ClientException as ce:
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, ce)
|
||||
LOG.error(ce)
|
||||
|
||||
# Redirect to failure page
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
|
||||
except Exception:
|
||||
msg = _('Failed to update interface "%s".') % data['ifname']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
86
cgcs_dashboard/dashboards/admin/inventory/interfaces/route/forms.py
Executable file
86
cgcs_dashboard/dashboards/admin/inventory/interfaces/route/forms.py
Executable file
@ -0,0 +1,86 @@
|
||||
# Copyright 2015 Wind River Systems, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import shortcuts
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import netaddr
|
||||
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateRoute(forms.SelfHandlingForm):
|
||||
|
||||
host_id = forms.CharField(widget=forms.HiddenInput())
|
||||
interface_id = forms.CharField(widget=forms.HiddenInput())
|
||||
success_url = 'horizon:admin:inventory:viewinterface'
|
||||
failure_url = 'horizon:admin:inventory:viewinterface'
|
||||
|
||||
network = forms.IPField(
|
||||
label=_("Network Address"),
|
||||
required=True,
|
||||
initial="",
|
||||
help_text=_("IP network address in CIDR format "
|
||||
"(e.g. 192.168.0.0/24, 2001:DB8::/48"),
|
||||
version=forms.IPv4 | forms.IPv6,
|
||||
mask=True)
|
||||
|
||||
gateway = forms.IPField(
|
||||
label=_("Gateway Address"),
|
||||
required=True,
|
||||
initial="",
|
||||
help_text=_("Gateway IP address "
|
||||
"(e.g. 192.168.0.1/24, 2001:DB8::1/48"),
|
||||
version=forms.IPv4 | forms.IPv6,
|
||||
mask=False)
|
||||
|
||||
metric = forms.IntegerField(
|
||||
label=_("Route Metric"),
|
||||
initial="1",
|
||||
required=True)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
ip_network = netaddr.IPNetwork(data['network'])
|
||||
body = {'interface_uuid': data['interface_id'],
|
||||
'network': str(ip_network.ip),
|
||||
'prefix': ip_network.prefixlen,
|
||||
'gateway': data['gateway'],
|
||||
'metric': data['metric']}
|
||||
route = api.sysinv.route_create(request, **body)
|
||||
msg = (_('Route to %(network)s/%(prefix)s via %(gateway)s was '
|
||||
'successfully created') % body)
|
||||
messages.success(request, msg)
|
||||
return route
|
||||
except Exception as e:
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, e)
|
||||
LOG.error(e)
|
||||
# Redirect to failure page
|
||||
redirect = reverse(self.failure_url,
|
||||
args=(data['host_id'], data['interface_id']))
|
||||
return shortcuts.redirect(redirect)
|
130
cgcs_dashboard/dashboards/admin/inventory/interfaces/route/tables.py
Executable file
130
cgcs_dashboard/dashboards/admin/inventory/interfaces/route/tables.py
Executable file
@ -0,0 +1,130 @@
|
||||
# Copyright 2015 Wind River Systems, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeleteRoute(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Route",
|
||||
u"Delete Routes",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Route",
|
||||
u"Deleted Routes",
|
||||
count
|
||||
)
|
||||
|
||||
def get_redirect_url(self):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
interface_id = self.table.kwargs['interface_id']
|
||||
return reverse('horizon:admin:inventory:viewinterface',
|
||||
args=[host_id, interface_id])
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.sysinv.route_delete(request, obj_id)
|
||||
except Exception:
|
||||
exceptions.handle(request, redirect=self.get_redirect_url())
|
||||
|
||||
|
||||
class CreateRoute(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Route")
|
||||
url = "horizon:admin:inventory:addroute"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
interface_id = self.table.kwargs['interface_id']
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id, interface_id))
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
interface = self.table.get_interface()
|
||||
if not interface:
|
||||
return False
|
||||
if interface.networktype not in ['data', 'control']:
|
||||
return False
|
||||
if interface.ipv4_mode in ['static']:
|
||||
return True
|
||||
if interface.ipv6_mode in ['static']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_network_column(route):
|
||||
network = getattr(route, 'network')
|
||||
prefix = getattr(route, 'prefix')
|
||||
return network + '/' + str(prefix)
|
||||
|
||||
|
||||
class RouteTable(tables.DataTable):
|
||||
network = tables.Column(get_network_column,
|
||||
verbose_name=_("Network"))
|
||||
gateway = tables.Column("gateway",
|
||||
verbose_name=_("Gateway"))
|
||||
metric = tables.Column("metric",
|
||||
verbose_name=_("Metric"))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return ("%(network)s/%(prefix)s via %(gateway)s" %
|
||||
{'network': datum.network,
|
||||
'prefix': datum.prefix,
|
||||
'gateway': datum.gateway})
|
||||
|
||||
class Meta(object):
|
||||
name = "routes"
|
||||
verbose_name = _("Route List")
|
||||
table_actions = (CreateRoute, DeleteRoute)
|
||||
row_actions = (DeleteRoute,)
|
||||
|
||||
def get_interface(self):
|
||||
if not hasattr(self, "_interface"):
|
||||
try:
|
||||
interface_id = self.kwargs["interface_id"]
|
||||
self._interface = api.sysinv.host_interface_get(
|
||||
self.request, interface_id)
|
||||
except Exception:
|
||||
redirect = reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],
|
||||
self.kwargs['interface_id'],))
|
||||
msg = _("Unable to retrieve interface details.")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return
|
||||
return self._interface
|
||||
|
||||
def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
|
||||
super(RouteTable, self).__init__(
|
||||
request, data=data, needs_form_wrapper=needs_form_wrapper,
|
||||
**kwargs)
|
51
cgcs_dashboard/dashboards/admin/inventory/interfaces/route/views.py
Executable file
51
cgcs_dashboard/dashboards/admin/inventory/interfaces/route/views.py
Executable file
@ -0,0 +1,51 @@
|
||||
# Copyright 2015 Wind River Systems, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
|
||||
from horizon import forms
|
||||
from openstack_dashboard.dashboards.admin.inventory.interfaces.route import \
|
||||
forms as route_forms
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = route_forms.CreateRoute
|
||||
template_name = 'admin/inventory/interfaces/route/create.html'
|
||||
success_url = 'horizon:admin:inventory:viewinterface'
|
||||
failure_url = 'horizon:admin:inventory:viewinterface'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],
|
||||
self.kwargs['interface_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],
|
||||
self.kwargs['interface_id'],))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
context['interface_id'] = self.kwargs['interface_id']
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
return {'interface_id': self.kwargs['interface_id'],
|
||||
'host_id': self.kwargs['host_id']}
|
211
cgcs_dashboard/dashboards/admin/inventory/interfaces/tables.py
Normal file
211
cgcs_dashboard/dashboards/admin/inventory/interfaces/tables.py
Normal file
@ -0,0 +1,211 @@
|
||||
#
|
||||
# Copyright (c) 2013-2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django.template import defaultfilters as filters
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
NETWORK_TYPES = ["oam", "infra", "mgmt", "pxeboot"]
|
||||
|
||||
|
||||
class DeleteInterface(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Interface",
|
||||
u"Delete Interfaces",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Interface",
|
||||
u"Deleted Interfaces",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, interface=None):
|
||||
host = self.table.kwargs['host']
|
||||
return (host._administrative == 'locked' and
|
||||
interface.iftype != 'ethernet')
|
||||
|
||||
def delete(self, request, interface_id):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
try:
|
||||
api.sysinv.host_interface_delete(request, interface_id)
|
||||
except Exception:
|
||||
msg = _('Failed to delete host %(hid)s interface %(iid)s') % {
|
||||
'hid': host_id, 'iid': interface_id}
|
||||
LOG.info(msg)
|
||||
redirect = reverse('horizon:admin:inventory:detail',
|
||||
args=(host_id,))
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class CreateInterfaceProfile(tables.LinkAction):
|
||||
name = "createProfile"
|
||||
verbose_name = _("Create Interface Profile")
|
||||
url = "horizon:admin:inventory:addinterfaceprofile"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
return not api.sysinv.is_system_mode_simplex(request)
|
||||
|
||||
|
||||
class CreateInterface(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Interface")
|
||||
url = "horizon:admin:inventory:addinterface"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
host = self.table.kwargs['host']
|
||||
|
||||
if host._administrative != 'locked':
|
||||
return False
|
||||
|
||||
count = 0
|
||||
for i in host.interfaces:
|
||||
if i.networktype:
|
||||
count = count + 1
|
||||
|
||||
if host.subfunctions and 'compute' not in host.subfunctions and \
|
||||
count >= len(NETWORK_TYPES):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class EditInterface(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit Interface")
|
||||
url = "horizon:admin:inventory:editinterface"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, interface=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id, interface.uuid))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
host = self.table.kwargs['host']
|
||||
return host._administrative == 'locked'
|
||||
|
||||
|
||||
def get_attributes(interface):
|
||||
attr_str = "MTU=%s" % interface.imtu
|
||||
if interface.iftype == 'ae':
|
||||
attr_str = "%s, AE_MODE=%s" % (attr_str, interface.aemode)
|
||||
if interface.aemode in ['balanced', '802.3ad']:
|
||||
attr_str = "%s, AE_XMIT_HASH_POLICY=%s" % (
|
||||
attr_str, interface.txhashpolicy)
|
||||
if (interface.networktype and
|
||||
any(network in ['data', 'data-external'] for network in
|
||||
interface.networktype.split(","))):
|
||||
attrs = [attr.strip() for attr in attr_str.split(",")]
|
||||
for a in attrs:
|
||||
if 'accelerated' in a:
|
||||
attrs.remove(a)
|
||||
attr_str = ",".join(attrs)
|
||||
|
||||
if 'False' in interface.dpdksupport:
|
||||
attr_str = "%s, accelerated=%s" % (attr_str, 'False')
|
||||
else:
|
||||
attr_str = "%s, accelerated=%s" % (attr_str, 'True')
|
||||
return attr_str
|
||||
|
||||
|
||||
def get_ports(interface):
|
||||
port_str_list = ", ".join(interface.portNameList)
|
||||
return port_str_list
|
||||
|
||||
|
||||
def get_port_neighbours(interface):
|
||||
return interface.portNeighbourList
|
||||
|
||||
|
||||
def get_uses(interface):
|
||||
uses_list = ", ".join(interface.uses)
|
||||
return uses_list
|
||||
|
||||
|
||||
def get_used_by(interface):
|
||||
used_by_list = ", ".join(interface.used_by)
|
||||
return used_by_list
|
||||
|
||||
|
||||
def get_link_url(interface):
|
||||
return reverse("horizon:admin:inventory:viewinterface",
|
||||
args=(interface.host_id, interface.uuid))
|
||||
|
||||
|
||||
class InterfacesTable(tables.DataTable):
|
||||
ifname = tables.Column('ifname',
|
||||
verbose_name=_('Name'),
|
||||
link=get_link_url)
|
||||
|
||||
networktype = tables.Column('networktype',
|
||||
verbose_name=_('Network Type'))
|
||||
|
||||
iftype = tables.Column('iftype',
|
||||
verbose_name=_('Type'))
|
||||
|
||||
vlan_id = tables.Column('vlan_id',
|
||||
verbose_name=_('Vlan ID'))
|
||||
|
||||
ports = tables.Column(get_ports,
|
||||
verbose_name=_('Port'))
|
||||
|
||||
port_neighbours = tables.Column(get_port_neighbours,
|
||||
verbose_name=_('Neighbors'),
|
||||
wrap_list=True,
|
||||
filters=(filters.unordered_list,))
|
||||
|
||||
uses = tables.Column(get_uses,
|
||||
verbose_name=_('Uses'))
|
||||
|
||||
used_by = tables.Column(get_used_by,
|
||||
verbose_name=_('Used By'))
|
||||
|
||||
providernetworks = tables.Column('providernetworks',
|
||||
verbose_name=_('Provider Network(s)'))
|
||||
attributes = tables.Column(get_attributes,
|
||||
verbose_name=_('Attributes'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(InterfacesTable, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.ifname
|
||||
|
||||
class Meta(object):
|
||||
name = "interfaces"
|
||||
verbose_name = _("Interfaces")
|
||||
multi_select = False
|
||||
table_actions = (CreateInterfaceProfile, CreateInterface,)
|
||||
row_actions = (EditInterface, DeleteInterface,)
|
41
cgcs_dashboard/dashboards/admin/inventory/interfaces/tabs.py
Executable file
41
cgcs_dashboard/dashboards/admin/inventory/interfaces/tabs.py
Executable file
@ -0,0 +1,41 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import tabs
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "admin/inventory/interfaces/_detail_overview.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
return {}
|
||||
|
||||
|
||||
class InterfaceDetailTabs(tabs.TabGroup):
|
||||
slug = "interface_details"
|
||||
tabs = (OverviewTab,)
|
397
cgcs_dashboard/dashboards/admin/inventory/interfaces/views.py
Executable file
397
cgcs_dashboard/dashboards/admin/inventory/interfaces/views.py
Executable file
@ -0,0 +1,397 @@
|
||||
#
|
||||
# Copyright (c) 2013-2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
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 tables
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.interfaces.address import \
|
||||
tables as address_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.interfaces.forms import \
|
||||
AddInterface
|
||||
from openstack_dashboard.dashboards.admin.inventory.interfaces.forms import \
|
||||
AddInterfaceProfile
|
||||
from openstack_dashboard.dashboards.admin.inventory.interfaces.forms import \
|
||||
UpdateInterface
|
||||
from openstack_dashboard.dashboards.admin.inventory.interfaces.route import \
|
||||
tables as route_tables
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_port_data(request, host_id, interface=None):
|
||||
port_data = []
|
||||
show_all_ports = True
|
||||
|
||||
try:
|
||||
if not interface:
|
||||
# Create case, host id is not UUID. Need to get the UUID in order
|
||||
# to retrieve the ports for this host
|
||||
host = api.sysinv.host_get(request, host_id)
|
||||
host_id = host.uuid
|
||||
else:
|
||||
if not interface.uses:
|
||||
show_all_ports = False
|
||||
|
||||
port_list = \
|
||||
api.sysinv.host_port_list(request, host_id)
|
||||
|
||||
if show_all_ports:
|
||||
# This is either a create or edit non-default interface
|
||||
# operation. Get the list of available ports and their
|
||||
# neighbours
|
||||
neighbour_list = \
|
||||
api.sysinv.host_lldpneighbour_list(request, host_id)
|
||||
interface_list = api.sysinv.host_interface_list(request, host_id)
|
||||
|
||||
for p in port_list:
|
||||
port_info = "%s (%s, %s, " % (p.get_port_display_name(),
|
||||
p.mac, p.pciaddr)
|
||||
interface_name = ''
|
||||
for i in interface_list:
|
||||
if p.interface_uuid == i.uuid:
|
||||
interface_name = i.ifname
|
||||
|
||||
if interface_name:
|
||||
port_info += interface_name + ")"
|
||||
else:
|
||||
port_info += _("none") + ")"
|
||||
|
||||
if p.bootp:
|
||||
port_info += " - bootif"
|
||||
|
||||
neighbour_info = []
|
||||
for n in neighbour_list:
|
||||
if p.uuid == n.port_uuid:
|
||||
if n.port_description:
|
||||
neighbour = "%s (%s)" % (
|
||||
n.port_identifier, n.port_description)
|
||||
else:
|
||||
neighbour = "%s" % n.port_identifier
|
||||
neighbour_info.append(neighbour)
|
||||
neighbour_info.sort()
|
||||
port_data_item = port_info, neighbour_info
|
||||
port_data.append(port_data_item)
|
||||
else:
|
||||
# Edit default-interface operation
|
||||
for p in port_list:
|
||||
# Since the port->default interface mapping is now strictly
|
||||
# 1:1, the below condition can only be met at most once for
|
||||
# the available ports
|
||||
if p.interface_uuid == interface.uuid:
|
||||
port_info = "%s (%s, %s, %s)" % (
|
||||
p.get_port_display_name(), p.mac, p.pciaddr,
|
||||
interface.ifname)
|
||||
|
||||
if p.bootp:
|
||||
port_info += " - bootif"
|
||||
# Retrieve the neighbours for the port
|
||||
neighbours = \
|
||||
api.sysinv.port_lldpneighbour_list(request, p.uuid)
|
||||
neighbour_info = []
|
||||
if neighbours:
|
||||
for n in neighbours:
|
||||
if n.port_description:
|
||||
neighbour = "%s (%s)" % (
|
||||
n.port_identifier, n.port_description)
|
||||
else:
|
||||
neighbour = "%s\n" % n.port_identifier
|
||||
neighbour_info.append(neighbour)
|
||||
neighbour_info.sort()
|
||||
port_data_item = port_info, neighbour_info
|
||||
port_data.append(port_data_item)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve port info details for host '
|
||||
'"%s".') % host_id, redirect=redirect)
|
||||
|
||||
return port_data
|
||||
|
||||
|
||||
class AddInterfaceView(forms.ModalFormView):
|
||||
form_class = AddInterface
|
||||
template_name = 'admin/inventory/interfaces/create.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddInterfaceView, self).get_context_data(**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
context['ports'] = get_port_data(self.request, self.kwargs['host_id'])
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddInterfaceView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, initial['host_id'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Unable to retrieve host.'))
|
||||
initial['ihost_uuid'] = host.uuid
|
||||
initial['host'] = host
|
||||
|
||||
# get SDN configuration status
|
||||
try:
|
||||
sdn_enabled = api.sysinv.get_sdn_enabled(self.request)
|
||||
sdn_l3_mode = api.sysinv.get_sdn_l3_mode_enabled(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve SDN configuration.'))
|
||||
initial['sdn_enabled'] = sdn_enabled
|
||||
initial['sdn_l3_mode_enabled'] = sdn_l3_mode
|
||||
return initial
|
||||
|
||||
|
||||
class AddInterfaceProfileView(forms.ModalFormView):
|
||||
form_class = AddInterfaceProfile
|
||||
template_name = 'admin/inventory/interfaces/createprofile.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_myhost_data(self):
|
||||
if not hasattr(self, "_host"):
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_id)
|
||||
|
||||
all_ports = api.sysinv.host_port_list(self.request, host.uuid)
|
||||
host.ports = [p for p in all_ports if p.interface_uuid]
|
||||
for p in host.ports:
|
||||
p.namedisplay = p.get_port_display_name()
|
||||
|
||||
host.interfaces = api.sysinv.host_interface_list(self.request,
|
||||
host.uuid)
|
||||
for i in host.interfaces:
|
||||
i.ports = [p.get_port_display_name()
|
||||
for p in all_ports if
|
||||
p.interface_uuid and p.interface_uuid == i.uuid]
|
||||
i.ports = ", ".join(i.ports)
|
||||
i.uses = ", ".join(i.uses)
|
||||
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'host "%s".') % host_id,
|
||||
redirect=redirect)
|
||||
self._host = host
|
||||
return self._host
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddInterfaceProfileView, self).get_context_data(
|
||||
**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
context['host'] = self.get_myhost_data()
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddInterfaceProfileView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
return initial
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdateInterface
|
||||
template_name = 'admin/inventory/interfaces/update.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
interface_id = self.kwargs['interface_id']
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
self._object = api.sysinv.host_interface_get(self.request,
|
||||
interface_id)
|
||||
self._object.host_id = host_id
|
||||
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:networks:detail",
|
||||
args=(self.kwargs['host_id'],))
|
||||
msg = _('Unable to retrieve interface details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
interface = self._get_object()
|
||||
context['interface_id'] = interface.uuid
|
||||
context['host_id'] = interface.host_id
|
||||
ports = get_port_data(self.request, interface.ihost_uuid, interface)
|
||||
if ports:
|
||||
context['ports'] = ports
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
interface = self._get_object()
|
||||
networktype = []
|
||||
if interface.networktype:
|
||||
for network in interface.networktype.split(","):
|
||||
networktype.append(str(network))
|
||||
providernetworks = []
|
||||
if interface.providernetworks:
|
||||
for pn in interface.providernetworks.split(","):
|
||||
providernetworks.append(str(pn))
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, interface.host_id)
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Unable to retrieve host.'))
|
||||
|
||||
# get SDN configuration status
|
||||
try:
|
||||
sdn_enabled, sdn_l3_mode = False, False
|
||||
sdn_enabled = api.sysinv.get_sdn_enabled(self.request)
|
||||
sdn_l3_mode = api.sysinv.get_sdn_l3_mode_enabled(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve SDN configuration.'))
|
||||
|
||||
return {'id': interface.uuid,
|
||||
'host_id': interface.host_id,
|
||||
'host': host,
|
||||
'ihost_uuid': interface.ihost_uuid,
|
||||
'ifname': interface.ifname,
|
||||
'iftype': interface.iftype,
|
||||
'aemode': interface.aemode,
|
||||
'txhashpolicy': interface.txhashpolicy,
|
||||
# 'ports': interface.ports,
|
||||
# 'uses': interface.uses,
|
||||
'networktype': networktype,
|
||||
'providernetworks_data': providernetworks,
|
||||
'providernetworks_data-external': providernetworks,
|
||||
'providernetworks_pci': providernetworks,
|
||||
'providernetworks_sriov': providernetworks,
|
||||
'sriov_numvfs': interface.sriov_numvfs,
|
||||
'imtu': interface.imtu,
|
||||
'ipv4_mode': getattr(interface, 'ipv4_mode', 'disabled'),
|
||||
'ipv4_pool': getattr(interface, 'ipv4_pool', None),
|
||||
'ipv6_mode': getattr(interface, 'ipv6_mode', 'disabled'),
|
||||
'ipv6_pool': getattr(interface, 'ipv6_pool', None),
|
||||
'sdn_enabled': sdn_enabled,
|
||||
'sdn_l3_mode_enabled': sdn_l3_mode}
|
||||
|
||||
|
||||
class DetailView(tables.MultiTableView):
|
||||
table_classes = (address_tables.AddressTable,
|
||||
route_tables.RouteTable)
|
||||
template_name = 'admin/inventory/interfaces/detail.html'
|
||||
failure_url = reverse_lazy('horizon:admin:inventory:detail')
|
||||
page_title = "{{ interface.ifname }}"
|
||||
|
||||
def get_addresses_data(self):
|
||||
try:
|
||||
interface_id = self.kwargs['interface_id']
|
||||
addresses = api.sysinv.address_list_by_interface(
|
||||
self.request, interface_id=interface_id)
|
||||
addresses.sort(key=lambda f: (f.address, f.prefix))
|
||||
except Exception:
|
||||
addresses = []
|
||||
msg = _('Address list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return addresses
|
||||
|
||||
def get_routes_data(self):
|
||||
try:
|
||||
interface_id = self.kwargs['interface_id']
|
||||
routes = api.sysinv.route_list_by_interface(
|
||||
self.request, interface_id=interface_id)
|
||||
routes.sort(key=lambda f: (f.network, f.prefix))
|
||||
except Exception:
|
||||
routes = []
|
||||
msg = _('Route list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return routes
|
||||
|
||||
def _get_address_pools(self):
|
||||
pools = api.sysinv.address_pool_list(self.request)
|
||||
return {p.uuid: p for p in pools}
|
||||
|
||||
def _add_pool_names(self, interface):
|
||||
pools = self._get_address_pools()
|
||||
if getattr(interface, 'ipv4_mode', '') == 'pool':
|
||||
interface.ipv4_pool_name = pools[interface.ipv4_pool].name
|
||||
if getattr(interface, 'ipv6_mode', '') == 'pool':
|
||||
interface.ipv6_pool_name = pools[interface.ipv6_pool].name
|
||||
return interface
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
interface_id = self.kwargs['interface_id']
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
self._object = api.sysinv.host_interface_get(self.request,
|
||||
interface_id)
|
||||
self._object.host_id = host_id
|
||||
self._object = self._add_pool_names(self._object)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:admin:inventory:detail",
|
||||
args=(self.kwargs['host_id'],))
|
||||
msg = _('Unable to retrieve interface details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
return self._object
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_hostname(self, host_uuid):
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_uuid)
|
||||
except Exception:
|
||||
host = {}
|
||||
msg = _('Unable to retrieve hostname details.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return host.hostname
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
interface = self._get_object()
|
||||
|
||||
hostname = self.get_hostname(interface.host_id)
|
||||
host_nav = hostname or "Unprovisioned Node"
|
||||
breadcrumb = [
|
||||
(host_nav, reverse('horizon:admin:inventory:detail',
|
||||
args=(interface.host_id,))),
|
||||
(_("Interfaces"), None)
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
||||
context['interface_id'] = interface.uuid
|
||||
context['host_id'] = interface.host_id
|
||||
context['interface'] = interface
|
||||
return context
|
0
cgcs_dashboard/dashboards/admin/inventory/lldp/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/lldp/__init__.py
Executable file
46
cgcs_dashboard/dashboards/admin/inventory/lldp/tables.py
Executable file
46
cgcs_dashboard/dashboards/admin/inventory/lldp/tables.py
Executable file
@ -0,0 +1,46 @@
|
||||
#
|
||||
# Copyright (c) 2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from horizon import tables
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_name(neighbour):
|
||||
return neighbour.get_local_port_display_name()
|
||||
|
||||
|
||||
class LldpNeighboursTable(tables.DataTable):
|
||||
name = tables.Column(get_name,
|
||||
verbose_name=_('Name'),
|
||||
link="horizon:admin:inventory:viewneighbour")
|
||||
port_identifier = tables.Column('port_identifier',
|
||||
verbose_name=_('Neighbor'))
|
||||
port_description = tables.Column('port_description',
|
||||
verbose_name=_('Port Description'))
|
||||
ttl = tables.Column('ttl', verbose_name=_('Time To Live (Rx)'))
|
||||
|
||||
system_name = tables.Column('system_name',
|
||||
verbose_name=_('System Name'),
|
||||
truncate=100)
|
||||
dot3_max_frame = tables.Column('dot3_max_frame',
|
||||
verbose_name=_('Max Frame Size'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.get_local_port_display_name()
|
||||
|
||||
class Meta(object):
|
||||
name = "neighbours"
|
||||
verbose_name = _("LLDP Neighbors")
|
||||
multi_select = False
|
101
cgcs_dashboard/dashboards/admin/inventory/lldp/views.py
Executable file
101
cgcs_dashboard/dashboards/admin/inventory/lldp/views.py
Executable file
@ -0,0 +1,101 @@
|
||||
#
|
||||
# Copyright (c) 2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DetailNeighbourView(views.HorizonTemplateView):
|
||||
template_name = 'admin/inventory/_detail_neighbour.html'
|
||||
page_title = "{{ localportname }}"
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
neighbour_uuid = self.kwargs['neighbour_uuid']
|
||||
try:
|
||||
self._object = \
|
||||
api.sysinv.host_lldpneighbour_get(self.request,
|
||||
neighbour_uuid)
|
||||
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
msg = _('Unable to retrieve LLDP neighbor details "%s".') \
|
||||
% neighbour_uuid
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailNeighbourView, self).get_context_data(**kwargs)
|
||||
# Context "neighbour" is referenced in _detail_neighbour.html
|
||||
# Reformat some attributes for better display
|
||||
neighbour = self._get_object()
|
||||
context['localportname'] = neighbour.get_local_port_display_name()
|
||||
if neighbour.system_capabilities:
|
||||
context['systemcaps'] = \
|
||||
neighbour.system_capabilities.replace(',', '\n')
|
||||
if neighbour.dot1_proto_vids:
|
||||
context['dot1provids'] = \
|
||||
neighbour.dot1_proto_vids.replace(',', '\n')
|
||||
if neighbour.dot1_vlan_names:
|
||||
context['vlannames'] = neighbour.dot1_vlan_names.replace(',', '\n')
|
||||
if neighbour.dot1_proto_ids:
|
||||
context['dot1protoids'] = \
|
||||
neighbour.dot1_proto_ids.replace(',', '\n')
|
||||
if neighbour.dot1_lag:
|
||||
context['dot1lag'] = neighbour.dot1_lag.replace(',', '\n')
|
||||
if neighbour.dot3_mac_status:
|
||||
# The dot3_mac_status has irregular format. An example is
|
||||
# auto-negotiation-capable=y,auto-negotiation-enabled=y,capability
|
||||
# =10-base-t-fd,100-base-t4,1000-base-t-fd,mau-type=4-pair-
|
||||
# category-5-utp-fd
|
||||
status_list = neighbour.dot3_mac_status.split(',')
|
||||
if status_list:
|
||||
mac_status = []
|
||||
for i in status_list:
|
||||
if "=" in i:
|
||||
mac_status.append(i)
|
||||
else:
|
||||
mac_status[-1] += "," + i
|
||||
context['dot3macstatus'] = "\n".join(mac_status)
|
||||
if neighbour.dot3_power_mdi:
|
||||
context['dot3powermdi'] = \
|
||||
neighbour.dot3_power_mdi.replace(',', '\n')
|
||||
context['neighbour'] = neighbour
|
||||
|
||||
hostname = self.get_hostname(neighbour.host_uuid)
|
||||
host_nav = hostname or "Unprovisioned Node"
|
||||
breadcrumb = [
|
||||
(host_nav, reverse('horizon:admin:inventory:detail',
|
||||
args=(neighbour.host_uuid,))),
|
||||
(_("Neighbors"), None)
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_hostname(self, host_uuid):
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_uuid)
|
||||
except Exception:
|
||||
host = {}
|
||||
msg = _('Unable to retrieve hostname details.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return host.hostname
|
0
cgcs_dashboard/dashboards/admin/inventory/memorys/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/memorys/__init__.py
Executable file
363
cgcs_dashboard/dashboards/admin/inventory/memorys/forms.py
Executable file
363
cgcs_dashboard/dashboards/admin/inventory/memorys/forms.py
Executable file
@ -0,0 +1,363 @@
|
||||
#
|
||||
# Copyright (c) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from cgtsclient import exc
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import shortcuts
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateMemory(forms.SelfHandlingForm):
|
||||
host = forms.CharField(label=_("host"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
host_id = forms.CharField(label=_("host_id"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
platform_memory = forms.CharField(
|
||||
label=_("Platform Memory for Node 0"),
|
||||
required=False)
|
||||
|
||||
vm_hugepages_nr_2M = forms.CharField(
|
||||
label=_("# of VM 2M Hugepages Node 0"),
|
||||
required=False)
|
||||
|
||||
vm_hugepages_nr_1G = forms.CharField(
|
||||
label=_("# of VM 1G Hugepages Node 0"),
|
||||
required=False)
|
||||
|
||||
platform_memory_two = forms.CharField(
|
||||
label=_("Platform Memory for Node 1"),
|
||||
required=False)
|
||||
|
||||
vm_hugepages_nr_2M_two = forms.CharField(
|
||||
label=_("# of VM 2M Hugepages Node 1"),
|
||||
required=False)
|
||||
|
||||
vm_hugepages_nr_1G_two = forms.CharField(
|
||||
label=_("# of VM 1G Hugepages Node 1"),
|
||||
required=False)
|
||||
|
||||
platform_memory_three = forms.CharField(
|
||||
label=_("Platform Memory for Node 2"),
|
||||
required=False)
|
||||
|
||||
vm_hugepages_nr_2M_three = forms.CharField(
|
||||
label=_("# of VM 2M Hugepages Node 2"),
|
||||
required=False)
|
||||
|
||||
vm_hugepages_nr_1G_three = forms.CharField(
|
||||
label=_("# of VM 1G Hugepages Node 2"),
|
||||
required=False)
|
||||
|
||||
platform_memory_four = forms.CharField(
|
||||
label=_("Platform Memory for Node 3"),
|
||||
required=False)
|
||||
|
||||
vm_hugepages_nr_2M_four = forms.CharField(
|
||||
label=_("# of VM 2M Hugepages Node 3"),
|
||||
required=False)
|
||||
|
||||
vm_hugepages_nr_1G_four = forms.CharField(
|
||||
label=_("# of VM 1G Hugepages Node 3"),
|
||||
required=False)
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UpdateMemory, self).__init__(request, *args, **kwargs)
|
||||
|
||||
self.host = kwargs['initial']['host']
|
||||
|
||||
memory_fieldsets = [
|
||||
{
|
||||
'platform_memory': self.fields['platform_memory'],
|
||||
'vm_hugepages_nr_2M': self.fields['vm_hugepages_nr_2M'],
|
||||
'vm_hugepages_nr_1G': self.fields['vm_hugepages_nr_1G']
|
||||
},
|
||||
{
|
||||
'platform_memory': self.fields['platform_memory_two'],
|
||||
'vm_hugepages_nr_2M': self.fields['vm_hugepages_nr_2M_two'],
|
||||
'vm_hugepages_nr_1G': self.fields['vm_hugepages_nr_1G_two']
|
||||
},
|
||||
{
|
||||
'platform_memory': self.fields['platform_memory_three'],
|
||||
'vm_hugepages_nr_2M': self.fields['vm_hugepages_nr_2M_three'],
|
||||
'vm_hugepages_nr_1G': self.fields['vm_hugepages_nr_1G_three']
|
||||
},
|
||||
{
|
||||
'platform_memory': self.fields['platform_memory_four'],
|
||||
'vm_hugepages_nr_2M': self.fields['vm_hugepages_nr_2M_four'],
|
||||
'vm_hugepages_nr_1G': self.fields['vm_hugepages_nr_1G_four']
|
||||
}
|
||||
]
|
||||
|
||||
count = 0
|
||||
for m in self.host.memorys:
|
||||
count = count + 1
|
||||
for n in self.host.nodes:
|
||||
if m.inode_uuid == n.uuid:
|
||||
field_set = memory_fieldsets[int(n.numa_node)]
|
||||
platform_field = field_set['platform_memory']
|
||||
platform_field.help_text = \
|
||||
'Minimum platform memory(MiB): ' + \
|
||||
str(m.minimum_platform_reserved_mib)
|
||||
|
||||
platform_field.initial = str(m.platform_reserved_mib)
|
||||
|
||||
vm_2M_field = field_set['vm_hugepages_nr_2M']
|
||||
vm_2M_field.help_text = \
|
||||
'Maximum 2M pages: ' + \
|
||||
str(m.vm_hugepages_possible_2M)
|
||||
|
||||
if m.vm_hugepages_nr_2M_pending:
|
||||
vm_2M_field.initial = str(m.vm_hugepages_nr_2M_pending)
|
||||
elif m.vm_hugepages_nr_2M:
|
||||
vm_2M_field.initial = str(m.vm_hugepages_nr_2M)
|
||||
else:
|
||||
vm_2M_field.initial = '0'
|
||||
|
||||
vm_1G_field = field_set['vm_hugepages_nr_1G']
|
||||
vm_1g_supported = m.vm_hugepages_use_1G != 'False'
|
||||
if vm_1g_supported:
|
||||
help_msg = 'Maximum 1G pages: ' + \
|
||||
str(m.vm_hugepages_possible_1G)
|
||||
else:
|
||||
help_msg = 'This node does not support 1G hugepages'
|
||||
|
||||
vm_1G_field.help_text = help_msg
|
||||
|
||||
if m.vm_hugepages_nr_1G_pending:
|
||||
vm_1G_field.initial = str(m.vm_hugepages_nr_1G_pending)
|
||||
elif m.vm_hugepages_nr_1G:
|
||||
vm_1G_field.initial = str(m.vm_hugepages_nr_1G)
|
||||
elif vm_1g_supported:
|
||||
vm_1G_field.initial = '0'
|
||||
else:
|
||||
vm_1G_field.initial = ''
|
||||
|
||||
if not vm_1g_supported:
|
||||
vm_1G_field.widget.attrs['disabled'] = 'disabled'
|
||||
|
||||
break
|
||||
|
||||
while count < 4:
|
||||
field_set = memory_fieldsets[count]
|
||||
field_set['platform_memory'].widget = \
|
||||
forms.widgets.HiddenInput()
|
||||
field_set['vm_hugepages_nr_2M'].widget = \
|
||||
forms.widgets.HiddenInput()
|
||||
field_set['vm_hugepages_nr_1G'].widget = \
|
||||
forms.widgets.HiddenInput()
|
||||
count += 1
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(UpdateMemory, self).clean()
|
||||
# host_id = cleaned_data.get('host_id')
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
|
||||
host_id = data['host_id']
|
||||
del data['host_id']
|
||||
del data['host']
|
||||
|
||||
node = []
|
||||
node.append('node0')
|
||||
|
||||
if data['platform_memory_two'] or \
|
||||
data['vm_hugepages_nr_2M_two'] or \
|
||||
data['vm_hugepages_nr_1G_two']:
|
||||
node.append('node1')
|
||||
|
||||
if data['platform_memory_three'] or \
|
||||
data['vm_hugepages_nr_2M_three'] or \
|
||||
data['vm_hugepages_nr_1G_three']:
|
||||
node.append('node2')
|
||||
|
||||
if data['platform_memory_four'] or \
|
||||
data['vm_hugepages_nr_2M_four'] or \
|
||||
data['vm_hugepages_nr_1G_four']:
|
||||
node.append('node3')
|
||||
|
||||
# host = api.sysinv.host_get(request, host_id)
|
||||
pages_1G = {}
|
||||
pages_2M = {}
|
||||
plat_mem = {}
|
||||
|
||||
# Node 0 arguments
|
||||
if not data['platform_memory']:
|
||||
del data['platform_memory']
|
||||
else:
|
||||
plat_mem['node0'] = data['platform_memory']
|
||||
|
||||
if not data['vm_hugepages_nr_2M']:
|
||||
del data['vm_hugepages_nr_2M']
|
||||
else:
|
||||
pages_2M['node0'] = data['vm_hugepages_nr_2M']
|
||||
|
||||
if not data['vm_hugepages_nr_1G']:
|
||||
del data['vm_hugepages_nr_1G']
|
||||
else:
|
||||
pages_1G['node0'] = data['vm_hugepages_nr_1G']
|
||||
|
||||
# Node 1 arguments
|
||||
if not data['platform_memory_two']:
|
||||
del data['platform_memory_two']
|
||||
else:
|
||||
plat_mem['node1'] = data['platform_memory_two']
|
||||
|
||||
if not data['vm_hugepages_nr_2M_two']:
|
||||
del data['vm_hugepages_nr_2M_two']
|
||||
else:
|
||||
pages_2M['node1'] = data['vm_hugepages_nr_2M_two']
|
||||
|
||||
if not data['vm_hugepages_nr_1G_two']:
|
||||
del data['vm_hugepages_nr_1G_two']
|
||||
else:
|
||||
pages_1G['node1'] = data['vm_hugepages_nr_1G_two']
|
||||
|
||||
# Node 2 arguments
|
||||
if not data['platform_memory_three']:
|
||||
del data['platform_memory_three']
|
||||
else:
|
||||
plat_mem['node2'] = data['platform_memory_three']
|
||||
|
||||
if not data['vm_hugepages_nr_2M_three']:
|
||||
del data['vm_hugepages_nr_2M_three']
|
||||
else:
|
||||
pages_2M['node2'] = data['vm_hugepages_nr_2M_three']
|
||||
|
||||
if not data['vm_hugepages_nr_1G_three']:
|
||||
del data['vm_hugepages_nr_1G_three']
|
||||
else:
|
||||
pages_1G['node2'] = data['vm_hugepages_nr_1G_three']
|
||||
|
||||
# Node 3 arguments
|
||||
if not data['platform_memory_four']:
|
||||
del data['platform_memory_four']
|
||||
else:
|
||||
plat_mem['node3'] = data['platform_memory_four']
|
||||
|
||||
if not data['vm_hugepages_nr_2M_four']:
|
||||
del data['vm_hugepages_nr_2M_four']
|
||||
else:
|
||||
pages_2M['node3'] = data['vm_hugepages_nr_2M_four']
|
||||
|
||||
if not data['vm_hugepages_nr_1G_four']:
|
||||
del data['vm_hugepages_nr_1G_four']
|
||||
else:
|
||||
pages_1G['node3'] = data['vm_hugepages_nr_1G_four']
|
||||
|
||||
try:
|
||||
for nd in node:
|
||||
node_found = False
|
||||
for m in self.host.memorys:
|
||||
for n in self.host.nodes:
|
||||
if m.inode_uuid == n.uuid:
|
||||
if int(n.numa_node) == int(node.index(nd)):
|
||||
node_found = True
|
||||
break
|
||||
if node_found:
|
||||
break
|
||||
|
||||
if node_found:
|
||||
new_data = {}
|
||||
if nd in plat_mem:
|
||||
new_data['platform_reserved_mib'] = plat_mem[nd]
|
||||
if nd in pages_2M:
|
||||
new_data['vm_hugepages_nr_2M_pending'] = pages_2M[nd]
|
||||
if nd in pages_1G:
|
||||
new_data['vm_hugepages_nr_1G_pending'] = pages_1G[nd]
|
||||
|
||||
if new_data:
|
||||
api.sysinv.host_memory_update(request, m.uuid,
|
||||
**new_data)
|
||||
|
||||
else:
|
||||
msg = _('Failed to find %s') % nd
|
||||
messages.error(request, msg)
|
||||
LOG.error(msg)
|
||||
# Redirect to failure pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
|
||||
msg = _('Memory allocation has been successfully '
|
||||
'updated.')
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return self.host.memorys
|
||||
except exc.ClientException as ce:
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, ce)
|
||||
LOG.error(ce)
|
||||
|
||||
# Redirect to failure pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
|
||||
except Exception:
|
||||
msg = _('Failed to update memory allocation')
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class AddMemoryProfile(forms.SelfHandlingForm):
|
||||
host_id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
profilename = forms.CharField(label=_("Memory Profile Name"),
|
||||
required=True)
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddMemoryProfile, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddMemoryProfile, self).clean()
|
||||
# host_id = cleaned_data.get('host_id')
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
|
||||
memoryProfileName = data['profilename']
|
||||
try:
|
||||
memoryProfile = api.sysinv.host_memprofile_create(request, **data)
|
||||
msg = _('Memory Profile "%s" was successfully created.') % \
|
||||
memoryProfileName
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return memoryProfile
|
||||
except exc.ClientException as ce:
|
||||
# Display REST API error message on UI
|
||||
messages.error(request, ce)
|
||||
LOG.error(ce)
|
||||
|
||||
# Redirect to failure pg
|
||||
redirect = reverse(self.failure_url, args=[data['host_id']])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception:
|
||||
msg = _('Failed to create memory profile "%s".') % \
|
||||
memoryProfileName
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url,
|
||||
args=[data['host_id']])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
99
cgcs_dashboard/dashboards/admin/inventory/memorys/tables.py
Normal file
99
cgcs_dashboard/dashboards/admin/inventory/memorys/tables.py
Normal file
@ -0,0 +1,99 @@
|
||||
#
|
||||
# Copyright (c) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateMemory(tables.LinkAction):
|
||||
name = "updatememory"
|
||||
verbose_name = _("Update Memory")
|
||||
url = "horizon:admin:inventory:updatememory"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, memory=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, memory=None):
|
||||
host = self.table.kwargs['host']
|
||||
return (host._administrative == 'locked' and
|
||||
host.subfunctions and
|
||||
'compute' in host.subfunctions)
|
||||
|
||||
|
||||
class CreateMemoryProfile(tables.LinkAction):
|
||||
name = "createMemoryProfile"
|
||||
verbose_name = _("Create Memory Profile")
|
||||
url = "horizon:admin:inventory:addmemoryprofile"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
host = self.table.kwargs['host']
|
||||
if host.subfunctions and 'compute' not in host.subfunctions:
|
||||
return False
|
||||
return (host.invprovision == 'provisioned' and
|
||||
not api.sysinv.is_system_mode_simplex(request))
|
||||
|
||||
|
||||
def get_processor_memory(memory):
|
||||
if memory.hugepages_configured == 'True':
|
||||
template_name = \
|
||||
'admin/inventory/memorys/_memoryfunction_hugepages.html'
|
||||
else:
|
||||
template_name = \
|
||||
'admin/inventory/memorys/_memoryfunction_hugepages_other.html'
|
||||
context = {"memory": memory}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
def get_vswitch_hugepages(memory):
|
||||
template_name = 'admin/inventory/memorys/_vswitchfunction_hugepages.html'
|
||||
context = {"memory": memory}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
def get_vm_hugepages(memory):
|
||||
template_name = 'admin/inventory/memorys/_vmfunction_hugepages.html'
|
||||
context = {"memory": memory}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
class MemorysTable(tables.DataTable):
|
||||
processor = tables.Column('numa_node',
|
||||
verbose_name=_('Processor'))
|
||||
|
||||
memory = tables.Column(get_processor_memory,
|
||||
verbose_name=_('Memory'))
|
||||
|
||||
vswitch_huge = tables.Column(get_vswitch_hugepages,
|
||||
verbose_name=_('VSwitch Huge Pages'))
|
||||
|
||||
vm_huge = tables.Column(get_vm_hugepages,
|
||||
verbose_name=_('VM Pages'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
class Meta(object):
|
||||
name = "memorys"
|
||||
verbose_name = _("Memory")
|
||||
multi_select = False
|
||||
table_actions = (UpdateMemory, CreateMemoryProfile,)
|
117
cgcs_dashboard/dashboards/admin/inventory/memorys/views.py
Executable file
117
cgcs_dashboard/dashboards/admin/inventory/memorys/views.py
Executable file
@ -0,0 +1,117 @@
|
||||
#
|
||||
# Copyright (c) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.memorys.forms import \
|
||||
AddMemoryProfile
|
||||
from openstack_dashboard.dashboards.admin.inventory.memorys.forms import \
|
||||
UpdateMemory
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateMemoryView(forms.ModalFormView):
|
||||
form_class = UpdateMemory
|
||||
template_name = 'admin/inventory/memorys/edit_hp_memory.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
context_object_name = "memorys"
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_id)
|
||||
host.memorys = api.sysinv.host_memory_list(self.request,
|
||||
host.uuid)
|
||||
host.nodes = \
|
||||
api.sysinv.host_node_list(self.request, host.uuid)
|
||||
self._object = host
|
||||
self._object.host_id = host_id
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
redirect = reverse("horizon:project:networks:detail",
|
||||
args=(host_id))
|
||||
msg = _('Unable to retrieve memory details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateMemoryView, self).get_context_data(
|
||||
**kwargs)
|
||||
host = self._get_object()
|
||||
context['host_id'] = host.host_id
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
host = self._get_object()
|
||||
|
||||
return {'host': host,
|
||||
'host_id': host.host_id, }
|
||||
|
||||
|
||||
class AddMemoryProfileView(forms.ModalFormView):
|
||||
form_class = AddMemoryProfile
|
||||
template_name = 'admin/inventory/memorys/createprofile.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_myhost_data(self):
|
||||
if not hasattr(self, "_host"):
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_id)
|
||||
host.nodes = api.sysinv.host_node_list(self.request, host.uuid)
|
||||
host.memory = \
|
||||
api.sysinv.host_memory_list(self.request, host.uuid)
|
||||
|
||||
numa_node_tuple_list = []
|
||||
for m in host.memory:
|
||||
node = api.sysinv.host_node_get(self.request, m.inode_uuid)
|
||||
numa_node_tuple_list.append((node.numa_node, m))
|
||||
|
||||
host.numa_nodes = numa_node_tuple_list
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'host "%s".') % host_id,
|
||||
redirect=redirect)
|
||||
self._host = host
|
||||
return self._host
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddMemoryProfileView, self).get_context_data(**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
context['host'] = self.get_myhost_data()
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddMemoryProfileView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
return initial
|
37
cgcs_dashboard/dashboards/admin/inventory/panel.py
Executable file
37
cgcs_dashboard/dashboards/admin/inventory/panel.py
Executable file
@ -0,0 +1,37 @@
|
||||
#
|
||||
# Copyright (c) 2013-2016 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.dashboards.admin import dashboard
|
||||
|
||||
|
||||
class Inventory(horizon.Panel):
|
||||
name = _("Host Inventory")
|
||||
slug = 'inventory'
|
||||
# permissions = ('openstack.roles.admin',)
|
||||
permissions = ('openstack.services.platform',)
|
||||
|
||||
def allowed(self, context):
|
||||
if not base.is_service_enabled(context['request'], 'platform'):
|
||||
return False
|
||||
else:
|
||||
return super(Inventory, self).allowed(context)
|
||||
|
||||
def nav(self, context):
|
||||
if not base.is_service_enabled(context['request'], 'platform'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
dashboard.Admin.register(Inventory)
|
0
cgcs_dashboard/dashboards/admin/inventory/ports/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/ports/__init__.py
Executable file
123
cgcs_dashboard/dashboards/admin/inventory/ports/forms.py
Executable file
123
cgcs_dashboard/dashboards/admin/inventory/ports/forms.py
Executable file
@ -0,0 +1,123 @@
|
||||
#
|
||||
# Copyright (c) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
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 openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdatePort(forms.SelfHandlingForm):
|
||||
SPEED_CHOICES = (
|
||||
('10', _(" 10baseT")),
|
||||
('100', _(" 100baseT")),
|
||||
('1000', _(" 1000baseT")),
|
||||
('10000', _("10000baseT")),
|
||||
)
|
||||
|
||||
AUTO_NEG_CHOICES = (
|
||||
('yes', _("yes")),
|
||||
('no', _("no")),
|
||||
('na', _("na")),
|
||||
)
|
||||
|
||||
host_uuid = forms.CharField(label=_("host_uuid"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
host_id = forms.CharField(label=_("host_id"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
id = forms.CharField(label=_("id"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
name = forms.CharField(label=_("Name"),
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
newname = forms.CharField(label=_("Name"),
|
||||
required=False)
|
||||
oldname = forms.CharField(label=_("Name"),
|
||||
required=False,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
autoneg = forms.ChoiceField(label=_("Hidden Auto Neg"),
|
||||
required=False,
|
||||
choices=AUTO_NEG_CHOICES,
|
||||
widget=forms.widgets.HiddenInput)
|
||||
autonegbool = forms.BooleanField(label=_("Auto Negotiation"),
|
||||
required=False)
|
||||
|
||||
# Configurable Speed will be added later
|
||||
#
|
||||
# speed = forms.ChoiceField(label=_("Speed"),
|
||||
# initial='speed',
|
||||
# choices=SPEED_CHOICES,
|
||||
# required=False)
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UpdatePort, self).__init__(*args, **kwargs)
|
||||
|
||||
name = kwargs['initial']['name']
|
||||
if name:
|
||||
self.fields['newname'].widget = forms.widgets.HiddenInput()
|
||||
else:
|
||||
self.fields['name'].widget = forms.widgets.HiddenInput()
|
||||
|
||||
autoneg = kwargs['initial']['autoneg']
|
||||
if autoneg.lower() == 'na':
|
||||
self.fields['autonegbool'].widget = forms.widgets.HiddenInput()
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(UpdatePort, self).clean()
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
deviceName = data['newname']
|
||||
if data['name']:
|
||||
deviceName = data['name']
|
||||
elif data['newname'] != data['oldname']:
|
||||
data['namedisplay'] = data['newname']
|
||||
del data['newname']
|
||||
del data['oldname']
|
||||
|
||||
if data['autoneg'] != 'na':
|
||||
if data['autonegbool']:
|
||||
data['autoneg'] = 'Yes'
|
||||
else:
|
||||
data['autoneg'] = 'No'
|
||||
del data['autonegbool']
|
||||
|
||||
host_id = data['host_id']
|
||||
del data['host_id']
|
||||
|
||||
port_id = data['id']
|
||||
del data['id']
|
||||
|
||||
try:
|
||||
port = api.sysinv.host_port_update(request, port_id, **data)
|
||||
msg = _('Port "%s" was successfully updated.') % deviceName
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return port
|
||||
except Exception:
|
||||
msg = _('Failed to update port "%s".') % deviceName
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
89
cgcs_dashboard/dashboards/admin/inventory/ports/tables.py
Executable file
89
cgcs_dashboard/dashboards/admin/inventory/ports/tables.py
Executable file
@ -0,0 +1,89 @@
|
||||
#
|
||||
# Copyright (c) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdatePort(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit Port")
|
||||
url = "horizon:admin:inventory:editport"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, port):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id, port.uuid))
|
||||
|
||||
def allowed(self, request, port=None):
|
||||
host = self.table.kwargs['host']
|
||||
return host._administrative == 'locked'
|
||||
|
||||
|
||||
def get_devicetype(port):
|
||||
template_name = 'admin/inventory/ports/_ports_devicetype.html'
|
||||
context = {"port": port}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
def get_name(port):
|
||||
return port.get_port_display_name()
|
||||
|
||||
|
||||
def get_bootp(port):
|
||||
if port.bootp:
|
||||
return port.bootp
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_link_url(port):
|
||||
return reverse("horizon:admin:inventory:viewport",
|
||||
args=(port.host_uuid, port.uuid))
|
||||
|
||||
|
||||
class PortsTable(tables.DataTable):
|
||||
name = tables.Column(get_name,
|
||||
verbose_name=_('Name'),
|
||||
link=get_link_url)
|
||||
|
||||
mac = tables.Column('mac',
|
||||
verbose_name=_('MAC Address'))
|
||||
pciaddr = tables.Column('pciaddr',
|
||||
verbose_name=_('PCI Address'))
|
||||
numa_node = tables.Column('numa_node',
|
||||
verbose_name=_('Processor'))
|
||||
autoneg = tables.Column('autoneg',
|
||||
verbose_name=_('Auto Negotiation'))
|
||||
# speed = tables.Column('speed',
|
||||
# verbose_name=_('Speed (Mbps)'))
|
||||
bootp = tables.Column(get_bootp,
|
||||
verbose_name=_('Boot Interface'))
|
||||
dpdksupport = tables.Column('dpdksupport',
|
||||
verbose_name=_('Accelerated'))
|
||||
devicetype = tables.Column(get_devicetype,
|
||||
verbose_name=_('Device Type'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.get_port_display_name()
|
||||
|
||||
class Meta(object):
|
||||
name = "ports"
|
||||
verbose_name = _("Ports")
|
||||
multi_select = False
|
||||
# row_actions = (UpdatePort,)
|
41
cgcs_dashboard/dashboards/admin/inventory/ports/tabs.py
Executable file
41
cgcs_dashboard/dashboards/admin/inventory/ports/tabs.py
Executable file
@ -0,0 +1,41 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import tabs
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "admin/inventory/ports/_detail_overview.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
return {}
|
||||
|
||||
|
||||
class PortDetailTabs(tabs.TabGroup):
|
||||
slug = "port_details"
|
||||
tabs = (OverviewTab,)
|
128
cgcs_dashboard/dashboards/admin/inventory/ports/views.py
Executable file
128
cgcs_dashboard/dashboards/admin/inventory/ports/views.py
Executable file
@ -0,0 +1,128 @@
|
||||
#
|
||||
# Copyright (c) 2013-2014 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.ports.forms import \
|
||||
UpdatePort
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdatePort
|
||||
template_name = 'admin/inventory/ports/update.html'
|
||||
context_object_name = 'port'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
port_id = self.kwargs['port_id']
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
self._object = api.sysinv.host_port_get(self.request, port_id)
|
||||
self._object.host_id = host_id
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:networks:detail",
|
||||
args=(host_id))
|
||||
msg = _('Unable to retrieve port details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
port = self._get_object()
|
||||
context['port_id'] = port.uuid
|
||||
context['host_id'] = port.host_id
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
port = self._get_object()
|
||||
name = port.get_port_display_name()
|
||||
if port.autoneg:
|
||||
autonegbool = (port.autoneg.lower() == 'yes')
|
||||
autoneg = port.autoneg.lower()
|
||||
else:
|
||||
autonegbool = False
|
||||
autoneg = 'na'
|
||||
return {'host_uuid': port.host_uuid,
|
||||
'host_id': port.host_id,
|
||||
'id': port.uuid,
|
||||
'name': port.name,
|
||||
'newname': name,
|
||||
'oldname': name,
|
||||
# 'speed': port.speed, # to be added in future
|
||||
'autoneg': autoneg,
|
||||
'autonegbool': autonegbool}
|
||||
|
||||
|
||||
class DetailView(views.HorizonTemplateView):
|
||||
template_name = 'admin/inventory/ports/detail.html'
|
||||
page_title = '{% if port.name %} {{ port.name }}' \
|
||||
'{% else %}{{ port.namedisplay }}' \
|
||||
'{% endif %}'
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
port_id = self.kwargs['port_id']
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
self._object = api.sysinv.host_port_get(self.request,
|
||||
port_id)
|
||||
self._object.host_id = host_id
|
||||
|
||||
except Exception:
|
||||
redirect = reverse("horizon:admin:inventory:detail",
|
||||
args=(self.kwargs['host_id'],))
|
||||
msg = _('Unable to retrieve port details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
return self._object
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_hostname(self, host_uuid):
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_uuid)
|
||||
except Exception:
|
||||
host = {}
|
||||
msg = _('Unable to retrieve hostname details.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return host.hostname
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
port = self._get_object()
|
||||
|
||||
hostname = self.get_hostname(port.host_id)
|
||||
host_nav = hostname or "Unprovisioned Node"
|
||||
breadcrumb = [
|
||||
(host_nav, reverse('horizon:admin:inventory:detail',
|
||||
args=(port.host_id,))),
|
||||
(_("Ports"), None)
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
||||
context['port_id'] = port.uuid
|
||||
context['host_id'] = port.host_id
|
||||
context['port'] = port
|
||||
return context
|
235
cgcs_dashboard/dashboards/admin/inventory/sensors/forms.py
Normal file
235
cgcs_dashboard/dashboards/admin/inventory/sensors/forms.py
Normal file
@ -0,0 +1,235 @@
|
||||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from cgtsclient import exc
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import shortcuts
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddSensorGroup(forms.SelfHandlingForm):
|
||||
|
||||
DATA_TYPE_CHOICES = (
|
||||
(None, _("<Select Sensor Data type>")),
|
||||
('analog', _("Analog Sensor")),
|
||||
('discrete', _("Discrete Sensor")),
|
||||
)
|
||||
|
||||
host_uuid = forms.CharField(label=_("host_uuid"),
|
||||
initial='host_uuid',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
hostname = forms.CharField(label=_("Hostname"),
|
||||
initial='hostname',
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly'}))
|
||||
|
||||
sensorgroup_name = forms.CharField(label=_("Sensor Group Name"),
|
||||
required=True,
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
|
||||
sensorgroup_datatype = forms.ChoiceField(
|
||||
label=_("Sensor Group Data Type"),
|
||||
required=True,
|
||||
choices=DATA_TYPE_CHOICES,
|
||||
widget=forms.Select(
|
||||
attrs={'class': 'switchable',
|
||||
'data-slug': 'datatype'}))
|
||||
|
||||
sensortype = forms.CharField(label=_("Sensor Type"),
|
||||
required=True,
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddSensorGroup, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddSensorGroup, self).clean()
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
host_id = data['host_id']
|
||||
|
||||
try:
|
||||
del data['host_id']
|
||||
del data['hostname']
|
||||
|
||||
# The REST API takes care of creating the sensorgroup and assoc
|
||||
sensorgroup = api.sysinv.host_sensorgroup_create(request, **data)
|
||||
|
||||
msg = _('Sensor group was successfully created.')
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
return sensorgroup
|
||||
except exc.ClientException as ce:
|
||||
msg = _('Failed to create sensor group.')
|
||||
LOG.info(msg)
|
||||
LOG.error(ce)
|
||||
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, ce)
|
||||
|
||||
# Redirect to host details pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception as e:
|
||||
msg = _('Failed to create sensor group.')
|
||||
LOG.info(msg)
|
||||
LOG.error(e)
|
||||
|
||||
# if not a rest API error, throw default
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return exceptions.handle(request, message=e, redirect=redirect)
|
||||
|
||||
|
||||
class UpdateSensorGroup(forms.SelfHandlingForm):
|
||||
|
||||
DATA_TYPE_CHOICES = (
|
||||
('analog', _("Analog Sensor")),
|
||||
('discrete', _("Discrete Sensor")),
|
||||
)
|
||||
|
||||
id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
|
||||
sensorgroupname = forms.CharField(
|
||||
label=_("SensorGroup Name"),
|
||||
required=False,
|
||||
widget=forms.widgets.TextInput(
|
||||
attrs={
|
||||
'class': 'switchable',
|
||||
'readonly': 'readonly',
|
||||
'data-slug': 'sensorgroupname'}))
|
||||
|
||||
sensortype = forms.CharField(
|
||||
label=_("SensorType"),
|
||||
required=False,
|
||||
widget=forms.widgets.TextInput(
|
||||
attrs={
|
||||
'class': 'switchable',
|
||||
'readonly': 'readonly',
|
||||
'data-slug': 'sensortype'}))
|
||||
|
||||
audit_interval_group = forms.IntegerField(
|
||||
label=_("Audit Interval (secs)"),
|
||||
help_text=_("Sensor Group Audit Interval in seconds."),
|
||||
required=False)
|
||||
|
||||
actions_critical_group = forms.ChoiceField(
|
||||
label=_("Sensor Group Critical Actions"),
|
||||
required=False,
|
||||
help_text=_("Actions to take upon Sensor Group Critical event."))
|
||||
|
||||
actions_major_group = forms.ChoiceField(
|
||||
label=_("Sensor Group Major Actions"),
|
||||
required=False,
|
||||
help_text=_("Actions to take upon Sensor Group Major event."))
|
||||
|
||||
actions_minor_group = forms.ChoiceField(
|
||||
label=_("Sensor Group Minor Actions"),
|
||||
required=False,
|
||||
help_text=_("Actions to take upon Sensor Group Minor event."))
|
||||
|
||||
failure_url = 'horizon:admin:inventory:index'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UpdateSensorGroup, self).__init__(*args, **kwargs)
|
||||
|
||||
sensorgroup = api.sysinv.host_sensorgroup_get(
|
||||
self.request, kwargs['initial']['uuid'])
|
||||
|
||||
self.fields['actions_critical_group'].choices = \
|
||||
sensorgroup.sensorgroup_actions_critical_choices_tuple_list
|
||||
|
||||
self.fields['actions_major_group'].choices = \
|
||||
sensorgroup.sensorgroup_actions_major_choices_tuple_list
|
||||
|
||||
self.fields['actions_minor_group'].choices = \
|
||||
sensorgroup.sensorgroup_actions_minor_choices_tuple_list
|
||||
|
||||
LOG.debug("actions_critical_choices_choices = %s %s",
|
||||
sensorgroup.sensorgroup_actions_critical_choices,
|
||||
sensorgroup.sensorgroup_actions_critical_choices_tuple_list)
|
||||
|
||||
LOG.debug("actions_major_choices_choices = %s %s",
|
||||
sensorgroup.sensorgroup_actions_major_choices,
|
||||
sensorgroup.sensorgroup_actions_major_choices_tuple_list)
|
||||
|
||||
LOG.debug("actions_minor_choices_choices = %s %s",
|
||||
sensorgroup.sensorgroup_actions_minor_choices,
|
||||
sensorgroup.sensorgroup_actions_minor_choices_tuple_list)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(UpdateSensorGroup, self).clean()
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
sensorgroup_id = data['id']
|
||||
# host_uuid = data['host_uuid']
|
||||
if data['audit_interval_group']:
|
||||
data['audit_interval_group'] = \
|
||||
unicode(data['audit_interval_group'])
|
||||
else:
|
||||
data['audit_interval_group'] = unicode("0")
|
||||
|
||||
del data['id']
|
||||
|
||||
if not data['actions_critical_group']:
|
||||
data['actions_critical_group'] = "none"
|
||||
|
||||
if not data['actions_major_group']:
|
||||
data['actions_major_group'] = "none"
|
||||
|
||||
if not data['actions_minor_group']:
|
||||
data['actions_minor_group'] = "none"
|
||||
|
||||
data.pop('datatype', None)
|
||||
data.pop('sensortype', None)
|
||||
mysensorgroupname = data.pop('sensorgroupname', None)
|
||||
|
||||
try:
|
||||
sensorgroup = api.sysinv.host_sensorgroup_update(request,
|
||||
sensorgroup_id,
|
||||
**data)
|
||||
|
||||
msg = _('SensorGroup "%s" was '
|
||||
'successfully updated.') % mysensorgroupname
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return sensorgroup
|
||||
|
||||
except exc.ClientException as ce:
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, ce)
|
||||
LOG.error(ce)
|
||||
|
||||
# Redirect to failure page
|
||||
redirect = reverse(self.failure_url, args=[sensorgroup_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
|
||||
except Exception:
|
||||
msg = (_('Failed to update sensorgroup "%s".') %
|
||||
data['sensorgroupname'])
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[sensorgroup_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
401
cgcs_dashboard/dashboards/admin/inventory/sensors/tables.py
Executable file
401
cgcs_dashboard/dashboards/admin/inventory/sensors/tables.py
Executable file
@ -0,0 +1,401 @@
|
||||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
# from django.template import defaultfilters as filters
|
||||
from django import template
|
||||
from django.utils.translation import string_concat # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory import tables as itables
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddSensorGroup(tables.LinkAction):
|
||||
name = "addsensorgroup"
|
||||
verbose_name = ("Add Sensor Group")
|
||||
url = "horizon:admin:inventory:addsensorgroup"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
icon = "plus"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
host = self.table.kwargs['host']
|
||||
self.verbose_name = _("Add Sensor Group")
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
if not host._administrative == 'locked':
|
||||
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)"))
|
||||
return True # The action should always be displayed
|
||||
|
||||
|
||||
class RemoveSensorGroup(tables.DeleteAction):
|
||||
data_type_singular = _("Sensor Group")
|
||||
data_type_plural = _("Sensor Groups")
|
||||
|
||||
def allowed(self, request, sensorgroup=None):
|
||||
host = self.table.kwargs['host']
|
||||
return host._administrative == 'locked'
|
||||
|
||||
def delete(self, request, sensorgroup_id):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
try:
|
||||
api.sysinv.host_sensorgroup_delete(request, sensorgroup_id)
|
||||
except Exception:
|
||||
msg = _('Failed to delete host %(hid)s '
|
||||
'sensor group %(sensorgroup)s') % \
|
||||
{'hid': host_id, 'sensorgroup': sensorgroup_id}
|
||||
redirect = reverse('horizon:admin:inventory:detail',
|
||||
args=(host_id,))
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class EditSensorGroup(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit SensorGroup")
|
||||
url = "horizon:admin:inventory:editsensorgroup"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, sensorgroup=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
# sensorgroup_uuid = self.table.kwargs['sensorgroup_id']
|
||||
return reverse(self.url, args=(host_id, sensorgroup.uuid))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
# host = self.table.kwargs['host']
|
||||
return True
|
||||
# return host._administrative == 'locked'
|
||||
|
||||
|
||||
def sensorgroup_suppressed(sensorgroup=None):
|
||||
if not sensorgroup:
|
||||
return False
|
||||
return (sensorgroup.suppress == "True")
|
||||
|
||||
|
||||
def get_sensorgroup_suppress(sensorgroup):
|
||||
suppress_str = ""
|
||||
if sensorgroup_suppressed(sensorgroup):
|
||||
suppress_str = "suppressed"
|
||||
|
||||
return suppress_str
|
||||
|
||||
|
||||
class SuppressSensorGroup(tables.BatchAction):
|
||||
name = "suppress"
|
||||
action_type = 'danger'
|
||||
confirm_class = 'btn-danger'
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Suppress SensorGroup",
|
||||
u"Suppress SensorGroups",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Suppressed SensorGroup",
|
||||
u"Suppressed SensorGroups",
|
||||
count
|
||||
)
|
||||
|
||||
def get_confirm_message(self, request, datum):
|
||||
return _("<b>WARNING</b>: This operation will suppress actions "
|
||||
"for sensorgroup '%s'. This will affect all sensors in "
|
||||
"this sensorgroup.") % datum.sensorgroupname
|
||||
|
||||
def allowed(self, request, sensorgroup=None):
|
||||
return not sensorgroup_suppressed(sensorgroup)
|
||||
|
||||
def action(self, request, sensorgroup_id):
|
||||
api.sysinv.host_sensorgroup_suppress(request, sensorgroup_id)
|
||||
|
||||
def handle(self, table, request, obj_ids):
|
||||
return itables.handle_sysinv(self, table, request, obj_ids)
|
||||
|
||||
|
||||
class UnSuppressSensorGroup(tables.BatchAction):
|
||||
name = "unsuppress"
|
||||
classes = ('btn-warning', 'btn-unsuppress')
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"UnSuppress SensorGroup",
|
||||
u"UnSuppress SensorGroups",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"UnSuppressed SensorGroup",
|
||||
u"UnSuppressed SensorGroups",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, sensorgroup=None):
|
||||
return sensorgroup_suppressed(sensorgroup)
|
||||
|
||||
def action(self, request, sensorgroup_id):
|
||||
api.sysinv.host_sensorgroup_unsuppress(request, sensorgroup_id)
|
||||
|
||||
def handle(self, table, request, obj_ids):
|
||||
return itables.handle_sysinv(self, table, request, obj_ids)
|
||||
|
||||
|
||||
def get_sensors(sensorgroup):
|
||||
sensor_str_list = ", ".join(sensorgroup.sensorNameList)
|
||||
return sensor_str_list
|
||||
|
||||
|
||||
def get_sensorgroups(sensor):
|
||||
sensorgroup_str_list = ", ".join(sensor.sensorgroupNameList)
|
||||
return sensorgroup_str_list
|
||||
|
||||
|
||||
def get_sensorgroup_actions(sensorgroup):
|
||||
# if sensorgroup.something_configured == 'True':
|
||||
template_name = \
|
||||
'admin/inventory/sensors/_sensorgroup_actions.html'
|
||||
context = {"sensorgroup": sensorgroup}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
def get_sensor_actions(sensor):
|
||||
# if sensor.something_configured == 'True':
|
||||
template_name = \
|
||||
'admin/inventory/sensors/_sensor_actions.html'
|
||||
context = {"sensor": sensor}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
class RelearnSensorModel(tables.Action):
|
||||
name = "relearn"
|
||||
requires_input = False
|
||||
icon = "refresh"
|
||||
action_type = 'danger'
|
||||
verbose_name = _("Relearn Sensor Model")
|
||||
confirm_message = "This operation will delete this sensor model." \
|
||||
"All alarm assertions against this model will be " \
|
||||
"cleared. Any sensor suppression settings at the " \
|
||||
"group or sensor levels will be lost. " \
|
||||
"Will attempt to preserve customized group actions " \
|
||||
"and monitor interval in new model."
|
||||
|
||||
def allowed(self, request, datum):
|
||||
bm_type = self.table.kwargs['host'].bm_type
|
||||
return bm_type and bm_type.lower() != 'none'
|
||||
|
||||
def single(self, table, request, obj_ids):
|
||||
LOG.debug("requesting relearn of sensor model for host "
|
||||
"%s", table.kwargs['host'].uuid)
|
||||
api.sysinv.host_sensorgroup_relearn(request, table.kwargs['host'].uuid)
|
||||
|
||||
|
||||
class SensorGroupsTable(tables.DataTable):
|
||||
name = tables.Column('sensorgroupname',
|
||||
link="horizon:admin:inventory:sensorgroupdetail",
|
||||
verbose_name=('Name'))
|
||||
sensor_type = tables.Column('sensortype',
|
||||
verbose_name=('SensorType'))
|
||||
sensor_state = tables.Column('state',
|
||||
verbose_name=('State'))
|
||||
sensors = tables.Column(get_sensors,
|
||||
verbose_name=('Sensors'),
|
||||
help_text=_("Sensors in SensorGroup."))
|
||||
actions_group = tables.Column(get_sensorgroup_actions,
|
||||
verbose_name=('Sensor Handling Actions'),
|
||||
help_text=_("Actions performed on "
|
||||
"Sensor Event."))
|
||||
suppressed = tables.Column(get_sensorgroup_suppress,
|
||||
verbose_name=('Suppression'),
|
||||
help_text=_("Indicates 'suppressed' if Actions "
|
||||
"are suppressed."))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.sensorgroupname
|
||||
|
||||
class Meta(object):
|
||||
name = "sensorgroups"
|
||||
verbose_name = ("Sensor Groups")
|
||||
columns = ('name', 'sensor_type', 'sensor_state', 'sensors',
|
||||
'actions_group', 'suppressed')
|
||||
multi_select = False
|
||||
row_actions = (EditSensorGroup,
|
||||
UnSuppressSensorGroup,
|
||||
SuppressSensorGroup)
|
||||
table_actions = (RelearnSensorModel,)
|
||||
hidden_title = False
|
||||
|
||||
|
||||
class EditSensor(tables.LinkAction):
|
||||
name = "updatesensor"
|
||||
verbose_name = _("Edit Sensor")
|
||||
url = "horizon:admin:inventory:editsensor"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, sensor=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
# sensorgroup_uuid = self.table.kwargs['sensorgroup_id']
|
||||
return reverse(self.url, args=(host_id, sensor.uuid))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
# host = self.table.kwargs['host']
|
||||
return True
|
||||
# return host._administrative == 'locked'
|
||||
|
||||
|
||||
def sensor_suppressed(sensor=None):
|
||||
if not sensor:
|
||||
return False
|
||||
return (sensor.suppress == "True")
|
||||
|
||||
|
||||
def get_suppress(sensor):
|
||||
suppress_str = ""
|
||||
if sensor_suppressed(sensor):
|
||||
suppress_str = "suppressed"
|
||||
|
||||
return suppress_str
|
||||
|
||||
|
||||
class SuppressSensor(tables.BatchAction):
|
||||
name = "suppress"
|
||||
classes = ('btn-confirm', 'btn-suppress')
|
||||
|
||||
confirm_class = 'btn-confirm'
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Suppress Sensor",
|
||||
u"Suppress Sensors",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Suppressed Sensor",
|
||||
u"Suppressed Sensors",
|
||||
count
|
||||
)
|
||||
|
||||
def get_confirm_message(self, request, datum):
|
||||
return _("<b>WARNING</b>: This operation will suppress actions "
|
||||
" for sensor '%s'. ") % datum.sensorname
|
||||
|
||||
def allowed(self, request, sensor=None):
|
||||
return not sensor_suppressed(sensor)
|
||||
|
||||
def action(self, request, sensor_id):
|
||||
api.sysinv.host_sensor_suppress(request, sensor_id)
|
||||
|
||||
def handle(self, table, request, obj_ids):
|
||||
return itables.handle_sysinv(self, table, request, obj_ids)
|
||||
|
||||
|
||||
class UnSuppressSensor(tables.BatchAction):
|
||||
name = "unsuppress"
|
||||
classes = ('btn-warning', 'btn-unsuppress')
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"UnSuppress Sensor",
|
||||
u"UnSuppress Sensors",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"UnSuppressed Sensor",
|
||||
u"UnSuppressed Sensors",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, sensor=None):
|
||||
return sensor_suppressed(sensor)
|
||||
|
||||
def action(self, request, sensor_id):
|
||||
api.sysinv.host_sensor_unsuppress(request, sensor_id)
|
||||
|
||||
def handle(self, table, request, obj_ids):
|
||||
return itables.handle_sysinv(self, table, request, obj_ids)
|
||||
|
||||
|
||||
class SensorsFilterAction(tables.FilterAction):
|
||||
def filter(self, table, sensors, filter_string):
|
||||
"""Naive case-insensitive search."""
|
||||
q = filter_string.lower()
|
||||
|
||||
def comp(sensor):
|
||||
if (q in sensor.sensorname.lower() or
|
||||
q in sensor.sensorgroupname.lower() or
|
||||
q in sensor.sensortype.lower() or
|
||||
q in sensor.state or
|
||||
q in sensor.status):
|
||||
return True
|
||||
return False
|
||||
|
||||
return filter(comp, sensors)
|
||||
|
||||
|
||||
class SensorsTable(tables.DataTable):
|
||||
name = tables.Column('sensorname',
|
||||
link="horizon:admin:inventory:sensordetail",
|
||||
verbose_name=('Name'))
|
||||
sensor_type = tables.Column('sensortype',
|
||||
verbose_name=('SensorType'))
|
||||
sensor_status = tables.Column('status',
|
||||
verbose_name=('Status'))
|
||||
sensor_state = tables.Column('state',
|
||||
verbose_name=('State'))
|
||||
suppressed = tables.Column(get_suppress,
|
||||
verbose_name=('Suppression'),
|
||||
help_text=_("Indicates 'suppressed' if Actions "
|
||||
"are suppressed."))
|
||||
sensorgroupname = tables.Column(get_sensorgroups,
|
||||
verbose_name=('Sensor Group Name'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.sensorname
|
||||
|
||||
class Meta(object):
|
||||
name = "sensors"
|
||||
verbose_name = ("Sensors")
|
||||
columns = ('name', 'sensorgroupname', 'sensor_type', 'sensor_state',
|
||||
'sensor_status', 'suppressed')
|
||||
multi_select = False
|
||||
row_actions = (SuppressSensor, UnSuppressSensor) # EditSensor
|
||||
table_actions = (SensorsFilterAction,)
|
||||
hidden_title = False
|
218
cgcs_dashboard/dashboards/admin/inventory/sensors/views.py
Executable file
218
cgcs_dashboard/dashboards/admin/inventory/sensors/views.py
Executable file
@ -0,0 +1,218 @@
|
||||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.sensors.forms import \
|
||||
AddSensorGroup
|
||||
from openstack_dashboard.dashboards.admin.inventory.sensors.forms import \
|
||||
UpdateSensorGroup
|
||||
# from openstack_dashboard.dashboards.admin.inventory.sensors.forms import \
|
||||
# AddSensor
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddSensorGroupView(forms.ModalFormView):
|
||||
form_class = AddSensorGroup
|
||||
template_name = 'admin/inventory/storages/createsensorgroup.html'
|
||||
# template_name = 'admin/inventory/storages/createlocalvolumegroup.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddSensorGroupView, self) \
|
||||
.get_context_data(**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddSensorGroupView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, initial['host_id'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Unable to retrieve host.'))
|
||||
initial['host_uuid'] = host.uuid
|
||||
initial['hostname'] = host.hostname
|
||||
return initial
|
||||
|
||||
|
||||
class UpdateSensorGroupView(forms.ModalFormView):
|
||||
form_class = UpdateSensorGroup
|
||||
template_name = 'admin/inventory/sensors/updatesensorgroup.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
sensorgroup_id = self.kwargs['sensorgroup_id']
|
||||
host_id = self.kwargs['host_id']
|
||||
LOG.debug("sensorgroup_id=%s kwargs=%s",
|
||||
sensorgroup_id, self.kwargs)
|
||||
try:
|
||||
self._object = api.sysinv.host_sensorgroup_get(self.request,
|
||||
sensorgroup_id)
|
||||
self._object.host_id = host_id
|
||||
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:networks:detail",
|
||||
args=(self.kwargs['host_id'],))
|
||||
msg = _('Unable to retrieve sensorgroup details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateSensorGroupView, self).get_context_data(**kwargs)
|
||||
sensorgroup = self._get_object()
|
||||
context['sensorgroup_id'] = sensorgroup.uuid
|
||||
context['host_id'] = sensorgroup.host_id
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
sensorgroup = self._get_object()
|
||||
# try:
|
||||
# host = api.sysinv.host_get(self.request, sensorgroup.host_uuid)
|
||||
# except Exception:
|
||||
# exceptions.handle(self.request, _('Unable to retrieve host.'))
|
||||
# this is how we can do the analog vs discrete
|
||||
# 'ipv4_mode': getattr(sensorgroup, 'ipv4_mode', 'disabled'),
|
||||
# 'ipv6_mode': getattr(sensorgroup, 'ipv6_mode', 'disabled')}
|
||||
|
||||
return {'id': sensorgroup.uuid,
|
||||
'uuid': sensorgroup.uuid,
|
||||
'host_uuid': sensorgroup.host_uuid,
|
||||
'sensorgroupname': sensorgroup.sensorgroupname,
|
||||
'sensortype': sensorgroup.sensortype,
|
||||
'datatype': sensorgroup.datatype,
|
||||
'audit_interval_group': sensorgroup.audit_interval_group,
|
||||
'actions_critical_choices':
|
||||
sensorgroup.actions_critical_choices,
|
||||
'actions_major_choices': sensorgroup.actions_major_choices,
|
||||
'actions_minor_choices': sensorgroup.actions_minor_choices,
|
||||
'actions_critical_group': sensorgroup.actions_critical_group,
|
||||
'actions_major_group': sensorgroup.actions_major_group,
|
||||
'actions_minor_group': sensorgroup.actions_minor_group,
|
||||
'algorithm': sensorgroup.algorithm}
|
||||
|
||||
|
||||
class DetailSensorView(views.HorizonTemplateView):
|
||||
template_name = 'admin/inventory/_detail_sensor.html'
|
||||
page_title = '{{ sensor.sensorname }}'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailSensorView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
sensor = self.get_data()
|
||||
|
||||
hostname = self.get_hostname(sensor.host_uuid)
|
||||
host_nav = hostname or "Unprovisioned Node"
|
||||
breadcrumb = [
|
||||
(host_nav, reverse('horizon:admin:inventory:detail',
|
||||
args=(sensor.host_uuid,))),
|
||||
(_("Sensors"), None)
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
||||
context["sensor"] = sensor
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_hostname(self, host_uuid):
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_uuid)
|
||||
except Exception:
|
||||
host = {}
|
||||
msg = _('Unable to retrieve hostname details.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return host.hostname
|
||||
|
||||
def get_data(self):
|
||||
if not hasattr(self, "_sensor"):
|
||||
sensor_id = self.kwargs['sensor_id']
|
||||
try:
|
||||
sensor = api.sysinv.host_sensor_get(self.request, sensor_id)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'Sensor "%s".') % sensor_id,
|
||||
redirect=redirect)
|
||||
|
||||
self._sensor = sensor
|
||||
return self._sensor
|
||||
|
||||
|
||||
class DetailSensorGroupView(views.HorizonTemplateView):
|
||||
template_name = 'admin/inventory/_detail_sensor_group.html'
|
||||
page_title = '{{ sensorgroup.sensorgroupname }}'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailSensorGroupView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
sensorgroup = self.get_data()
|
||||
|
||||
hostname = self.get_hostname(sensorgroup.host_uuid)
|
||||
host_nav = hostname or "Unprovisioned Node"
|
||||
breadcrumb = [
|
||||
(host_nav, reverse('horizon:admin:inventory:detail',
|
||||
args=(sensorgroup.host_uuid,))),
|
||||
(_("Sensor Groups"), None)
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
||||
context["sensorgroup"] = sensorgroup
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_hostname(self, host_uuid):
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_uuid)
|
||||
except Exception:
|
||||
host = {}
|
||||
msg = _('Unable to retrieve hostname details.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return host.hostname
|
||||
|
||||
def get_data(self):
|
||||
if not hasattr(self, "_sensorgroup"):
|
||||
sensorgroup_id = self.kwargs['sensorgroup_id']
|
||||
try:
|
||||
sensorgroup = api.sysinv.host_sensorgroup_get(self.request,
|
||||
sensorgroup_id)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'SensorGroup "%s".') % sensorgroup_id,
|
||||
redirect=redirect)
|
||||
|
||||
self._sensorgroup = sensorgroup
|
||||
return self._sensorgroup
|
0
cgcs_dashboard/dashboards/admin/inventory/storages/__init__.py
Executable file
0
cgcs_dashboard/dashboards/admin/inventory/storages/__init__.py
Executable file
816
cgcs_dashboard/dashboards/admin/inventory/storages/forms.py
Executable file
816
cgcs_dashboard/dashboards/admin/inventory/storages/forms.py
Executable file
@ -0,0 +1,816 @@
|
||||
#
|
||||
# Copyright (c) 2013-2014, 2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from cgtsclient import exc
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import shortcuts
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import sysinv
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddDiskProfile(forms.SelfHandlingForm):
|
||||
host_id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
profilename = forms.CharField(label=_("Storage Profile Name"),
|
||||
required=True)
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddDiskProfile, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddDiskProfile, self).clean()
|
||||
# host_id = cleaned_data.get('host_id')
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
diskProfileName = data['profilename']
|
||||
try:
|
||||
diskProfile = api.sysinv.host_diskprofile_create(request, **data)
|
||||
|
||||
msg = _('Storage Profile "%s" was successfully created.') \
|
||||
% diskProfileName
|
||||
LOG.debug(msg)
|
||||
|
||||
messages.success(request, msg)
|
||||
|
||||
return diskProfile
|
||||
except Exception as e:
|
||||
msg = _('Failed to create storage profile "%s".') % diskProfileName
|
||||
LOG.info(msg)
|
||||
LOG.error(e)
|
||||
|
||||
messages.error(request, e)
|
||||
|
||||
redirect = reverse(self.failure_url,
|
||||
args=[data['host_id']])
|
||||
return shortcuts.redirect(redirect)
|
||||
|
||||
|
||||
class EditStorageVolume(forms.SelfHandlingForm):
|
||||
id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
|
||||
journal_locations = forms.ChoiceField(label=_("Journal"),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'data-slug':
|
||||
'journal_locations'}),
|
||||
help_text=_("Assign disk to journal "
|
||||
"storage volume."))
|
||||
|
||||
journal_size_mib = forms.CharField(label=_("Journal Size MiB"),
|
||||
required=False,
|
||||
initial=sysinv.JOURNAL_DEFAULT_SIZE,
|
||||
widget=forms.TextInput(attrs={
|
||||
'data-slug': 'journal_size_mib'}),
|
||||
help_text=_("Journal's size for the "
|
||||
"current OSD."))
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(EditStorageVolume, self).__init__(request, *args, **kwargs)
|
||||
|
||||
stor = api.sysinv.host_stor_get(
|
||||
self.request, kwargs['initial']['uuid'])
|
||||
|
||||
initial_journal_location = kwargs['initial']['journal_location']
|
||||
host_uuid = kwargs['initial']['host_uuid']
|
||||
|
||||
# Populate available journal choices. If no journal is available,
|
||||
# then the journal is collocated.
|
||||
avail_journal_list = api.sysinv.host_stor_get_by_function(
|
||||
self.request,
|
||||
host_uuid,
|
||||
'journal')
|
||||
|
||||
journal_tuple_list = []
|
||||
|
||||
if stor.uuid == initial_journal_location:
|
||||
journal_tuple_list.append((stor.uuid, "Collocated with OSD"))
|
||||
else:
|
||||
journal_tuple_list.append((initial_journal_location,
|
||||
"%s " % initial_journal_location))
|
||||
|
||||
if avail_journal_list:
|
||||
for j in avail_journal_list:
|
||||
if j.uuid != initial_journal_location:
|
||||
journal_tuple_list.append((j.uuid, "%s " % j.uuid))
|
||||
|
||||
if stor.uuid != initial_journal_location:
|
||||
journal_tuple_list.append((stor.uuid, "Collocated with OSD"))
|
||||
|
||||
self.fields['journal_locations'].choices = journal_tuple_list
|
||||
|
||||
def handle(self, request, data):
|
||||
stor_id = data['id']
|
||||
|
||||
try:
|
||||
# Obtain journal information.
|
||||
journal = data['journal_locations'][:]
|
||||
|
||||
if journal:
|
||||
data['journal_location'] = journal
|
||||
else:
|
||||
data['journal_location'] = None
|
||||
data['journal_size_mib'] = sysinv.JOURNAL_DEFAULT_SIZE
|
||||
|
||||
del data['journal_locations']
|
||||
del data['id']
|
||||
|
||||
# The REST API takes care of updating the stor journal information.
|
||||
stor = api.sysinv.host_stor_update(request, stor_id, **data)
|
||||
|
||||
msg = _('Storage volume was successfully updated.')
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
return stor
|
||||
except exc.ClientException as ce:
|
||||
msg = _('Failed to update storage volume.')
|
||||
LOG.info(msg)
|
||||
LOG.error(ce)
|
||||
|
||||
# Allow REST API error message to appear on UI.
|
||||
messages.error(request, ce)
|
||||
|
||||
# Redirect to host details pg.
|
||||
redirect = reverse(self.failure_url, args=[stor_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception as e:
|
||||
msg = _('Failed to update storage volume.')
|
||||
LOG.info(msg)
|
||||
LOG.error(e)
|
||||
|
||||
# if not a rest API error, throw default
|
||||
redirect = reverse(self.failure_url, args=[stor_id])
|
||||
return exceptions.handle(request, message=e, redirect=redirect)
|
||||
|
||||
|
||||
class AddStorageVolume(forms.SelfHandlingForm):
|
||||
# Only allowed to choose 'osd'
|
||||
FUNCTION_CHOICES = (
|
||||
('osd', _("osd")),
|
||||
('journal', _("journal")),
|
||||
# ('monitor', _("monitor")),
|
||||
)
|
||||
|
||||
host_id = forms.CharField(label=_("host_id"),
|
||||
initial='host_id',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
ihost_uuid = forms.CharField(label=_("ihost_uuid"),
|
||||
initial='ihost_uuid',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
idisk_uuid = forms.CharField(label=_("idisk_uuid"),
|
||||
initial='idisk_uuid',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
hostname = forms.CharField(label=_("Hostname"),
|
||||
initial='hostname',
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly'}))
|
||||
|
||||
function = forms.ChoiceField(label=_("Function"),
|
||||
required=False,
|
||||
choices=FUNCTION_CHOICES,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'function'}))
|
||||
|
||||
disks = forms.ChoiceField(label=_("Disks"),
|
||||
required=True,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'disk'}),
|
||||
help_text=_("Assign disk to storage volume."))
|
||||
|
||||
journal_locations = forms.ChoiceField(label=_("Journal"),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'function',
|
||||
'data-function-osd': _(
|
||||
"Journal")}),
|
||||
help_text=_("Assign disk to journal "
|
||||
"storage volume."))
|
||||
|
||||
journal_size_mib = forms.CharField(label=_("Journal Size MiB"),
|
||||
required=False,
|
||||
initial=sysinv.JOURNAL_DEFAULT_SIZE,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'function',
|
||||
'data-function-osd':
|
||||
_("Journal Size MiB")}),
|
||||
help_text=_("Journal's size for the"
|
||||
"current OSD."))
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddStorageVolume, self).__init__(*args, **kwargs)
|
||||
|
||||
# Populate available disk choices
|
||||
this_stor_uuid = 0
|
||||
host_uuid = kwargs['initial']['ihost_uuid']
|
||||
|
||||
ihost = api.sysinv.host_get(self.request, host_uuid)
|
||||
ceph_caching = ((ihost.capabilities.get('pers_subtype') ==
|
||||
sysinv.PERSONALITY_SUBTYPE_CEPH_CACHING))
|
||||
|
||||
avail_disk_list = api.sysinv.host_disk_list(self.request, host_uuid)
|
||||
disk_tuple_list = []
|
||||
for d in avail_disk_list:
|
||||
if d.istor_uuid and d.istor_uuid != this_stor_uuid:
|
||||
continue
|
||||
is_rootfs_device = \
|
||||
(('stor_function' in d.capabilities)
|
||||
and (d.capabilities['stor_function'] == 'rootfs'))
|
||||
if is_rootfs_device:
|
||||
continue
|
||||
disk_model = d.get_model_num()
|
||||
if disk_model is not None and "floppy" in disk_model.lower():
|
||||
continue
|
||||
if (ceph_caching and d.device_type != 'SSD' and
|
||||
d.device_type != 'NVME'):
|
||||
continue
|
||||
disk_tuple_list.append(
|
||||
(d.uuid, "%s (path: %s size:%s model:%s type: %s)" % (
|
||||
d.device_node,
|
||||
d.device_path,
|
||||
str(d.size_mib),
|
||||
disk_model,
|
||||
d.device_type)))
|
||||
|
||||
# Populate available journal choices. If no journal is available,
|
||||
# then the journal is collocated.
|
||||
if ceph_caching:
|
||||
avail_journal_list = []
|
||||
else:
|
||||
avail_journal_list = api.sysinv.host_stor_get_by_function(
|
||||
self.request,
|
||||
host_uuid,
|
||||
'journal')
|
||||
|
||||
journal_tuple_list = []
|
||||
if avail_journal_list:
|
||||
for j in avail_journal_list:
|
||||
journal_tuple_list.append((j.uuid, "%s " % j.uuid))
|
||||
else:
|
||||
journal_tuple_list.append((None, "Collocated with OSD"))
|
||||
self.fields['journal_size_mib'].widget.attrs['disabled'] = \
|
||||
'disabled'
|
||||
|
||||
if ceph_caching:
|
||||
self.fields['function'].choices = (
|
||||
AddStorageVolume.FUNCTION_CHOICES[:1])
|
||||
|
||||
self.fields['disks'].choices = disk_tuple_list
|
||||
self.fields['journal_locations'].choices = journal_tuple_list
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddStorageVolume, self).clean()
|
||||
# host_id = cleaned_data.get('host_id')
|
||||
# ihost_uuid = cleaned_data.get('ihost_uuid')
|
||||
|
||||
# disks = cleaned_data.get('disks')
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
host_id = data['host_id']
|
||||
# host_uuid = data['ihost_uuid']
|
||||
disks = data['disks'][:] # copy
|
||||
|
||||
# GUI only allows one disk to be picked
|
||||
data['idisk_uuid'] = disks
|
||||
|
||||
# Obtain journal information.
|
||||
journal = data['journal_locations'][:]
|
||||
|
||||
if journal:
|
||||
data['journal_location'] = journal
|
||||
else:
|
||||
data['journal_location'] = None
|
||||
data['journal_size_mib'] = sysinv.JOURNAL_DEFAULT_SIZE
|
||||
|
||||
try:
|
||||
del data['host_id']
|
||||
del data['disks']
|
||||
del data['hostname']
|
||||
del data['journal_locations']
|
||||
|
||||
# The REST API takes care of creating the stor
|
||||
# and updating disk.foristorid
|
||||
stor = api.sysinv.host_stor_create(request, **data)
|
||||
|
||||
msg = _('Storage volume was successfully created.')
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
return stor
|
||||
except exc.ClientException as ce:
|
||||
msg = _('Failed to create storage volume.')
|
||||
LOG.info(msg)
|
||||
LOG.error(ce)
|
||||
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, ce)
|
||||
|
||||
# Redirect to host details pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception as e:
|
||||
msg = _('Failed to create storage volume.')
|
||||
LOG.info(msg)
|
||||
LOG.error(e)
|
||||
|
||||
# if not a rest API error, throw default
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return exceptions.handle(request, message=e, redirect=redirect)
|
||||
|
||||
|
||||
class AddLocalVolumeGroup(forms.SelfHandlingForm):
|
||||
|
||||
host_id = forms.CharField(label=_("host_id"),
|
||||
initial='host_id',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
ihost_uuid = forms.CharField(label=_("ihost_uuid"),
|
||||
initial='ihost_uuid',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
hostname = forms.CharField(label=_("Hostname"),
|
||||
initial='hostname',
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly'}))
|
||||
|
||||
lvm_vg_name = forms.ChoiceField(label=_("Local Volume Group Name"),
|
||||
required=True,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'lvm_vg_name'}))
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddLocalVolumeGroup, self).__init__(*args, **kwargs)
|
||||
|
||||
# Populate available volume group choices
|
||||
host_uuid = kwargs['initial']['ihost_uuid']
|
||||
host_id = kwargs['initial']['host_id']
|
||||
|
||||
host = api.sysinv.host_get(self.request, host_id)
|
||||
subfunctions = host.subfunctions
|
||||
|
||||
# LVGs that are considered as "present" in the system are those
|
||||
# in an adding or provisioned state.
|
||||
ilvg_list = api.sysinv.host_lvg_list(self.request, host_uuid)
|
||||
current_lvg_states = [api.sysinv.LVG_ADD, api.sysinv.LVG_PROV]
|
||||
current_lvgs = [lvg.lvm_vg_name for lvg in ilvg_list
|
||||
if lvg.vg_state in current_lvg_states]
|
||||
|
||||
compatible_lvgs = []
|
||||
if host.personality.lower().startswith(
|
||||
api.sysinv.PERSONALITY_CONTROLLER):
|
||||
compatible_lvgs += [api.sysinv.LVG_CINDER_VOLUMES]
|
||||
|
||||
if api.sysinv.SUBFUNCTIONS_COMPUTE in subfunctions:
|
||||
compatible_lvgs += [api.sysinv.LVG_NOVA_LOCAL]
|
||||
|
||||
allowed_lvgs = set(compatible_lvgs) - set(current_lvgs)
|
||||
|
||||
ilvg_tuple_list = []
|
||||
for lvg in allowed_lvgs:
|
||||
ilvg_tuple_list.append((lvg, lvg))
|
||||
|
||||
self.fields['lvm_vg_name'].choices = ilvg_tuple_list
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddLocalVolumeGroup, self).clean()
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
host_id = data['host_id']
|
||||
|
||||
try:
|
||||
del data['host_id']
|
||||
del data['hostname']
|
||||
|
||||
# The REST API takes care of creating the stor
|
||||
# and updating disk.foristorid
|
||||
lvg = api.sysinv.host_lvg_create(request, **data)
|
||||
|
||||
msg = _('Local volume group was successfully created.')
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
return lvg
|
||||
except exc.ClientException as ce:
|
||||
msg = _('Failed to create local volume group.')
|
||||
LOG.info(msg)
|
||||
LOG.error(ce)
|
||||
|
||||
# Allow REST API error message to appear on UI
|
||||
w_msg = str(ce)
|
||||
if ('Warning' in w_msg):
|
||||
LOG.info(ce)
|
||||
messages.warning(request, w_msg.split(':', 1)[-1])
|
||||
else:
|
||||
LOG.error(ce)
|
||||
messages.error(request, w_msg)
|
||||
|
||||
# Redirect to host details pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception as e:
|
||||
msg = _('Failed to create local volume group.')
|
||||
LOG.info(msg)
|
||||
LOG.error(e)
|
||||
|
||||
# if not a rest API error, throw default
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return exceptions.handle(request, message=e, redirect=redirect)
|
||||
|
||||
|
||||
class AddPhysicalVolume(forms.SelfHandlingForm):
|
||||
|
||||
PV_TYPE_CHOICES = (
|
||||
('disk', _("Disk")),
|
||||
('partition', _("Partition")),
|
||||
)
|
||||
|
||||
host_id = forms.CharField(label=_("host_id"),
|
||||
initial='host_id',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
ihost_uuid = forms.CharField(label=_("ihost_uuid"),
|
||||
initial='ihost_uuid',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
disk_or_part_uuid = forms.CharField(label=_("disk_or_part_uuid"),
|
||||
initial='disk_or_part_uuid',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
hostname = forms.CharField(label=_("Hostname"),
|
||||
initial='hostname',
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly'}))
|
||||
|
||||
lvg = forms.ChoiceField(label=_("Local Volume Group"),
|
||||
required=True,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'lvg'}),
|
||||
help_text=_("Associate this physical volume to a "
|
||||
"volume group "))
|
||||
|
||||
pv_type = forms.ChoiceField(label=_("PV Type"),
|
||||
required=False,
|
||||
choices=PV_TYPE_CHOICES,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'pv_type',
|
||||
'initial': 'Disk'}))
|
||||
|
||||
disks = forms.ChoiceField(label=_("Disks"),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'pv_type',
|
||||
'data-pv_type-disk': _("Disks")}),
|
||||
help_text=_("Assign disk to physical volume."))
|
||||
|
||||
partitions = forms.ChoiceField(label=_("Partitions"),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'pv_type',
|
||||
'data-pv_type-partition':
|
||||
_("Partitions")}),
|
||||
help_text=_("Assign partition to physical "
|
||||
"volume."))
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddPhysicalVolume, self).__init__(*args, **kwargs)
|
||||
|
||||
# Populate available partition, disk, and volume group choices
|
||||
host_uuid = kwargs['initial']['ihost_uuid']
|
||||
host_id = kwargs['initial']['host_id']
|
||||
|
||||
host = api.sysinv.host_get(self.request, host_id)
|
||||
subfunctions = host.subfunctions
|
||||
|
||||
compatible_lvgs = []
|
||||
if host.personality.lower().startswith(
|
||||
api.sysinv.PERSONALITY_CONTROLLER):
|
||||
compatible_lvgs += [api.sysinv.LVG_CGTS_VG,
|
||||
api.sysinv.LVG_CINDER_VOLUMES]
|
||||
|
||||
if api.sysinv.SUBFUNCTIONS_COMPUTE in subfunctions:
|
||||
compatible_lvgs += [api.sysinv.LVG_NOVA_LOCAL]
|
||||
|
||||
avail_disk_list = api.sysinv.host_disk_list(self.request, host_uuid)
|
||||
ilvg_list = api.sysinv.host_lvg_list(self.request, host_uuid)
|
||||
partitions = api.sysinv.host_disk_partition_list(self.request,
|
||||
host_uuid)
|
||||
ipv_list = api.sysinv.host_pv_list(self.request, host_uuid)
|
||||
disk_tuple_list = []
|
||||
partitions_tuple_list = []
|
||||
ilvg_tuple_list = []
|
||||
|
||||
for lvg in ilvg_list:
|
||||
if (lvg.lvm_vg_name in compatible_lvgs and
|
||||
lvg.vg_state in [api.sysinv.LVG_ADD, api.sysinv.LVG_PROV]):
|
||||
ilvg_tuple_list.append((lvg.uuid, lvg.lvm_vg_name))
|
||||
|
||||
for disk in avail_disk_list:
|
||||
capabilities = disk.capabilities
|
||||
|
||||
if capabilities.get('stor_function') == 'rootfs':
|
||||
continue
|
||||
# TODO(rchurch): re-factor
|
||||
elif capabilities.get('device_function') == 'cinder_device':
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
for d in avail_disk_list:
|
||||
disk_cap = d.capabilities
|
||||
# TODO(rchurch): re-factor
|
||||
is_cinder_device = \
|
||||
(('device_function' in disk_cap)
|
||||
and (disk_cap['device_function'] == 'cinder_device'))
|
||||
|
||||
is_rootfs_device = \
|
||||
(('stor_function' in disk_cap)
|
||||
and (disk_cap['stor_function'] == 'rootfs'))
|
||||
|
||||
disk_model = d.get_model_num()
|
||||
# TODO(rchurch): re-factor
|
||||
if not d.ipv_uuid and is_cinder_device:
|
||||
continue
|
||||
|
||||
if is_rootfs_device or d.ipv_uuid:
|
||||
continue
|
||||
|
||||
if disk_model is not None and "floppy" in disk_model.lower():
|
||||
continue
|
||||
|
||||
disk_tuple_list.append(
|
||||
(d.uuid, "%s (path:%s size:%s model:%s)" % (
|
||||
d.device_node,
|
||||
d.device_path,
|
||||
str(d.size_mib),
|
||||
disk_model)))
|
||||
|
||||
for p in partitions:
|
||||
if p.type_guid != api.sysinv.USER_PARTITION_PHYS_VOL:
|
||||
continue
|
||||
if p.ipv_uuid:
|
||||
continue
|
||||
if p.status == api.sysinv.PARTITION_IN_USE_STATUS:
|
||||
# If partition is in use, but the PV it is attached to
|
||||
# is in a "removing" state, we should allow the partition
|
||||
# to be listed as a possible option.
|
||||
for pv in ipv_list:
|
||||
if (pv.disk_or_part_device_path == p.device_path and
|
||||
pv.pv_state == api.sysinv.PV_DEL):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
|
||||
partitions_tuple_list.append(
|
||||
(p.uuid, "%s (size:%s)" % (
|
||||
p.device_path,
|
||||
str(p.size_mib))))
|
||||
|
||||
self.fields['disks'].choices = disk_tuple_list
|
||||
self.fields['lvg'].choices = ilvg_tuple_list
|
||||
self.fields['partitions'].choices = partitions_tuple_list
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddPhysicalVolume, self).clean()
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
host_id = data['host_id']
|
||||
|
||||
lvgs = data['lvg'][:]
|
||||
|
||||
# GUI only allows one disk to be picked
|
||||
if data['pv_type'] == 'disk':
|
||||
data['disk_or_part_uuid'] = data['disks'][:]
|
||||
else:
|
||||
data['disk_or_part_uuid'] = data['partitions'][:]
|
||||
|
||||
data['ilvg_uuid'] = lvgs
|
||||
|
||||
try:
|
||||
del data['host_id']
|
||||
del data['disks']
|
||||
del data['hostname']
|
||||
del data['lvg']
|
||||
del data['partitions']
|
||||
|
||||
stor = api.sysinv.host_pv_create(request, **data)
|
||||
|
||||
msg = _('Physical volume was successfully created.')
|
||||
messages.success(request, msg)
|
||||
return stor
|
||||
except exc.ClientException as ce:
|
||||
msg = _('Failed to create physical volume.')
|
||||
|
||||
# Allow REST API error message to appear on UI
|
||||
w_msg = str(ce)
|
||||
if ('Warning:' in w_msg):
|
||||
LOG.info(ce)
|
||||
messages.warning(request, w_msg.split(':', 1)[-1])
|
||||
else:
|
||||
LOG.error(ce)
|
||||
messages.error(request, w_msg)
|
||||
|
||||
# Redirect to host details pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception as e:
|
||||
msg = _('Failed to create physical volume.')
|
||||
LOG.error(e)
|
||||
|
||||
# if not a rest API error, throw default
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return exceptions.handle(request, message=e, redirect=redirect)
|
||||
|
||||
|
||||
class EditPartition(forms.SelfHandlingForm):
|
||||
id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
|
||||
size_mib = forms.CharField(label=_("Partition Size MiB"),
|
||||
required=False,
|
||||
initial='size_mib',
|
||||
widget=forms.TextInput(attrs={
|
||||
'data-slug': 'size_mib'}),
|
||||
help_text=_(
|
||||
"New partition size. Has to be "
|
||||
"larger than current size."))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(EditPartition, self).__init__(request, *args, **kwargs)
|
||||
|
||||
def handle(self, request, data):
|
||||
partition_id = data['id']
|
||||
|
||||
try:
|
||||
del data['id']
|
||||
# The REST API takes care of updating the partition information.
|
||||
partition = api.sysinv.host_disk_partition_update(
|
||||
request, partition_id, **data)
|
||||
|
||||
msg = _('Partition was successfully updated.')
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
return partition
|
||||
except exc.ClientException as ce:
|
||||
msg = _('Failed to update partition.')
|
||||
LOG.info(msg)
|
||||
LOG.error(ce)
|
||||
|
||||
# No redirect, return to previous storage tab view.
|
||||
# The REST API error message will appear on UI as
|
||||
# "horizon.exceptions.handle" will invoke "messages.error".
|
||||
return exceptions.handle(request, message=ce)
|
||||
except Exception as e:
|
||||
msg = _('Failed to update partition.')
|
||||
LOG.info(msg)
|
||||
LOG.error(e)
|
||||
|
||||
# If not a rest API error, throw default.
|
||||
# No redirect, return to previous storage tab view.
|
||||
return exceptions.handle(request, message=e)
|
||||
|
||||
|
||||
class CreatePartition(forms.SelfHandlingForm):
|
||||
host_id = forms.CharField(label=_("host_id"),
|
||||
initial='host_id',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
ihost_uuid = forms.CharField(label=_("ihost_uuid"),
|
||||
initial='ihost_uuid',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
idisk_uuid = forms.CharField(label=_("idisk_uuid"),
|
||||
initial='idisk_uuid',
|
||||
widget=forms.widgets.HiddenInput)
|
||||
|
||||
hostname = forms.CharField(label=_("Hostname"),
|
||||
initial='hostname',
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly'}))
|
||||
|
||||
disks = forms.ChoiceField(label=_("Disks"),
|
||||
required=True,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'disk'}),
|
||||
help_text=_("Disk to create partition on."))
|
||||
|
||||
size_mib = forms.IntegerField(label=_("Partition Size MiB"),
|
||||
required=True,
|
||||
initial=1024,
|
||||
widget=forms.TextInput(attrs={
|
||||
'data-slug': 'size_mib'}),
|
||||
help_text=_("Size in MiB for the new "
|
||||
"partition."))
|
||||
|
||||
type_guid = forms.CharField(label=_("Partition Type"),
|
||||
initial='LVM Physical Volume',
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly'}))
|
||||
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreatePartition, self).__init__(*args, **kwargs)
|
||||
|
||||
# Populate disk choices.
|
||||
host_uuid = kwargs['initial']['ihost_uuid']
|
||||
avail_disk_list = api.sysinv.host_disk_list(self.request, host_uuid)
|
||||
disk_tuple_list = []
|
||||
for d in avail_disk_list:
|
||||
disk_model = d.get_model_num()
|
||||
if disk_model is not None and "floppy" in disk_model.lower():
|
||||
continue
|
||||
if d.available_mib == 0:
|
||||
continue
|
||||
disk_tuple_list.append(
|
||||
(d.uuid, "%s (path: %s size:%s available_mib:%s type: %s)" % (
|
||||
d.device_node,
|
||||
d.device_path,
|
||||
str(d.size_mib),
|
||||
str(d.available_mib),
|
||||
d.device_type)))
|
||||
|
||||
self.fields['disks'].choices = disk_tuple_list
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(CreatePartition, self).clean()
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
host_id = data['host_id']
|
||||
disks = data['disks'][:]
|
||||
data['idisk_uuid'] = disks
|
||||
|
||||
try:
|
||||
del data['host_id']
|
||||
del data['disks']
|
||||
del data['hostname']
|
||||
data['type_guid'] = sysinv.USER_PARTITION_PHYS_VOL
|
||||
# The REST API takes care of creating the partition.
|
||||
partition = api.sysinv.host_disk_partition_create(request, **data)
|
||||
|
||||
msg = _('Partition was successfully created.')
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
return partition
|
||||
except exc.ClientException as ce:
|
||||
msg = _('Failed to create partition.')
|
||||
LOG.info(msg)
|
||||
LOG.error(ce)
|
||||
|
||||
# Allow REST API error message to appear on UI
|
||||
messages.error(request, ce)
|
||||
|
||||
# Redirect to host details pg
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return shortcuts.redirect(redirect)
|
||||
except Exception as e:
|
||||
msg = _('Failed to create partition.')
|
||||
LOG.info(msg)
|
||||
LOG.error(e)
|
||||
|
||||
# If not a REST API error, throw default.
|
||||
redirect = reverse(self.failure_url, args=[host_id])
|
||||
return exceptions.handle(request, message=e, redirect=redirect)
|
@ -0,0 +1,354 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2015-2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django.core import validators # noqa
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from openstack_dashboard import api
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from openstack_dashboard.api import sysinv
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
NOVA_PARAMS_FIELD_MAP = {
|
||||
sysinv.LVG_NOVA_PARAM_BACKING:
|
||||
sysinv.LVG_NOVA_PARAM_BACKING,
|
||||
sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB:
|
||||
sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB,
|
||||
sysinv.LVG_NOVA_PARAM_DISK_OPS:
|
||||
sysinv.LVG_NOVA_PARAM_DISK_OPS,
|
||||
}
|
||||
|
||||
CINDER_PARAMS_FIELD_MAP = {
|
||||
sysinv.LVG_CINDER_PARAM_LVM_TYPE:
|
||||
sysinv.LVG_CINDER_PARAM_LVM_TYPE,
|
||||
}
|
||||
|
||||
NOVA_PARAMS_KEY_MAP = (
|
||||
(sysinv.LVG_NOVA_PARAM_BACKING,
|
||||
_("Instance Backing")),
|
||||
(sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB,
|
||||
_("Instances LV Size [in MiB]")),
|
||||
(sysinv.LVG_NOVA_PARAM_DISK_OPS,
|
||||
_("Concurrent Disk Operations")),
|
||||
)
|
||||
|
||||
CINDER_PARAMS_KEY_MAP = (
|
||||
(sysinv.LVG_CINDER_PARAM_LVM_TYPE,
|
||||
_("LVM Provisioning Type")),
|
||||
)
|
||||
|
||||
PARAMS_HELP = {
|
||||
sysinv.LVG_NOVA_PARAM_BACKING:
|
||||
'Determines the format and location of instance disks. Local CoW image \
|
||||
file backed, local RAW LVM logical volume backed, or remote RAW Ceph \
|
||||
storage backed',
|
||||
sysinv.LVG_NOVA_PARAM_DISK_OPS:
|
||||
'Number of parallel disk I/O intensive operations (glance image downloads, \
|
||||
image format conversions, etc.).',
|
||||
sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB:
|
||||
'An integer specifying the size (in MiB) of the instances logical volume. \
|
||||
(.e.g. 10 GiB = 10240). Volume is created from nova-local and will be \
|
||||
mounted at /etc/nova/instances.',
|
||||
sysinv.LVG_CINDER_PARAM_LVM_TYPE:
|
||||
'Cinder configuration setting which determines how the volume group is \
|
||||
provisioned. Thick provisioning will be used if the value is set to: \
|
||||
default. Thin provisioning will be used in the value is set to: thin',
|
||||
}
|
||||
|
||||
NOVA_PARAMS_KEY_NAMES = dict(NOVA_PARAMS_KEY_MAP)
|
||||
|
||||
NOVA_PARAMS_CHOICES = NOVA_PARAMS_KEY_MAP
|
||||
|
||||
CINDER_PARAMS_KEY_NAMES = dict(CINDER_PARAMS_KEY_MAP)
|
||||
|
||||
CINDER_PARAMS_CHOICES = CINDER_PARAMS_KEY_MAP
|
||||
|
||||
BACKING_CHOICES = (
|
||||
(sysinv.LVG_NOVA_BACKING_LVM, _("Local RAW LVM backed")),
|
||||
(sysinv.LVG_NOVA_BACKING_IMAGE, _("Local CoW image backed")),
|
||||
(sysinv.LVG_NOVA_BACKING_REMOTE, _("Remote RAW Ceph storage backed")),
|
||||
)
|
||||
|
||||
LVM_TYPE_CHOICES = (
|
||||
(sysinv.LVG_CINDER_LVM_TYPE_THICK, _("Thick Provisioning (default)")),
|
||||
(sysinv.LVG_CINDER_LVM_TYPE_THIN, _("Thin Provisioning (thin)")),
|
||||
)
|
||||
|
||||
|
||||
def get_param_key_name(key):
|
||||
name = NOVA_PARAMS_KEY_NAMES.get(key, None)
|
||||
if not name:
|
||||
name = CINDER_PARAMS_KEY_NAMES.get(key, None)
|
||||
return name
|
||||
|
||||
|
||||
class ParamMixin(object):
|
||||
|
||||
def _host_lvg_get(self, lvg_id):
|
||||
try:
|
||||
return api.sysinv.host_lvg_get(self.request, lvg_id)
|
||||
except Exception:
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
_("Unable to retrieve local volume group data. "
|
||||
"lvg=%s") % str(lvg_id))
|
||||
|
||||
def _host_pv_list(self, host_id):
|
||||
try:
|
||||
return api.sysinv.host_pv_list(self.request, host_id)
|
||||
except Exception:
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
_("Unable to retrieve physical volume list. "
|
||||
"host=%s") % str(host_id))
|
||||
|
||||
def _host_pv_disk_get(self, pv):
|
||||
try:
|
||||
return api.sysinv.host_disk_get(self.request, pv.disk_or_part_uuid)
|
||||
except Exception:
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
_("Unable to retrieve disk %(disk)s for PV %(pv)s.") % {
|
||||
'disk': pv.disk_or_part_uuid,
|
||||
'pv': pv.uuid})
|
||||
|
||||
def get_lvg_lvm_info(self, lvg_id):
|
||||
lvg = self._host_lvg_get(lvg_id)
|
||||
caps = lvg.capabilities
|
||||
info = {'lvg': lvg}
|
||||
if caps.get(sysinv.LVG_NOVA_PARAM_BACKING) != \
|
||||
sysinv.LVG_NOVA_BACKING_LVM:
|
||||
return info
|
||||
info['total'] = 0
|
||||
for pv in self._host_pv_list(info['lvg'].ihost_uuid):
|
||||
if pv.lvm_vg_name != lvg.lvm_vg_name:
|
||||
continue
|
||||
if pv.pv_state == sysinv.PV_DEL:
|
||||
continue
|
||||
disk = self._host_pv_disk_get(pv)
|
||||
if not disk:
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
_("PV %s does not have an associated "
|
||||
"disk.") % pv.uuid)
|
||||
disk_caps = disk.capabilities
|
||||
if 'pv_dev' in disk_caps:
|
||||
if 'pv_size_mib' in disk_caps:
|
||||
info['total'] += disk_caps['pv_size_mib']
|
||||
else:
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
_("PV partition %s does not have a "
|
||||
"recorded size.") % disk_caps['pv_dev'])
|
||||
else:
|
||||
info['total'] += disk.size_mib
|
||||
info['used'] = caps[sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB]
|
||||
info['free'] = info['total'] - info['used']
|
||||
|
||||
# Limit the allowed size to provide a usable configuration when
|
||||
# provisioned. This is the same range that is enforced in sysinv:
|
||||
# sysinv/api/controllers/v1/ipv.py:_instances_lv_min_allowed_mib().
|
||||
# Here we calculate the values to display to the end user so that they
|
||||
# know the acceptable range to use.
|
||||
|
||||
# The following comment below is from sysinv to provide context:
|
||||
#
|
||||
# 80GB is the cutoff in the kickstart files for a virtualbox disk vs. a
|
||||
# normal disk. Use a similar cutoff here for the volume group size. If
|
||||
# the volume group is large enough then bump the min_mib value. The
|
||||
# min_mib value is set to provide a reasonable minimum amount of space
|
||||
# for /etc/nova/instances
|
||||
if info['total'] < (80 * 1024):
|
||||
info['allowed_min'] = 2 * 1024
|
||||
else:
|
||||
info['allowed_min'] = 5 * 1024
|
||||
info['allowed_max'] = info['total'] >> 1
|
||||
return info
|
||||
|
||||
|
||||
class ParamForm(ParamMixin, forms.SelfHandlingForm):
|
||||
type = forms.ChoiceField(
|
||||
label=_("Parameters"),
|
||||
required=True,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'type'}))
|
||||
|
||||
lvg_id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
failure_url = 'horizon:admin:inventory:localvolumegroupdetail'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ParamForm, self).__init__(*args, **kwargs)
|
||||
self._lvg = self.get_lvg_lvm_info(kwargs['initial']['lvg_id'])
|
||||
caps = self._lvg['lvg'].capabilities
|
||||
|
||||
if self._lvg['lvg'].lvm_vg_name == sysinv.LVG_NOVA_LOCAL:
|
||||
self.fields[sysinv.LVG_NOVA_PARAM_BACKING] = forms.ChoiceField(
|
||||
label=_("Instance Backing"),
|
||||
initial=caps.get(sysinv.LVG_NOVA_PARAM_BACKING),
|
||||
required=True,
|
||||
choices=BACKING_CHOICES,
|
||||
help_text=(_("%s") %
|
||||
PARAMS_HELP.get(sysinv.LVG_NOVA_PARAM_BACKING,
|
||||
None)),
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'type',
|
||||
'data-type-instance_backing': ''}))
|
||||
|
||||
self.fields[sysinv.LVG_NOVA_PARAM_DISK_OPS] = forms.IntegerField(
|
||||
label=_("Concurrent Disk Operations"),
|
||||
initial=caps.get(sysinv.LVG_NOVA_PARAM_DISK_OPS),
|
||||
required=True,
|
||||
help_text=(_("%s") %
|
||||
PARAMS_HELP.get(sysinv.LVG_NOVA_PARAM_DISK_OPS,
|
||||
None)),
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'type',
|
||||
'data-type-concurrent_disk_operations': ''}))
|
||||
|
||||
if caps.get(sysinv.LVG_NOVA_PARAM_BACKING) == \
|
||||
sysinv.LVG_NOVA_BACKING_LVM:
|
||||
inst_size_mib = sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB
|
||||
self.fields[inst_size_mib] = \
|
||||
forms.IntegerField(
|
||||
label=_("Instances Logical Volume Size"),
|
||||
initial=caps.get(inst_size_mib),
|
||||
required=True,
|
||||
help_text=(_("%s") %
|
||||
PARAMS_HELP.get(inst_size_mib, None)),
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'type',
|
||||
'data-type-instances_lv_size_mib': ''}))
|
||||
|
||||
elif self._lvg['lvg'].lvm_vg_name == sysinv.LVG_CINDER_VOLUMES:
|
||||
self.fields[sysinv.LVG_CINDER_PARAM_LVM_TYPE] = forms.ChoiceField(
|
||||
label=_("LVM Provisioning Type"),
|
||||
initial=caps.get(sysinv.LVG_CINDER_PARAM_LVM_TYPE),
|
||||
required=True,
|
||||
choices=LVM_TYPE_CHOICES,
|
||||
help_text=(_("%s") %
|
||||
PARAMS_HELP.get(sysinv.LVG_CINDER_PARAM_LVM_TYPE,
|
||||
None)),
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'type',
|
||||
'data-type-lvm_type': ''}))
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(ParamForm, self).clean()
|
||||
key = cleaned_data.get('type', None)
|
||||
if self._lvg['lvg'].lvm_vg_name == sysinv.LVG_NOVA_LOCAL:
|
||||
field = NOVA_PARAMS_FIELD_MAP.get(key, None)
|
||||
elif self._lvg['lvg'].lvm_vg_name == sysinv.LVG_CINDER_VOLUMES:
|
||||
field = CINDER_PARAMS_FIELD_MAP.get(key, None)
|
||||
|
||||
if field is not None:
|
||||
value = cleaned_data.get(field)
|
||||
|
||||
cleaned_data['key'] = key
|
||||
cleaned_data['value'] = value
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def _clean_required_value(self, key, field):
|
||||
"""Validate required fields for a specific key type."""
|
||||
keytype = self.cleaned_data.get('type', None)
|
||||
if keytype == key:
|
||||
value = self.cleaned_data.get(field, None)
|
||||
if value in validators.EMPTY_VALUES:
|
||||
raise forms.ValidationError(_('This field is required.'))
|
||||
return value
|
||||
|
||||
def clean_instances_lv_size_mib(self):
|
||||
data = self.cleaned_data[sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB]
|
||||
if self.cleaned_data[sysinv.LVG_NOVA_PARAM_BACKING] == sysinv.LVG_NOVA_BACKING_LVM \
|
||||
and 'allowed_min' in self._lvg:
|
||||
validators.MinValueValidator(self._lvg['allowed_min'])(
|
||||
data)
|
||||
validators.MaxValueValidator(self._lvg['allowed_max'])(
|
||||
data)
|
||||
return data
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditParam, self).get_context_data(**kwargs)
|
||||
context.update(self._lvg)
|
||||
return context
|
||||
|
||||
|
||||
class EditParam(ParamForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditParam, self).__init__(*args, **kwargs)
|
||||
|
||||
# cannot change the type/key during edit
|
||||
self.fields['type'].widget.attrs['readonly'] = True
|
||||
|
||||
key = self.initial['key']
|
||||
value = self.initial['value']
|
||||
|
||||
# ensure checkboxes receive a boolean value as the initial value
|
||||
# so that they don't get an override value attribute
|
||||
if isinstance(value, basestring) and value.lower() == 'false':
|
||||
value = False
|
||||
elif isinstance(value, basestring) and value.lower() == 'true':
|
||||
value = True
|
||||
|
||||
# setup initial values for the fields based on the defined key/value
|
||||
if self._lvg['lvg'].lvm_vg_name == sysinv.LVG_NOVA_LOCAL:
|
||||
field = NOVA_PARAMS_FIELD_MAP.get(key, None)
|
||||
param_choices = NOVA_PARAMS_CHOICES
|
||||
elif self._lvg['lvg'].lvm_vg_name == sysinv.LVG_CINDER_VOLUMES:
|
||||
field = CINDER_PARAMS_FIELD_MAP.get(key, None)
|
||||
param_choices = CINDER_PARAMS_CHOICES
|
||||
|
||||
if field is not None:
|
||||
self.initial['type'] = key
|
||||
self.initial[field] = value
|
||||
|
||||
# instances_lv_size_mib only valid for lvm backing
|
||||
if self._lvg['lvg'].capabilities.get(sysinv.LVG_NOVA_PARAM_BACKING) \
|
||||
== sysinv.LVG_NOVA_BACKING_IMAGE:
|
||||
self.fields['type'].choices = \
|
||||
[(k, v) for k, v in param_choices
|
||||
if k != sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB]
|
||||
else:
|
||||
self.fields['type'].choices = [(k, v) for k, v in param_choices]
|
||||
|
||||
def handle(self, request, data):
|
||||
lvg_id = data['lvg_id']
|
||||
try:
|
||||
if isinstance(data['value'], bool):
|
||||
value = str(data['value'])
|
||||
data['value'] = value
|
||||
metadata = {data['key']: data['value']}
|
||||
|
||||
patch = []
|
||||
patch.append({'path': '/capabilities',
|
||||
'value': jsonutils.dumps(metadata),
|
||||
'op': 'replace'})
|
||||
|
||||
api.sysinv.host_lvg_update(request, lvg_id, patch)
|
||||
msg = _('Updated parameter "%s".') % data['key']
|
||||
messages.success(request, msg)
|
||||
return True
|
||||
except Exception as e:
|
||||
msg = _('Unable to edit parameter "{0}".'
|
||||
' Details: {1}').format(data['key'], e)
|
||||
redirect = reverse(self.failure_url, args=[lvg_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
@ -0,0 +1,67 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2015-2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django.template import defaultfilters as filters
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.lvg_params \
|
||||
import forms
|
||||
|
||||
from openstack_dashboard.api import sysinv
|
||||
|
||||
|
||||
class ParamEdit(tables.LinkAction):
|
||||
name = "edit"
|
||||
verbose_name = _("Edit")
|
||||
url = "horizon:admin:inventory:storages:lvg:edit"
|
||||
classes = ("btn-edit", "ajax-modal")
|
||||
|
||||
def get_link_url(self, params):
|
||||
return reverse(self.url, args=[self.table.kwargs['lvg_id'],
|
||||
params.key])
|
||||
|
||||
|
||||
def get_parameters_name(datum):
|
||||
return forms.get_param_key_name(datum.key)
|
||||
|
||||
|
||||
def get_parameters_value(datum):
|
||||
if datum is None or datum.value is None:
|
||||
return None
|
||||
if datum.key == sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB:
|
||||
value = datum.value
|
||||
if datum.key == sysinv.LVG_NOVA_PARAM_BACKING:
|
||||
value = datum.value
|
||||
if datum.key == sysinv.LVG_NOVA_PARAM_DISK_OPS:
|
||||
value = datum.value
|
||||
if datum.key == sysinv.LVG_CINDER_PARAM_LVM_TYPE:
|
||||
value = datum.value
|
||||
return value
|
||||
|
||||
|
||||
class ParamsTable(tables.DataTable):
|
||||
name = tables.Column(get_parameters_name,
|
||||
verbose_name=_('Name'))
|
||||
key = tables.Column('key', verbose_name=_('Key'))
|
||||
value = tables.Column(get_parameters_value,
|
||||
verbose_name=_('Value'),
|
||||
filters=[filters.linebreaksbr])
|
||||
|
||||
class Meta(object):
|
||||
name = "params"
|
||||
verbose_name = _("Parameters")
|
||||
row_actions = (ParamEdit,)
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return datum.key
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.key
|
@ -0,0 +1,17 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.lvg_params \
|
||||
import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^(?P<key>[^/]+)/edit/$', views.EditView.as_view(),
|
||||
name='edit')]
|
@ -0,0 +1,56 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.lvg_params \
|
||||
import forms as project_forms
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EditView(forms.ModalFormView):
|
||||
form_class = project_forms.EditParam
|
||||
template_name = 'admin/inventory/storages/lvg/edit.html'
|
||||
success_url = 'horizon:admin:inventory:localvolumegroupdetail'
|
||||
|
||||
def get_form(self, form_class):
|
||||
self.form = super(self.__class__, self).get_form(form_class)
|
||||
return self.form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditView, self).get_context_data(**kwargs)
|
||||
context['key'] = self.kwargs['key']
|
||||
context.update(self.form.get_lvg_lvm_info(self.kwargs['lvg_id']))
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
lvg_id = self.kwargs['lvg_id']
|
||||
key = self.kwargs['key']
|
||||
try:
|
||||
params = api.sysinv.host_lvg_get_params(
|
||||
self.request, lvg_id, raw=True)
|
||||
except Exception:
|
||||
params = {}
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve lvg parameter data."))
|
||||
|
||||
return {'lvg_id': lvg_id,
|
||||
'key': key,
|
||||
'value': params.get(key, '')}
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.__class__.success_url,
|
||||
args=(self.kwargs['lvg_id'],))
|
631
cgcs_dashboard/dashboards/admin/inventory/storages/tables.py
Executable file
631
cgcs_dashboard/dashboards/admin/inventory/storages/tables.py
Executable file
@ -0,0 +1,631 @@
|
||||
#
|
||||
# Copyright (c) 2013-2015, 2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import template
|
||||
from django.template import defaultfilters as filters
|
||||
from django.utils.translation import string_concat # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from openstack_dashboard import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ##########
|
||||
# ACTIONS
|
||||
# ##########
|
||||
class CreateStorageVolume(tables.LinkAction):
|
||||
name = "createstoragevolume"
|
||||
verbose_name = ("Assign Storage Function")
|
||||
url = "horizon:admin:inventory:addstoragevolume"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
icon = "plus"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
host = self.table.kwargs['host']
|
||||
self.verbose_name = _("Assign Storage Function")
|
||||
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
|
||||
if host._personality != 'storage':
|
||||
return False
|
||||
|
||||
if host._administrative == 'unlocked':
|
||||
if 'storage' in host._subfunctions:
|
||||
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)"))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class CreateDiskProfile(tables.LinkAction):
|
||||
name = "creatediskprofile"
|
||||
verbose_name = ("Create Storage Profile")
|
||||
url = "horizon:admin:inventory:adddiskprofile"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
return True
|
||||
|
||||
|
||||
class CreatePartition(tables.LinkAction):
|
||||
name = "createpartition"
|
||||
verbose_name = ("Create a new partition")
|
||||
url = "horizon:admin:inventory:createpartition"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
icon = "plus"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
host = self.table.kwargs['host']
|
||||
self.verbose_name = _("Create a new partition")
|
||||
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
|
||||
if host._personality != 'storage':
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class DeletePartition(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Partition",
|
||||
u"Delete Partitions",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Partition",
|
||||
u"Deleted Partitions",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, partition=None):
|
||||
host = self.table.kwargs['host']
|
||||
PARTITION_IN_USE_STATUS = api.sysinv.PARTITION_IN_USE_STATUS
|
||||
PARTITION_STATUS_MSG = api.sysinv.PARTITION_STATUS_MSG
|
||||
|
||||
if partition:
|
||||
if partition.type_guid != api.sysinv.USER_PARTITION_PHYS_VOL:
|
||||
return False
|
||||
|
||||
if (partition.status ==
|
||||
PARTITION_STATUS_MSG[PARTITION_IN_USE_STATUS]):
|
||||
return False
|
||||
|
||||
if partition.ipv_uuid:
|
||||
return False
|
||||
|
||||
# Get all the partitions from the same disk.
|
||||
disk_partitions = \
|
||||
api.sysinv.host_disk_partition_list(request, host.uuid,
|
||||
partition.idisk_uuid)
|
||||
|
||||
if partition.device_path:
|
||||
partition_number = re.match('.*?([0-9]+)$',
|
||||
partition.device_path).group(1)
|
||||
for dpart in disk_partitions:
|
||||
dpart_number = re.match('.*?([0-9]+)$',
|
||||
dpart.device_path).group(1)
|
||||
if int(dpart_number) > int(partition_number):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def delete(self, request, partition_id):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
try:
|
||||
api.sysinv.host_disk_partition_delete(request, partition_id)
|
||||
except Exception as e:
|
||||
msg = _('Failed to delete host %(hid)s partition %(pv)s. '
|
||||
'%(e_msg)s') % {'hid': host_id,
|
||||
'pv': partition_id,
|
||||
'e_msg': e}
|
||||
LOG.info(msg)
|
||||
redirect = reverse('horizon:admin:inventory:detail',
|
||||
args=(host_id,))
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class EditPartition(tables.LinkAction):
|
||||
name = "editpartition"
|
||||
verbose_name = _("Edit")
|
||||
url = "horizon:admin:inventory:editpartition"
|
||||
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, partition):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id, partition.uuid))
|
||||
|
||||
def allowed(self, request, partition=None):
|
||||
host = self.table.kwargs['host']
|
||||
PARTITION_IN_USE_STATUS = api.sysinv.PARTITION_IN_USE_STATUS
|
||||
PARTITION_STATUS_MSG = api.sysinv.PARTITION_STATUS_MSG
|
||||
|
||||
if partition:
|
||||
if partition.type_guid != api.sysinv.USER_PARTITION_PHYS_VOL:
|
||||
return False
|
||||
|
||||
if (partition.status ==
|
||||
PARTITION_STATUS_MSG[PARTITION_IN_USE_STATUS]):
|
||||
return False
|
||||
|
||||
if partition.ipv_uuid:
|
||||
return False
|
||||
|
||||
# Get all the partitions from the same disk.
|
||||
disk_partitions = \
|
||||
api.sysinv.host_disk_partition_list(request,
|
||||
host.uuid,
|
||||
partition.idisk_uuid)
|
||||
|
||||
if partition.device_path:
|
||||
partition_number = re.match('.*?([0-9]+)$',
|
||||
partition.device_path).group(1)
|
||||
for dpart in disk_partitions:
|
||||
dpart_number = re.match('.*?([0-9]+)$',
|
||||
dpart.device_path).group(1)
|
||||
if int(dpart_number) > int(partition_number):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# ##########
|
||||
# TABLES
|
||||
# ##########
|
||||
|
||||
|
||||
def get_model_num(disk):
|
||||
return disk.get_model_num()
|
||||
|
||||
|
||||
def get_disk_info(disk):
|
||||
template_name = 'admin/inventory/_disk_info.html'
|
||||
context = {
|
||||
"disk": disk,
|
||||
"disk_info": disk.device_path,
|
||||
"id": disk.uuid,
|
||||
}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
class DisksTable(tables.DataTable):
|
||||
uuid = tables.Column('uuid',
|
||||
verbose_name=('UUID'))
|
||||
disk_info = tables.Column(get_disk_info,
|
||||
verbose_name=_("Disk info"),
|
||||
attrs={'data-type': 'disk_info'})
|
||||
type = tables.Column('device_type',
|
||||
verbose_name=('Type'))
|
||||
size = tables.Column('size_mib',
|
||||
verbose_name=('Size (MiB)'))
|
||||
available_size = tables.Column('available_mib',
|
||||
verbose_name=('Available Size (MiB)'))
|
||||
rpm = tables.Column('rpm',
|
||||
verbose_name=('RPM'))
|
||||
|
||||
serial_id = tables.Column('serial_id',
|
||||
verbose_name=('Serial ID'))
|
||||
|
||||
model_num = tables.Column(get_model_num,
|
||||
verbose_name=('Model'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
class Meta(object):
|
||||
name = "disks"
|
||||
verbose_name = ("Disks")
|
||||
columns = ('uuid', 'disk_info', 'type', 'size', 'available_size',
|
||||
'rpm', 'serial_id', 'model_num')
|
||||
multi_select = False
|
||||
table_actions = ()
|
||||
|
||||
|
||||
class EditStor(tables.LinkAction):
|
||||
name = "editstoragevolume"
|
||||
verbose_name = _("Edit")
|
||||
url = "horizon:admin:inventory:editstoragevolume"
|
||||
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, stor):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id, stor.uuid))
|
||||
|
||||
def allowed(self, request, stor=None):
|
||||
host = self.table.kwargs['host']
|
||||
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
|
||||
if host._administrative == 'unlocked':
|
||||
if 'storage' in host._subfunctions:
|
||||
if "disabled" not in self.classes:
|
||||
self.classes = [c for c in self.classes] + ['disabled']
|
||||
|
||||
if stor and stor.function == 'osd':
|
||||
forihostuuid = self.table.kwargs['host'].uuid
|
||||
journal_stors = \
|
||||
api.sysinv.host_stor_get_by_function(request, forihostuuid,
|
||||
'journal')
|
||||
|
||||
if not journal_stors:
|
||||
self.classes = [c for c in self.classes] + ['disabled']
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class DeleteStor(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Journal",
|
||||
u"Delete Journals",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Journal",
|
||||
u"Deleted Journals",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, stor):
|
||||
host = self.table.kwargs['host']
|
||||
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
|
||||
if host._administrative == 'unlocked':
|
||||
if 'storage' in host._subfunctions:
|
||||
if "disabled" not in self.classes:
|
||||
self.classes = [c for c in self.classes] + ['disabled']
|
||||
|
||||
if stor:
|
||||
return stor.function == 'journal'
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
api.sysinv.host_stor_delete(request, obj_id)
|
||||
|
||||
|
||||
class StorageVolumesTable(tables.DataTable):
|
||||
uuid = tables.Column('uuid',
|
||||
verbose_name=('UUID'))
|
||||
osdid = tables.Column('osdid',
|
||||
verbose_name=('OSD ID'))
|
||||
function = tables.Column('function',
|
||||
verbose_name=('Function'))
|
||||
idisk_uuid = tables.Column('idisk_uuid',
|
||||
verbose_name=('Disk UUID'))
|
||||
journal_path = tables.Column('journal_path',
|
||||
verbose_name=('Journal Path'))
|
||||
journal_size_mib = tables.Column('journal_size_mib',
|
||||
verbose_name=('Journal MiB'))
|
||||
journal_location = tables.Column('journal_location',
|
||||
verbose_name=('Journal Location'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
class Meta(object):
|
||||
name = "storagevolumes"
|
||||
verbose_name = ("Storage Functions")
|
||||
columns = ('uuid', 'function', 'osdid', 'idisk_uuid', 'journal_path',
|
||||
'journal_size_mib', 'journal_location')
|
||||
multi_select = False
|
||||
row_actions = (DeleteStor, EditStor,)
|
||||
table_actions = (CreateStorageVolume, CreateDiskProfile,)
|
||||
|
||||
|
||||
class AddLocalVolumeGroup(tables.LinkAction):
|
||||
name = "addlocalvolumegroup"
|
||||
verbose_name = ("Add Local Volume Group")
|
||||
url = "horizon:admin:inventory:addlocalvolumegroup"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
icon = "plus"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
host = self.table.kwargs['host']
|
||||
self.verbose_name = _("Add Local Volume Group")
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
|
||||
if not host._administrative == 'locked':
|
||||
if 'compute' in host._subfunctions and \
|
||||
host.compute_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)"))
|
||||
|
||||
# LVGs that are considered as "present" in the system are those
|
||||
# in an adding or provisioned state.
|
||||
current_lvg_states = [api.sysinv.LVG_ADD, api.sysinv.LVG_PROV]
|
||||
ilvg_list = api.sysinv.host_lvg_list(request, host.uuid)
|
||||
current_lvgs = [lvg.lvm_vg_name for lvg in ilvg_list
|
||||
if lvg.vg_state in current_lvg_states]
|
||||
compatible_lvgs = []
|
||||
|
||||
if host._personality == 'controller':
|
||||
compatible_lvgs += [api.sysinv.LVG_CINDER_VOLUMES]
|
||||
|
||||
if 'compute' in host._subfunctions:
|
||||
compatible_lvgs += [api.sysinv.LVG_NOVA_LOCAL]
|
||||
|
||||
allowed_lvgs = set(compatible_lvgs) - set(current_lvgs)
|
||||
if not any(allowed_lvgs):
|
||||
if "disabled" not in self.classes:
|
||||
self.classes = [c for c in self.classes] + ['disabled']
|
||||
self.verbose_name = string_concat(self.verbose_name, ' ',
|
||||
_("(All Added)"))
|
||||
|
||||
return True # The action should always be displayed
|
||||
|
||||
|
||||
class RemoveLocalVolumeGroup(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Local Volume Group",
|
||||
u"Delete Local Volume Groups",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Local Volume Group",
|
||||
u"Deleted Local Volume Groups",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, lvg=None):
|
||||
host = self.table.kwargs['host']
|
||||
return ((((host._administrative == 'locked') or
|
||||
(('compute' in host._subfunctions) and
|
||||
(host.compute_config_required is True))) and
|
||||
(lvg.lvm_vg_name == api.sysinv.LVG_NOVA_LOCAL)) or
|
||||
((api.sysinv.CINDER_BACKEND_LVM in
|
||||
api.sysinv.get_cinder_backend(request)) and
|
||||
(lvg.lvm_vg_name == api.sysinv.LVG_CINDER_VOLUMES) and
|
||||
(api.sysinv.LVG_ADD in lvg.vg_state)))
|
||||
|
||||
def delete(self, request, lvg_id):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
try:
|
||||
api.sysinv.host_lvg_delete(request, lvg_id)
|
||||
except Exception as e:
|
||||
msg = _('Failed to delete host %(hid)s local '
|
||||
'volume group %(lvg)s '
|
||||
'%(e_msg)s') % \
|
||||
{'hid': host_id, 'lvg': lvg_id, 'e_msg': e}
|
||||
|
||||
redirect = reverse('horizon:admin:inventory:detail',
|
||||
args=(host_id,))
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class LocalVolumeGroupsTable(tables.DataTable):
|
||||
name = tables.Column('lvm_vg_name',
|
||||
link="horizon:admin:inventory:localvolumegroupdetail",
|
||||
verbose_name=('Name'))
|
||||
state = tables.Column('vg_state',
|
||||
verbose_name=('State'))
|
||||
access = tables.Column('lvm_vg_access',
|
||||
verbose_name=('Access'))
|
||||
size = tables.Column('lvm_vg_size',
|
||||
verbose_name=('Size'),
|
||||
filters=(filters.filesizeformat,))
|
||||
pvs = tables.Column('lvm_cur_pv',
|
||||
verbose_name=('Current Physical Volumes'))
|
||||
lvs = tables.Column('lvm_cur_lv',
|
||||
verbose_name=('Current Logical Volumes'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
msg = datum.uuid
|
||||
if datum.lvm_vg_name:
|
||||
msg += " (%s)" % datum.lvm_vg_name
|
||||
return unicode(msg)
|
||||
|
||||
class Meta(object):
|
||||
name = "localvolumegroups"
|
||||
verbose_name = ("Local Volume Groups")
|
||||
columns = ('name', 'state', 'access', 'size', 'pvs', 'lvs',)
|
||||
multi_select = False
|
||||
row_actions = (RemoveLocalVolumeGroup,)
|
||||
table_actions = (AddLocalVolumeGroup, CreateDiskProfile)
|
||||
|
||||
|
||||
class AddPhysicalVolume(tables.LinkAction):
|
||||
name = "addphysicalvolume"
|
||||
verbose_name = ("Add Physical Volume")
|
||||
url = "horizon:admin:inventory:addphysicalvolume"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
icon = "plus"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
return reverse(self.url, args=(host_id,))
|
||||
|
||||
def allowed(self, request, datum):
|
||||
host = self.table.kwargs['host']
|
||||
self.verbose_name = _("Add Physical Volume")
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
|
||||
# cgts-vg, cinder-volumes: Allow adding to any controller
|
||||
if host._personality == api.sysinv.PERSONALITY_CONTROLLER:
|
||||
return True
|
||||
|
||||
# nova-local: Allow adding to any locked host with a compute
|
||||
# subfunction. On an AIO, the previous check superceeds this.
|
||||
if host._administrative != 'locked':
|
||||
if 'compute' in host._subfunctions and \
|
||||
host.compute_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
|
||||
api.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
|
||||
|
||||
|
||||
class RemovePhysicalVolume(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Physical Volume",
|
||||
u"Delete Physical Volumes",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Physical Volume",
|
||||
u"Deleted Physical Volumes",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, pv=None):
|
||||
host = self.table.kwargs['host']
|
||||
return ((((host._administrative == 'locked') or
|
||||
(('compute' in host._subfunctions) and
|
||||
(host.compute_config_required is True))) and
|
||||
(pv.lvm_vg_name == api.sysinv.LVG_NOVA_LOCAL)) or
|
||||
((api.sysinv.CINDER_BACKEND_LVM in
|
||||
api.sysinv.get_cinder_backend(request)) and
|
||||
((pv.lvm_vg_name == api.sysinv.LVG_CINDER_VOLUMES) and
|
||||
(api.sysinv.PV_ADD in pv.pv_state))))
|
||||
|
||||
def delete(self, request, pv_id):
|
||||
host_id = self.table.kwargs['host_id']
|
||||
try:
|
||||
api.sysinv.host_pv_delete(request, pv_id)
|
||||
except Exception as e:
|
||||
msg = _('Failed to delete host %(hid)s physical volume %(pv)s. '
|
||||
'%(e_msg)s') % {'hid': host_id, 'pv': pv_id, 'e_msg': e}
|
||||
LOG.info(msg)
|
||||
redirect = reverse('horizon:admin:inventory:detail',
|
||||
args=(host_id,))
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class PhysicalVolumesTable(tables.DataTable):
|
||||
name = tables.Column('lvm_pv_name',
|
||||
link="horizon:admin:inventory:physicalvolumedetail",
|
||||
verbose_name=('Name'))
|
||||
pv_state = tables.Column('pv_state',
|
||||
verbose_name=('State'))
|
||||
pv_type = tables.Column('pv_type',
|
||||
verbose_name=('Type'))
|
||||
disk_or_part_uuid = tables.Column('disk_or_part_uuid',
|
||||
verbose_name=('Disk or Partition UUID'))
|
||||
disk_or_part_device_path = tables.Column('disk_or_part_device_path',
|
||||
verbose_name=('Disk or Partition'
|
||||
' Device Path'))
|
||||
lvm_vg_name = tables.Column('lvm_vg_name',
|
||||
verbose_name=('LVM Volume Group Name'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
msg = datum.uuid
|
||||
if datum.lvm_pv_name:
|
||||
msg += " (%s)" % datum.lvm_pv_name
|
||||
return unicode(msg)
|
||||
|
||||
class Meta(object):
|
||||
name = "physicalvolumes"
|
||||
verbose_name = ("Physical Volumes")
|
||||
columns = ('name', 'pv_state', 'pv_type', 'disk_or_part_uuid',
|
||||
'disk_or_part_device_node', 'disk_or_part_device_path',
|
||||
'lvm_vg_name')
|
||||
multi_select = False
|
||||
table_actions = (AddPhysicalVolume,)
|
||||
row_actions = (RemovePhysicalVolume,)
|
||||
|
||||
|
||||
class PartitionsTable(tables.DataTable):
|
||||
uuid = tables.Column('uuid',
|
||||
verbose_name=('UUID'))
|
||||
size_mib = tables.Column('size_mib',
|
||||
verbose_name=('Size (MiB)'))
|
||||
device_path = tables.Column('device_path',
|
||||
verbose_name=('Partition Device Path'))
|
||||
type_name = tables.Column('type_name',
|
||||
verbose_name=('Partition Type'))
|
||||
ipv_uuid = tables.Column('ipv_uuid',
|
||||
verbose_name=('Physical Volume UUID'))
|
||||
idisk_uuid = tables.Column('disk_uuid',
|
||||
verbose_name=('Disk UUID'))
|
||||
status = tables.Column('status',
|
||||
verbose_name=('Status'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return unicode(datum.uuid)
|
||||
|
||||
def get_object_display(self, datum):
|
||||
msg = datum.uuid
|
||||
if datum.device_path:
|
||||
msg += " (%s)" % datum.device_path
|
||||
return unicode(msg)
|
||||
|
||||
class Meta(object):
|
||||
name = "partitions"
|
||||
verbose_name = ("Partitions")
|
||||
columns = ('uuid', 'device_path', 'size_mib', 'type_name', 'status')
|
||||
multi_select = False
|
||||
row_actions = (EditPartition, DeletePartition,)
|
||||
table_actions = (CreatePartition,)
|
64
cgcs_dashboard/dashboards/admin/inventory/storages/tabs.py
Normal file
64
cgcs_dashboard/dashboards/admin/inventory/storages/tabs.py
Normal file
@ -0,0 +1,64 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.lvg_params \
|
||||
import tables as params_table
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LocalVolumeGroupOverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "lvg_overview"
|
||||
template_name = ("admin/inventory/"
|
||||
"_detail_local_volume_group_overview.html")
|
||||
|
||||
def get_context_data(self, request):
|
||||
lvg_id = self.tab_group.kwargs['lvg_id']
|
||||
try:
|
||||
lvg = api.sysinv.host_lvg_get(request, lvg_id)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:storages:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve flavor details.'),
|
||||
redirect=redirect)
|
||||
return {'lvg': lvg}
|
||||
|
||||
|
||||
class LocalVolumeGroupParametersTab(tabs.TableTab):
|
||||
table_classes = (params_table.ParamsTable,)
|
||||
name = _("Parameters")
|
||||
slug = "lvg_params"
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
|
||||
def get_params_data(self):
|
||||
request = self.tab_group.request
|
||||
lvg_id = self.tab_group.kwargs['lvg_id']
|
||||
try:
|
||||
params = api.sysinv.host_lvg_get_params(request, lvg_id)
|
||||
params.sort(key=lambda es: (es.key,))
|
||||
except Exception:
|
||||
params = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve parameter list.'))
|
||||
return params
|
||||
|
||||
|
||||
class LocalVolumeGroupDetailTabs(tabs.TabGroup):
|
||||
slug = "lvg_details"
|
||||
tabs = (LocalVolumeGroupOverviewTab, LocalVolumeGroupParametersTab)
|
||||
sticky = True
|
19
cgcs_dashboard/dashboards/admin/inventory/storages/urls.py
Normal file
19
cgcs_dashboard/dashboards/admin/inventory/storages/urls.py
Normal file
@ -0,0 +1,19 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2015 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
from django.conf.urls import include # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.lvg_params \
|
||||
import urls as lvg_params_urls
|
||||
|
||||
urlpatterns = [
|
||||
url(r'lvg/',
|
||||
include(lvg_params_urls, namespace='lvg'))
|
||||
]
|
456
cgcs_dashboard/dashboards/admin/inventory/storages/views.py
Executable file
456
cgcs_dashboard/dashboards/admin/inventory/storages/views.py
Executable file
@ -0,0 +1,456 @@
|
||||
#
|
||||
# Copyright (c) 2013-2015, 2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tabs
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.forms import \
|
||||
AddDiskProfile
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.forms import \
|
||||
AddLocalVolumeGroup
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.forms import \
|
||||
AddPhysicalVolume
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.forms import \
|
||||
AddStorageVolume
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.forms import \
|
||||
CreatePartition
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.forms import \
|
||||
EditPartition
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.forms import \
|
||||
EditStorageVolume
|
||||
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages.tabs \
|
||||
import LocalVolumeGroupDetailTabs
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddStorageVolumeView(forms.ModalFormView):
|
||||
form_class = AddStorageVolume
|
||||
template_name = 'admin/inventory/storages/createstoragevolume.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddStorageVolumeView, self).get_context_data(**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddStorageVolumeView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, initial['host_id'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Unable to retrieve host.'))
|
||||
initial['ihost_uuid'] = host.uuid
|
||||
initial['hostname'] = host.hostname
|
||||
return initial
|
||||
|
||||
|
||||
class EditStorageVolumeView(forms.ModalFormView):
|
||||
form_class = EditStorageVolume
|
||||
template_name = 'admin/inventory/storages/editstoragevolume.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
stor_uuid = self.kwargs['stor_uuid']
|
||||
host_id = self.kwargs['host_id']
|
||||
LOG.debug("stor_id=%s kwargs=%s",
|
||||
stor_uuid, self.kwargs)
|
||||
try:
|
||||
self._object = api.sysinv.host_stor_get(self.request,
|
||||
stor_uuid)
|
||||
self._object.host_id = host_id
|
||||
except Exception:
|
||||
redirect = reverse("horizon:admin:inventory:detail",
|
||||
args=(self.kwargs['host_id'],))
|
||||
msg = _('Unable to retrieve stor details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditStorageVolumeView, self).get_context_data(**kwargs)
|
||||
stor = self._get_object()
|
||||
context['stor_uuid'] = stor.uuid
|
||||
context['host_id'] = stor.host_id
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
stor = self._get_object()
|
||||
return {'id': stor.uuid,
|
||||
'uuid': stor.uuid,
|
||||
'host_uuid': stor.ihost_uuid,
|
||||
'journal_location': stor.journal_location,
|
||||
'journal_size_mib': stor.journal_size_mib}
|
||||
|
||||
|
||||
class AddDiskProfileView(forms.ModalFormView):
|
||||
form_class = AddDiskProfile
|
||||
template_name = 'admin/inventory/storages/creatediskprofile.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_myhost_data(self):
|
||||
if not hasattr(self, "_host"):
|
||||
host_id = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_id)
|
||||
|
||||
all_disks = api.sysinv.host_disk_list(self.request, host.uuid)
|
||||
host.disks = [d for d in all_disks if
|
||||
(d.istor_uuid or d.ipv_uuid)]
|
||||
|
||||
host.partitions = api.sysinv.host_disk_partition_list(
|
||||
self.request, host.uuid)
|
||||
|
||||
host.stors = api.sysinv.host_stor_list(self.request, host.uuid)
|
||||
|
||||
all_lvgs = api.sysinv.host_lvg_list(self.request, host.uuid)
|
||||
host.lvgs = [l for l in all_lvgs if
|
||||
l.lvm_vg_name == api.sysinv.LVG_NOVA_LOCAL]
|
||||
|
||||
all_pvs = api.sysinv.host_pv_list(self.request, host.uuid)
|
||||
host.pvs = [p for p in all_pvs if
|
||||
p.lvm_vg_name == api.sysinv.LVG_NOVA_LOCAL]
|
||||
|
||||
journals = {}
|
||||
count = 0
|
||||
for s in host.stors:
|
||||
# count journals
|
||||
if s.function == 'journal':
|
||||
count += 1
|
||||
journals.update({s.uuid: count})
|
||||
|
||||
for s in host.stors:
|
||||
if s.function == 'journal' and count > 1:
|
||||
setattr(s, "count", journals[s.uuid])
|
||||
if s.function == 'osd':
|
||||
if s.journal_location != s.uuid:
|
||||
if count > 1:
|
||||
setattr(s, "count",
|
||||
journals[s.journal_location])
|
||||
|
||||
s.disks = [d.device_path
|
||||
for d in all_disks if
|
||||
d.istor_uuid and d.istor_uuid == s.uuid]
|
||||
s.disks = ", ".join(s.disks)
|
||||
|
||||
for l in host.lvgs:
|
||||
l.instance_backing = l.capabilities.get(
|
||||
api.sysinv.LVG_NOVA_PARAM_BACKING)
|
||||
l.concurrent_disk_operations = l.capabilities.get(
|
||||
api.sysinv.LVG_NOVA_PARAM_DISK_OPS)
|
||||
if (l.instance_backing and
|
||||
l.instance_backing == api.sysinv.LVG_NOVA_BACKING_LVM):
|
||||
l.instances_lv_size_mib = l.capabilities.get(
|
||||
api.sysinv.LVG_NOVA_PARAM_INSTANCES_SIZE_MIB)
|
||||
|
||||
l.lvm_type = l.capabilities.get(
|
||||
api.sysinv.LVG_CINDER_PARAM_LVM_TYPE)
|
||||
|
||||
l.dev_paths = [p.disk_or_part_device_path
|
||||
for p in all_pvs if
|
||||
p.lvm_vg_name and
|
||||
p.lvm_vg_name == api.sysinv.LVG_NOVA_LOCAL]
|
||||
l.dev_paths = ", ".join(l.dev_paths)
|
||||
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'host "%s".') % host_id,
|
||||
redirect=redirect)
|
||||
self._host = host
|
||||
|
||||
return self._host
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddDiskProfileView, self).get_context_data(**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
context['host'] = self.get_myhost_data()
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddDiskProfileView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, initial['host_id'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Unable to retrieve host.'))
|
||||
initial['personality'] = host._personality
|
||||
return initial
|
||||
|
||||
|
||||
class AddLocalVolumeGroupView(forms.ModalFormView):
|
||||
form_class = AddLocalVolumeGroup
|
||||
template_name = 'admin/inventory/storages/createlocalvolumegroup.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddLocalVolumeGroupView, self) \
|
||||
.get_context_data(**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddLocalVolumeGroupView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, initial['host_id'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Unable to retrieve host.'))
|
||||
initial['ihost_uuid'] = host.uuid
|
||||
initial['hostname'] = host.hostname
|
||||
return initial
|
||||
|
||||
|
||||
class AddPhysicalVolumeView(forms.ModalFormView):
|
||||
form_class = AddPhysicalVolume
|
||||
template_name = 'admin/inventory/storages/createphysicalvolume.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddPhysicalVolumeView, self).get_context_data(**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddPhysicalVolumeView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, initial['host_id'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Unable to retrieve host.'))
|
||||
initial['ihost_uuid'] = host.uuid
|
||||
initial['hostname'] = host.hostname
|
||||
return initial
|
||||
|
||||
|
||||
class DetailPhysicalVolumeView(views.HorizonTemplateView):
|
||||
template_name = 'admin/inventory/_detail_physical_volume.html'
|
||||
page_title = "{{ pv.lvm_pv_name }}"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailPhysicalVolumeView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
pv = self.get_data()
|
||||
|
||||
hostname = self.get_hostname(pv.ihost_uuid)
|
||||
host_nav = hostname or "Unprovisioned Node"
|
||||
breadcrumb = [
|
||||
(host_nav, reverse('horizon:admin:inventory:detail',
|
||||
args=(pv.ihost_uuid,))),
|
||||
(_("Physical Volumes"), None)
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
||||
context["pv"] = pv
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_hostname(self, host_uuid):
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_uuid)
|
||||
except Exception:
|
||||
host = {}
|
||||
msg = _('Unable to retrieve hostname details.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return host.hostname
|
||||
|
||||
def get_data(self):
|
||||
if not hasattr(self, "_pv"):
|
||||
pv_id = self.kwargs['pv_id']
|
||||
try:
|
||||
pv = api.sysinv.host_pv_get(self.request, pv_id)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'physical volume "%s".') % pv_id,
|
||||
redirect=redirect)
|
||||
|
||||
self._pv = pv
|
||||
return self._pv
|
||||
|
||||
|
||||
class DetailLocalVolumeGroupView(tabs.TabbedTableView):
|
||||
tab_group_class = LocalVolumeGroupDetailTabs
|
||||
template_name = 'admin/inventory/_detail_local_volume_group.html'
|
||||
page_title = "{{ lvg.lvm_vg_name}}"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailLocalVolumeGroupView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
lvg = self.get_data()
|
||||
|
||||
hostname = self.get_hostname(lvg.ihost_uuid)
|
||||
host_nav = hostname or "Unprovisioned Node"
|
||||
breadcrumb = [
|
||||
(host_nav, reverse('horizon:admin:inventory:detail',
|
||||
args=(lvg.ihost_uuid,))),
|
||||
(_("Local Volume Groups"), None)
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
||||
context["lvg"] = lvg
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
if not hasattr(self, "_lvg"):
|
||||
lvg_id = self.kwargs['lvg_id']
|
||||
try:
|
||||
lvg = api.sysinv.host_lvg_get(self.request, lvg_id)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'local volume group "%s".') % lvg_id,
|
||||
redirect=redirect)
|
||||
|
||||
self._lvg = lvg
|
||||
return self._lvg
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_hostname(self, host_uuid):
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, host_uuid)
|
||||
except Exception:
|
||||
host = {}
|
||||
msg = _('Unable to retrieve hostname details.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return host.hostname
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
lvg = self.get_data()
|
||||
return self.tab_group_class(request, lvg=lvg, **kwargs)
|
||||
|
||||
|
||||
class CreatePartitionView(forms.ModalFormView):
|
||||
form_class = CreatePartition
|
||||
template_name = 'admin/inventory/storages/createpartition.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
failure_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreatePartitionView, self).get_context_data(**kwargs)
|
||||
context['host_id'] = self.kwargs['host_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(CreatePartitionView, self).get_initial()
|
||||
initial['host_id'] = self.kwargs['host_id']
|
||||
try:
|
||||
host = api.sysinv.host_get(self.request, initial['host_id'])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, _('Unable to retrieve host.'))
|
||||
initial['ihost_uuid'] = host.uuid
|
||||
initial['hostname'] = host.hostname
|
||||
return initial
|
||||
|
||||
|
||||
class EditPartitionView(forms.ModalFormView):
|
||||
form_class = EditPartition
|
||||
template_name = 'admin/inventory/storages/editpartition.html'
|
||||
success_url = 'horizon:admin:inventory:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['host_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
partition_uuid = self.kwargs['partition_uuid']
|
||||
host_id = self.kwargs['host_id']
|
||||
LOG.debug("partition_id=%s kwargs=%s",
|
||||
partition_uuid, self.kwargs)
|
||||
try:
|
||||
self._object = api.sysinv.host_disk_partition_get(
|
||||
self.request,
|
||||
partition_uuid)
|
||||
self._object.host_id = host_id
|
||||
except Exception:
|
||||
redirect = reverse("horizon:admin:inventory:detail",
|
||||
args=(self.kwargs['host_id'],))
|
||||
msg = _('Unable to retrieve partition details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditPartitionView, self).get_context_data(**kwargs)
|
||||
partition = self._get_object()
|
||||
context['partition_uuid'] = partition.uuid
|
||||
context['host_id'] = partition.host_id
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
partition = self._get_object()
|
||||
return {'id': partition.uuid,
|
||||
'uuid': partition.uuid,
|
||||
'host_uuid': partition.ihost_uuid,
|
||||
'size_mib': partition.size_mib}
|
1102
cgcs_dashboard/dashboards/admin/inventory/tables.py
Executable file
1102
cgcs_dashboard/dashboards/admin/inventory/tables.py
Executable file
File diff suppressed because it is too large
Load Diff
671
cgcs_dashboard/dashboards/admin/inventory/tabs.py
Executable file
671
cgcs_dashboard/dashboards/admin/inventory/tabs.py
Executable file
@ -0,0 +1,671 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Wind River Systems, Inc.
|
||||
#
|
||||
# The right to copy, distribute, modify, or otherwise make use
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.inventory.cpu_functions import \
|
||||
tables as cpufunctions_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.devices import \
|
||||
tables as device_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.interfaces import \
|
||||
tables as interface_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.lldp import \
|
||||
tables as lldp_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.memorys import \
|
||||
tables as memory_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.ports import \
|
||||
tables as port_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.sensors import \
|
||||
tables as sensor_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory.storages import \
|
||||
tables as storage_tables
|
||||
from openstack_dashboard.dashboards.admin.inventory import \
|
||||
tables as toplevel_tables
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HostsTab(tabs.TableTab):
|
||||
table_classes = (toplevel_tables.HostsController,
|
||||
toplevel_tables.HostsStorage,
|
||||
toplevel_tables.HostsCompute,
|
||||
toplevel_tables.HostsUnProvisioned,)
|
||||
name = _("Hosts")
|
||||
slug = "hosts"
|
||||
template_name = ("admin/inventory/_hosts.html")
|
||||
|
||||
# for optimization, the complete hosts list, and phosts list from
|
||||
# patching service, are in class scope.
|
||||
all_hosts = []
|
||||
all_phosts = []
|
||||
|
||||
def get_all_hosts_data(self):
|
||||
request = self.request
|
||||
self.all_hosts = []
|
||||
try:
|
||||
self.all_hosts = api.sysinv.host_list(request)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve host list.'))
|
||||
self.all_phosts = []
|
||||
try:
|
||||
self.all_phosts = api.patch.get_hosts(request)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve host list from'
|
||||
' patching service.'))
|
||||
|
||||
def get_hosts_data(self, personality):
|
||||
hosts = self.all_hosts
|
||||
phosts = self.all_phosts
|
||||
|
||||
if personality == api.sysinv.PERSONALITY_CONTROLLER:
|
||||
hosts = [h for h in hosts if h._personality and
|
||||
h._personality.lower().startswith(
|
||||
api.sysinv.PERSONALITY_CONTROLLER)]
|
||||
elif personality == api.sysinv.PERSONALITY_UNKNOWN:
|
||||
hosts = [h for h in hosts if not h._personality]
|
||||
else:
|
||||
hosts = [h for h in hosts if h._personality and
|
||||
h._personality.lower() == personality]
|
||||
|
||||
# Add patching status data to hosts
|
||||
for h in hosts:
|
||||
for ph in phosts:
|
||||
if h.hostname == ph.hostname:
|
||||
if ph.interim_state is True:
|
||||
h.patch_current = "Pending"
|
||||
elif ph.patch_failed is True:
|
||||
h.patch_current = "Failed"
|
||||
else:
|
||||
h.patch_current = ph.patch_current
|
||||
h.requires_reboot = ph.requires_reboot
|
||||
h._patch_state = ph.state
|
||||
h.allow_insvc_patching = ph.allow_insvc_patching
|
||||
|
||||
# Sort hosts by hostname
|
||||
hosts.sort(key=lambda f: (f.hostname))
|
||||
return hosts
|
||||
|
||||
def get_hostscontroller_data(self):
|
||||
controllers = self.get_hosts_data(api.sysinv.PERSONALITY_CONTROLLER)
|
||||
|
||||
return controllers
|
||||
|
||||
def get_hostsstorage_data(self):
|
||||
storages = self.get_hosts_data(api.sysinv.PERSONALITY_STORAGE)
|
||||
|
||||
return storages
|
||||
|
||||
def get_hostscompute_data(self):
|
||||
computes = self.get_hosts_data(api.sysinv.PERSONALITY_COMPUTE)
|
||||
|
||||
return computes
|
||||
|
||||
def get_hostsunprovisioned_data(self):
|
||||
unprovisioned = self.get_hosts_data(api.sysinv.PERSONALITY_UNKNOWN)
|
||||
|
||||
return unprovisioned
|
||||
|
||||
def load_table_data(self):
|
||||
# Calls the get_{{ table_name }}_data methods for each table class
|
||||
# and sets the data on the tables
|
||||
self.get_all_hosts_data()
|
||||
return super(HostsTab, self).load_table_data()
|
||||
|
||||
def get_context_data(self, request):
|
||||
# Adds a {{ table_name }}_table item to the context for each table
|
||||
# in the table_classes attribute
|
||||
context = super(HostsTab, self).get_context_data(request)
|
||||
|
||||
controllers = context['hostscontroller_table'].data
|
||||
storages = context['hostsstorage_table'].data
|
||||
computes = context['hostscompute_table'].data
|
||||
unprovisioned = context['hostsunprovisioned_table'].data
|
||||
|
||||
context['controllers'] = controllers
|
||||
context['storages'] = storages
|
||||
context['computes'] = computes
|
||||
context['unprovisioned'] = unprovisioned
|
||||
|
||||
totals = []
|
||||
ctrl_cnt = 0
|
||||
comp_cnt = 0
|
||||
stor_cnt = 0
|
||||
degr_cnt = 0
|
||||
fail_cnt = 0
|
||||
|
||||
for h in controllers:
|
||||
ctrl_cnt += 1
|
||||
if h._availability == 'degraded':
|
||||
degr_cnt += 1
|
||||
elif h._availability == 'failed':
|
||||
fail_cnt += 1
|
||||
|
||||
for h in storages:
|
||||
stor_cnt += 1
|
||||
if h._availability == 'degraded':
|
||||
degr_cnt += 1
|
||||
elif h._availability == 'failed':
|
||||
fail_cnt += 1
|
||||
|
||||
for h in computes:
|
||||
comp_cnt += 1
|
||||
if h._availability == 'degraded':
|
||||
degr_cnt += 1
|
||||
elif h._availability == 'failed':
|
||||
fail_cnt += 1
|
||||
|
||||
if (ctrl_cnt > 0):
|
||||
badge = "badge-success"
|
||||
totals.append(
|
||||
{'name': "Controller", 'value': ctrl_cnt, 'badge': badge})
|
||||
|
||||
if (stor_cnt > 0):
|
||||
badge = "badge-success"
|
||||
totals.append(
|
||||
{'name': "Storage", 'value': stor_cnt, 'badge': badge})
|
||||
|
||||
if (comp_cnt > 0):
|
||||
badge = "badge-success"
|
||||
totals.append(
|
||||
{'name': "Compute", 'value': comp_cnt, 'badge': badge})
|
||||
|
||||
if (degr_cnt > 0):
|
||||
badge = "badge-warning"
|
||||
else:
|
||||
badge = ""
|
||||
totals.append({'name': "Degraded", 'value': degr_cnt, 'badge': badge})
|
||||
|
||||
if (fail_cnt > 0):
|
||||
badge = "badge-danger"
|
||||
else:
|
||||
badge = ""
|
||||
totals.append({'name': "Failed", 'value': fail_cnt, 'badge': badge})
|
||||
|
||||
context['totals'] = totals
|
||||
return context
|
||||
|
||||
|
||||
class CpuProfilesTab(tabs.TableTab):
|
||||
table_classes = (toplevel_tables.CpuProfilesTable, )
|
||||
name = _("Cpu Profiles")
|
||||
slug = "cpuprofiles"
|
||||
template_name = ("admin/inventory/_cpuprofiles.html")
|
||||
preload = False
|
||||
|
||||
def get_cpuprofiles_data(self):
|
||||
cpuprofiles = []
|
||||
try:
|
||||
cpuprofiles = api.sysinv.host_cpuprofile_list(self.request)
|
||||
cpuprofiles.sort(key=lambda f: (f.profilename))
|
||||
except Exception:
|
||||
cpuprofiles = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve host list.'))
|
||||
return cpuprofiles
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
return not api.sysinv.is_system_mode_simplex(request)
|
||||
|
||||
|
||||
class InterfaceProfilesTab(tabs.TableTab):
|
||||
table_classes = (toplevel_tables.InterfaceProfilesTable, )
|
||||
name = _("Interface Profiles")
|
||||
slug = "interfaceprofiles"
|
||||
template_name = ("admin/inventory/_interfaceprofiles.html")
|
||||
preload = False
|
||||
|
||||
def get_interfaceprofiles_data(self):
|
||||
interfaceprofiles = []
|
||||
try:
|
||||
interfaceprofiles = api.sysinv.host_interfaceprofile_list(
|
||||
self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve host list.'))
|
||||
interfaceprofiles.sort(key=lambda f: (f.profilename))
|
||||
return interfaceprofiles
|
||||
|
||||
def allowed(self, request, dataum=None):
|
||||
return not api.sysinv.is_system_mode_simplex(request)
|
||||
|
||||
|
||||
class DiskProfilesTab(tabs.TableTab):
|
||||
table_classes = (toplevel_tables.DiskProfilesTable, )
|
||||
name = _("Storage Profiles")
|
||||
slug = "diskprofiles"
|
||||
template_name = ("admin/inventory/_diskprofiles.html")
|
||||
preload = False
|
||||
|
||||
def get_diskprofiles_data(self):
|
||||
diskprofiles = []
|
||||
try:
|
||||
diskprofiles = api.sysinv.host_diskprofile_list(self.request)
|
||||
|
||||
for diskprofile in diskprofiles:
|
||||
journals = {}
|
||||
count = 0
|
||||
for stor in diskprofile.stors:
|
||||
if stor.function == 'journal':
|
||||
count += 1
|
||||
journals.update({stor.uuid: count})
|
||||
|
||||
for s in diskprofile.stors:
|
||||
if s.function == 'journal' and count > 1:
|
||||
setattr(s, "count", journals[s.uuid])
|
||||
if s.function == 'osd':
|
||||
if s.journal_location != s.uuid:
|
||||
if count > 1:
|
||||
setattr(s, "count",
|
||||
journals[s.journal_location])
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve storage profile list.'))
|
||||
diskprofiles.sort(key=lambda f: (f.profilename))
|
||||
return diskprofiles
|
||||
|
||||
def allowed(self, request, dataum=None):
|
||||
return not api.sysinv.is_system_mode_simplex(request)
|
||||
|
||||
|
||||
class MemoryProfilesTab(tabs.TableTab):
|
||||
table_classes = (toplevel_tables.MemoryProfilesTable, )
|
||||
name = _("Memory Profiles")
|
||||
slug = "memoryprofiles"
|
||||
template_name = ("admin/inventory/_memoryprofiles.html")
|
||||
preload = False
|
||||
|
||||
def get_memoryprofiles_data(self):
|
||||
memoryprofiles = []
|
||||
try:
|
||||
memoryprofiles = api.sysinv.host_memprofile_list(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve memory profile list.'))
|
||||
memoryprofiles.sort(key=lambda f: (f.profilename))
|
||||
return memoryprofiles
|
||||
|
||||
def allowed(self, request, dataum=None):
|
||||
return not api.sysinv.is_system_mode_simplex(request)
|
||||
|
||||
|
||||
class DeviceUsageTab(tabs.TableTab):
|
||||
table_classes = (device_tables.DeviceUsageTable, )
|
||||
name = _("Device Usage")
|
||||
slug = "deviceusage"
|
||||
template_name = ("admin/inventory/_deviceusage.html")
|
||||
preload = False
|
||||
|
||||
def get_sysinv_devices(self, request):
|
||||
device_list = api.sysinv.device_list_all(request)
|
||||
return device_list
|
||||
|
||||
def get_device_description(self, usage, devices):
|
||||
# Correlate the (device_id, vendor_id) or class_id to the
|
||||
# Sysinv view of devices to get a user-friendly description to
|
||||
# display.
|
||||
for device in devices:
|
||||
if (usage.device_id == device.pdevice_id and
|
||||
usage.vendor_id == device.pvendor_id):
|
||||
return device.pdevice
|
||||
elif (usage.class_id == device.pclass_id):
|
||||
return device.pclass
|
||||
|
||||
def get_deviceusage_data(self):
|
||||
deviceusage = []
|
||||
devices = []
|
||||
try:
|
||||
deviceusage = api.nova.get_device_usage_list(self.request)
|
||||
devices = self.get_sysinv_devices(self.request)
|
||||
for du in deviceusage:
|
||||
du.description = self.get_device_description(du, devices)
|
||||
except Exception:
|
||||
# exceptions.handle(self.request,
|
||||
# _('Unable to retrieve device usage.'))
|
||||
pass
|
||||
|
||||
return deviceusage
|
||||
|
||||
|
||||
class InventoryTabs(tabs.TabGroup):
|
||||
slug = "inventory"
|
||||
tabs = (
|
||||
HostsTab,
|
||||
CpuProfilesTab, InterfaceProfilesTab,
|
||||
DiskProfilesTab, MemoryProfilesTab, DeviceUsageTab)
|
||||
sticky = True
|
||||
|
||||
|
||||
class OverviewTab(tabs.TableTab):
|
||||
table_classes = (
|
||||
cpufunctions_tables.CpuFunctionsTable, port_tables.PortsTable,
|
||||
interface_tables.InterfacesTable)
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = ("admin/inventory/_detail_overview.html")
|
||||
|
||||
def get_cpufunctions_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
return host.core_assignment
|
||||
|
||||
def get_ports_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
host.ports.sort(key=lambda f: (f.name))
|
||||
return host.ports
|
||||
|
||||
def get_interfaces_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
|
||||
# add 'ports' member to interface class for easier mgmt in table
|
||||
if host.interfaces:
|
||||
for i in host.interfaces:
|
||||
if i.iftype == 'ethernet':
|
||||
i.ports = [p.uuid for p in host.ports if
|
||||
i.uuid == p.interface_uuid]
|
||||
i.portNameList = [p.get_port_display_name() for p in
|
||||
host.ports if
|
||||
i.uuid == p.interface_uuid]
|
||||
i.dpdksupport = [p.dpdksupport for p in host.ports if
|
||||
i.uuid == p.interface_uuid]
|
||||
elif i.iftype == 'vlan':
|
||||
for u in i.uses:
|
||||
for j in host.interfaces:
|
||||
if j.ifname == str(u):
|
||||
if j.iftype == 'ethernet':
|
||||
i.dpdksupport = [p.dpdksupport for p in
|
||||
host.ports if
|
||||
j.uuid ==
|
||||
p.interface_uuid]
|
||||
elif j.iftype == 'ae':
|
||||
for ae_u in j.uses:
|
||||
for k in host.interfaces:
|
||||
if k.ifname == str(ae_u):
|
||||
i.dpdksupport = [
|
||||
p.dpdksupport for p in
|
||||
host.ports if
|
||||
k.uuid ==
|
||||
p.interface_uuid]
|
||||
elif i.iftype == 'ae':
|
||||
for u in i.uses:
|
||||
for j in host.interfaces:
|
||||
if j.ifname == str(u):
|
||||
i.dpdksupport = [p.dpdksupport for p in
|
||||
host.ports if
|
||||
j.uuid == p.interface_uuid]
|
||||
|
||||
host.interfaces.sort(key=lambda f: (f.ifname))
|
||||
return host.interfaces
|
||||
|
||||
def get_context_data(self, request):
|
||||
context = super(OverviewTab, self).get_context_data(request)
|
||||
try:
|
||||
context['host'] = self.tab_group.kwargs['host']
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve inventory details.'),
|
||||
redirect=redirect)
|
||||
return context
|
||||
|
||||
|
||||
class CpuFunctionsTab(tabs.TableTab):
|
||||
table_classes = (cpufunctions_tables.CpuFunctionsTable, )
|
||||
name = _("Processor")
|
||||
slug = "cpufunctions"
|
||||
template_name = ("admin/inventory/_detail_cpufunctions.html")
|
||||
|
||||
def get_cpufunctions_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
return host.core_assignment
|
||||
|
||||
def get_context_data(self, request):
|
||||
context = super(CpuFunctionsTab, self).get_context_data(request)
|
||||
try:
|
||||
context['host'] = self.tab_group.kwargs['host']
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve inventory details.'),
|
||||
redirect=redirect)
|
||||
return context
|
||||
|
||||
|
||||
class MemorysTab(tabs.TableTab):
|
||||
table_classes = (memory_tables.MemorysTable, )
|
||||
name = _("Memory")
|
||||
slug = "memorys"
|
||||
template_name = ("admin/inventory/_detail_memorys.html")
|
||||
|
||||
def get_memorys_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
host.memorys.sort(key=lambda f: (f.numa_node))
|
||||
return host.memorys
|
||||
|
||||
|
||||
class StorageTab(tabs.TableTab):
|
||||
table_classes = (storage_tables.DisksTable,
|
||||
storage_tables.StorageVolumesTable,
|
||||
storage_tables.PhysicalVolumesTable,
|
||||
storage_tables.LocalVolumeGroupsTable,
|
||||
storage_tables.PartitionsTable,)
|
||||
name = _("Storage")
|
||||
slug = "storages"
|
||||
template_name = ("admin/inventory/_detail_storages.html")
|
||||
|
||||
def get_disks_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
return host.disks
|
||||
|
||||
def get_storagevolumes_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
return host.stors
|
||||
|
||||
def get_physicalvolumes_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
return host.pvs
|
||||
|
||||
def get_localvolumegroups_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
return host.lvgs
|
||||
|
||||
def get_partitions_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
return host.partitions
|
||||
|
||||
def get_context_data(self, request):
|
||||
context = super(StorageTab, self).get_context_data(request)
|
||||
try:
|
||||
context['host'] = self.tab_group.kwargs['host']
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve inventory details.'),
|
||||
redirect=redirect)
|
||||
|
||||
context['cinder_backend'] = api.sysinv.get_cinder_backend(request)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class PortsTab(tabs.TableTab):
|
||||
table_classes = (port_tables.PortsTable, )
|
||||
name = _("Ports")
|
||||
slug = "ports"
|
||||
template_name = ("admin/inventory/_detail_ports.html")
|
||||
|
||||
def get_ports_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
host.ports.sort(key=lambda f: (f.name))
|
||||
return host.ports
|
||||
|
||||
|
||||
class InterfacesTab(tabs.TableTab):
|
||||
table_classes = (interface_tables.InterfacesTable, )
|
||||
name = _("Interfaces")
|
||||
slug = "interfaces"
|
||||
template_name = ("admin/inventory/_detail_interfaces.html")
|
||||
|
||||
def get_interfaces_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
|
||||
# add 'ports' member to interface class for easier mgmt in table
|
||||
if host.interfaces:
|
||||
for i in host.interfaces:
|
||||
i.host_id = host.id
|
||||
|
||||
port_data = \
|
||||
map(list, zip(*[(p.get_port_display_name(),
|
||||
p.neighbours) for p in host.ports if
|
||||
i.uuid == p.interface_uuid]))
|
||||
|
||||
if port_data:
|
||||
# Default interface
|
||||
i.portNameList = port_data[0]
|
||||
i.portNeighbourList = port_data[1]
|
||||
else:
|
||||
# Non-default interface, no port data
|
||||
i.portNameList = []
|
||||
i.portNeighbourList = []
|
||||
|
||||
if i.iftype == 'ethernet':
|
||||
i.dpdksupport = [p.dpdksupport for p in host.ports if
|
||||
i.uuid == p.interface_uuid]
|
||||
elif i.iftype == 'vlan':
|
||||
for u in i.uses:
|
||||
for j in host.interfaces:
|
||||
if j.ifname == str(u):
|
||||
if j.iftype == 'ethernet':
|
||||
i.dpdksupport = [p.dpdksupport for p in
|
||||
host.ports if
|
||||
j.uuid ==
|
||||
p.interface_uuid]
|
||||
elif j.iftype == 'ae':
|
||||
for ae_u in j.uses:
|
||||
for k in host.interfaces:
|
||||
if k.ifname == str(ae_u):
|
||||
i.dpdksupport = [
|
||||
p.dpdksupport for p in
|
||||
host.ports if
|
||||
k.uuid ==
|
||||
p.interface_uuid]
|
||||
elif i.iftype == 'ae':
|
||||
for u in i.uses:
|
||||
for j in host.interfaces:
|
||||
if j.ifname == str(u):
|
||||
i.dpdksupport = [p.dpdksupport for p in
|
||||
host.ports if
|
||||
j.uuid == p.interface_uuid]
|
||||
|
||||
host.interfaces.sort(key=lambda f: (f.ifname))
|
||||
return host.interfaces
|
||||
|
||||
|
||||
class SensorTab(tabs.TableTab):
|
||||
table_classes = (sensor_tables.SensorsTable,
|
||||
sensor_tables.SensorGroupsTable,)
|
||||
name = _("Sensors")
|
||||
slug = "sensors"
|
||||
template_name = ("admin/inventory/_detail_sensors.html")
|
||||
|
||||
def get_sensorgroups_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
|
||||
# To extract the sensors in this group
|
||||
if host.sensorgroups:
|
||||
for i in host.sensorgroups:
|
||||
i.host_id = host.id
|
||||
i.sensors = [s.uuid for s in host.sensors if
|
||||
i.uuid == s.sensorgroup_uuid]
|
||||
i.sensorNameList = [s.get_sensor_display_name()
|
||||
for s in host.sensors
|
||||
if i.uuid == s.sensorgroup_uuid]
|
||||
|
||||
return host.sensorgroups
|
||||
|
||||
def get_sensors_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
if host.sensors:
|
||||
for i in host.sensors:
|
||||
i.host_id = host.id
|
||||
i.sensorgroups = [s.uuid for s in host.sensorgroups if
|
||||
i.sensorgroup_uuid == s.uuid]
|
||||
i.sensorgroupNameList = [s.get_sensorgroup_display_name()
|
||||
for s in host.sensorgroups
|
||||
if i.sensorgroup_uuid == s.uuid]
|
||||
return host.sensors
|
||||
# .sort(key=lambda s: (s.status))
|
||||
|
||||
def get_context_data(self, request):
|
||||
context = super(SensorTab, self).get_context_data(request)
|
||||
|
||||
sensors = self.get_sensors_data()
|
||||
|
||||
context["critical"] = len(
|
||||
[s for s in sensors if (s.status == 'critical' and
|
||||
s.suppress != 'True')])
|
||||
context["major"] = len([s for s in sensors if (s.status == 'major' and
|
||||
s.suppress != 'True')])
|
||||
context["minor"] = len([s for s in sensors if (s.status == 'minor' and
|
||||
s.suppress != 'True')])
|
||||
context["suppressed"] = \
|
||||
len([s for s in sensors if s.suppress == 'True'])
|
||||
context["total"] = len(sensors)
|
||||
|
||||
try:
|
||||
context['host'] = self.tab_group.kwargs['host']
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:inventory:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve inventory details.'),
|
||||
redirect=redirect)
|
||||
return context
|
||||
|
||||
|
||||
class DevicesTab(tabs.TableTab):
|
||||
table_classes = (device_tables.DevicesTable, )
|
||||
name = _("Devices")
|
||||
slug = "devices"
|
||||
template_name = ("admin/inventory/_detail_devices.html")
|
||||
|
||||
def get_devices_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
if host.devices:
|
||||
for d in host.devices:
|
||||
d.host_id = host.id
|
||||
return host.devices
|
||||
|
||||
|
||||
class LldpTab(tabs.TableTab):
|
||||
table_classes = (lldp_tables.LldpNeighboursTable,)
|
||||
name = _("LLDP")
|
||||
slug = "lldp"
|
||||
template_name = ("admin/inventory/_detail_lldp.html")
|
||||
|
||||
def get_neighbours_data(self):
|
||||
host = self.tab_group.kwargs['host']
|
||||
# The LLDP neighbours data have been retrieved when HostDetailTabs
|
||||
# is loaded
|
||||
host.lldpneighbours.sort(key=lambda f: f.port_name)
|
||||
return host.lldpneighbours
|
||||
|
||||
|
||||
class HostDetailTabs(tabs.TabGroup):
|
||||
slug = "inventory_details"
|
||||
tabs = (OverviewTab, CpuFunctionsTab, MemorysTab, StorageTab, PortsTab,
|
||||
InterfacesTab, LldpTab, SensorTab, DevicesTab, )
|
||||
sticky = True
|
@ -0,0 +1,9 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block main %}
|
||||
{% autoescape off %}
|
||||
<div id="cpuprofiles">
|
||||
{{ cpuprofiles_table.render }}
|
||||
</div>
|
||||
{% endautoescape %}
|
||||
{% endblock %}
|
@ -0,0 +1,10 @@
|
||||
{% load horizon i18n %}
|
||||
|
||||
{% for cpufunc in cpuProfile.core_assignment %}
|
||||
<dt>{{ cpuFormats|get_value:cpufunc.allocated_function }}</dt>
|
||||
{% for s,cores in cpufunc.socket_cores.items %}
|
||||
<dd>
|
||||
{{ "Processor " }} {{ s }} {{ ": " }} {{ cores }} {{ "<br>" }}
|
||||
</dd>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
25
cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_create.html
Executable file
25
cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_create.html
Executable file
@ -0,0 +1,25 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}create_host_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:inventory:create' %}{% endblock %}
|
||||
|
||||
{% block modal_id %}create_host_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Create Host" %}{% 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 "From here you can define the configuration of a new host." %}</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 Host" %}" />
|
||||
{% endblock %}
|
@ -0,0 +1,25 @@
|
||||
{% load horizon i18n sizeformat %}
|
||||
|
||||
{% block main %}
|
||||
{% autoescape off %}
|
||||
<div id="cpufunctions">
|
||||
{% if host.cpus %}
|
||||
<dl>
|
||||
<dt>{% trans "Processor Model: " %}</dt>
|
||||
<dd>{{ host.cpu_model }}</dd>
|
||||
<dt>{% trans "Processors: " %}</dt>
|
||||
<dd>{{ host.nodes|length }}</dd>
|
||||
<dt>{% trans "Physical Cores Per Processor: " %}</dt>
|
||||
<dd>{{ host.physical_cores|get_value:0 }}</dd>
|
||||
<dt>{% trans "Hyper-Threading: " %}</dt>
|
||||
<dd>{{ host.hyperthreading }}</dd>
|
||||
</dl>
|
||||
<div id="cpufunctions">
|
||||
{{ cpufunctions_table.render }}
|
||||
</div>
|
||||
{% else %}
|
||||
<dl><dd><em>{% trans "No CPU topology information available" %}</em></dd></dl>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endautoescape %}
|
||||
{% endblock %}
|
@ -0,0 +1,9 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block main %}
|
||||
{% autoescape off %}
|
||||
<div id="devices">
|
||||
{{ devices_table.render }}
|
||||
</div>
|
||||
{% endautoescape %}
|
||||
{% endblock %}
|
@ -0,0 +1,9 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block main %}
|
||||
{% autoescape off %}
|
||||
<div id="interfaces">
|
||||
{{ interfaces_table.render }}
|
||||
</div>
|
||||
{% endautoescape %}
|
||||
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user