Retire repo
This repo was created by accident, use deb-python-oslo.reports instead. Needed-By: I1ac1a06931c8b6dd7c2e73620a0302c29e605f03 Change-Id: I81894aea69b9d09b0977039623c26781093a397a
This commit is contained in:
parent
d332969661
commit
3b7b843eb5
@ -1,8 +0,0 @@
|
|||||||
[run]
|
|
||||||
branch = True
|
|
||||||
source = oslo_reports
|
|
||||||
omit = oslo_reports/tests/*
|
|
||||||
|
|
||||||
[report]
|
|
||||||
ignore_errors = True
|
|
||||||
precision = 2
|
|
54
.gitignore
vendored
54
.gitignore
vendored
@ -1,54 +0,0 @@
|
|||||||
*.py[cod]
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Packages
|
|
||||||
*.egg
|
|
||||||
*.eggs
|
|
||||||
*.egg-info
|
|
||||||
dist
|
|
||||||
build
|
|
||||||
eggs
|
|
||||||
parts
|
|
||||||
bin
|
|
||||||
var
|
|
||||||
sdist
|
|
||||||
develop-eggs
|
|
||||||
.installed.cfg
|
|
||||||
lib
|
|
||||||
lib64
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
.coverage
|
|
||||||
cover
|
|
||||||
.tox
|
|
||||||
nosetests.xml
|
|
||||||
.testrepository
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
|
|
||||||
# Mr Developer
|
|
||||||
.mr.developer.cfg
|
|
||||||
.project
|
|
||||||
.pydevproject
|
|
||||||
|
|
||||||
# Complexity
|
|
||||||
output/*.html
|
|
||||||
output/*/index.html
|
|
||||||
|
|
||||||
# Sphinx
|
|
||||||
doc/build
|
|
||||||
|
|
||||||
# pbr generates these
|
|
||||||
AUTHORS
|
|
||||||
ChangeLog
|
|
||||||
/doc/source/api/
|
|
||||||
|
|
||||||
# Editors
|
|
||||||
*~
|
|
||||||
.*.swp
|
|
@ -1,4 +0,0 @@
|
|||||||
[gerrit]
|
|
||||||
host=review.openstack.org
|
|
||||||
port=29418
|
|
||||||
project=openstack/oslo.reports.git
|
|
3
.mailmap
3
.mailmap
@ -1,3 +0,0 @@
|
|||||||
# Format is:
|
|
||||||
# <preferred e-mail> <other e-mail 1>
|
|
||||||
# <preferred e-mail> <other e-mail 2>
|
|
@ -1,7 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
|
||||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
|
||||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
|
||||||
${PYTHON:-python} -m subunit.run discover -t ./ ./oslo_reports $LISTOPT $IDOPTION
|
|
||||||
test_id_option=--load-list $IDFILE
|
|
||||||
test_list_option=--list
|
|
@ -1,17 +0,0 @@
|
|||||||
If you would like to contribute to the development of OpenStack, you must
|
|
||||||
follow the steps in this page:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html
|
|
||||||
|
|
||||||
If you already have a good understanding of how the system works and your
|
|
||||||
OpenStack accounts are set up, you can skip to the development workflow
|
|
||||||
section of this documentation to learn how changes to OpenStack should be
|
|
||||||
submitted for review via the Gerrit tool:
|
|
||||||
|
|
||||||
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/oslo.reports
|
|
@ -1,4 +0,0 @@
|
|||||||
oslo.reports Style Commandments
|
|
||||||
======================================================
|
|
||||||
|
|
||||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
|
176
LICENSE
176
LICENSE
@ -1,176 +0,0 @@
|
|||||||
|
|
||||||
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.
|
|
||||||
|
|
51
README.rst
51
README.rst
@ -1,51 +0,0 @@
|
|||||||
===================================
|
|
||||||
oslo.reports
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/oslo.reports.svg
|
|
||||||
:target: https://pypi.python.org/pypi/oslo.reports/
|
|
||||||
:alt: Latest Version
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/dm/oslo.reports.svg
|
|
||||||
:target: https://pypi.python.org/pypi/oslo.reports/
|
|
||||||
:alt: Downloads
|
|
||||||
|
|
||||||
When things go wrong in (production) deployments of OpenStack collecting debug
|
|
||||||
data is a key first step in the process of triaging & ultimately resolving the
|
|
||||||
problem. Projects like Nova has extensively used logging capabilities which
|
|
||||||
produce a vast amount of data. This does not, however, enable an admin to
|
|
||||||
obtain an accurate view on the current live state of the system. For example,
|
|
||||||
what threads are running, what config parameters are in effect, and more.
|
|
||||||
|
|
||||||
The project oslo.reports hosts a general purpose error report generation
|
|
||||||
framework, known as the "guru meditation report"
|
|
||||||
(cf http://en.wikipedia.org/wiki/Guru_Meditation) to address the issues
|
|
||||||
described above.
|
|
||||||
|
|
||||||
Models: These classes define structured data for a variety of interesting
|
|
||||||
pieces of state. For example, stack traces, threads, config parameters,
|
|
||||||
package version info, etc. They are capable of being serialized to XML / JSON
|
|
||||||
or a plain text representation
|
|
||||||
|
|
||||||
Generators: These classes are used to populate the model classes with the
|
|
||||||
current runtime state of the system
|
|
||||||
|
|
||||||
Views: views serialize models into say JSON, text or xml. There is also
|
|
||||||
a predefined view that utilizes Jinja templating system.
|
|
||||||
|
|
||||||
There will be a number of standard models / generators available for all
|
|
||||||
OpenStack services
|
|
||||||
|
|
||||||
StackTraceModel: a base class for any model which includes a stack trace
|
|
||||||
ThreadModel: a class for information about a thread
|
|
||||||
ExceptionModel: a class for information about a caught exception
|
|
||||||
ConfigModel: a class for information about configuration file settings
|
|
||||||
PackageModel: a class for information about vendor/product/version/package information
|
|
||||||
|
|
||||||
Each OpenStack project will have the ability to register further generator
|
|
||||||
classes to provide custom project specific data.
|
|
||||||
|
|
||||||
* Free software: Apache license
|
|
||||||
* Documentation: http://docs.openstack.org/developer/oslo.reports
|
|
||||||
* Source: http://git.openstack.org/cgit/openstack/oslo.reports
|
|
||||||
* Bugs: http://bugs.launchpad.net/oslo.reports
|
|
13
README.txt
Normal file
13
README.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
This project is no longer maintained.
|
||||||
|
|
||||||
|
The contents of this repository are still available in the Git
|
||||||
|
source code management system. To see the contents of this
|
||||||
|
repository before it reached its end of life, please check out the
|
||||||
|
previous commit with "git checkout HEAD^1".
|
||||||
|
|
||||||
|
Use instead the project deb-python-oslo.reports at
|
||||||
|
http://git.openstack.org/cgit/openstack/deb-python-oslo.reports .
|
||||||
|
|
||||||
|
For any further questions, please email
|
||||||
|
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||||
|
Freenode.
|
@ -1,79 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# 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 os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('../..'))
|
|
||||||
# -- General configuration ----------------------------------------------------
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
|
||||||
extensions = [
|
|
||||||
'sphinx.ext.autodoc',
|
|
||||||
#'sphinx.ext.intersphinx',
|
|
||||||
'oslosphinx',
|
|
||||||
'oslo_config.sphinxext',
|
|
||||||
]
|
|
||||||
|
|
||||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
|
||||||
# text edit cycles.
|
|
||||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'oslo.reports'
|
|
||||||
copyright = u'2014, OpenStack Foundation'
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
add_module_names = True
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
modindex_common_prefix = ['oslo_reports.']
|
|
||||||
|
|
||||||
# -- Options for HTML output --------------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
|
||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
|
||||||
# html_theme_path = ["."]
|
|
||||||
# html_theme = '_theme'
|
|
||||||
# html_static_path = ['static']
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = '%sdoc' % project
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title, author, documentclass
|
|
||||||
# [howto/manual]).
|
|
||||||
latex_documents = [
|
|
||||||
('index',
|
|
||||||
'%s.tex' % project,
|
|
||||||
u'%s Documentation' % project,
|
|
||||||
u'OpenStack Foundation', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
|
||||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
|
@ -1,5 +0,0 @@
|
|||||||
==============
|
|
||||||
Contributing
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. include:: ../../CONTRIBUTING.rst
|
|
@ -1 +0,0 @@
|
|||||||
.. include:: ../../ChangeLog
|
|
@ -1,40 +0,0 @@
|
|||||||
==============
|
|
||||||
oslo.reports
|
|
||||||
==============
|
|
||||||
|
|
||||||
oslo.reports library
|
|
||||||
|
|
||||||
Contents
|
|
||||||
========
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
installation
|
|
||||||
usage
|
|
||||||
opts
|
|
||||||
contributing
|
|
||||||
|
|
||||||
API
|
|
||||||
===
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
api/autoindex
|
|
||||||
|
|
||||||
Release Notes
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
history
|
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
==================
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
|||||||
==============
|
|
||||||
Installation
|
|
||||||
==============
|
|
||||||
|
|
||||||
At the command line::
|
|
||||||
|
|
||||||
$ pip install oslo.reports
|
|
@ -1,8 +0,0 @@
|
|||||||
=======================
|
|
||||||
Configuration Options
|
|
||||||
=======================
|
|
||||||
|
|
||||||
oslo.reports uses oslo.config to define and manage configuration options
|
|
||||||
to allow the deployer to control where the GMR reports should be generated.
|
|
||||||
|
|
||||||
.. show-options:: oslo.reports
|
|
@ -1,755 +0,0 @@
|
|||||||
========================================================================
|
|
||||||
==== Guru Meditation ====
|
|
||||||
========================================================================
|
|
||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||
|
|
||||||
|
|
||||||
========================================================================
|
|
||||||
==== Package ====
|
|
||||||
========================================================================
|
|
||||||
product = OpenStack Nova
|
|
||||||
vendor = OpenStack Foundation
|
|
||||||
version = 13.0.0
|
|
||||||
========================================================================
|
|
||||||
==== Threads ====
|
|
||||||
========================================================================
|
|
||||||
------ Thread #140417215547200 ------
|
|
||||||
|
|
||||||
/usr/local/lib/python2.7/dist-packages/eventlet/hubs/hub.py:346 in run
|
|
||||||
`self.wait(sleep_time)`
|
|
||||||
|
|
||||||
/usr/local/lib/python2.7/dist-packages/eventlet/hubs/poll.py:82 in wait
|
|
||||||
`sleep(seconds)`
|
|
||||||
|
|
||||||
========================================================================
|
|
||||||
==== Green Threads ====
|
|
||||||
========================================================================
|
|
||||||
------ Green Thread ------
|
|
||||||
|
|
||||||
/usr/local/bin/nova-api:10 in <module>
|
|
||||||
`sys.exit(main())`
|
|
||||||
|
|
||||||
/opt/stack/nova/nova/cmd/api.py:57 in main
|
|
||||||
`launcher.wait()`
|
|
||||||
|
|
||||||
/usr/local/lib/python2.7/dist-packages/oslo_service/service.py:511 in wait
|
|
||||||
`self._respawn_children()`
|
|
||||||
|
|
||||||
/usr/local/lib/python2.7/dist-packages/oslo_service/service.py:495 in _respawn_children
|
|
||||||
`eventlet.greenthread.sleep(self.wait_interval)`
|
|
||||||
|
|
||||||
/usr/local/lib/python2.7/dist-packages/eventlet/greenthread.py:34 in sleep
|
|
||||||
`hub.switch()`
|
|
||||||
|
|
||||||
/usr/local/lib/python2.7/dist-packages/eventlet/hubs/hub.py:294 in switch
|
|
||||||
`return self.greenlet.switch()`
|
|
||||||
|
|
||||||
------ Green Thread ------
|
|
||||||
|
|
||||||
No Traceback!
|
|
||||||
|
|
||||||
========================================================================
|
|
||||||
==== Processes ====
|
|
||||||
========================================================================
|
|
||||||
Process 14756 (under 1) [ run by: stack (1001), state: running ]
|
|
||||||
Process 14770 (under 14756) [ run by: stack (1001), state: sleeping ]
|
|
||||||
Process 14771 (under 14756) [ run by: stack (1001), state: sleeping ]
|
|
||||||
Process 14776 (under 14756) [ run by: stack (1001), state: sleeping ]
|
|
||||||
Process 14777 (under 14756) [ run by: stack (1001), state: sleeping ]
|
|
||||||
Process 14784 (under 14756) [ run by: stack (1001), state: sleeping ]
|
|
||||||
Process 14785 (under 14756) [ run by: stack (1001), state: sleeping ]
|
|
||||||
|
|
||||||
========================================================================
|
|
||||||
==== Configuration ====
|
|
||||||
========================================================================
|
|
||||||
|
|
||||||
api_database:
|
|
||||||
connection = ***
|
|
||||||
connection_debug = 0
|
|
||||||
connection_trace = False
|
|
||||||
idle_timeout = 3600
|
|
||||||
max_overflow = None
|
|
||||||
max_pool_size = None
|
|
||||||
max_retries = 10
|
|
||||||
mysql_sql_mode = TRADITIONAL
|
|
||||||
pool_timeout = None
|
|
||||||
retry_interval = 10
|
|
||||||
slave_connection = ***
|
|
||||||
sqlite_synchronous = True
|
|
||||||
|
|
||||||
cells:
|
|
||||||
bandwidth_update_interval = 600
|
|
||||||
call_timeout = 60
|
|
||||||
capabilities =
|
|
||||||
hypervisor=xenserver;kvm
|
|
||||||
os=linux;windows
|
|
||||||
cell_type = compute
|
|
||||||
enable = False
|
|
||||||
instance_update_sync_database_limit = 100
|
|
||||||
manager = nova.cells.manager.CellsManager
|
|
||||||
mute_child_interval = 300
|
|
||||||
name = nova
|
|
||||||
reserve_percent = 10.0
|
|
||||||
topic = cells
|
|
||||||
|
|
||||||
cinder:
|
|
||||||
cafile = None
|
|
||||||
catalog_info = volumev2:cinderv2:publicURL
|
|
||||||
certfile = None
|
|
||||||
cross_az_attach = True
|
|
||||||
endpoint_template = None
|
|
||||||
http_retries = 3
|
|
||||||
insecure = False
|
|
||||||
keyfile = None
|
|
||||||
os_region_name = RegionOne
|
|
||||||
timeout = None
|
|
||||||
|
|
||||||
conductor:
|
|
||||||
manager = nova.conductor.manager.ConductorManager
|
|
||||||
topic = conductor
|
|
||||||
use_local = False
|
|
||||||
workers = 2
|
|
||||||
|
|
||||||
database:
|
|
||||||
backend = sqlalchemy
|
|
||||||
connection = ***
|
|
||||||
connection_debug = 0
|
|
||||||
connection_trace = False
|
|
||||||
db_inc_retry_interval = True
|
|
||||||
db_max_retries = 20
|
|
||||||
db_max_retry_interval = 10
|
|
||||||
db_retry_interval = 1
|
|
||||||
idle_timeout = 3600
|
|
||||||
max_overflow = None
|
|
||||||
max_pool_size = None
|
|
||||||
max_retries = 10
|
|
||||||
min_pool_size = 1
|
|
||||||
mysql_sql_mode = TRADITIONAL
|
|
||||||
pool_timeout = None
|
|
||||||
retry_interval = 10
|
|
||||||
slave_connection = ***
|
|
||||||
sqlite_db = nova.sqlite
|
|
||||||
sqlite_synchronous = True
|
|
||||||
use_db_reconnect = False
|
|
||||||
use_tpool = False
|
|
||||||
|
|
||||||
default:
|
|
||||||
allow_instance_snapshots = True
|
|
||||||
allow_resize_to_same_host = True
|
|
||||||
allow_same_net_traffic = True
|
|
||||||
api_paste_config = /etc/nova/api-paste.ini
|
|
||||||
api_rate_limit = False
|
|
||||||
auth_strategy = keystone
|
|
||||||
auto_assign_floating_ip = False
|
|
||||||
bandwidth_poll_interval = 600
|
|
||||||
bindir = /usr/local/bin
|
|
||||||
block_device_allocate_retries = 60
|
|
||||||
block_device_allocate_retries_interval = 3
|
|
||||||
boot_script_template = /opt/stack/nova/nova/cloudpipe/bootscript.template
|
|
||||||
ca_file = cacert.pem
|
|
||||||
ca_path = /opt/stack/data/nova/CA
|
|
||||||
cert_manager = nova.cert.manager.CertManager
|
|
||||||
cert_topic = cert
|
|
||||||
client_socket_timeout = 900
|
|
||||||
cnt_vpn_clients = 0
|
|
||||||
compute_available_monitors = None
|
|
||||||
compute_driver = libvirt.LibvirtDriver
|
|
||||||
compute_manager = nova.compute.manager.ComputeManager
|
|
||||||
compute_monitors =
|
|
||||||
compute_resources =
|
|
||||||
vcpu
|
|
||||||
compute_stats_class = nova.compute.stats.Stats
|
|
||||||
compute_topic = compute
|
|
||||||
config-dir = None
|
|
||||||
config-file =
|
|
||||||
/etc/nova/nova.conf
|
|
||||||
config_drive_format = iso9660
|
|
||||||
config_drive_skip_versions = 1.0 2007-01-19 2007-03-01 2007-08-29 2007-10-10 2007-12-15 2008-02-01 2008-09-01
|
|
||||||
console_host = dims-ubuntu
|
|
||||||
console_manager = nova.console.manager.ConsoleProxyManager
|
|
||||||
console_topic = console
|
|
||||||
consoleauth_manager = nova.consoleauth.manager.ConsoleAuthManager
|
|
||||||
consoleauth_topic = consoleauth
|
|
||||||
control_exchange = nova
|
|
||||||
cpu_allocation_ratio = 0.0
|
|
||||||
create_unique_mac_address_attempts = 5
|
|
||||||
crl_file = crl.pem
|
|
||||||
db_driver = nova.db
|
|
||||||
debug = True
|
|
||||||
default_access_ip_network_name = None
|
|
||||||
default_availability_zone = nova
|
|
||||||
default_ephemeral_format = ext4
|
|
||||||
default_flavor = m1.small
|
|
||||||
default_floating_pool = public
|
|
||||||
default_log_levels =
|
|
||||||
amqp=WARN
|
|
||||||
amqplib=WARN
|
|
||||||
boto=WARN
|
|
||||||
glanceclient=WARN
|
|
||||||
iso8601=WARN
|
|
||||||
keystonemiddleware=WARN
|
|
||||||
oslo_messaging=INFO
|
|
||||||
qpid=WARN
|
|
||||||
requests.packages.urllib3.connectionpool=WARN
|
|
||||||
routes.middleware=WARN
|
|
||||||
sqlalchemy=WARN
|
|
||||||
stevedore=WARN
|
|
||||||
suds=INFO
|
|
||||||
urllib3.connectionpool=WARN
|
|
||||||
websocket=WARN
|
|
||||||
default_notification_level = INFO
|
|
||||||
default_publisher_id = None
|
|
||||||
default_schedule_zone = None
|
|
||||||
defer_iptables_apply = False
|
|
||||||
dhcp_domain = novalocal
|
|
||||||
dhcp_lease_time = 86400
|
|
||||||
dhcpbridge = /usr/local/bin/nova-dhcpbridge
|
|
||||||
dhcpbridge_flagfile =
|
|
||||||
/etc/nova/nova.conf
|
|
||||||
dmz_cidr =
|
|
||||||
dmz_mask = 255.255.255.0
|
|
||||||
dmz_net = 10.0.0.0
|
|
||||||
dns_server =
|
|
||||||
dns_update_periodic_interval = -1
|
|
||||||
dnsmasq_config_file =
|
|
||||||
ebtables_exec_attempts = 3
|
|
||||||
ebtables_retry_interval = 1.0
|
|
||||||
ec2_dmz_host = 10.0.0.9
|
|
||||||
ec2_host = 10.0.0.9
|
|
||||||
ec2_listen = 0.0.0.0
|
|
||||||
ec2_listen_port = 8773
|
|
||||||
ec2_path = /
|
|
||||||
ec2_port = 8773
|
|
||||||
ec2_private_dns_show_ip = False
|
|
||||||
ec2_scheme = http
|
|
||||||
ec2_strict_validation = True
|
|
||||||
ec2_timestamp_expiry = 300
|
|
||||||
ec2_workers = 2
|
|
||||||
enable_instance_password = True
|
|
||||||
enable_network_quota = False
|
|
||||||
enable_new_services = True
|
|
||||||
enabled_apis =
|
|
||||||
ec2
|
|
||||||
metadata
|
|
||||||
osapi_compute
|
|
||||||
enabled_ssl_apis =
|
|
||||||
fake_call = False
|
|
||||||
fake_network = False
|
|
||||||
fatal_deprecations = False
|
|
||||||
fatal_exception_format_errors = False
|
|
||||||
firewall_driver = nova.virt.firewall.NoopFirewallDriver
|
|
||||||
fixed_ip_disassociate_timeout = 600
|
|
||||||
fixed_range_v6 = fd00::/48
|
|
||||||
flat_injected = False
|
|
||||||
flat_interface = None
|
|
||||||
flat_network_bridge = None
|
|
||||||
flat_network_dns = 8.8.4.4
|
|
||||||
floating_ip_dns_manager = nova.network.noop_dns_driver.NoopDNSDriver
|
|
||||||
force_config_drive = True
|
|
||||||
force_dhcp_release = True
|
|
||||||
force_raw_images = True
|
|
||||||
force_snat_range =
|
|
||||||
forward_bridge_interface =
|
|
||||||
all
|
|
||||||
fping_path = /usr/sbin/fping
|
|
||||||
gateway = None
|
|
||||||
gateway_v6 = None
|
|
||||||
heal_instance_info_cache_interval = 60
|
|
||||||
host = dims-ubuntu
|
|
||||||
image_cache_manager_interval = 2400
|
|
||||||
image_cache_subdirectory_name = _base
|
|
||||||
image_decryption_dir = /tmp
|
|
||||||
injected_network_template = /opt/stack/nova/nova/virt/interfaces.template
|
|
||||||
instance_build_timeout = 0
|
|
||||||
instance_delete_interval = 300
|
|
||||||
instance_dns_domain =
|
|
||||||
instance_dns_manager = nova.network.noop_dns_driver.NoopDNSDriver
|
|
||||||
instance_format = [instance: %(uuid)s]
|
|
||||||
instance_name_template = instance-%08x
|
|
||||||
instance_usage_audit = False
|
|
||||||
instance_usage_audit_period = month
|
|
||||||
instance_uuid_format = [instance: %(uuid)s]
|
|
||||||
instances_path = /opt/stack/data/nova/instances
|
|
||||||
internal_service_availability_zone = internal
|
|
||||||
iptables_bottom_regex =
|
|
||||||
iptables_drop_action = DROP
|
|
||||||
iptables_top_regex =
|
|
||||||
ipv6_backend = rfc2462
|
|
||||||
key_file = private/cakey.pem
|
|
||||||
keys_path = /opt/stack/data/nova/keys
|
|
||||||
keystone_ec2_insecure = False
|
|
||||||
keystone_ec2_url = http://10.0.0.9:5000/v2.0/ec2tokens
|
|
||||||
l3_lib = nova.network.l3.LinuxNetL3
|
|
||||||
linuxnet_interface_driver =
|
|
||||||
linuxnet_ovs_integration_bridge = br-int
|
|
||||||
live_migration_retry_count = 30
|
|
||||||
lockout_attempts = 5
|
|
||||||
lockout_minutes = 15
|
|
||||||
lockout_window = 15
|
|
||||||
log-config-append = None
|
|
||||||
log-date-format = %Y-%m-%d %H:%M:%S
|
|
||||||
log-dir = None
|
|
||||||
log-file = None
|
|
||||||
log-format = None
|
|
||||||
log_options = True
|
|
||||||
logging_context_format_string = %(asctime)s.%(msecs)03d %(color)s%(levelname)s %(name)s [%(request_id)s %(user_name)s %(project_name)s%(color)s] %(instance)s%(color)s%(message)s
|
|
||||||
logging_debug_format_suffix = from (pid=%(process)d) %(funcName)s %(pathname)s:%(lineno)d
|
|
||||||
logging_default_format_string = %(asctime)s.%(msecs)03d %(color)s%(levelname)s %(name)s [-%(color)s] %(instance)s%(color)s%(message)s
|
|
||||||
logging_exception_prefix = %(color)s%(asctime)s.%(msecs)03d TRACE %(name)s %(instance)s
|
|
||||||
max_age = 0
|
|
||||||
max_concurrent_builds = 10
|
|
||||||
max_concurrent_live_migrations = 1
|
|
||||||
max_header_line = 16384
|
|
||||||
max_local_block_devices = 3
|
|
||||||
maximum_instance_delete_attempts = 5
|
|
||||||
memcached_servers = None
|
|
||||||
metadata_cache_expiration = 15
|
|
||||||
metadata_host = 10.0.0.9
|
|
||||||
metadata_listen = 0.0.0.0
|
|
||||||
metadata_listen_port = 8775
|
|
||||||
metadata_manager = nova.api.manager.MetadataManager
|
|
||||||
metadata_port = 8775
|
|
||||||
metadata_workers = 2
|
|
||||||
migrate_max_retries = -1
|
|
||||||
mkisofs_cmd = genisoimage
|
|
||||||
monkey_patch = False
|
|
||||||
monkey_patch_modules =
|
|
||||||
nova.api.ec2.cloud:nova.notifications.notify_decorator
|
|
||||||
nova.compute.api:nova.notifications.notify_decorator
|
|
||||||
multi_host = False
|
|
||||||
multi_instance_display_name_template = %(name)s-%(count)d
|
|
||||||
my_block_storage_ip = 10.0.0.9
|
|
||||||
my_ip = 10.0.0.9
|
|
||||||
network_allocate_retries = 0
|
|
||||||
network_api_class = nova.network.neutronv2.api.API
|
|
||||||
network_device_mtu = None
|
|
||||||
network_driver = nova.network.linux_net
|
|
||||||
network_manager = nova.network.manager.VlanManager
|
|
||||||
network_size = 256
|
|
||||||
network_topic = network
|
|
||||||
networks_path = /opt/stack/data/nova/networks
|
|
||||||
neutron_default_tenant_id = default
|
|
||||||
non_inheritable_image_properties =
|
|
||||||
bittorrent
|
|
||||||
cache_in_nova
|
|
||||||
notification_driver =
|
|
||||||
notification_topics =
|
|
||||||
notifications
|
|
||||||
notify_api_faults = False
|
|
||||||
notify_on_state_change = None
|
|
||||||
null_kernel = nokernel
|
|
||||||
num_networks = 1
|
|
||||||
osapi_compute_ext_list =
|
|
||||||
osapi_compute_extension =
|
|
||||||
nova.api.openstack.compute.legacy_v2.contrib.standard_extensions
|
|
||||||
osapi_compute_link_prefix = None
|
|
||||||
osapi_compute_listen = 0.0.0.0
|
|
||||||
osapi_compute_listen_port = 8774
|
|
||||||
osapi_compute_unique_server_name_scope =
|
|
||||||
osapi_compute_workers = 2
|
|
||||||
osapi_glance_link_prefix = None
|
|
||||||
osapi_hide_server_address_states =
|
|
||||||
building
|
|
||||||
osapi_max_limit = 1000
|
|
||||||
ovs_vsctl_timeout = 120
|
|
||||||
password_length = 12
|
|
||||||
pci_alias =
|
|
||||||
pci_passthrough_whitelist =
|
|
||||||
periodic_enable = True
|
|
||||||
periodic_fuzzy_delay = 60
|
|
||||||
policy_default_rule = default
|
|
||||||
policy_dirs =
|
|
||||||
policy.d
|
|
||||||
policy_file = policy.json
|
|
||||||
preallocate_images = none
|
|
||||||
project_cert_subject = /C=US/ST=California/O=OpenStack/OU=NovaDev/CN=project-ca-%.16s-%s
|
|
||||||
public_interface = eth0
|
|
||||||
publish_errors = False
|
|
||||||
pybasedir = /opt/stack/nova
|
|
||||||
quota_cores = 20
|
|
||||||
quota_driver = nova.quota.DbQuotaDriver
|
|
||||||
quota_fixed_ips = -1
|
|
||||||
quota_floating_ips = 10
|
|
||||||
quota_injected_file_content_bytes = 10240
|
|
||||||
quota_injected_file_path_length = 255
|
|
||||||
quota_injected_files = 5
|
|
||||||
quota_instances = 10
|
|
||||||
quota_key_pairs = 100
|
|
||||||
quota_metadata_items = 128
|
|
||||||
quota_networks = 3
|
|
||||||
quota_ram = 51200
|
|
||||||
quota_security_group_rules = 20
|
|
||||||
quota_security_groups = 10
|
|
||||||
quota_server_group_members = 10
|
|
||||||
quota_server_groups = 10
|
|
||||||
ram_allocation_ratio = 0.0
|
|
||||||
reboot_timeout = 0
|
|
||||||
reclaim_instance_interval = 0
|
|
||||||
region_list =
|
|
||||||
remove_unused_base_images = True
|
|
||||||
remove_unused_original_minimum_age_seconds = 86400
|
|
||||||
report_interval = 10
|
|
||||||
rescue_timeout = 0
|
|
||||||
reservation_expire = 86400
|
|
||||||
reserved_host_disk_mb = 0
|
|
||||||
reserved_host_memory_mb = 512
|
|
||||||
resize_confirm_window = 0
|
|
||||||
resize_fs_using_block_device = False
|
|
||||||
resume_guests_state_on_host_boot = False
|
|
||||||
rootwrap_config = /etc/nova/rootwrap.conf
|
|
||||||
routing_source_ip = 10.0.0.9
|
|
||||||
rpc_backend = rabbit
|
|
||||||
rpc_response_timeout = 60
|
|
||||||
run_external_periodic_tasks = True
|
|
||||||
running_deleted_instance_action = reap
|
|
||||||
running_deleted_instance_poll_interval = 1800
|
|
||||||
running_deleted_instance_timeout = 0
|
|
||||||
s3_access_key = notchecked
|
|
||||||
s3_affix_tenant = False
|
|
||||||
s3_host = 10.0.0.9
|
|
||||||
s3_port = 3333
|
|
||||||
s3_secret_key = notchecked
|
|
||||||
s3_use_ssl = False
|
|
||||||
scheduler_available_filters =
|
|
||||||
nova.scheduler.filters.all_filters
|
|
||||||
scheduler_default_filters =
|
|
||||||
AvailabilityZoneFilter
|
|
||||||
ComputeCapabilitiesFilter
|
|
||||||
ComputeFilter
|
|
||||||
DiskFilter
|
|
||||||
ImagePropertiesFilter
|
|
||||||
RamFilter
|
|
||||||
RetryFilter
|
|
||||||
ServerGroupAffinityFilter
|
|
||||||
ServerGroupAntiAffinityFilter
|
|
||||||
scheduler_instance_sync_interval = 120
|
|
||||||
scheduler_manager = nova.scheduler.manager.SchedulerManager
|
|
||||||
scheduler_max_attempts = 3
|
|
||||||
scheduler_topic = scheduler
|
|
||||||
scheduler_tracks_instance_changes = True
|
|
||||||
scheduler_weight_classes =
|
|
||||||
nova.scheduler.weights.all_weighers
|
|
||||||
secure_proxy_ssl_header = None
|
|
||||||
security_group_api = neutron
|
|
||||||
send_arp_for_ha = False
|
|
||||||
send_arp_for_ha_count = 3
|
|
||||||
service_down_time = 60
|
|
||||||
servicegroup_driver = db
|
|
||||||
share_dhcp_address = False
|
|
||||||
shelved_offload_time = 0
|
|
||||||
shelved_poll_interval = 3600
|
|
||||||
shutdown_timeout = 60
|
|
||||||
snapshot_name_template = snapshot-%s
|
|
||||||
ssl_ca_file = None
|
|
||||||
ssl_cert_file = None
|
|
||||||
ssl_key_file = None
|
|
||||||
state_path = /opt/stack/data/nova
|
|
||||||
sync_power_state_interval = 600
|
|
||||||
syslog-log-facility = LOG_USER
|
|
||||||
tcp_keepidle = 600
|
|
||||||
teardown_unused_network_gateway = False
|
|
||||||
tempdir = None
|
|
||||||
transport_url = None
|
|
||||||
until_refresh = 0
|
|
||||||
update_dns_entries = False
|
|
||||||
update_resources_interval = 0
|
|
||||||
use-syslog = False
|
|
||||||
use-syslog-rfc-format = True
|
|
||||||
use_cow_images = True
|
|
||||||
use_forwarded_for = False
|
|
||||||
use_ipv6 = False
|
|
||||||
use_network_dns_servers = False
|
|
||||||
use_neutron_default_nets = False
|
|
||||||
use_project_ca = False
|
|
||||||
use_rootwrap_daemon = False
|
|
||||||
use_single_default_gateway = False
|
|
||||||
use_stderr = True
|
|
||||||
user_cert_subject = /C=US/ST=California/O=OpenStack/OU=NovaDev/CN=%.16s-%.16s-%s
|
|
||||||
vcpu_pin_set = None
|
|
||||||
vendordata_driver = nova.api.metadata.vendordata_json.JsonFileVendorData
|
|
||||||
verbose = True
|
|
||||||
vif_plugging_is_fatal = True
|
|
||||||
vif_plugging_timeout = 300
|
|
||||||
virt_mkfs =
|
|
||||||
vlan_interface = None
|
|
||||||
vlan_start = 100
|
|
||||||
volume_api_class = nova.volume.cinder.API
|
|
||||||
volume_usage_poll_interval = 0
|
|
||||||
vpn_flavor = m1.tiny
|
|
||||||
vpn_image_id = 0
|
|
||||||
vpn_ip = 10.0.0.9
|
|
||||||
vpn_key_suffix = -vpn
|
|
||||||
vpn_start = 1000
|
|
||||||
wsgi_default_pool_size = 1000
|
|
||||||
wsgi_keep_alive = True
|
|
||||||
wsgi_log_format = %(client_ip)s "%(request_line)s" status: %(status_code)s len: %(body_length)s time: %(wall_seconds).7f
|
|
||||||
|
|
||||||
ephemeral_storage_encryption:
|
|
||||||
cipher = aes-xts-plain64
|
|
||||||
enabled = False
|
|
||||||
key_size = 512
|
|
||||||
|
|
||||||
glance:
|
|
||||||
allowed_direct_url_schemes =
|
|
||||||
api_insecure = False
|
|
||||||
api_servers =
|
|
||||||
http://10.0.0.9:9292
|
|
||||||
host = 10.0.0.9
|
|
||||||
num_retries = 0
|
|
||||||
port = 9292
|
|
||||||
protocol = http
|
|
||||||
|
|
||||||
guestfs:
|
|
||||||
debug = False
|
|
||||||
|
|
||||||
image_file_url:
|
|
||||||
filesystems =
|
|
||||||
|
|
||||||
ironic:
|
|
||||||
admin_auth_token = ***
|
|
||||||
admin_password = ***
|
|
||||||
admin_tenant_name = None
|
|
||||||
admin_url = None
|
|
||||||
admin_username = None
|
|
||||||
api_endpoint = None
|
|
||||||
api_max_retries = 60
|
|
||||||
api_retry_interval = 2
|
|
||||||
api_version = 1
|
|
||||||
client_log_level = None
|
|
||||||
|
|
||||||
keymgr:
|
|
||||||
api_class = nova.keymgr.conf_key_mgr.ConfKeyManager
|
|
||||||
|
|
||||||
keystone_authtoken:
|
|
||||||
admin_password = ***
|
|
||||||
admin_tenant_name = admin
|
|
||||||
admin_token = ***
|
|
||||||
admin_user = None
|
|
||||||
auth-url = http://10.0.0.9:35357
|
|
||||||
auth_admin_prefix =
|
|
||||||
auth_host = 127.0.0.1
|
|
||||||
auth_plugin = password
|
|
||||||
auth_port = 35357
|
|
||||||
auth_protocol = https
|
|
||||||
auth_section = None
|
|
||||||
auth_uri = http://10.0.0.9:5000
|
|
||||||
auth_version = None
|
|
||||||
cache = None
|
|
||||||
cafile = /opt/stack/data/ca-bundle.pem
|
|
||||||
certfile = None
|
|
||||||
check_revocations_for_cached = False
|
|
||||||
delay_auth_decision = False
|
|
||||||
domain-id = None
|
|
||||||
domain-name = None
|
|
||||||
enforce_token_bind = permissive
|
|
||||||
hash_algorithms =
|
|
||||||
md5
|
|
||||||
http_connect_timeout = None
|
|
||||||
http_request_max_retries = 3
|
|
||||||
identity_uri = None
|
|
||||||
include_service_catalog = True
|
|
||||||
insecure = False
|
|
||||||
keyfile = None
|
|
||||||
memcache_pool_conn_get_timeout = 10
|
|
||||||
memcache_pool_dead_retry = 300
|
|
||||||
memcache_pool_maxsize = 10
|
|
||||||
memcache_pool_socket_timeout = 3
|
|
||||||
memcache_pool_unused_timeout = 60
|
|
||||||
memcache_secret_key = ***
|
|
||||||
memcache_security_strategy = None
|
|
||||||
memcache_use_advanced_pool = False
|
|
||||||
memcached_servers = None
|
|
||||||
password = password
|
|
||||||
project-domain-id = default
|
|
||||||
project-domain-name = None
|
|
||||||
project-id = None
|
|
||||||
project-name = service
|
|
||||||
region_name = None
|
|
||||||
revocation_cache_time = 10
|
|
||||||
signing_dir = /var/cache/nova
|
|
||||||
tenant-id = None
|
|
||||||
tenant-name = None
|
|
||||||
token_cache_time = 300
|
|
||||||
trust-id = None
|
|
||||||
user-domain-id = default
|
|
||||||
user-domain-name = None
|
|
||||||
user-id = None
|
|
||||||
user-name = nova
|
|
||||||
|
|
||||||
libvirt:
|
|
||||||
block_migration_flag = VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE, VIR_MIGRATE_TUNNELLED, VIR_MIGRATE_NON_SHARED_INC
|
|
||||||
checksum_base_images = False
|
|
||||||
checksum_interval_seconds = 3600
|
|
||||||
connection_uri =
|
|
||||||
cpu_mode = none
|
|
||||||
cpu_model = None
|
|
||||||
disk_cachemodes =
|
|
||||||
disk_prefix = None
|
|
||||||
gid_maps =
|
|
||||||
hw_disk_discard = None
|
|
||||||
hw_machine_type = None
|
|
||||||
image_info_filename_pattern = /opt/stack/data/nova/instances/_base/%(image)s.info
|
|
||||||
images_rbd_ceph_conf =
|
|
||||||
images_rbd_pool = rbd
|
|
||||||
images_type = default
|
|
||||||
images_volume_group = None
|
|
||||||
inject_key = False
|
|
||||||
inject_partition = -2
|
|
||||||
inject_password = False
|
|
||||||
iscsi_iface = None
|
|
||||||
iscsi_use_multipath = False
|
|
||||||
live_migration_bandwidth = 0
|
|
||||||
live_migration_completion_timeout = 800
|
|
||||||
live_migration_downtime = 500
|
|
||||||
live_migration_downtime_delay = 75
|
|
||||||
live_migration_downtime_steps = 10
|
|
||||||
live_migration_flag = VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE, VIR_MIGRATE_TUNNELLED
|
|
||||||
live_migration_progress_timeout = 150
|
|
||||||
live_migration_uri = qemu+ssh://stack@%s/system
|
|
||||||
mem_stats_period_seconds = 10
|
|
||||||
num_iscsi_scan_tries = 5
|
|
||||||
qemu_allowed_storage_drivers =
|
|
||||||
rbd_secret_uuid = None
|
|
||||||
rbd_user = None
|
|
||||||
remote_filesystem_transport = ssh
|
|
||||||
remove_unused_kernels = True
|
|
||||||
remove_unused_resized_minimum_age_seconds = 3600
|
|
||||||
rescue_image_id = None
|
|
||||||
rescue_kernel_id = None
|
|
||||||
rescue_ramdisk_id = None
|
|
||||||
rng_dev_path = None
|
|
||||||
snapshot_compression = False
|
|
||||||
snapshot_image_format = None
|
|
||||||
snapshots_directory = /opt/stack/data/nova/instances/snapshots
|
|
||||||
sparse_logical_volumes = False
|
|
||||||
sysinfo_serial = auto
|
|
||||||
uid_maps =
|
|
||||||
use_usb_tablet = False
|
|
||||||
use_virtio_for_bridges = True
|
|
||||||
virt_type = kvm
|
|
||||||
volume_clear = zero
|
|
||||||
volume_clear_size = 0
|
|
||||||
wait_soft_reboot_seconds = 120
|
|
||||||
xen_hvmloader_path = /usr/lib/xen/boot/hvmloader
|
|
||||||
|
|
||||||
mks:
|
|
||||||
enabled = False
|
|
||||||
mksproxy_base_url = http://127.0.0.1:6090/
|
|
||||||
|
|
||||||
neutron:
|
|
||||||
admin_auth_url = http://10.0.0.9:35357/v2.0
|
|
||||||
admin_password = ***
|
|
||||||
admin_tenant_id = None
|
|
||||||
admin_tenant_name = service
|
|
||||||
admin_user_id = None
|
|
||||||
admin_username = neutron
|
|
||||||
auth_plugin = None
|
|
||||||
auth_section = None
|
|
||||||
auth_strategy = keystone
|
|
||||||
cafile = None
|
|
||||||
certfile = None
|
|
||||||
extension_sync_interval = 600
|
|
||||||
insecure = False
|
|
||||||
keyfile = None
|
|
||||||
metadata_proxy_shared_secret = ***
|
|
||||||
ovs_bridge = br-int
|
|
||||||
region_name = RegionOne
|
|
||||||
service_metadata_proxy = True
|
|
||||||
timeout = None
|
|
||||||
url = http://10.0.0.9:9696
|
|
||||||
|
|
||||||
osapi_v21:
|
|
||||||
enabled = True
|
|
||||||
extensions_blacklist =
|
|
||||||
extensions_whitelist =
|
|
||||||
|
|
||||||
oslo_concurrency:
|
|
||||||
disable_process_locking = False
|
|
||||||
lock_path = /opt/stack/data/nova
|
|
||||||
|
|
||||||
oslo_messaging_rabbit:
|
|
||||||
amqp_auto_delete = False
|
|
||||||
amqp_durable_queues = False
|
|
||||||
fake_rabbit = False
|
|
||||||
heartbeat_rate = 2
|
|
||||||
heartbeat_timeout_threshold = 60
|
|
||||||
kombu_reconnect_delay = 1.0
|
|
||||||
kombu_reconnect_timeout = 60
|
|
||||||
kombu_ssl_ca_certs =
|
|
||||||
kombu_ssl_certfile =
|
|
||||||
kombu_ssl_keyfile =
|
|
||||||
kombu_ssl_version =
|
|
||||||
rabbit_ha_queues = False
|
|
||||||
rabbit_host = localhost
|
|
||||||
rabbit_hosts =
|
|
||||||
10.0.0.9
|
|
||||||
rabbit_login_method = AMQPLAIN
|
|
||||||
rabbit_max_retries = 0
|
|
||||||
rabbit_password = ***
|
|
||||||
rabbit_port = 5672
|
|
||||||
rabbit_retry_backoff = 2
|
|
||||||
rabbit_retry_interval = 1
|
|
||||||
rabbit_use_ssl = False
|
|
||||||
rabbit_userid = stackrabbit
|
|
||||||
rabbit_virtual_host = /
|
|
||||||
rpc_conn_pool_size = 30
|
|
||||||
send_single_reply = False
|
|
||||||
|
|
||||||
oslo_middleware:
|
|
||||||
max_request_body_size = 114688
|
|
||||||
|
|
||||||
oslo_versionedobjects:
|
|
||||||
fatal_exception_format_errors = False
|
|
||||||
|
|
||||||
rdp:
|
|
||||||
enabled = False
|
|
||||||
html5_proxy_base_url = http://127.0.0.1:6083/
|
|
||||||
|
|
||||||
remote_debug:
|
|
||||||
host = None
|
|
||||||
port = None
|
|
||||||
|
|
||||||
serial_console:
|
|
||||||
base_url = ws://127.0.0.1:6083/
|
|
||||||
enabled = False
|
|
||||||
listen = 127.0.0.1
|
|
||||||
port_range = 10000:20000
|
|
||||||
proxyclient_address = 127.0.0.1
|
|
||||||
|
|
||||||
spice:
|
|
||||||
agent_enabled = True
|
|
||||||
enabled = False
|
|
||||||
html5proxy_base_url = http://10.0.0.9:6082/spice_auto.html
|
|
||||||
keymap = en-us
|
|
||||||
server_listen = 127.0.0.1
|
|
||||||
server_proxyclient_address = 127.0.0.1
|
|
||||||
|
|
||||||
ssl:
|
|
||||||
ca_file = None
|
|
||||||
cert_file = None
|
|
||||||
key_file = None
|
|
||||||
|
|
||||||
upgrade_levels:
|
|
||||||
baseapi = None
|
|
||||||
cells = None
|
|
||||||
cert = None
|
|
||||||
compute = None
|
|
||||||
conductor = None
|
|
||||||
console = None
|
|
||||||
consoleauth = None
|
|
||||||
network = None
|
|
||||||
scheduler = None
|
|
||||||
|
|
||||||
vnc:
|
|
||||||
enabled = False
|
|
||||||
keymap = en-us
|
|
||||||
novncproxy_base_url = http://10.0.0.9:6080/vnc_auto.html
|
|
||||||
vncserver_listen = 127.0.0.1
|
|
||||||
vncserver_proxyclient_address = 127.0.0.1
|
|
||||||
xvpvncproxy_base_url = http://10.0.0.9:6081/console
|
|
||||||
|
|
||||||
workarounds:
|
|
||||||
destroy_after_evacuate = True
|
|
||||||
disable_libvirt_livesnapshot = True
|
|
||||||
disable_rootwrap = False
|
|
||||||
handle_virt_lifecycle_events = True
|
|
@ -1,35 +0,0 @@
|
|||||||
=======
|
|
||||||
Usage
|
|
||||||
=======
|
|
||||||
|
|
||||||
Every long running service process should have a call to install a
|
|
||||||
signal handler which will trigger the guru meditation framework upon
|
|
||||||
receipt of SIGUSR1/SIGUSR2. This will result in the process dumping a
|
|
||||||
complete report of its current state to stderr.
|
|
||||||
|
|
||||||
For RPC listeners, it may also be desirable to install some kind of hook in
|
|
||||||
the RPC request dispatcher that will save a guru meditation report whenever
|
|
||||||
the processing of a request results in an uncaught exception. It could save
|
|
||||||
these reports to a well known directory
|
|
||||||
(/var/log/openstack/<project>/<service>/) for later analysis by the sysadmin
|
|
||||||
or automated bug analysis tools.
|
|
||||||
|
|
||||||
To use oslo.reports in a project, you need to add the following call to
|
|
||||||
:py:func:`~oslo_reports.TextGuruMeditation.setup_autorun` somewhere really
|
|
||||||
early in the startup sequence of the process::
|
|
||||||
|
|
||||||
from oslo_reports import guru_meditation_report as gmr
|
|
||||||
|
|
||||||
gmr.TextGuruMeditation.setup_autorun(version='13.0.0')
|
|
||||||
|
|
||||||
Note that the version parameter is the version of the component itself.
|
|
||||||
|
|
||||||
To trigger the report to be generated::
|
|
||||||
|
|
||||||
kill -SIGUSR2 <process_id>
|
|
||||||
|
|
||||||
|
|
||||||
Here is a sample report:
|
|
||||||
|
|
||||||
.. include:: report.txt
|
|
||||||
:literal:
|
|
@ -1,25 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides a way to generate serializable reports
|
|
||||||
|
|
||||||
This package/module provides mechanisms for defining reports
|
|
||||||
which may then be serialized into various data types. Each
|
|
||||||
report ( :class:`oslo_reports.report.BasicReport` )
|
|
||||||
is composed of one or more report sections
|
|
||||||
( :class:`oslo_reports.report.BasicSection` ),
|
|
||||||
which contain generators which generate data models
|
|
||||||
( :class:`oslo_reports.models.base.ReportModels` ),
|
|
||||||
which are then serialized by views.
|
|
||||||
"""
|
|
@ -1,35 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
"""oslo.i18n integration module.
|
|
||||||
|
|
||||||
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import oslo_i18n
|
|
||||||
|
|
||||||
|
|
||||||
_translators = oslo_i18n.TranslatorFactory(domain='oslo_reports')
|
|
||||||
|
|
||||||
# The primary translation function using the well-known name "_"
|
|
||||||
_ = _translators.primary
|
|
||||||
|
|
||||||
# Translators for log levels.
|
|
||||||
#
|
|
||||||
# The abbreviated names are meant to reflect the usual use of a short
|
|
||||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
|
||||||
# the level.
|
|
||||||
_LI = _translators.log_info
|
|
||||||
_LW = _translators.log_warning
|
|
||||||
_LE = _translators.log_error
|
|
||||||
_LC = _translators.log_critical
|
|
@ -1,27 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Various utilities for report generation
|
|
||||||
|
|
||||||
This module includes various utilities
|
|
||||||
used in generating reports.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
class StringWithAttrs(six.text_type):
|
|
||||||
"""A String that can have arbitrary attributes"""
|
|
||||||
|
|
||||||
pass
|
|
@ -1,21 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides Data Model Generators
|
|
||||||
|
|
||||||
This module defines classes for generating data models
|
|
||||||
( :class:`oslo_reports.models.base.ReportModel` ).
|
|
||||||
A generator is any object which is callable with no parameters
|
|
||||||
and returns a data model.
|
|
||||||
"""
|
|
@ -1,44 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides OpenStack config generators
|
|
||||||
|
|
||||||
This module defines a class for configuration
|
|
||||||
generators for generating the model in
|
|
||||||
:mod:`oslo_reports.models.conf`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from oslo_reports.models import conf as cm
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigReportGenerator(object):
|
|
||||||
"""A Configuration Data Generator
|
|
||||||
|
|
||||||
This generator returns
|
|
||||||
:class:`oslo_reports.models.conf.ConfigModel`,
|
|
||||||
by default using the configuration options stored
|
|
||||||
in :attr:`oslo_config.cfg.CONF`, which is where
|
|
||||||
OpenStack stores everything.
|
|
||||||
|
|
||||||
:param cnf: the configuration option object
|
|
||||||
:type cnf: :class:`oslo_config.cfg.ConfigOpts`
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, cnf=cfg.CONF):
|
|
||||||
self.conf_obj = cnf
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
return cm.ConfigModel(self.conf_obj)
|
|
@ -1,38 +0,0 @@
|
|||||||
# Copyright 2014 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides process-data generators
|
|
||||||
|
|
||||||
This modules defines a class for generating
|
|
||||||
process data by way of the psutil package.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import psutil
|
|
||||||
|
|
||||||
from oslo_reports.models import process as pm
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessReportGenerator(object):
|
|
||||||
"""A Process Data Generator
|
|
||||||
|
|
||||||
This generator returns a
|
|
||||||
:class:`oslo_reports.models.process.ProcessModel`
|
|
||||||
based on the current process (which will also include
|
|
||||||
all subprocesses, recursively) using the :class:`psutil.Process` class`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
return pm.ProcessModel(psutil.Process(os.getpid()))
|
|
@ -1,104 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides thread-related generators
|
|
||||||
|
|
||||||
This module defines classes for threading-related
|
|
||||||
generators for generating the models in
|
|
||||||
:mod:`oslo_reports.models.threading`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import gc
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from oslo_reports.models import threading as tm
|
|
||||||
from oslo_reports.models import with_default_views as mwdv
|
|
||||||
from oslo_reports.views.text import generic as text_views
|
|
||||||
|
|
||||||
|
|
||||||
def _find_objects(t):
|
|
||||||
"""Find Objects in the GC State
|
|
||||||
|
|
||||||
This horribly hackish method locates objects of a
|
|
||||||
given class in the current python instance's garbage
|
|
||||||
collection state. In case you couldn't tell, this is
|
|
||||||
horribly hackish, but is necessary for locating all
|
|
||||||
green threads, since they don't keep track of themselves
|
|
||||||
like normal threads do in python.
|
|
||||||
|
|
||||||
:param class t: the class of object to locate
|
|
||||||
:rtype: list
|
|
||||||
:returns: a list of objects of the given type
|
|
||||||
"""
|
|
||||||
|
|
||||||
return [o for o in gc.get_objects() if isinstance(o, t)]
|
|
||||||
|
|
||||||
|
|
||||||
class ThreadReportGenerator(object):
|
|
||||||
"""A Thread Data Generator
|
|
||||||
|
|
||||||
This generator returns a collection of
|
|
||||||
:class:`oslo_reports.models.threading.ThreadModel`
|
|
||||||
objects by introspecting the current python state using
|
|
||||||
:func:`sys._current_frames()` . Its constructor may optionally
|
|
||||||
be passed a frame object. This frame object will be interpreted
|
|
||||||
as the actual stack trace for the current thread, and, come generation
|
|
||||||
time, will be used to replace the stack trace of the thread in which
|
|
||||||
this code is running.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, curr_thread_traceback=None):
|
|
||||||
self.traceback = curr_thread_traceback
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
threadModels = dict(
|
|
||||||
(thread_id, tm.ThreadModel(thread_id, stack))
|
|
||||||
for thread_id, stack in sys._current_frames().items()
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.traceback is not None:
|
|
||||||
curr_thread_id = threading.current_thread().ident
|
|
||||||
threadModels[curr_thread_id] = tm.ThreadModel(curr_thread_id,
|
|
||||||
self.traceback)
|
|
||||||
|
|
||||||
return mwdv.ModelWithDefaultViews(threadModels,
|
|
||||||
text_view=text_views.MultiView())
|
|
||||||
|
|
||||||
|
|
||||||
class GreenThreadReportGenerator(object):
|
|
||||||
"""A Green Thread Data Generator
|
|
||||||
|
|
||||||
This generator returns a collection of
|
|
||||||
:class:`oslo_reports.models.threading.GreenThreadModel`
|
|
||||||
objects by introspecting the current python garbage collection
|
|
||||||
state, and sifting through for :class:`greenlet.greenlet` objects.
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
Function :func:`_find_objects`
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
import greenlet
|
|
||||||
|
|
||||||
threadModels = [
|
|
||||||
tm.GreenThreadModel(gr.gr_frame)
|
|
||||||
for gr in _find_objects(greenlet.greenlet)
|
|
||||||
]
|
|
||||||
|
|
||||||
return mwdv.ModelWithDefaultViews(threadModels,
|
|
||||||
text_view=text_views.MultiView())
|
|
@ -1,60 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides OpenStack version generators
|
|
||||||
|
|
||||||
This module defines a class for OpenStack
|
|
||||||
version and package information
|
|
||||||
generators for generating the model in
|
|
||||||
:mod:`oslo_reports.models.version`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from oslo_reports.models import version as vm
|
|
||||||
|
|
||||||
|
|
||||||
class PackageReportGenerator(object):
|
|
||||||
"""A Package Information Data Generator
|
|
||||||
|
|
||||||
This generator returns
|
|
||||||
:class:`oslo_reports.models.version.PackageModel`,
|
|
||||||
extracting data from the given version object, which should follow
|
|
||||||
the general format defined in Nova's version information (i.e. it
|
|
||||||
should contain the methods vendor_string, product_string, and
|
|
||||||
version_string_with_package).
|
|
||||||
|
|
||||||
:param version_object: the version information object
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, version_obj):
|
|
||||||
self.version_obj = version_obj
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
if hasattr(self.version_obj, "vendor_string"):
|
|
||||||
vendor_string = self.version_obj.vendor_string()
|
|
||||||
else:
|
|
||||||
vendor_string = None
|
|
||||||
|
|
||||||
if hasattr(self.version_obj, "product_string"):
|
|
||||||
product_string = self.version_obj.product_string()
|
|
||||||
else:
|
|
||||||
product_string = None
|
|
||||||
|
|
||||||
if hasattr(self.version_obj, "version_string_with_package"):
|
|
||||||
version_string_with_package = self.version_obj.\
|
|
||||||
version_string_with_package()
|
|
||||||
else:
|
|
||||||
version_string_with_package = None
|
|
||||||
|
|
||||||
return vm.PackageModel(vendor_string, product_string,
|
|
||||||
version_string_with_package)
|
|
@ -1,299 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides Guru Meditation Report
|
|
||||||
|
|
||||||
This module defines the actual OpenStack Guru Meditation
|
|
||||||
Report class.
|
|
||||||
|
|
||||||
This can be used in the OpenStack command definition files.
|
|
||||||
For example, in a nova command module (under nova/cmd):
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
:emphasize-lines: 8,9,10
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as oslo_logging
|
|
||||||
from oslo_reports import opts as gmr_opts
|
|
||||||
from oslo_reports import guru_meditation_report as gmr
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
# maybe import some options here...
|
|
||||||
|
|
||||||
def main():
|
|
||||||
oslo_logging.register_options(CONF)
|
|
||||||
gmr_opts.set_defaults(CONF)
|
|
||||||
|
|
||||||
CONF(sys.argv[1:], default_config_files=['myapp.conf'])
|
|
||||||
oslo_logging.setup(CONF, 'myapp')
|
|
||||||
|
|
||||||
gmr.TextGuruMeditation.register_section('Some Special Section',
|
|
||||||
special_section_generator)
|
|
||||||
gmr.TextGuruMeditation.setup_autorun(version_object, conf=CONF)
|
|
||||||
|
|
||||||
server = service.Service.create(binary='some-service',
|
|
||||||
topic=CONF.some_service_topic)
|
|
||||||
service.serve(server)
|
|
||||||
service.wait()
|
|
||||||
|
|
||||||
Then, you can do
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ kill -USR2 $SERVICE_PID
|
|
||||||
|
|
||||||
and get a Guru Meditation Report in the file or terminal
|
|
||||||
where stderr is logged for that given service.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import signal
|
|
||||||
import stat
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
from oslo_reports._i18n import _LE
|
|
||||||
from oslo_reports._i18n import _LW
|
|
||||||
from oslo_reports.generators import conf as cgen
|
|
||||||
from oslo_reports.generators import process as prgen
|
|
||||||
from oslo_reports.generators import threading as tgen
|
|
||||||
from oslo_reports.generators import version as pgen
|
|
||||||
from oslo_reports import report
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class GuruMeditation(object):
|
|
||||||
"""A Guru Meditation Report Mixin/Base Class
|
|
||||||
|
|
||||||
This class is a base class for Guru Meditation Reports.
|
|
||||||
It provides facilities for registering sections and
|
|
||||||
setting up functionality to auto-run the report on
|
|
||||||
a certain signal or use file modification events.
|
|
||||||
|
|
||||||
This class should always be used in conjunction with
|
|
||||||
a Report class via multiple inheritance. It should
|
|
||||||
always come first in the class list to ensure the
|
|
||||||
MRO is correct.
|
|
||||||
"""
|
|
||||||
|
|
||||||
timestamp_fmt = "%Y%m%d%H%M%S"
|
|
||||||
|
|
||||||
def __init__(self, version_obj, sig_handler_tb=None, *args, **kwargs):
|
|
||||||
self.version_obj = version_obj
|
|
||||||
self.traceback = sig_handler_tb
|
|
||||||
|
|
||||||
super(GuruMeditation, self).__init__(*args, **kwargs)
|
|
||||||
self.start_section_index = len(self.sections)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def register_section(cls, section_title, generator):
|
|
||||||
"""Register a New Section
|
|
||||||
|
|
||||||
This method registers a persistent section for the current
|
|
||||||
class.
|
|
||||||
|
|
||||||
:param str section_title: the title of the section
|
|
||||||
:param generator: the generator for the section
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
cls.persistent_sections.append([section_title, generator])
|
|
||||||
except AttributeError:
|
|
||||||
cls.persistent_sections = [[section_title, generator]]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setup_autorun(cls, version, service_name=None,
|
|
||||||
log_dir=None, signum=None, conf=None):
|
|
||||||
"""Set Up Auto-Run
|
|
||||||
|
|
||||||
This method sets up the Guru Meditation Report to automatically
|
|
||||||
get dumped to stderr or a file in a given dir when the given signal
|
|
||||||
is received. It can also use file modification events instead of
|
|
||||||
signals.
|
|
||||||
|
|
||||||
:param version: the version object for the current product
|
|
||||||
:param service_name: this program name used to construct logfile name
|
|
||||||
:param logdir: path to a log directory where to create a file
|
|
||||||
:param signum: the signal to associate with running the report
|
|
||||||
:param conf: Configuration object, managed by the caller.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if log_dir is None and conf is not None:
|
|
||||||
log_dir = conf.oslo_reports.log_dir
|
|
||||||
|
|
||||||
if signum:
|
|
||||||
cls._setup_signal(signum, version, service_name, log_dir)
|
|
||||||
return
|
|
||||||
|
|
||||||
if conf and conf.oslo_reports.file_event_handler:
|
|
||||||
cls._setup_file_watcher(
|
|
||||||
conf.oslo_reports.file_event_handler,
|
|
||||||
conf.oslo_reports.file_event_handler_interval,
|
|
||||||
version, service_name, log_dir)
|
|
||||||
else:
|
|
||||||
if hasattr(signal, 'SIGUSR1'):
|
|
||||||
# TODO(dims) We need to remove this in the "O" release cycle
|
|
||||||
LOG.warning(_LW("Guru meditation now registers SIGUSR1 and "
|
|
||||||
"SIGUSR2 by default for backward "
|
|
||||||
"compatibility. SIGUSR1 will no longer be "
|
|
||||||
"registered in a future release, so please "
|
|
||||||
"use SIGUSR2 to generate reports."))
|
|
||||||
cls._setup_signal(signal.SIGUSR1,
|
|
||||||
version, service_name, log_dir)
|
|
||||||
if hasattr(signal, 'SIGUSR2'):
|
|
||||||
cls._setup_signal(signal.SIGUSR2,
|
|
||||||
version, service_name, log_dir)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _setup_file_watcher(cls, filepath, interval, version, service_name,
|
|
||||||
log_dir):
|
|
||||||
|
|
||||||
st = os.stat(filepath)
|
|
||||||
if not bool(st.st_mode & stat.S_IRGRP):
|
|
||||||
LOG.error(_LE("Guru Meditation Report does not have read "
|
|
||||||
"permissions to '%s' file."), filepath)
|
|
||||||
|
|
||||||
def _handler():
|
|
||||||
mtime = time.time()
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
stat = os.stat(filepath)
|
|
||||||
if stat.st_mtime > mtime:
|
|
||||||
cls.handle_signal(version, service_name, log_dir, None)
|
|
||||||
mtime = stat.st_mtime
|
|
||||||
except OSError:
|
|
||||||
msg = ("Guru Meditation Report cannot read " +
|
|
||||||
"'{0}' file".format(filepath))
|
|
||||||
raise IOError(msg)
|
|
||||||
finally:
|
|
||||||
time.sleep(interval)
|
|
||||||
|
|
||||||
th = threading.Thread(target=_handler)
|
|
||||||
th.daemon = True
|
|
||||||
th.start()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _setup_signal(cls, signum, version, service_name, log_dir):
|
|
||||||
signal.signal(signum,
|
|
||||||
lambda sn, f: cls.handle_signal(
|
|
||||||
version, service_name, log_dir, f))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def handle_signal(cls, version, service_name, log_dir, frame):
|
|
||||||
"""The Signal Handler
|
|
||||||
|
|
||||||
This method (indirectly) handles receiving a registered signal and
|
|
||||||
dumping the Guru Meditation Report to stderr or a file in a given dir.
|
|
||||||
If service name and log dir are not None, the report will be dumped to
|
|
||||||
a file named $service_name_gurumeditation_$current_time in the log_dir
|
|
||||||
directory.
|
|
||||||
This method is designed to be curried into a proper signal handler by
|
|
||||||
currying out the version
|
|
||||||
parameter.
|
|
||||||
|
|
||||||
:param version: the version object for the current product
|
|
||||||
:param service_name: this program name used to construct logfile name
|
|
||||||
:param logdir: path to a log directory where to create a file
|
|
||||||
:param frame: the frame object provided to the signal handler
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
res = cls(version, frame).run()
|
|
||||||
except Exception:
|
|
||||||
traceback.print_exc(file=sys.stderr)
|
|
||||||
print("Unable to run Guru Meditation Report!",
|
|
||||||
file=sys.stderr)
|
|
||||||
else:
|
|
||||||
if log_dir:
|
|
||||||
service_name = service_name or os.path.basename(
|
|
||||||
inspect.stack()[-1][1])
|
|
||||||
filename = "%s_gurumeditation_%s" % (
|
|
||||||
service_name, timeutils.utcnow().strftime(
|
|
||||||
cls.timestamp_fmt))
|
|
||||||
filepath = os.path.join(log_dir, filename)
|
|
||||||
try:
|
|
||||||
with open(filepath, "w") as dumpfile:
|
|
||||||
dumpfile.write(res)
|
|
||||||
except Exception:
|
|
||||||
print("Unable to dump Guru Meditation Report to file %s" %
|
|
||||||
(filepath,), file=sys.stderr)
|
|
||||||
else:
|
|
||||||
print(res, file=sys.stderr)
|
|
||||||
|
|
||||||
def _readd_sections(self):
|
|
||||||
del self.sections[self.start_section_index:]
|
|
||||||
|
|
||||||
self.add_section('Package',
|
|
||||||
pgen.PackageReportGenerator(self.version_obj))
|
|
||||||
|
|
||||||
self.add_section('Threads',
|
|
||||||
tgen.ThreadReportGenerator(self.traceback))
|
|
||||||
|
|
||||||
self.add_section('Green Threads',
|
|
||||||
tgen.GreenThreadReportGenerator())
|
|
||||||
|
|
||||||
self.add_section('Processes',
|
|
||||||
prgen.ProcessReportGenerator())
|
|
||||||
|
|
||||||
self.add_section('Configuration',
|
|
||||||
cgen.ConfigReportGenerator())
|
|
||||||
|
|
||||||
try:
|
|
||||||
for section_title, generator in self.persistent_sections:
|
|
||||||
self.add_section(section_title, generator)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self._readd_sections()
|
|
||||||
return super(GuruMeditation, self).run()
|
|
||||||
|
|
||||||
|
|
||||||
# GuruMeditation must come first to get the correct MRO
|
|
||||||
class TextGuruMeditation(GuruMeditation, report.TextReport):
|
|
||||||
"""A Text Guru Meditation Report
|
|
||||||
|
|
||||||
This report is the basic human-readable Guru Meditation Report
|
|
||||||
|
|
||||||
It contains the following sections by default
|
|
||||||
(in addition to any registered persistent sections):
|
|
||||||
|
|
||||||
- Package Information
|
|
||||||
|
|
||||||
- Threads List
|
|
||||||
|
|
||||||
- Green Threads List
|
|
||||||
|
|
||||||
- Process List
|
|
||||||
|
|
||||||
- Configuration Options
|
|
||||||
|
|
||||||
:param version_obj: the version object for the current product
|
|
||||||
:param traceback: an (optional) frame object providing the actual
|
|
||||||
traceback for the current thread
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, version_obj, traceback=None):
|
|
||||||
super(TextGuruMeditation, self).__init__(version_obj, traceback,
|
|
||||||
'Guru Meditation')
|
|
@ -1,18 +0,0 @@
|
|||||||
# Andi Chandler <andi@gowling.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.reports 1.9.1.dev1\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
|
||||||
"POT-Creation-Date: 2016-06-10 16:23+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2016-06-09 11:13+0000\n"
|
|
||||||
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
|
|
||||||
"Language-Team: English (United Kingdom)\n"
|
|
||||||
"Language: en-GB\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
msgid "Path to a log directory where to create a file"
|
|
||||||
msgstr "Path to a log directory where to create a file"
|
|
@ -1,20 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides data models
|
|
||||||
|
|
||||||
This module provides both the base data model,
|
|
||||||
as well as several predefined specific data models
|
|
||||||
to be used in reports.
|
|
||||||
"""
|
|
@ -1,162 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides the base report model
|
|
||||||
|
|
||||||
This module defines a class representing the basic report
|
|
||||||
data model from which all data models should inherit (or
|
|
||||||
at least implement similar functionality). Data models
|
|
||||||
store unserialized data generated by generators during
|
|
||||||
the report serialization process.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import collections as col
|
|
||||||
import copy
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
class ReportModel(col.MutableMapping):
|
|
||||||
"""A Report Data Model
|
|
||||||
|
|
||||||
A report data model contains data generated by some
|
|
||||||
generator method or class. Data may be read or written
|
|
||||||
using dictionary-style access, and may be read (but not
|
|
||||||
written) using object-member-style access. Additionally,
|
|
||||||
a data model may have an associated view. This view is
|
|
||||||
used to serialize the model when str() is called on the
|
|
||||||
model. An appropriate object for a view is callable with
|
|
||||||
a single parameter: the model to be serialized.
|
|
||||||
|
|
||||||
If present, the object passed in as data will be transformed
|
|
||||||
into a standard python dict. For mappings, this is fairly
|
|
||||||
straightforward. For sequences, the indices become keys
|
|
||||||
and the items become values.
|
|
||||||
|
|
||||||
:param data: a sequence or mapping of data to associate with the model
|
|
||||||
:param attached_view: a view object to attach to this model
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, data=None, attached_view=None):
|
|
||||||
self.attached_view = attached_view
|
|
||||||
|
|
||||||
if data is not None:
|
|
||||||
if isinstance(data, col.Mapping):
|
|
||||||
self.data = dict(data)
|
|
||||||
elif isinstance(data, col.Sequence):
|
|
||||||
# convert a list [a, b, c] to a dict {0: a, 1: b, 2: c}
|
|
||||||
self.data = dict(enumerate(data))
|
|
||||||
else:
|
|
||||||
raise TypeError('Data for the model must be a sequence '
|
|
||||||
'or mapping.')
|
|
||||||
else:
|
|
||||||
self.data = {}
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
self_cpy = copy.deepcopy(self)
|
|
||||||
for key in self_cpy:
|
|
||||||
if getattr(self_cpy[key], 'attached_view', None) is not None:
|
|
||||||
self_cpy[key] = six.text_type(self_cpy[key])
|
|
||||||
|
|
||||||
if self.attached_view is not None:
|
|
||||||
return self.attached_view(self_cpy)
|
|
||||||
else:
|
|
||||||
raise Exception("Cannot stringify model: no attached view")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.attached_view is not None:
|
|
||||||
return ("<Model {cl.__module__}.{cl.__name__} {dt}"
|
|
||||||
" with view {vw.__module__}."
|
|
||||||
"{vw.__name__}>").format(cl=type(self),
|
|
||||||
dt=self.data,
|
|
||||||
vw=type(self.attached_view))
|
|
||||||
else:
|
|
||||||
return ("<Model {cl.__module__}.{cl.__name__} {dt}"
|
|
||||||
" with no view>").format(cl=type(self),
|
|
||||||
dt=self.data)
|
|
||||||
|
|
||||||
def __getitem__(self, attrname):
|
|
||||||
return self.data[attrname]
|
|
||||||
|
|
||||||
def __setitem__(self, attrname, attrval):
|
|
||||||
self.data[attrname] = attrval
|
|
||||||
|
|
||||||
def __delitem__(self, attrname):
|
|
||||||
del self.data[attrname]
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
return self.data.__contains__(key)
|
|
||||||
|
|
||||||
def __getattr__(self, attrname):
|
|
||||||
# Needed for deepcopy in Python3. That will avoid an infinite loop
|
|
||||||
# in __getattr__ .
|
|
||||||
if 'data' not in self.__dict__:
|
|
||||||
self.data = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self.data[attrname]
|
|
||||||
except KeyError:
|
|
||||||
# we don't have that key in data, and the
|
|
||||||
# model class doesn't have that attribute
|
|
||||||
raise AttributeError(
|
|
||||||
"'{cl}' object has no attribute '{an}'".format(
|
|
||||||
cl=type(self).__name__, an=attrname
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.data)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self.data.__iter__()
|
|
||||||
|
|
||||||
def set_current_view_type(self, tp, visited=None):
|
|
||||||
"""Set the current view type
|
|
||||||
|
|
||||||
This method attempts to set the current view
|
|
||||||
type for this model and all submodels by calling
|
|
||||||
itself recursively on all values, traversing
|
|
||||||
intervening sequences and mappings when possible,
|
|
||||||
and ignoring all other objects.
|
|
||||||
|
|
||||||
:param tp: the type of the view ('text', 'json', 'xml', etc)
|
|
||||||
:param visited: a set of object ids for which the corresponding objects
|
|
||||||
have already had their view type set
|
|
||||||
"""
|
|
||||||
|
|
||||||
if visited is None:
|
|
||||||
visited = set()
|
|
||||||
|
|
||||||
def traverse_obj(obj):
|
|
||||||
oid = id(obj)
|
|
||||||
|
|
||||||
# don't die on recursive structures,
|
|
||||||
# and don't treat strings like sequences
|
|
||||||
if oid in visited or isinstance(obj, six.string_types):
|
|
||||||
return
|
|
||||||
|
|
||||||
visited.add(oid)
|
|
||||||
|
|
||||||
if hasattr(obj, 'set_current_view_type'):
|
|
||||||
obj.set_current_view_type(tp, visited=visited)
|
|
||||||
|
|
||||||
if isinstance(obj, col.Sequence):
|
|
||||||
for item in obj:
|
|
||||||
traverse_obj(item)
|
|
||||||
|
|
||||||
elif isinstance(obj, col.Mapping):
|
|
||||||
for val in six.itervalues(obj):
|
|
||||||
traverse_obj(val)
|
|
||||||
|
|
||||||
traverse_obj(self)
|
|
@ -1,66 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides OpenStack Configuration Model
|
|
||||||
|
|
||||||
This module defines a class representing the data
|
|
||||||
model for :mod:`oslo_config` configuration options
|
|
||||||
"""
|
|
||||||
|
|
||||||
from oslo_reports.models import with_default_views as mwdv
|
|
||||||
from oslo_reports.views.text import generic as generic_text_views
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigModel(mwdv.ModelWithDefaultViews):
|
|
||||||
"""A Configuration Options Model
|
|
||||||
|
|
||||||
This model holds data about a set of configuration options
|
|
||||||
from :mod:`oslo_config`. It supports both the default group
|
|
||||||
of options and named option groups.
|
|
||||||
|
|
||||||
:param conf_obj: a configuration object
|
|
||||||
:type conf_obj: :class:`oslo_config.cfg.ConfigOpts`
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, conf_obj):
|
|
||||||
kv_view = generic_text_views.KeyValueView(dict_sep=": ",
|
|
||||||
before_dict='')
|
|
||||||
super(ConfigModel, self).__init__(text_view=kv_view)
|
|
||||||
|
|
||||||
def opt_title(optname, co):
|
|
||||||
return co._opts[optname]['opt'].name
|
|
||||||
|
|
||||||
def opt_value(opt_obj, value):
|
|
||||||
if opt_obj['opt'].secret:
|
|
||||||
return '***'
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
|
|
||||||
self['default'] = dict(
|
|
||||||
(opt_title(optname, conf_obj),
|
|
||||||
opt_value(conf_obj._opts[optname], conf_obj[optname]))
|
|
||||||
for optname in conf_obj._opts
|
|
||||||
)
|
|
||||||
|
|
||||||
groups = {}
|
|
||||||
for groupname in conf_obj._groups:
|
|
||||||
group_obj = conf_obj._groups[groupname]
|
|
||||||
curr_group_opts = dict(
|
|
||||||
(opt_title(optname, group_obj),
|
|
||||||
opt_value(group_obj._opts[optname],
|
|
||||||
conf_obj[groupname][optname]))
|
|
||||||
for optname in group_obj._opts)
|
|
||||||
groups[group_obj.name] = curr_group_opts
|
|
||||||
|
|
||||||
self.update(groups)
|
|
@ -1,73 +0,0 @@
|
|||||||
# Copyright 2014 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides a process model
|
|
||||||
|
|
||||||
This module defines a class representing a process,
|
|
||||||
potentially with subprocesses.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import psutil
|
|
||||||
|
|
||||||
import oslo_reports.models.with_default_views as mwdv
|
|
||||||
import oslo_reports.views.text.process as text_views
|
|
||||||
|
|
||||||
PS1 = psutil.version_info[0] == 1
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessModel(mwdv.ModelWithDefaultViews):
|
|
||||||
"""A Process Model
|
|
||||||
|
|
||||||
This model holds data about a process,
|
|
||||||
including references to any subprocesses
|
|
||||||
|
|
||||||
:param process: a :class:`psutil.Process` object
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, process):
|
|
||||||
super(ProcessModel, self).__init__(
|
|
||||||
text_view=text_views.ProcessView())
|
|
||||||
|
|
||||||
self['pid'] = process.pid
|
|
||||||
self['parent_pid'] = (process.ppid if PS1 else process.ppid())
|
|
||||||
if hasattr(process, 'uids'):
|
|
||||||
self['uids'] = {
|
|
||||||
'real': (process.uids.real if PS1 else process.uids().real),
|
|
||||||
'effective': (process.uids.effective if PS1
|
|
||||||
else process.uids().effective),
|
|
||||||
'saved': (process.uids.saved if PS1 else process.uids().saved)
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
self['uids'] = {'real': None,
|
|
||||||
'effective': None,
|
|
||||||
'saved': None}
|
|
||||||
|
|
||||||
if hasattr(process, 'gids'):
|
|
||||||
self['gids'] = {
|
|
||||||
'real': (process.gids.real if PS1 else process.gids().real),
|
|
||||||
'effective': (process.gids.effective if PS1
|
|
||||||
else process.gids().effective),
|
|
||||||
'saved': (process.gids.saved if PS1 else process.gids().saved)
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
self['gids'] = {'real': None,
|
|
||||||
'effective': None,
|
|
||||||
'saved': None}
|
|
||||||
|
|
||||||
self['username'] = process.username if PS1 else process.username()
|
|
||||||
self['command'] = process.cmdline if PS1 else process.cmdline()
|
|
||||||
self['state'] = process.status if PS1 else process.status()
|
|
||||||
|
|
||||||
children = process.get_children() if PS1 else process.children()
|
|
||||||
self['children'] = [ProcessModel(pr) for pr in children]
|
|
@ -1,100 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides threading and stack-trace models
|
|
||||||
|
|
||||||
This module defines classes representing thread, green
|
|
||||||
thread, and stack trace data models
|
|
||||||
"""
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from oslo_reports.models import with_default_views as mwdv
|
|
||||||
from oslo_reports.views.text import threading as text_views
|
|
||||||
|
|
||||||
|
|
||||||
class StackTraceModel(mwdv.ModelWithDefaultViews):
|
|
||||||
"""A Stack Trace Model
|
|
||||||
|
|
||||||
This model holds data from a python stack trace,
|
|
||||||
commonly extracted from running thread information
|
|
||||||
|
|
||||||
:param stack_state: the python stack_state object
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, stack_state):
|
|
||||||
super(StackTraceModel, self).__init__(
|
|
||||||
text_view=text_views.StackTraceView())
|
|
||||||
|
|
||||||
if (stack_state is not None):
|
|
||||||
self['lines'] = [
|
|
||||||
{'filename': fn, 'line': ln, 'name': nm, 'code': cd}
|
|
||||||
for fn, ln, nm, cd in traceback.extract_stack(stack_state)
|
|
||||||
]
|
|
||||||
# FIXME(flepied): under Python3 f_exc_type doesn't exist
|
|
||||||
# anymore so we lose information about exceptions
|
|
||||||
if getattr(stack_state, 'f_exc_type', None) is not None:
|
|
||||||
self['root_exception'] = {
|
|
||||||
'type': stack_state.f_exc_type,
|
|
||||||
'value': stack_state.f_exc_value}
|
|
||||||
else:
|
|
||||||
self['root_exception'] = None
|
|
||||||
else:
|
|
||||||
self['lines'] = []
|
|
||||||
self['root_exception'] = None
|
|
||||||
|
|
||||||
|
|
||||||
class ThreadModel(mwdv.ModelWithDefaultViews):
|
|
||||||
"""A Thread Model
|
|
||||||
|
|
||||||
This model holds data for information about an
|
|
||||||
individual thread. It holds both a thread id,
|
|
||||||
as well as a stack trace for the thread
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
Class :class:`StackTraceModel`
|
|
||||||
|
|
||||||
:param int thread_id: the id of the thread
|
|
||||||
:param stack: the python stack state for the current thread
|
|
||||||
"""
|
|
||||||
|
|
||||||
# threadId, stack in sys._current_frams().items()
|
|
||||||
def __init__(self, thread_id, stack):
|
|
||||||
super(ThreadModel, self).__init__(text_view=text_views.ThreadView())
|
|
||||||
|
|
||||||
self['thread_id'] = thread_id
|
|
||||||
self['stack_trace'] = StackTraceModel(stack)
|
|
||||||
|
|
||||||
|
|
||||||
class GreenThreadModel(mwdv.ModelWithDefaultViews):
|
|
||||||
"""A Green Thread Model
|
|
||||||
|
|
||||||
This model holds data for information about an
|
|
||||||
individual thread. Unlike the thread model,
|
|
||||||
it holds just a stack trace, since green threads
|
|
||||||
do not have thread ids.
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
Class :class:`StackTraceModel`
|
|
||||||
|
|
||||||
:param stack: the python stack state for the green thread
|
|
||||||
"""
|
|
||||||
|
|
||||||
# gr in greenpool.coroutines_running --> gr.gr_frame
|
|
||||||
def __init__(self, stack):
|
|
||||||
super(GreenThreadModel, self).__init__(
|
|
||||||
{'stack_trace': StackTraceModel(stack)},
|
|
||||||
text_view=text_views.GreenThreadView())
|
|
@ -1,44 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides OpenStack Version Info Model
|
|
||||||
|
|
||||||
This module defines a class representing the data
|
|
||||||
model for OpenStack package and version information
|
|
||||||
"""
|
|
||||||
|
|
||||||
from oslo_reports.models import with_default_views as mwdv
|
|
||||||
from oslo_reports.views.text import generic as generic_text_views
|
|
||||||
|
|
||||||
|
|
||||||
class PackageModel(mwdv.ModelWithDefaultViews):
|
|
||||||
"""A Package Information Model
|
|
||||||
|
|
||||||
This model holds information about the current
|
|
||||||
package. It contains vendor, product, and version
|
|
||||||
information.
|
|
||||||
|
|
||||||
:param str vendor: the product vendor
|
|
||||||
:param str product: the product name
|
|
||||||
:param str version: the product version
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, vendor, product, version):
|
|
||||||
super(PackageModel, self).__init__(
|
|
||||||
text_view=generic_text_views.KeyValueView()
|
|
||||||
)
|
|
||||||
|
|
||||||
self['vendor'] = vendor
|
|
||||||
self['product'] = product
|
|
||||||
self['version'] = version
|
|
@ -1,81 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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 copy
|
|
||||||
|
|
||||||
from oslo_reports.models import base as base_model
|
|
||||||
from oslo_reports.views.json import generic as jsonviews
|
|
||||||
from oslo_reports.views.text import generic as textviews
|
|
||||||
from oslo_reports.views.xml import generic as xmlviews
|
|
||||||
|
|
||||||
|
|
||||||
class ModelWithDefaultViews(base_model.ReportModel):
|
|
||||||
"""A Model With Default Views of Various Types
|
|
||||||
|
|
||||||
A model with default views has several predefined views,
|
|
||||||
each associated with a given type. This is often used for
|
|
||||||
when a submodel should have an attached view, but the view
|
|
||||||
differs depending on the serialization format
|
|
||||||
|
|
||||||
Parameters are as the superclass, except for any
|
|
||||||
parameters ending in '_view': these parameters
|
|
||||||
get stored as default views.
|
|
||||||
|
|
||||||
The default 'default views' are
|
|
||||||
|
|
||||||
text
|
|
||||||
:class:`oslo_reports.views.text.generic.KeyValueView`
|
|
||||||
xml
|
|
||||||
:class:`oslo_reports.views.xml.generic.KeyValueView`
|
|
||||||
json
|
|
||||||
:class:`oslo_reports.views.json.generic.KeyValueView`
|
|
||||||
|
|
||||||
.. function:: to_type()
|
|
||||||
|
|
||||||
('type' is one of the 'default views' defined for this model)
|
|
||||||
Serializes this model using the default view for 'type'
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
:returns: this model serialized as 'type'
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.views = {
|
|
||||||
'text': textviews.KeyValueView(),
|
|
||||||
'json': jsonviews.KeyValueView(),
|
|
||||||
'xml': xmlviews.KeyValueView()
|
|
||||||
}
|
|
||||||
|
|
||||||
newargs = copy.copy(kwargs)
|
|
||||||
for k in kwargs:
|
|
||||||
if k.endswith('_view'):
|
|
||||||
self.views[k[:-5]] = kwargs[k]
|
|
||||||
del newargs[k]
|
|
||||||
super(ModelWithDefaultViews, self).__init__(*args, **newargs)
|
|
||||||
|
|
||||||
def set_current_view_type(self, tp, visited=None):
|
|
||||||
self.attached_view = self.views[tp]
|
|
||||||
super(ModelWithDefaultViews, self).set_current_view_type(tp, visited)
|
|
||||||
|
|
||||||
def __getattr__(self, attrname):
|
|
||||||
if attrname[:3] == 'to_':
|
|
||||||
if self.views[attrname[3:]] is not None:
|
|
||||||
return lambda: self.views[attrname[3:]](self)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError((
|
|
||||||
"Model {cn.__module__}.{cn.__name__} does not have" +
|
|
||||||
" a default view for "
|
|
||||||
"{tp}").format(cn=type(self), tp=attrname[3:]))
|
|
||||||
else:
|
|
||||||
return super(ModelWithDefaultViews, self).__getattr__(attrname)
|
|
@ -1,71 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'list_opts',
|
|
||||||
'set_defaults',
|
|
||||||
]
|
|
||||||
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from oslo_reports._i18n import _
|
|
||||||
|
|
||||||
|
|
||||||
_option_group = 'oslo_reports'
|
|
||||||
|
|
||||||
_options = [
|
|
||||||
cfg.StrOpt('log_dir',
|
|
||||||
help=_('Path to a log directory where to create a file')),
|
|
||||||
cfg.StrOpt('file_event_handler',
|
|
||||||
help=_('The path to a file to watch for changes to trigger '
|
|
||||||
'the reports, instead of signals. Setting this option '
|
|
||||||
'disables the signal trigger for the reports. If '
|
|
||||||
'application is running as a WSGI application it is '
|
|
||||||
'recommended to use this instead of signals.')),
|
|
||||||
cfg.IntOpt('file_event_handler_interval',
|
|
||||||
default=1,
|
|
||||||
help=_('How many seconds to wait between polls when '
|
|
||||||
'file_event_handler is set'))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
"""Return a list of oslo.config options available in the library.
|
|
||||||
|
|
||||||
The returned list includes all oslo.config options which may be registered
|
|
||||||
at runtime by the library.
|
|
||||||
Each element of the list is a tuple. The first element is the name of the
|
|
||||||
group under which the list of elements in the second element will be
|
|
||||||
registered. A group name of None corresponds to the [DEFAULT] group in
|
|
||||||
config files.
|
|
||||||
This function is also discoverable via the 'oslo_messaging' entry point
|
|
||||||
under the 'oslo.config.opts' namespace.
|
|
||||||
The purpose of this is to allow tools like the Oslo sample config file
|
|
||||||
generator to discover the options exposed to users by this library.
|
|
||||||
|
|
||||||
:returns: a list of (group_name, opts) tuples
|
|
||||||
"""
|
|
||||||
|
|
||||||
return [(_option_group, copy.deepcopy(_options))]
|
|
||||||
|
|
||||||
|
|
||||||
def set_defaults(conf):
|
|
||||||
"""Set defaults for configuration variables.
|
|
||||||
|
|
||||||
Overrides default options values.
|
|
||||||
|
|
||||||
:param conf: Configuration object, managed by the caller.
|
|
||||||
:type conf: oslo.config.cfg.ConfigOpts
|
|
||||||
"""
|
|
||||||
conf.register_opts(_options, group=_option_group)
|
|
@ -1,189 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides Report classes
|
|
||||||
|
|
||||||
This module defines various classes representing reports and report sections.
|
|
||||||
All reports take the form of a report class containing various report
|
|
||||||
sections.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_reports.views.text import header as header_views
|
|
||||||
|
|
||||||
|
|
||||||
class BasicReport(object):
|
|
||||||
"""A Basic Report
|
|
||||||
|
|
||||||
A Basic Report consists of a collection of :class:`ReportSection`
|
|
||||||
objects, each of which contains a top-level model and generator.
|
|
||||||
It collects these sections into a cohesive report which may then
|
|
||||||
be serialized by calling :func:`run`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.sections = []
|
|
||||||
self._state = 0
|
|
||||||
|
|
||||||
def add_section(self, view, generator, index=None):
|
|
||||||
"""Add a section to the report
|
|
||||||
|
|
||||||
This method adds a section with the given view and
|
|
||||||
generator to the report. An index may be specified to
|
|
||||||
insert the section at a given location in the list;
|
|
||||||
If no index is specified, the section is appended to the
|
|
||||||
list. The view is called on the model which results from
|
|
||||||
the generator when the report is run. A generator is simply
|
|
||||||
a method or callable object which takes no arguments and
|
|
||||||
returns a :class:`oslo_reports.models.base.ReportModel`
|
|
||||||
or similar object.
|
|
||||||
|
|
||||||
:param view: the top-level view for the section
|
|
||||||
:param generator: the method or class which generates the model
|
|
||||||
:param index: the index at which to insert the section
|
|
||||||
(or None to append it)
|
|
||||||
:type index: int or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
if index is None:
|
|
||||||
self.sections.append(ReportSection(view, generator))
|
|
||||||
else:
|
|
||||||
self.sections.insert(index, ReportSection(view, generator))
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""Run the report
|
|
||||||
|
|
||||||
This method runs the report, having each section generate
|
|
||||||
its data and serialize itself before joining the sections
|
|
||||||
together. The BasicReport accomplishes the joining
|
|
||||||
by joining the serialized sections together with newlines.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
:returns: the serialized report
|
|
||||||
"""
|
|
||||||
|
|
||||||
return "\n".join(six.text_type(sect) for sect in self.sections)
|
|
||||||
|
|
||||||
|
|
||||||
class ReportSection(object):
|
|
||||||
"""A Report Section
|
|
||||||
|
|
||||||
A report section contains a generator and a top-level view. When something
|
|
||||||
attempts to serialize the section by calling str() or unicode() on it, the
|
|
||||||
section runs the generator and calls the view on the resulting model.
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
Class :class:`BasicReport`
|
|
||||||
:func:`BasicReport.add_section`
|
|
||||||
|
|
||||||
:param view: the top-level view for this section
|
|
||||||
:param generator: the generator for this section
|
|
||||||
(any callable object which takes no parameters and returns a data model)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, view, generator):
|
|
||||||
self.view = view
|
|
||||||
self.generator = generator
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.view(self.generator())
|
|
||||||
|
|
||||||
|
|
||||||
class ReportOfType(BasicReport):
|
|
||||||
"""A Report of a Certain Type
|
|
||||||
|
|
||||||
A ReportOfType has a predefined type associated with it.
|
|
||||||
This type is automatically propagated down to the each of
|
|
||||||
the sections upon serialization by wrapping the generator
|
|
||||||
for each section.
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
Class :class:`oslo_reports.models.with_default_view.ModelWithDefaultView` # noqa
|
|
||||||
(the entire class)
|
|
||||||
|
|
||||||
Class :class:`oslo_reports.models.base.ReportModel`
|
|
||||||
:func:`oslo_reports.models.base.ReportModel.set_current_view_type` # noqa
|
|
||||||
|
|
||||||
:param str tp: the type of the report
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, tp):
|
|
||||||
self.output_type = tp
|
|
||||||
super(ReportOfType, self).__init__()
|
|
||||||
|
|
||||||
def add_section(self, view, generator, index=None):
|
|
||||||
def with_type(gen):
|
|
||||||
def newgen():
|
|
||||||
res = gen()
|
|
||||||
try:
|
|
||||||
res.set_current_view_type(self.output_type)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return res
|
|
||||||
return newgen
|
|
||||||
|
|
||||||
super(ReportOfType, self).add_section(
|
|
||||||
view,
|
|
||||||
with_type(generator),
|
|
||||||
index
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TextReport(ReportOfType):
|
|
||||||
"""A Human-Readable Text Report
|
|
||||||
|
|
||||||
This class defines a report that is designed to be read by a human
|
|
||||||
being. It has nice section headers, and a formatted title.
|
|
||||||
|
|
||||||
:param str name: the title of the report
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
super(TextReport, self).__init__('text')
|
|
||||||
self.name = name
|
|
||||||
# add a title with a generator that creates an empty result model
|
|
||||||
self.add_section(name, lambda: ('|' * 72) + "\n\n")
|
|
||||||
|
|
||||||
def add_section(self, heading, generator, index=None):
|
|
||||||
"""Add a section to the report
|
|
||||||
|
|
||||||
This method adds a section with the given title, and
|
|
||||||
generator to the report. An index may be specified to
|
|
||||||
insert the section at a given location in the list;
|
|
||||||
If no index is specified, the section is appended to the
|
|
||||||
list. The view is called on the model which results from
|
|
||||||
the generator when the report is run. A generator is simply
|
|
||||||
a method or callable object which takes no arguments and
|
|
||||||
returns a :class:`oslo_reports.models.base.ReportModel`
|
|
||||||
or similar object.
|
|
||||||
|
|
||||||
The model is told to serialize as text (if possible) at serialization
|
|
||||||
time by wrapping the generator. The view model's attached view
|
|
||||||
(if any) is wrapped in a
|
|
||||||
:class:`oslo_reports.views.text.header.TitledView`
|
|
||||||
|
|
||||||
:param str heading: the title for the section
|
|
||||||
:param generator: the method or class which generates the model
|
|
||||||
:param index: the index at which to insert the section
|
|
||||||
(or None to append)
|
|
||||||
:type index: int or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
super(TextReport, self).add_section(header_views.TitledView(heading),
|
|
||||||
generator,
|
|
||||||
index)
|
|
@ -1,144 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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 collections as col
|
|
||||||
import re
|
|
||||||
|
|
||||||
from oslotest import base
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_reports.models import base as base_model
|
|
||||||
from oslo_reports import report
|
|
||||||
|
|
||||||
|
|
||||||
class BasicView(object):
|
|
||||||
def __call__(self, model):
|
|
||||||
res = ""
|
|
||||||
for k in sorted(model.keys()):
|
|
||||||
res += six.text_type(k) + ": " + six.text_type(model[k]) + ";"
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def basic_generator():
|
|
||||||
return base_model.ReportModel(data={'string': 'value', 'int': 1})
|
|
||||||
|
|
||||||
|
|
||||||
class TestBasicReport(base.BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestBasicReport, self).setUp()
|
|
||||||
|
|
||||||
self.report = report.BasicReport()
|
|
||||||
|
|
||||||
def test_add_section(self):
|
|
||||||
self.report.add_section(BasicView(), basic_generator)
|
|
||||||
self.assertEqual(len(self.report.sections), 1)
|
|
||||||
|
|
||||||
def test_append_section(self):
|
|
||||||
self.report.add_section(BasicView(), lambda: {'a': 1})
|
|
||||||
self.report.add_section(BasicView(), basic_generator)
|
|
||||||
|
|
||||||
self.assertEqual(len(self.report.sections), 2)
|
|
||||||
self.assertEqual(self.report.sections[1].generator, basic_generator)
|
|
||||||
|
|
||||||
def test_insert_section(self):
|
|
||||||
self.report.add_section(BasicView(), lambda: {'a': 1})
|
|
||||||
self.report.add_section(BasicView(), basic_generator, 0)
|
|
||||||
|
|
||||||
self.assertEqual(len(self.report.sections), 2)
|
|
||||||
self.assertEqual(self.report.sections[0].generator, basic_generator)
|
|
||||||
|
|
||||||
def test_basic_render(self):
|
|
||||||
self.report.add_section(BasicView(), basic_generator)
|
|
||||||
self.assertEqual(self.report.run(), "int: 1;string: value;")
|
|
||||||
|
|
||||||
|
|
||||||
class TestBaseModel(base.BaseTestCase):
|
|
||||||
def test_submodel_attached_view(self):
|
|
||||||
class TmpView(object):
|
|
||||||
def __call__(self, model):
|
|
||||||
return '{len: ' + six.text_type(len(model.c)) + '}'
|
|
||||||
|
|
||||||
def generate_model_with_submodel():
|
|
||||||
base_m = basic_generator()
|
|
||||||
tv = TmpView()
|
|
||||||
base_m['submodel'] = base_model.ReportModel(data={'c': [1, 2, 3]},
|
|
||||||
attached_view=tv)
|
|
||||||
return base_m
|
|
||||||
|
|
||||||
self.assertEqual(BasicView()(generate_model_with_submodel()),
|
|
||||||
'int: 1;string: value;submodel: {len: 3};')
|
|
||||||
|
|
||||||
def test_str_throws_error_with_no_attached_view(self):
|
|
||||||
model = base_model.ReportModel(data={'c': [1, 2, 3]})
|
|
||||||
self.assertRaisesRegex(Exception,
|
|
||||||
'Cannot stringify model: no attached view',
|
|
||||||
six.text_type, model)
|
|
||||||
|
|
||||||
def test_str_returns_string_with_attached_view(self):
|
|
||||||
model = base_model.ReportModel(data={'a': 1, 'b': 2},
|
|
||||||
attached_view=BasicView())
|
|
||||||
|
|
||||||
self.assertEqual(six.text_type(model), 'a: 1;b: 2;')
|
|
||||||
|
|
||||||
def test_model_repr(self):
|
|
||||||
model1 = base_model.ReportModel(data={'a': 1, 'b': 2},
|
|
||||||
attached_view=BasicView())
|
|
||||||
|
|
||||||
model2 = base_model.ReportModel(data={'a': 1, 'b': 2})
|
|
||||||
|
|
||||||
base_re = r"<Model [^ ]+\.[^ ]+ \{.+\} with "
|
|
||||||
with_view_re = base_re + r"view [^ ]+\.[^ ]+>"
|
|
||||||
without_view_re = base_re + r"no view>"
|
|
||||||
|
|
||||||
self.assertTrue(re.match(with_view_re, repr(model1)))
|
|
||||||
self.assertTrue(re.match(without_view_re, repr(model2)))
|
|
||||||
|
|
||||||
def test_getattr(self):
|
|
||||||
model = base_model.ReportModel(data={'a': 1})
|
|
||||||
|
|
||||||
self.assertEqual(model.a, 1)
|
|
||||||
|
|
||||||
self.assertRaises(AttributeError, getattr, model, 'b')
|
|
||||||
|
|
||||||
def test_data_as_sequence_is_handled_properly(self):
|
|
||||||
model = base_model.ReportModel(data=['a', 'b'])
|
|
||||||
model.attached_view = BasicView()
|
|
||||||
|
|
||||||
# if we don't handle lists properly, we'll get a TypeError here
|
|
||||||
self.assertEqual('0: a;1: b;', six.text_type(model))
|
|
||||||
|
|
||||||
def test_immutable_mappings_produce_mutable_models(self):
|
|
||||||
class SomeImmutableMapping(col.Mapping):
|
|
||||||
def __init__(self):
|
|
||||||
self.data = {'a': 2, 'b': 4, 'c': 8}
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self.data[key]
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.data)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self.data)
|
|
||||||
|
|
||||||
mp = SomeImmutableMapping()
|
|
||||||
model = base_model.ReportModel(data=mp)
|
|
||||||
model.attached_view = BasicView()
|
|
||||||
|
|
||||||
self.assertEqual('a: 2;b: 4;c: 8;', six.text_type(model))
|
|
||||||
|
|
||||||
model['d'] = 16
|
|
||||||
|
|
||||||
self.assertEqual('a: 2;b: 4;c: 8;d: 16;', six.text_type(model))
|
|
||||||
self.assertEqual({'a': 2, 'b': 4, 'c': 8}, mp.data)
|
|
@ -1,249 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
|
|
||||||
# needed to get greenthreads
|
|
||||||
import fixtures
|
|
||||||
import greenlet
|
|
||||||
import mock
|
|
||||||
from oslotest import base
|
|
||||||
import six
|
|
||||||
|
|
||||||
import oslo_config
|
|
||||||
from oslo_config import fixture
|
|
||||||
from oslo_reports import guru_meditation_report as gmr
|
|
||||||
from oslo_reports.models import with_default_views as mwdv
|
|
||||||
from oslo_reports import opts
|
|
||||||
|
|
||||||
|
|
||||||
CONF = oslo_config.cfg.CONF
|
|
||||||
opts.set_defaults(CONF)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeVersionObj(object):
|
|
||||||
def vendor_string(self):
|
|
||||||
return 'Cheese Shoppe'
|
|
||||||
|
|
||||||
def product_string(self):
|
|
||||||
return 'Sharp Cheddar'
|
|
||||||
|
|
||||||
def version_string_with_package(self):
|
|
||||||
return '1.0.0'
|
|
||||||
|
|
||||||
|
|
||||||
def skip_body_lines(start_line, report_lines):
|
|
||||||
curr_line = start_line
|
|
||||||
while (len(report_lines[curr_line]) == 0
|
|
||||||
or report_lines[curr_line][0] != '='):
|
|
||||||
curr_line += 1
|
|
||||||
|
|
||||||
return curr_line
|
|
||||||
|
|
||||||
|
|
||||||
class GmrConfigFixture(fixture.Config):
|
|
||||||
def setUp(self):
|
|
||||||
super(GmrConfigFixture, self).setUp()
|
|
||||||
|
|
||||||
self.conf.set_override(
|
|
||||||
'file_event_handler',
|
|
||||||
'/tmp/file',
|
|
||||||
group='oslo_reports')
|
|
||||||
self.conf.set_override(
|
|
||||||
'file_event_handler_interval',
|
|
||||||
10,
|
|
||||||
group='oslo_reports')
|
|
||||||
self.conf.set_override(
|
|
||||||
'log_dir',
|
|
||||||
'/var/fake_log',
|
|
||||||
group='oslo_reports')
|
|
||||||
|
|
||||||
|
|
||||||
class TestGuruMeditationReport(base.BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestGuruMeditationReport, self).setUp()
|
|
||||||
|
|
||||||
self.curr_g = greenlet.getcurrent()
|
|
||||||
|
|
||||||
self.report = gmr.TextGuruMeditation(FakeVersionObj())
|
|
||||||
|
|
||||||
self.old_stderr = None
|
|
||||||
|
|
||||||
self.CONF = self.useFixture(GmrConfigFixture(CONF)).conf
|
|
||||||
|
|
||||||
def test_basic_report(self):
|
|
||||||
report_lines = self.report.run().split('\n')
|
|
||||||
|
|
||||||
target_str_header = ['========================================================================', # noqa
|
|
||||||
'==== Guru Meditation ====', # noqa
|
|
||||||
'========================================================================', # noqa
|
|
||||||
'||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||', # noqa
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'========================================================================', # noqa
|
|
||||||
'==== Package ====', # noqa
|
|
||||||
'========================================================================', # noqa
|
|
||||||
'product = Sharp Cheddar',
|
|
||||||
'vendor = Cheese Shoppe',
|
|
||||||
'version = 1.0.0',
|
|
||||||
'========================================================================', # noqa
|
|
||||||
'==== Threads ====', # noqa
|
|
||||||
'========================================================================'] # noqa
|
|
||||||
|
|
||||||
# first the header and version info...
|
|
||||||
self.assertEqual(target_str_header,
|
|
||||||
report_lines[0:len(target_str_header)])
|
|
||||||
|
|
||||||
# followed by at least one thread...
|
|
||||||
# NOTE(zqfan): add an optional '-' because sys._current_frames()
|
|
||||||
# may return a negative thread id on 32 bit operating system.
|
|
||||||
self.assertTrue(re.match(r'------(\s+)Thread #-?\d+\1\s?------',
|
|
||||||
report_lines[len(target_str_header)]))
|
|
||||||
self.assertEqual('', report_lines[len(target_str_header) + 1])
|
|
||||||
|
|
||||||
# followed by more thread stuff stuff...
|
|
||||||
curr_line = skip_body_lines(len(target_str_header) + 2, report_lines)
|
|
||||||
|
|
||||||
# followed by at least one green thread
|
|
||||||
target_str_gt = ['========================================================================', # noqa
|
|
||||||
'==== Green Threads ====', # noqa
|
|
||||||
'========================================================================', # noqa
|
|
||||||
'------ Green Thread ------', # noqa
|
|
||||||
'']
|
|
||||||
end_bound = curr_line + len(target_str_gt)
|
|
||||||
self.assertEqual(target_str_gt,
|
|
||||||
report_lines[curr_line:end_bound])
|
|
||||||
|
|
||||||
# followed by some more green thread stuff
|
|
||||||
curr_line = skip_body_lines(curr_line + len(target_str_gt),
|
|
||||||
report_lines)
|
|
||||||
|
|
||||||
# followed by the processes header
|
|
||||||
target_str_p_head = ['========================================================================', # noqa
|
|
||||||
'==== Processes ====', # noqa
|
|
||||||
'========================================================================'] # noqa
|
|
||||||
end_bound = curr_line + len(target_str_p_head)
|
|
||||||
self.assertEqual(target_str_p_head,
|
|
||||||
report_lines[curr_line:end_bound])
|
|
||||||
|
|
||||||
curr_line += len(target_str_p_head)
|
|
||||||
|
|
||||||
# followed by at least one process
|
|
||||||
self.assertTrue(re.match("Process \d+ \(under \d+\)",
|
|
||||||
report_lines[curr_line]))
|
|
||||||
|
|
||||||
# followed by some more process stuff
|
|
||||||
curr_line = skip_body_lines(curr_line + 1, report_lines)
|
|
||||||
|
|
||||||
# followed finally by the configuration
|
|
||||||
target_str_config = ['========================================================================', # noqa
|
|
||||||
'==== Configuration ====', # noqa
|
|
||||||
'========================================================================', # noqa
|
|
||||||
'']
|
|
||||||
end_bound = curr_line + len(target_str_config)
|
|
||||||
self.assertEqual(target_str_config,
|
|
||||||
report_lines[curr_line:end_bound])
|
|
||||||
|
|
||||||
def test_reg_persistent_section(self):
|
|
||||||
def fake_gen():
|
|
||||||
fake_data = {'cheddar': ['sharp', 'mild'],
|
|
||||||
'swiss': ['with holes', 'with lots of holes'],
|
|
||||||
'american': ['orange', 'yellow']}
|
|
||||||
|
|
||||||
return mwdv.ModelWithDefaultViews(data=fake_data)
|
|
||||||
|
|
||||||
gmr.TextGuruMeditation.register_section('Cheese Types', fake_gen)
|
|
||||||
|
|
||||||
report_lines = self.report.run()
|
|
||||||
target_lst = ['========================================================================', # noqa
|
|
||||||
'==== Cheese Types ====', # noqa
|
|
||||||
'========================================================================', # noqa
|
|
||||||
'american = ',
|
|
||||||
' orange',
|
|
||||||
' yellow',
|
|
||||||
'cheddar = ',
|
|
||||||
' mild',
|
|
||||||
' sharp',
|
|
||||||
'swiss = ',
|
|
||||||
' with holes',
|
|
||||||
' with lots of holes']
|
|
||||||
target_str = '\n'.join(target_lst)
|
|
||||||
self.assertIn(target_str, report_lines)
|
|
||||||
|
|
||||||
def test_register_autorun(self):
|
|
||||||
gmr.TextGuruMeditation.setup_autorun(FakeVersionObj())
|
|
||||||
self.old_stderr = sys.stderr
|
|
||||||
sys.stderr = six.StringIO()
|
|
||||||
|
|
||||||
os.kill(os.getpid(), signal.SIGUSR2)
|
|
||||||
self.assertIn('Guru Meditation', sys.stderr.getvalue())
|
|
||||||
|
|
||||||
@mock.patch.object(gmr.TextGuruMeditation, '_setup_file_watcher')
|
|
||||||
def test_register_autorun_without_signals(self, mock_setup_fh):
|
|
||||||
version = FakeVersionObj()
|
|
||||||
gmr.TextGuruMeditation.setup_autorun(version, conf=self.CONF)
|
|
||||||
mock_setup_fh.assert_called_once_with(
|
|
||||||
'/tmp/file', 10, version, None, '/var/fake_log')
|
|
||||||
|
|
||||||
@mock.patch('os.stat')
|
|
||||||
@mock.patch('time.sleep')
|
|
||||||
@mock.patch.object(threading.Thread, 'start')
|
|
||||||
def test_setup_file_watcher(self, mock_thread, mock_sleep, mock_stat):
|
|
||||||
version = FakeVersionObj()
|
|
||||||
mock_stat.return_value.st_mtime = 3
|
|
||||||
|
|
||||||
gmr.TextGuruMeditation._setup_file_watcher(
|
|
||||||
self.CONF.oslo_reports.file_event_handler,
|
|
||||||
self.CONF.oslo_reports.file_event_handler_interval,
|
|
||||||
version, None, self.CONF.oslo_reports.log_dir)
|
|
||||||
|
|
||||||
mock_stat.assert_called_once_with('/tmp/file')
|
|
||||||
self.assertEqual(1, mock_thread.called)
|
|
||||||
|
|
||||||
@mock.patch('oslo_utils.timeutils.utcnow',
|
|
||||||
return_value=datetime.datetime(2014, 1, 1, 12, 0, 0))
|
|
||||||
def test_register_autorun_log_dir(self, mock_strtime):
|
|
||||||
log_dir = self.useFixture(fixtures.TempDir()).path
|
|
||||||
gmr.TextGuruMeditation.setup_autorun(
|
|
||||||
FakeVersionObj(), "fake-service", log_dir)
|
|
||||||
|
|
||||||
os.kill(os.getpid(), signal.SIGUSR2)
|
|
||||||
with open(os.path.join(
|
|
||||||
log_dir, "fake-service_gurumeditation_20140101120000")) as df:
|
|
||||||
self.assertIn('Guru Meditation', df.read())
|
|
||||||
|
|
||||||
@mock.patch.object(gmr.TextGuruMeditation, 'run')
|
|
||||||
def test_fail_prints_traceback(self, run_mock):
|
|
||||||
class RunFail(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
run_mock.side_effect = RunFail()
|
|
||||||
gmr.TextGuruMeditation.setup_autorun(FakeVersionObj())
|
|
||||||
self.old_stderr = sys.stderr
|
|
||||||
sys.stderr = six.StringIO()
|
|
||||||
|
|
||||||
os.kill(os.getpid(), signal.SIGUSR2)
|
|
||||||
self.assertIn('RunFail', sys.stderr.getvalue())
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
super(TestGuruMeditationReport, self).tearDown()
|
|
||||||
if self.old_stderr is not None:
|
|
||||||
sys.stderr = self.old_stderr
|
|
@ -1,141 +0,0 @@
|
|||||||
# Copyright 2011 OpenStack Foundation.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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 threading
|
|
||||||
|
|
||||||
import greenlet
|
|
||||||
import mock
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslotest import base
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_reports.generators import conf as os_cgen
|
|
||||||
from oslo_reports.generators import threading as os_tgen
|
|
||||||
from oslo_reports.generators import version as os_pgen
|
|
||||||
from oslo_reports.models import threading as os_tmod
|
|
||||||
|
|
||||||
|
|
||||||
class TestOpenstackGenerators(base.BaseTestCase):
|
|
||||||
def test_thread_generator(self):
|
|
||||||
model = os_tgen.ThreadReportGenerator()()
|
|
||||||
# self.assertGreaterEqual(len(model.keys()), 1)
|
|
||||||
self.assertTrue(len(model.keys()) >= 1)
|
|
||||||
was_ok = False
|
|
||||||
for val in model.values():
|
|
||||||
self.assertIsInstance(val, os_tmod.ThreadModel)
|
|
||||||
self.assertIsNotNone(val.stack_trace)
|
|
||||||
if val.thread_id == threading.current_thread().ident:
|
|
||||||
was_ok = True
|
|
||||||
break
|
|
||||||
|
|
||||||
self.assertTrue(was_ok)
|
|
||||||
|
|
||||||
model.set_current_view_type('text')
|
|
||||||
self.assertIsNotNone(six.text_type(model))
|
|
||||||
|
|
||||||
def test_thread_generator_tb(self):
|
|
||||||
class FakeModel(object):
|
|
||||||
def __init__(self, thread_id, tb):
|
|
||||||
self.traceback = tb
|
|
||||||
|
|
||||||
with mock.patch('oslo_reports.models'
|
|
||||||
'.threading.ThreadModel', FakeModel):
|
|
||||||
model = os_tgen.ThreadReportGenerator("fake traceback")()
|
|
||||||
curr_thread = model.get(threading.current_thread().ident, None)
|
|
||||||
self.assertIsNotNone(curr_thread, None)
|
|
||||||
self.assertEqual("fake traceback", curr_thread.traceback)
|
|
||||||
|
|
||||||
def test_green_thread_generator(self):
|
|
||||||
curr_g = greenlet.getcurrent()
|
|
||||||
|
|
||||||
model = os_tgen.GreenThreadReportGenerator()()
|
|
||||||
|
|
||||||
# self.assertGreaterEqual(len(model.keys()), 1)
|
|
||||||
self.assertTrue(len(model.keys()) >= 1)
|
|
||||||
|
|
||||||
was_ok = False
|
|
||||||
for tm in model.values():
|
|
||||||
if tm.stack_trace == os_tmod.StackTraceModel(curr_g.gr_frame):
|
|
||||||
was_ok = True
|
|
||||||
break
|
|
||||||
self.assertTrue(was_ok)
|
|
||||||
|
|
||||||
model.set_current_view_type('text')
|
|
||||||
self.assertIsNotNone(six.text_type(model))
|
|
||||||
|
|
||||||
def test_config_model(self):
|
|
||||||
conf = cfg.ConfigOpts()
|
|
||||||
conf.register_opt(cfg.StrOpt('crackers', default='triscuit'))
|
|
||||||
conf.register_opt(cfg.StrOpt('secrets', secret=True,
|
|
||||||
default='should not show'))
|
|
||||||
conf.register_group(cfg.OptGroup('cheese', title='Cheese Info'))
|
|
||||||
conf.register_opt(cfg.IntOpt('sharpness', default=1),
|
|
||||||
group='cheese')
|
|
||||||
conf.register_opt(cfg.StrOpt('name', default='cheddar'),
|
|
||||||
group='cheese')
|
|
||||||
conf.register_opt(cfg.BoolOpt('from_cow', default=True),
|
|
||||||
group='cheese')
|
|
||||||
conf.register_opt(cfg.StrOpt('group_secrets', secret=True,
|
|
||||||
default='should not show'),
|
|
||||||
group='cheese')
|
|
||||||
|
|
||||||
model = os_cgen.ConfigReportGenerator(conf)()
|
|
||||||
model.set_current_view_type('text')
|
|
||||||
|
|
||||||
target_str = ('\ncheese: \n'
|
|
||||||
' from_cow = True\n'
|
|
||||||
' group_secrets = ***\n'
|
|
||||||
' name = cheddar\n'
|
|
||||||
' sharpness = 1\n'
|
|
||||||
'\n'
|
|
||||||
'default: \n'
|
|
||||||
' crackers = triscuit\n'
|
|
||||||
' secrets = ***')
|
|
||||||
self.assertEqual(target_str, six.text_type(model))
|
|
||||||
|
|
||||||
def test_package_report_generator(self):
|
|
||||||
class VersionObj(object):
|
|
||||||
def vendor_string(self):
|
|
||||||
return 'Cheese Shoppe'
|
|
||||||
|
|
||||||
def product_string(self):
|
|
||||||
return 'Sharp Cheddar'
|
|
||||||
|
|
||||||
def version_string_with_package(self):
|
|
||||||
return '1.0.0'
|
|
||||||
|
|
||||||
model = os_pgen.PackageReportGenerator(VersionObj())()
|
|
||||||
model.set_current_view_type('text')
|
|
||||||
|
|
||||||
target_str = ('product = Sharp Cheddar\n'
|
|
||||||
'vendor = Cheese Shoppe\n'
|
|
||||||
'version = 1.0.0')
|
|
||||||
self.assertEqual(target_str, six.text_type(model))
|
|
||||||
|
|
||||||
def test_package_report_generator_without_vendor_string(self):
|
|
||||||
class VersionObj(object):
|
|
||||||
def product_string(self):
|
|
||||||
return 'Sharp Cheddar'
|
|
||||||
|
|
||||||
def version_string_with_package(self):
|
|
||||||
return '1.0.0'
|
|
||||||
|
|
||||||
model = os_pgen.PackageReportGenerator(VersionObj())()
|
|
||||||
model.set_current_view_type('text')
|
|
||||||
|
|
||||||
target_str = ('product = Sharp Cheddar\n'
|
|
||||||
'vendor = None\n'
|
|
||||||
'version = 1.0.0')
|
|
||||||
self.assertEqual(target_str, six.text_type(model))
|
|
@ -1,426 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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 copy
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslotest import base
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_reports.models import base as base_model
|
|
||||||
from oslo_reports.models import with_default_views as mwdv
|
|
||||||
from oslo_reports import report
|
|
||||||
from oslo_reports.views import jinja_view as jv
|
|
||||||
from oslo_reports.views.json import generic as json_generic
|
|
||||||
from oslo_reports.views.text import generic as text_generic
|
|
||||||
|
|
||||||
|
|
||||||
def mwdv_generator():
|
|
||||||
return mwdv.ModelWithDefaultViews(data={'string': 'value', 'int': 1})
|
|
||||||
|
|
||||||
|
|
||||||
class TestModelReportType(base.BaseTestCase):
|
|
||||||
def test_model_with_default_views(self):
|
|
||||||
model = mwdv_generator()
|
|
||||||
|
|
||||||
model.set_current_view_type('text')
|
|
||||||
self.assertEqual('int = 1\nstring = value', six.text_type(model))
|
|
||||||
|
|
||||||
model.set_current_view_type('json')
|
|
||||||
self.assertEqual('{"int": 1, "string": "value"}', six.text_type(model))
|
|
||||||
|
|
||||||
model.set_current_view_type('xml')
|
|
||||||
|
|
||||||
self.assertEqual('<model><int>1</int><string>value</string></model>',
|
|
||||||
six.text_type(model))
|
|
||||||
|
|
||||||
def test_recursive_type_propagation_with_nested_models(self):
|
|
||||||
model = mwdv_generator()
|
|
||||||
model['submodel'] = mwdv_generator()
|
|
||||||
|
|
||||||
model.set_current_view_type('json')
|
|
||||||
|
|
||||||
self.assertEqual(model.submodel.views['json'],
|
|
||||||
model.submodel.attached_view)
|
|
||||||
|
|
||||||
def test_recursive_type_propagation_with_nested_dicts(self):
|
|
||||||
nested_model = mwdv.ModelWithDefaultViews(json_view='abc')
|
|
||||||
data = {'a': 1, 'b': {'c': nested_model}}
|
|
||||||
top_model = base_model.ReportModel(data=data)
|
|
||||||
|
|
||||||
top_model.set_current_view_type('json')
|
|
||||||
self.assertEqual(nested_model.attached_view,
|
|
||||||
nested_model.views['json'])
|
|
||||||
|
|
||||||
def test_recursive_type_propagation_with_nested_lists(self):
|
|
||||||
nested_model = mwdv_generator()
|
|
||||||
data = {'a': 1, 'b': [nested_model]}
|
|
||||||
top_model = base_model.ReportModel(data=data)
|
|
||||||
|
|
||||||
top_model.set_current_view_type('json')
|
|
||||||
self.assertEqual(nested_model.attached_view,
|
|
||||||
nested_model.views['json'])
|
|
||||||
|
|
||||||
def test_recursive_type_propogation_on_recursive_structures(self):
|
|
||||||
nested_model = mwdv_generator()
|
|
||||||
data = {'a': 1, 'b': [nested_model]}
|
|
||||||
nested_model['c'] = data
|
|
||||||
top_model = base_model.ReportModel(data=data)
|
|
||||||
|
|
||||||
top_model.set_current_view_type('json')
|
|
||||||
self.assertEqual(nested_model.attached_view,
|
|
||||||
nested_model.views['json'])
|
|
||||||
del nested_model['c']
|
|
||||||
|
|
||||||
def test_report_of_type(self):
|
|
||||||
rep = report.ReportOfType('json')
|
|
||||||
rep.add_section(lambda x: six.text_type(x), mwdv_generator)
|
|
||||||
|
|
||||||
self.assertEqual('{"int": 1, "string": "value"}', rep.run())
|
|
||||||
|
|
||||||
# NOTE: this also tests views.text.header
|
|
||||||
def test_text_report(self):
|
|
||||||
rep = report.TextReport('Test Report')
|
|
||||||
rep.add_section('An Important Section', mwdv_generator)
|
|
||||||
rep.add_section('Another Important Section', mwdv_generator)
|
|
||||||
|
|
||||||
target_str = ('========================================================================\n' # noqa
|
|
||||||
'==== Test Report ====\n' # noqa
|
|
||||||
'========================================================================\n' # noqa
|
|
||||||
'||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||\n' # noqa
|
|
||||||
'\n' # noqa
|
|
||||||
'\n' # noqa
|
|
||||||
'========================================================================\n' # noqa
|
|
||||||
'==== An Important Section ====\n' # noqa
|
|
||||||
'========================================================================\n' # noqa
|
|
||||||
'int = 1\n' # noqa
|
|
||||||
'string = value\n' # noqa
|
|
||||||
'========================================================================\n' # noqa
|
|
||||||
'==== Another Important Section ====\n' # noqa
|
|
||||||
'========================================================================\n' # noqa
|
|
||||||
'int = 1\n' # noqa
|
|
||||||
'string = value') # noqa
|
|
||||||
self.assertEqual(target_str, rep.run())
|
|
||||||
|
|
||||||
def test_to_type(self):
|
|
||||||
model = mwdv_generator()
|
|
||||||
|
|
||||||
self.assertEqual('<model><int>1</int><string>value</string></model>',
|
|
||||||
model.to_xml())
|
|
||||||
|
|
||||||
|
|
||||||
class TestGenericXMLView(base.BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestGenericXMLView, self).setUp()
|
|
||||||
|
|
||||||
self.model = mwdv_generator()
|
|
||||||
self.model.set_current_view_type('xml')
|
|
||||||
|
|
||||||
def test_dict_serialization(self):
|
|
||||||
self.model['dt'] = {'a': 1, 'b': 2}
|
|
||||||
|
|
||||||
target_str = ('<model>'
|
|
||||||
'<dt><a>1</a><b>2</b></dt>'
|
|
||||||
'<int>1</int>'
|
|
||||||
'<string>value</string></model>')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_list_serialization(self):
|
|
||||||
self.model['lt'] = ['a', 'b']
|
|
||||||
|
|
||||||
target_str = ('<model>'
|
|
||||||
'<int>1</int>'
|
|
||||||
'<lt><item>a</item><item>b</item></lt>'
|
|
||||||
'<string>value</string></model>')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_list_in_dict_serialization(self):
|
|
||||||
self.model['dt'] = {'a': 1, 'b': [2, 3]}
|
|
||||||
|
|
||||||
target_str = ('<model>'
|
|
||||||
'<dt><a>1</a>'
|
|
||||||
'<b><item>2</item><item>3</item></b></dt>'
|
|
||||||
'<int>1</int>'
|
|
||||||
'<string>value</string></model>')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_dict_in_list_serialization(self):
|
|
||||||
self.model['lt'] = [1, {'b': 2, 'c': 3}]
|
|
||||||
|
|
||||||
target_str = ('<model>'
|
|
||||||
'<int>1</int>'
|
|
||||||
'<lt><item>1</item>'
|
|
||||||
'<item><b>2</b><c>3</c></item></lt>'
|
|
||||||
'<string>value</string></model>')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_submodel_serialization(self):
|
|
||||||
sm = mwdv_generator()
|
|
||||||
sm.set_current_view_type('xml')
|
|
||||||
|
|
||||||
self.model['submodel'] = sm
|
|
||||||
|
|
||||||
target_str = ('<model>'
|
|
||||||
'<int>1</int>'
|
|
||||||
'<string>value</string>'
|
|
||||||
'<submodel>'
|
|
||||||
'<model><int>1</int><string>value</string></model>'
|
|
||||||
'</submodel>'
|
|
||||||
'</model>')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_wrapper_name(self):
|
|
||||||
self.model.attached_view.wrapper_name = 'cheese'
|
|
||||||
|
|
||||||
target_str = ('<cheese>'
|
|
||||||
'<int>1</int>'
|
|
||||||
'<string>value</string>'
|
|
||||||
'</cheese>')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
|
|
||||||
class TestGenericJSONViews(base.BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestGenericJSONViews, self).setUp()
|
|
||||||
|
|
||||||
self.model = mwdv_generator()
|
|
||||||
self.model.set_current_view_type('json')
|
|
||||||
|
|
||||||
def test_basic_kv_view(self):
|
|
||||||
attached_view = json_generic.BasicKeyValueView()
|
|
||||||
self.model = base_model.ReportModel(data={'string': 'value', 'int': 1},
|
|
||||||
attached_view=attached_view)
|
|
||||||
|
|
||||||
self.assertEqual('{"int": 1, "string": "value"}',
|
|
||||||
six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_dict_serialization(self):
|
|
||||||
self.model['dt'] = {'a': 1, 'b': 2}
|
|
||||||
|
|
||||||
target_str = ('{'
|
|
||||||
'"dt": {"a": 1, "b": 2}, '
|
|
||||||
'"int": 1, '
|
|
||||||
'"string": "value"'
|
|
||||||
'}')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_list_serialization(self):
|
|
||||||
self.model['lt'] = ['a', 'b']
|
|
||||||
|
|
||||||
target_str = ('{'
|
|
||||||
'"int": 1, '
|
|
||||||
'"lt": ["a", "b"], '
|
|
||||||
'"string": "value"'
|
|
||||||
'}')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_list_in_dict_serialization(self):
|
|
||||||
self.model['dt'] = {'a': 1, 'b': [2, 3]}
|
|
||||||
|
|
||||||
target_str = ('{'
|
|
||||||
'"dt": {"a": 1, "b": [2, 3]}, '
|
|
||||||
'"int": 1, '
|
|
||||||
'"string": "value"'
|
|
||||||
'}')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_dict_in_list_serialization(self):
|
|
||||||
self.model['lt'] = [1, {'b': 2, 'c': 3}]
|
|
||||||
|
|
||||||
target_str = ('{'
|
|
||||||
'"int": 1, '
|
|
||||||
'"lt": [1, {"b": 2, "c": 3}], '
|
|
||||||
'"string": "value"'
|
|
||||||
'}')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_submodel_serialization(self):
|
|
||||||
sm = mwdv_generator()
|
|
||||||
sm.set_current_view_type('json')
|
|
||||||
|
|
||||||
self.model['submodel'] = sm
|
|
||||||
|
|
||||||
target_str = ('{'
|
|
||||||
'"int": 1, '
|
|
||||||
'"string": "value", '
|
|
||||||
'"submodel": {"int": 1, "string": "value"}'
|
|
||||||
'}')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
|
|
||||||
class TestGenericTextViews(base.BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestGenericTextViews, self).setUp()
|
|
||||||
|
|
||||||
self.model = mwdv_generator()
|
|
||||||
self.model.set_current_view_type('text')
|
|
||||||
|
|
||||||
def test_multi_view(self):
|
|
||||||
attached_view = text_generic.MultiView()
|
|
||||||
self.model = base_model.ReportModel(data={},
|
|
||||||
attached_view=attached_view)
|
|
||||||
|
|
||||||
self.model['1'] = mwdv_generator()
|
|
||||||
self.model['2'] = mwdv_generator()
|
|
||||||
self.model['2']['int'] = 2
|
|
||||||
self.model.set_current_view_type('text')
|
|
||||||
|
|
||||||
target_str = ('int = 1\n'
|
|
||||||
'string = value\n'
|
|
||||||
'int = 2\n'
|
|
||||||
'string = value')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_basic_kv_view(self):
|
|
||||||
attached_view = text_generic.BasicKeyValueView()
|
|
||||||
self.model = base_model.ReportModel(data={'string': 'value', 'int': 1},
|
|
||||||
attached_view=attached_view)
|
|
||||||
|
|
||||||
self.assertEqual('int = 1\nstring = value\n',
|
|
||||||
six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_table_view(self):
|
|
||||||
column_names = ['Column A', 'Column B']
|
|
||||||
column_values = ['a', 'b']
|
|
||||||
attached_view = text_generic.TableView(column_names, column_values,
|
|
||||||
'table')
|
|
||||||
self.model = base_model.ReportModel(data={},
|
|
||||||
attached_view=attached_view)
|
|
||||||
|
|
||||||
self.model['table'] = [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}]
|
|
||||||
|
|
||||||
target_str = (' Column A | Column B \n' # noqa
|
|
||||||
'------------------------------------------------------------------------\n' # noqa
|
|
||||||
' 1 | 2 \n' # noqa
|
|
||||||
' 3 | 4 \n') # noqa
|
|
||||||
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_dict_serialization(self):
|
|
||||||
self.model['dt'] = {'a': 1, 'b': 2}
|
|
||||||
|
|
||||||
target_str = ('dt = \n'
|
|
||||||
' a = 1\n'
|
|
||||||
' b = 2\n'
|
|
||||||
'int = 1\n'
|
|
||||||
'string = value')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_list_serialization(self):
|
|
||||||
self.model['lt'] = ['a', 'b']
|
|
||||||
|
|
||||||
target_str = ('int = 1\n'
|
|
||||||
'lt = \n'
|
|
||||||
' a\n'
|
|
||||||
' b\n'
|
|
||||||
'string = value')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_list_in_dict_serialization(self):
|
|
||||||
self.model['dt'] = {'a': 1, 'b': [2, 3]}
|
|
||||||
|
|
||||||
target_str = ('dt = \n'
|
|
||||||
' a = 1\n'
|
|
||||||
' b = \n'
|
|
||||||
' 2\n'
|
|
||||||
' 3\n'
|
|
||||||
'int = 1\n'
|
|
||||||
'string = value')
|
|
||||||
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_dict_in_list_serialization(self):
|
|
||||||
self.model['lt'] = [1, {'b': 2, 'c': 3}]
|
|
||||||
|
|
||||||
target_str = ('int = 1\n'
|
|
||||||
'lt = \n'
|
|
||||||
' 1\n'
|
|
||||||
' [dict]\n'
|
|
||||||
' b = 2\n'
|
|
||||||
' c = 3\n'
|
|
||||||
'string = value')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_submodel_serialization(self):
|
|
||||||
sm = mwdv_generator()
|
|
||||||
sm.set_current_view_type('text')
|
|
||||||
|
|
||||||
self.model['submodel'] = sm
|
|
||||||
|
|
||||||
target_str = ('int = 1\n'
|
|
||||||
'string = value\n'
|
|
||||||
'submodel = \n'
|
|
||||||
' int = 1\n'
|
|
||||||
' string = value')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_custom_indent_string(self):
|
|
||||||
view = text_generic.KeyValueView(indent_str='~~')
|
|
||||||
|
|
||||||
self.model['lt'] = ['a', 'b']
|
|
||||||
self.model.attached_view = view
|
|
||||||
|
|
||||||
target_str = ('int = 1\n'
|
|
||||||
'lt = \n'
|
|
||||||
'~~a\n'
|
|
||||||
'~~b\n'
|
|
||||||
'string = value')
|
|
||||||
self.assertEqual(target_str, six.text_type(self.model))
|
|
||||||
|
|
||||||
|
|
||||||
def get_open_mocks(rv):
|
|
||||||
file_mock = mock.MagicMock(name='file_obj')
|
|
||||||
file_mock.read.return_value = rv
|
|
||||||
open_mock = mock.MagicMock(name='open')
|
|
||||||
open_mock().__enter__.return_value = file_mock
|
|
||||||
return (open_mock, file_mock)
|
|
||||||
|
|
||||||
|
|
||||||
class TestJinjaView(base.BaseTestCase):
|
|
||||||
|
|
||||||
TEMPL_STR = "int is {{ int }}, string is {{ string }}"
|
|
||||||
MM_OPEN, MM_FILE = get_open_mocks(TEMPL_STR)
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestJinjaView, self).setUp()
|
|
||||||
self.model = base_model.ReportModel(data={'int': 1, 'string': 'value'})
|
|
||||||
|
|
||||||
@mock.mock_open(MM_OPEN)
|
|
||||||
def test_load_from_file(self):
|
|
||||||
self.model.attached_view = jv.JinjaView(path='a/b/c/d.jinja.txt')
|
|
||||||
|
|
||||||
self.assertEqual('int is 1, string is value',
|
|
||||||
six.text_type(self.model))
|
|
||||||
self.MM_FILE.assert_called_with_once('a/b/c/d.jinja.txt')
|
|
||||||
|
|
||||||
def test_direct_pass(self):
|
|
||||||
self.model.attached_view = jv.JinjaView(text=self.TEMPL_STR)
|
|
||||||
|
|
||||||
self.assertEqual('int is 1, string is value',
|
|
||||||
six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_load_from_class(self):
|
|
||||||
class TmpJinjaView(jv.JinjaView):
|
|
||||||
VIEW_TEXT = TestJinjaView.TEMPL_STR
|
|
||||||
|
|
||||||
self.model.attached_view = TmpJinjaView()
|
|
||||||
|
|
||||||
self.assertEqual('int is 1, string is value',
|
|
||||||
six.text_type(self.model))
|
|
||||||
|
|
||||||
def test_is_deepcopiable(self):
|
|
||||||
view_orig = jv.JinjaView(text=self.TEMPL_STR)
|
|
||||||
view_cpy = copy.deepcopy(view_orig)
|
|
||||||
|
|
||||||
self.assertIsNot(view_orig, view_cpy)
|
|
@ -1,22 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides predefined views
|
|
||||||
|
|
||||||
This module provides a collection of predefined views
|
|
||||||
for use in reports. It is separated by type (xml, json, or text).
|
|
||||||
Each type contains a submodule called 'generic' containing
|
|
||||||
several basic, universal views for that type. There is also
|
|
||||||
a predefined view that utilizes Jinja.
|
|
||||||
"""
|
|
@ -1,137 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides Jinja Views
|
|
||||||
|
|
||||||
This module provides views that utilize the Jinja templating
|
|
||||||
system for serialization. For more information on Jinja, please
|
|
||||||
see http://jinja.pocoo.org/ .
|
|
||||||
"""
|
|
||||||
|
|
||||||
import copy
|
|
||||||
|
|
||||||
import jinja2
|
|
||||||
|
|
||||||
|
|
||||||
class JinjaView(object):
|
|
||||||
"""A Jinja View
|
|
||||||
|
|
||||||
This view renders the given model using the provided Jinja
|
|
||||||
template. The template can be given in various ways.
|
|
||||||
If the `VIEw_TEXT` property is defined, that is used as template.
|
|
||||||
Othewise, if a `path` parameter is passed to the constructor, that
|
|
||||||
is used to load a file containing the template. If the `path`
|
|
||||||
parameter is None, the `text` parameter is used as the template.
|
|
||||||
|
|
||||||
The leading newline character and trailing newline character are stripped
|
|
||||||
from the template (provided they exist). Baseline indentation is
|
|
||||||
also stripped from each line. The baseline indentation is determined by
|
|
||||||
checking the indentation of the first line, after stripping off the leading
|
|
||||||
newline (if any).
|
|
||||||
|
|
||||||
:param str path: the path to the Jinja template
|
|
||||||
:param str text: the text of the Jinja template
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, path=None, text=None):
|
|
||||||
try:
|
|
||||||
self._text = self.VIEW_TEXT
|
|
||||||
except AttributeError:
|
|
||||||
if path is not None:
|
|
||||||
with open(path, 'r') as f:
|
|
||||||
self._text = f.read()
|
|
||||||
elif text is not None:
|
|
||||||
self._text = text
|
|
||||||
else:
|
|
||||||
self._text = ""
|
|
||||||
|
|
||||||
if self._text[0] == "\n":
|
|
||||||
self._text = self._text[1:]
|
|
||||||
|
|
||||||
newtext = self._text.lstrip()
|
|
||||||
amt = len(self._text) - len(newtext)
|
|
||||||
if (amt > 0):
|
|
||||||
base_indent = self._text[0:amt]
|
|
||||||
lines = self._text.splitlines()
|
|
||||||
newlines = []
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith(base_indent):
|
|
||||||
newlines.append(line[amt:])
|
|
||||||
else:
|
|
||||||
newlines.append(line)
|
|
||||||
self._text = "\n".join(newlines)
|
|
||||||
|
|
||||||
if self._text[-1] == "\n":
|
|
||||||
self._text = self._text[:-1]
|
|
||||||
|
|
||||||
self._regentemplate = True
|
|
||||||
self._templatecache = None
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
return self.template.render(**model)
|
|
||||||
|
|
||||||
def __deepcopy__(self, memodict):
|
|
||||||
res = object.__new__(JinjaView)
|
|
||||||
res._text = copy.deepcopy(self._text, memodict)
|
|
||||||
|
|
||||||
# regenerate the template on a deepcopy
|
|
||||||
res._regentemplate = True
|
|
||||||
res._templatecache = None
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
@property
|
|
||||||
def template(self):
|
|
||||||
"""Get the Compiled Template
|
|
||||||
|
|
||||||
Gets the compiled template, using a cached copy if possible
|
|
||||||
(stored in attr:`_templatecache`) or otherwise recompiling
|
|
||||||
the template if the compiled template is not present or is
|
|
||||||
invalid (due to attr:`_regentemplate` being set to True).
|
|
||||||
|
|
||||||
:returns: the compiled Jinja template
|
|
||||||
:rtype: :class:`jinja2.Template`
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self._templatecache is None or self._regentemplate:
|
|
||||||
self._templatecache = jinja2.Template(self._text)
|
|
||||||
self._regentemplate = False
|
|
||||||
|
|
||||||
return self._templatecache
|
|
||||||
|
|
||||||
def _gettext(self):
|
|
||||||
"""Get the Template Text
|
|
||||||
|
|
||||||
Gets the text of the current template
|
|
||||||
|
|
||||||
:returns: the text of the Jinja template
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._text
|
|
||||||
|
|
||||||
def _settext(self, textval):
|
|
||||||
"""Set the Template Text
|
|
||||||
|
|
||||||
Sets the text of the current template, marking it
|
|
||||||
for recompilation next time the compiled template
|
|
||||||
is retrived via attr:`template` .
|
|
||||||
|
|
||||||
:param str textval: the new text of the Jinja template
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._text = textval
|
|
||||||
self.regentemplate = True
|
|
||||||
|
|
||||||
text = property(_gettext, _settext)
|
|
@ -1,19 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides basic JSON views
|
|
||||||
|
|
||||||
This module provides several basic views which serialize
|
|
||||||
models into JSON.
|
|
||||||
"""
|
|
@ -1,66 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides generic JSON views
|
|
||||||
|
|
||||||
This modules defines several basic views for serializing
|
|
||||||
data to JSON. Submodels that have already been serialized
|
|
||||||
as JSON may have their string values marked with `__is_json__
|
|
||||||
= True` using :class:`oslo_reports._utils.StringWithAttrs`
|
|
||||||
(each of the classes within this module does this automatically,
|
|
||||||
and non-naive serializers check for this attribute and handle
|
|
||||||
such strings specially)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils as json
|
|
||||||
|
|
||||||
from oslo_reports import _utils as utils
|
|
||||||
|
|
||||||
|
|
||||||
class BasicKeyValueView(object):
|
|
||||||
"""A Basic Key-Value JSON View
|
|
||||||
|
|
||||||
This view performs a naive serialization of a model
|
|
||||||
into JSON by simply calling :func:`json.dumps` on the model
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
res = utils.StringWithAttrs(json.dumps(model.data, sort_keys=True))
|
|
||||||
res.__is_json__ = True
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
class KeyValueView(object):
|
|
||||||
"""A Key-Value JSON View
|
|
||||||
|
|
||||||
This view performs advanced serialization to a model
|
|
||||||
into JSON. It does so by first checking all values to
|
|
||||||
see if they are marked as JSON. If so, they are deserialized
|
|
||||||
using :func:`json.loads`. Then, the copy of the model with all
|
|
||||||
JSON deserialized is reserialized into proper nested JSON using
|
|
||||||
:func:`json.dumps`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
# this part deals with subviews that were already serialized
|
|
||||||
cpy = copy.deepcopy(model)
|
|
||||||
for key in model.keys():
|
|
||||||
if getattr(model[key], '__is_json__', False):
|
|
||||||
cpy[key] = json.loads(model[key])
|
|
||||||
|
|
||||||
res = utils.StringWithAttrs(json.dumps(cpy.data, sort_keys=True))
|
|
||||||
res.__is_json__ = True
|
|
||||||
return res
|
|
@ -1,19 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides basic text views
|
|
||||||
|
|
||||||
This module provides several basic views which serialize
|
|
||||||
models into human-readable text.
|
|
||||||
"""
|
|
@ -1,203 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides generic text views
|
|
||||||
|
|
||||||
This modules provides several generic views for
|
|
||||||
serializing models into human-readable text.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import collections as col
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
class MultiView(object):
|
|
||||||
"""A Text View Containing Multiple Views
|
|
||||||
|
|
||||||
This view simply serializes each
|
|
||||||
value in the data model, and then
|
|
||||||
joins them with newlines (ignoring
|
|
||||||
the key values altogether). This is
|
|
||||||
useful for serializing lists of models
|
|
||||||
(as array-like dicts).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
res = sorted([six.text_type(model[key]) for key in model])
|
|
||||||
return "\n".join(res)
|
|
||||||
|
|
||||||
|
|
||||||
class BasicKeyValueView(object):
|
|
||||||
"""A Basic Key-Value Text View
|
|
||||||
|
|
||||||
This view performs a naive serialization of a model into
|
|
||||||
text using a basic key-value method, where each
|
|
||||||
key-value pair is rendered as "key = str(value)"
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
res = ""
|
|
||||||
for key in sorted(model):
|
|
||||||
res += "{key} = {value}\n".format(key=key, value=model[key])
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
class KeyValueView(object):
|
|
||||||
"""A Key-Value Text View
|
|
||||||
|
|
||||||
This view performs an advanced serialization of a model
|
|
||||||
into text by following the following set of rules:
|
|
||||||
|
|
||||||
key : text
|
|
||||||
key = text
|
|
||||||
|
|
||||||
rootkey : Mapping
|
|
||||||
::
|
|
||||||
|
|
||||||
rootkey =
|
|
||||||
serialize(key, value)
|
|
||||||
|
|
||||||
key : Sequence
|
|
||||||
::
|
|
||||||
|
|
||||||
key =
|
|
||||||
serialize(item)
|
|
||||||
|
|
||||||
:param str indent_str: the string used to represent one "indent"
|
|
||||||
:param str key_sep: the separator to use between keys and values
|
|
||||||
:param str dict_sep: the separator to use after a dictionary root key
|
|
||||||
:param str list_sep: the separator to use after a list root key
|
|
||||||
:param str anon_dict: the "key" to use when there is a dict in a list
|
|
||||||
(does not automatically use the dict separator)
|
|
||||||
:param before_dict: content to place on the line(s) before the a dict
|
|
||||||
root key (use None to avoid inserting an extra line)
|
|
||||||
:type before_dict: str or None
|
|
||||||
:param before_list: content to place on the line(s) before the a list
|
|
||||||
root key (use None to avoid inserting an extra line)
|
|
||||||
:type before_list: str or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
indent_str=' ',
|
|
||||||
key_sep=' = ',
|
|
||||||
dict_sep=' = ',
|
|
||||||
list_sep=' = ',
|
|
||||||
anon_dict='[dict]',
|
|
||||||
before_dict=None,
|
|
||||||
before_list=None):
|
|
||||||
self.indent_str = indent_str
|
|
||||||
self.key_sep = key_sep
|
|
||||||
self.dict_sep = dict_sep
|
|
||||||
self.list_sep = list_sep
|
|
||||||
self.anon_dict = anon_dict
|
|
||||||
self.before_dict = before_dict
|
|
||||||
self.before_list = before_list
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
def serialize(root, rootkey, indent):
|
|
||||||
res = []
|
|
||||||
if rootkey is not None:
|
|
||||||
res.append((self.indent_str * indent) + rootkey)
|
|
||||||
|
|
||||||
if isinstance(root, col.Mapping):
|
|
||||||
if rootkey is None and indent > 0:
|
|
||||||
res.append((self.indent_str * indent) + self.anon_dict)
|
|
||||||
elif rootkey is not None:
|
|
||||||
res[0] += self.dict_sep
|
|
||||||
if self.before_dict is not None:
|
|
||||||
res.insert(0, self.before_dict)
|
|
||||||
|
|
||||||
for key in sorted(root):
|
|
||||||
res.extend(serialize(root[key], key, indent + 1))
|
|
||||||
elif (isinstance(root, col.Sequence) and
|
|
||||||
not isinstance(root, six.string_types)):
|
|
||||||
if rootkey is not None:
|
|
||||||
res[0] += self.list_sep
|
|
||||||
if self.before_list is not None:
|
|
||||||
res.insert(0, self.before_list)
|
|
||||||
|
|
||||||
for val in sorted(root, key=str):
|
|
||||||
res.extend(serialize(val, None, indent + 1))
|
|
||||||
else:
|
|
||||||
str_root = six.text_type(root)
|
|
||||||
if '\n' in str_root:
|
|
||||||
# we are in a submodel
|
|
||||||
if rootkey is not None:
|
|
||||||
res[0] += self.dict_sep
|
|
||||||
|
|
||||||
list_root = [(self.indent_str * (indent + 1)) + line
|
|
||||||
for line in str_root.split('\n')]
|
|
||||||
res.extend(list_root)
|
|
||||||
else:
|
|
||||||
# just a normal key or list entry
|
|
||||||
try:
|
|
||||||
res[0] += self.key_sep + str_root
|
|
||||||
except IndexError:
|
|
||||||
res = [(self.indent_str * indent) + str_root]
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
return "\n".join(serialize(model, None, -1))
|
|
||||||
|
|
||||||
|
|
||||||
class TableView(object):
|
|
||||||
"""A Basic Table Text View
|
|
||||||
|
|
||||||
This view performs serialization of data into a basic table with
|
|
||||||
predefined column names and mappings. Column width is auto-calculated
|
|
||||||
evenly, column values are automatically truncated accordingly. Values
|
|
||||||
are centered in the columns.
|
|
||||||
|
|
||||||
:param [str] column_names: the headers for each of the columns
|
|
||||||
:param [str] column_values: the item name to match each column to in
|
|
||||||
each row
|
|
||||||
:param str table_prop_name: the name of the property within the model
|
|
||||||
containing the row models
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, column_names, column_values, table_prop_name):
|
|
||||||
self.table_prop_name = table_prop_name
|
|
||||||
self.column_names = column_names
|
|
||||||
self.column_values = column_values
|
|
||||||
self.column_width = (72 - len(column_names) + 1) // len(column_names)
|
|
||||||
|
|
||||||
column_headers = "|".join(
|
|
||||||
"{{ch[{n}]: ^{width}}}".format(n=n, width=self.column_width)
|
|
||||||
for n in range(len(column_names))
|
|
||||||
)
|
|
||||||
|
|
||||||
# correct for float-to-int roundoff error
|
|
||||||
test_fmt = column_headers.format(ch=column_names)
|
|
||||||
if len(test_fmt) < 72:
|
|
||||||
column_headers += ' ' * (72 - len(test_fmt))
|
|
||||||
|
|
||||||
vert_divider = '-' * 72
|
|
||||||
self.header_fmt_str = column_headers + "\n" + vert_divider + "\n"
|
|
||||||
|
|
||||||
self.row_fmt_str = "|".join(
|
|
||||||
"{{cv[{n}]: ^{width}}}".format(n=n, width=self.column_width)
|
|
||||||
for n in range(len(column_values))
|
|
||||||
)
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
res = self.header_fmt_str.format(ch=self.column_names)
|
|
||||||
for raw_row in model[self.table_prop_name]:
|
|
||||||
row = [six.text_type(raw_row[prop_name])
|
|
||||||
for prop_name in self.column_values]
|
|
||||||
# double format is in case we have roundoff error
|
|
||||||
res += '{0: <72}\n'.format(self.row_fmt_str.format(cv=row))
|
|
||||||
|
|
||||||
return res
|
|
@ -1,53 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Text Views With Headers
|
|
||||||
|
|
||||||
This package defines several text views with headers
|
|
||||||
"""
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
class HeaderView(object):
|
|
||||||
"""A Text View With a Header
|
|
||||||
|
|
||||||
This view simply serializes the model and places the given
|
|
||||||
header on top.
|
|
||||||
|
|
||||||
:param header: the header (can be anything on which str() can be called)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, header):
|
|
||||||
self.header = header
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
return six.text_type(self.header) + "\n" + six.text_type(model)
|
|
||||||
|
|
||||||
|
|
||||||
class TitledView(HeaderView):
|
|
||||||
"""A Text View With a Title
|
|
||||||
|
|
||||||
This view simply serializes the model, and places
|
|
||||||
a preformatted header containing the given title
|
|
||||||
text on top. The title text can be up to 64 characters
|
|
||||||
long.
|
|
||||||
|
|
||||||
:param str title: the title of the view
|
|
||||||
"""
|
|
||||||
|
|
||||||
FORMAT_STR = ('=' * 72) + "\n===={0: ^64}====\n" + ('=' * 72)
|
|
||||||
|
|
||||||
def __init__(self, title):
|
|
||||||
super(TitledView, self).__init__(self.FORMAT_STR.format(title))
|
|
@ -1,38 +0,0 @@
|
|||||||
# Copyright 2014 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides process view
|
|
||||||
|
|
||||||
This module provides a view for
|
|
||||||
visualizing processes in human-readable form
|
|
||||||
"""
|
|
||||||
|
|
||||||
import oslo_reports.views.jinja_view as jv
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessView(jv.JinjaView):
|
|
||||||
"""A Process View
|
|
||||||
|
|
||||||
This view displays process models defined by
|
|
||||||
:class:`oslo_reports.models.process.ProcessModel`
|
|
||||||
"""
|
|
||||||
|
|
||||||
VIEW_TEXT = (
|
|
||||||
"Process {{ pid }} (under {{ parent_pid }}) "
|
|
||||||
"[ run by: {{ username }} ({{ uids.real|default('unknown uid') }}),"
|
|
||||||
" state: {{ state }} ]\n"
|
|
||||||
"{% for child in children %}"
|
|
||||||
" {{ child }}"
|
|
||||||
"{% endfor %}"
|
|
||||||
)
|
|
@ -1,80 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides thread and stack-trace views
|
|
||||||
|
|
||||||
This module provides a collection of views for
|
|
||||||
visualizing threads, green threads, and stack traces
|
|
||||||
in human-readable form.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from oslo_reports.views import jinja_view as jv
|
|
||||||
|
|
||||||
|
|
||||||
class StackTraceView(jv.JinjaView):
|
|
||||||
"""A Stack Trace View
|
|
||||||
|
|
||||||
This view displays stack trace models defined by
|
|
||||||
:class:`oslo_reports.models.threading.StackTraceModel`
|
|
||||||
"""
|
|
||||||
|
|
||||||
VIEW_TEXT = (
|
|
||||||
"{% if root_exception is not none %}"
|
|
||||||
"Exception: {{ root_exception }}\n"
|
|
||||||
"------------------------------------\n"
|
|
||||||
"\n"
|
|
||||||
"{% endif %}"
|
|
||||||
"{% for line in lines %}\n"
|
|
||||||
"{{ line.filename }}:{{ line.line }} in {{ line.name }}\n"
|
|
||||||
" {% if line.code is not none %}"
|
|
||||||
"`{{ line.code }}`"
|
|
||||||
"{% else %}"
|
|
||||||
"(source not found)"
|
|
||||||
"{% endif %}\n"
|
|
||||||
"{% else %}\n"
|
|
||||||
"No Traceback!\n"
|
|
||||||
"{% endfor %}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GreenThreadView(object):
|
|
||||||
"""A Green Thread View
|
|
||||||
|
|
||||||
This view displays a green thread provided by the data
|
|
||||||
model :class:`oslo_reports.models.threading.GreenThreadModel`
|
|
||||||
"""
|
|
||||||
|
|
||||||
FORMAT_STR = "------{thread_str: ^60}------" + "\n" + "{stack_trace}"
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
return self.FORMAT_STR.format(
|
|
||||||
thread_str=" Green Thread ",
|
|
||||||
stack_trace=model.stack_trace
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ThreadView(object):
|
|
||||||
"""A Thread Collection View
|
|
||||||
|
|
||||||
This view displays a python thread provided by the data
|
|
||||||
model :class:`oslo_reports.models.threading.ThreadModel` # noqa
|
|
||||||
"""
|
|
||||||
|
|
||||||
FORMAT_STR = "------{thread_str: ^60}------" + "\n" + "{stack_trace}"
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
return self.FORMAT_STR.format(
|
|
||||||
thread_str=" Thread #{0} ".format(model.thread_id),
|
|
||||||
stack_trace=model.stack_trace
|
|
||||||
)
|
|
@ -1,19 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides basic XML views
|
|
||||||
|
|
||||||
This module provides several basic views which serialize
|
|
||||||
models into XML.
|
|
||||||
"""
|
|
@ -1,87 +0,0 @@
|
|||||||
# Copyright 2013 Red Hat, 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.
|
|
||||||
|
|
||||||
"""Provides generic XML views
|
|
||||||
|
|
||||||
This modules defines several basic views for serializing
|
|
||||||
data to XML. Submodels that have already been serialized
|
|
||||||
as XML may have their string values marked with `__is_xml__
|
|
||||||
= True` using :class:`oslo_reports._utils.StringWithAttrs`
|
|
||||||
(each of the classes within this module does this automatically,
|
|
||||||
and non-naive serializers check for this attribute and handle
|
|
||||||
such strings specially)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import collections as col
|
|
||||||
import copy
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_reports import _utils as utils
|
|
||||||
|
|
||||||
|
|
||||||
class KeyValueView(object):
|
|
||||||
"""A Key-Value XML View
|
|
||||||
|
|
||||||
This view performs advanced serialization of a data model
|
|
||||||
into XML. It first deserializes any values marked as XML so
|
|
||||||
that they can be properly reserialized later. It then follows
|
|
||||||
the following rules to perform serialization:
|
|
||||||
|
|
||||||
key : text/xml
|
|
||||||
The tag name is the key name, and the contents are the text or xml
|
|
||||||
key : Sequence
|
|
||||||
A wrapper tag is created with the key name, and each item is placed
|
|
||||||
in an 'item' tag
|
|
||||||
key : Mapping
|
|
||||||
A wrapper tag is created with the key name, and the serialize is called
|
|
||||||
on each key-value pair (such that each key gets its own tag)
|
|
||||||
|
|
||||||
:param str wrapper_name: the name of the top-level element
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, wrapper_name="model"):
|
|
||||||
self.wrapper_name = wrapper_name
|
|
||||||
|
|
||||||
def __call__(self, model):
|
|
||||||
# this part deals with subviews that were already serialized
|
|
||||||
cpy = copy.deepcopy(model)
|
|
||||||
for key, valstr in model.items():
|
|
||||||
if getattr(valstr, '__is_xml__', False):
|
|
||||||
cpy[key] = ET.fromstring(valstr)
|
|
||||||
|
|
||||||
def serialize(rootmodel, rootkeyname):
|
|
||||||
res = ET.Element(rootkeyname)
|
|
||||||
|
|
||||||
if isinstance(rootmodel, col.Mapping):
|
|
||||||
for key in sorted(rootmodel):
|
|
||||||
res.append(serialize(rootmodel[key], key))
|
|
||||||
elif (isinstance(rootmodel, col.Sequence)
|
|
||||||
and not isinstance(rootmodel, six.string_types)):
|
|
||||||
for val in sorted(rootmodel, key=str):
|
|
||||||
res.append(serialize(val, 'item'))
|
|
||||||
elif ET.iselement(rootmodel):
|
|
||||||
res.append(rootmodel)
|
|
||||||
else:
|
|
||||||
res.text = six.text_type(rootmodel)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
str_ = ET.tostring(serialize(cpy,
|
|
||||||
self.wrapper_name),
|
|
||||||
encoding="utf-8").decode("utf-8")
|
|
||||||
res = utils.StringWithAttrs(str_)
|
|
||||||
res.__is_xml__ = True
|
|
||||||
return res
|
|
@ -1,11 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
pbr>=1.6 # Apache-2.0
|
|
||||||
Jinja2>=2.8 # BSD License (3 clause)
|
|
||||||
oslo.serialization>=1.10.0 # Apache-2.0
|
|
||||||
psutil<2.0.0,>=1.1.1 # BSD
|
|
||||||
six>=1.9.0 # MIT
|
|
||||||
oslo.i18n>=2.1.0 # Apache-2.0
|
|
||||||
oslo.utils>=3.16.0 # Apache-2.0
|
|
61
setup.cfg
61
setup.cfg
@ -1,61 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = oslo.reports
|
|
||||||
summary = oslo.reports library
|
|
||||||
description-file =
|
|
||||||
README.rst
|
|
||||||
author = OpenStack
|
|
||||||
author-email = openstack-dev@lists.openstack.org
|
|
||||||
home-page = http://launchpad.net/oslo
|
|
||||||
classifier =
|
|
||||||
Environment :: OpenStack
|
|
||||||
Intended Audience :: Information Technology
|
|
||||||
Intended Audience :: System Administrators
|
|
||||||
License :: OSI Approved :: Apache Software License
|
|
||||||
Operating System :: POSIX :: Linux
|
|
||||||
Programming Language :: Python
|
|
||||||
Programming Language :: Python :: 2
|
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.4
|
|
||||||
Programming Language :: Python :: 3.5
|
|
||||||
|
|
||||||
[files]
|
|
||||||
packages =
|
|
||||||
oslo_reports
|
|
||||||
|
|
||||||
[pbr]
|
|
||||||
warnerrors = true
|
|
||||||
autodoc_index_modules = true
|
|
||||||
autodoc_exclude_modules =
|
|
||||||
oslo_reports._i18n
|
|
||||||
oslo_reports._utils
|
|
||||||
oslo_reports.tests.*
|
|
||||||
|
|
||||||
[entry_points]
|
|
||||||
oslo.config.opts =
|
|
||||||
oslo.reports = oslo_reports.opts:list_opts
|
|
||||||
|
|
||||||
[build_sphinx]
|
|
||||||
source-dir = doc/source
|
|
||||||
build-dir = doc/build
|
|
||||||
all_files = 1
|
|
||||||
|
|
||||||
[upload_sphinx]
|
|
||||||
upload-dir = doc/build/html
|
|
||||||
|
|
||||||
[compile_catalog]
|
|
||||||
directory = oslo_reports/locale
|
|
||||||
domain = oslo_reports
|
|
||||||
|
|
||||||
[update_catalog]
|
|
||||||
domain = oslo_reports
|
|
||||||
output_dir = oslo_reports/locale
|
|
||||||
input_file = oslo_reports/locale/oslo_reports.pot
|
|
||||||
|
|
||||||
[extract_messages]
|
|
||||||
keywords = _ gettext ngettext l_ lazy_gettext
|
|
||||||
mapping_file = babel.cfg
|
|
||||||
output_file = oslo_reports/locale/oslo_reports.pot
|
|
||||||
|
|
||||||
[wheel]
|
|
||||||
universal = true
|
|
29
setup.py
29
setup.py
@ -1,29 +0,0 @@
|
|||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# 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 FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
|
||||||
import setuptools
|
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
|
||||||
# setuptools if some other modules registered functions in `atexit`.
|
|
||||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
setup_requires=['pbr>=1.8'],
|
|
||||||
pbr=True)
|
|
@ -1,17 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
hacking<0.11,>=0.10.0
|
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
|
||||||
|
|
||||||
# These are needed for docs generation
|
|
||||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
|
||||||
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
|
|
||||||
|
|
||||||
# for testing optional parts
|
|
||||||
oslo.config>=3.14.0 # Apache-2.0
|
|
||||||
eventlet!=0.18.3,>=0.18.2 # MIT
|
|
||||||
greenlet>=0.3.2 # MIT
|
|
||||||
|
|
||||||
coverage>=3.6 # Apache-2.0
|
|
38
tox.ini
38
tox.ini
@ -1,38 +0,0 @@
|
|||||||
[tox]
|
|
||||||
minversion = 1.6
|
|
||||||
envlist = py35,py34,py27,pypy,pep8
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
|
||||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
|
||||||
|
|
||||||
[testenv:pep8]
|
|
||||||
commands = flake8
|
|
||||||
|
|
||||||
[testenv:venv]
|
|
||||||
commands = {posargs}
|
|
||||||
|
|
||||||
[testenv:docs]
|
|
||||||
commands = python setup.py build_sphinx
|
|
||||||
|
|
||||||
[testenv:cover]
|
|
||||||
commands = python setup.py test --coverage --coverage-package-name=oslo_reports --testr-args='{posargs}'
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
# E123, E125 skipped as they are invalid PEP-8.
|
|
||||||
|
|
||||||
show-source = True
|
|
||||||
ignore = E123,E125
|
|
||||||
builtins = _
|
|
||||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
|
||||||
|
|
||||||
[hacking]
|
|
||||||
import_exceptions =
|
|
||||||
|
|
||||||
[testenv:pip-missing-reqs]
|
|
||||||
# do not install test-requirements as that will pollute the virtualenv for
|
|
||||||
# determining missing packages
|
|
||||||
# this also means that pip-missing-reqs must be installed separately, outside
|
|
||||||
# of the requirements.txt files
|
|
||||||
deps = pip_missing_reqs
|
|
||||||
commands = pip-missing-reqs -d --ignore-module=oslo_reports* --ignore-module=pkg_resources --ignore-file=oslo_reports/test.py --ignore-file=oslo_reports/tests/* oslo_reports
|
|
Loading…
Reference in New Issue
Block a user