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