StarlingX open source release updates

Signed-off-by: Dean Troyer <dtroyer@gmail.com>
This commit is contained in:
Dean Troyer 2018-05-20 20:23:53 -07:00
parent d1d7322a82
commit 5a4b802dfc
365 changed files with 30125 additions and 0 deletions

47
.gitignore vendored Normal file
View 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
View 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
View 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
View 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.

View File

31
cgcs_dashboard/api/__init__.py Executable file
View 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",
]

View 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
View 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]

View 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
View 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
View 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)

View 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',
]

View 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]}

View 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

File diff suppressed because it is too large Load Diff

129
cgcs_dashboard/api/vim.py Executable file
View 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

View File

View 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)

View 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", "&nbsp;&nbsp;&nbsp;&nbsp;")
description = description.replace(" " * 4, "&nbsp;" * 4)
description = description.replace(" " * 3, "&nbsp;" * 3)
description = description.replace(" " * 2, "&nbsp;" * 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

View 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

View File

@ -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 }}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

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

View File

@ -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 %}

View 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')
]

View 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)

View 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)

View 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 = []

View 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

View File

@ -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>

View File

@ -0,0 +1,9 @@
{% load i18n sizeformat %}
{% block main %}
{% autoescape off %}
<div id="alarmss">
{{ alarms_table.render }}
</div>
{% endautoescape %}
{% endblock %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View 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')
]

View 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')

View 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)

View File

@ -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 = ()

View 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

View 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

View 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)

View 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")

View 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

View 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)

View 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)

View 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']}

View 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)

View 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)

View 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)

View 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']}

View 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,)

View 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,)

View 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

View 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

View 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

View 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)

View 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,)

View 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

View 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)

View 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)

View 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,)

View 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,)

View 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

View 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)

View 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

View 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

View 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)

View File

@ -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)

View File

@ -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

View File

@ -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')]

View File

@ -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'],))

View 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,)

View 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

View 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'))
]

View 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}

File diff suppressed because it is too large Load Diff

View 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

View File

@ -0,0 +1,9 @@
{% load i18n sizeformat %}
{% block main %}
{% autoescape off %}
<div id="cpuprofiles">
{{ cpuprofiles_table.render }}
</div>
{% endautoescape %}
{% endblock %}

View File

@ -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 %}

View 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 %}

View File

@ -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 %}

View File

@ -0,0 +1,9 @@
{% load i18n sizeformat %}
{% block main %}
{% autoescape off %}
<div id="devices">
{{ devices_table.render }}
</div>
{% endautoescape %}
{% endblock %}

View File

@ -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