├── .coveragerc ├── .gitignore ├── .gitreview ├── .mailmap ├── .pre-commit-config.yaml ├── .stestr.conf ├── .zuul.yaml ├── CONTRIBUTING.rst ├── HACKING.rst ├── LICENSE ├── README.rst ├── doc ├── requirements.txt └── source │ ├── conf.py │ ├── contributor │ └── index.rst │ ├── index.rst │ ├── install │ └── index.rst │ ├── reference │ ├── index.rst │ └── opts.rst │ └── user │ ├── history.rst │ ├── index.rst │ ├── report.txt │ └── usage.rst ├── oslo_reports ├── __init__.py ├── _i18n.py ├── _utils.py ├── generators │ ├── __init__.py │ ├── conf.py │ ├── process.py │ ├── threading.py │ └── version.py ├── guru_meditation_report.py ├── locale │ └── en_GB │ │ └── LC_MESSAGES │ │ └── oslo_reports.po ├── models │ ├── __init__.py │ ├── base.py │ ├── conf.py │ ├── process.py │ ├── threading.py │ ├── version.py │ └── with_default_views.py ├── opts.py ├── report.py ├── tests │ ├── __init__.py │ ├── test_base_report.py │ ├── test_guru_meditation_report.py │ ├── test_openstack_generators.py │ └── test_views.py └── views │ ├── __init__.py │ ├── jinja_view.py │ ├── json │ ├── __init__.py │ └── generic.py │ ├── text │ ├── __init__.py │ ├── generic.py │ ├── header.py │ ├── process.py │ └── threading.py │ └── xml │ ├── __init__.py │ └── generic.py ├── releasenotes ├── notes │ ├── add-reno-996dd44974d53238.yaml │ ├── drop-python27-support-26fad37c3f7a3d28.yaml │ └── remove-py38-9a3e6702c3d23445.yaml └── source │ ├── 2023.1.rst │ ├── 2023.2.rst │ ├── 2024.1.rst │ ├── 2024.2.rst │ ├── 2025.1.rst │ ├── _static │ └── .placeholder │ ├── _templates │ └── .placeholder │ ├── conf.py │ ├── index.rst │ ├── locale │ └── en_GB │ │ └── LC_MESSAGES │ │ └── releasenotes.po │ ├── ocata.rst │ ├── pike.rst │ ├── queens.rst │ ├── rocky.rst │ ├── stein.rst │ ├── train.rst │ ├── unreleased.rst │ ├── ussuri.rst │ └── victoria.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── test-requirements.txt └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = oslo_reports 4 | omit = oslo_reports/tests/* 5 | 6 | [report] 7 | ignore_errors = True 8 | precision = 2 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Add patterns in here to exclude files created by tools integrated with this 2 | # repository, such as test frameworks from the project's recommended workflow, 3 | # rendered documentation and package builds. 4 | # 5 | # Don't add patterns to exclude files created by preferred personal tools 6 | # (editors, IDEs, your operating system itself even). These should instead be 7 | # maintained outside the repository, for example in a ~/.gitignore file added 8 | # with: 9 | # 10 | # git config --global core.excludesfile '~/.gitignore' 11 | 12 | # Bytecompiled Python 13 | *.py[cod] 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Packages 19 | *.egg 20 | *.eggs 21 | *.egg-info 22 | dist 23 | build 24 | eggs 25 | parts 26 | bin 27 | var 28 | sdist 29 | develop-eggs 30 | .installed.cfg 31 | lib 32 | lib64 33 | 34 | # Installer logs 35 | pip-log.txt 36 | 37 | # Unit test / coverage reports 38 | .coverage 39 | cover 40 | .tox 41 | .stestr/ 42 | 43 | # Translations 44 | *.mo 45 | 46 | # Complexity 47 | output/*.html 48 | output/*/index.html 49 | 50 | # Sphinx 51 | doc/build 52 | 53 | # pbr generates these 54 | AUTHORS 55 | ChangeLog 56 | doc/source/reference/api 57 | 58 | # reno build 59 | releasenotes/build 60 | releasenotes/notes/reno.cache 61 | RELEASENOTES.rst 62 | 63 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/oslo.reports.git 5 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # Format is: 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: trailing-whitespace 6 | # Replaces or checks mixed line ending 7 | - id: mixed-line-ending 8 | args: ['--fix', 'lf'] 9 | exclude: '.*\.(svg)$' 10 | # Forbid files which have a UTF-8 byte-order marker 11 | - id: check-byte-order-marker 12 | # Checks that non-binary executables have a proper shebang 13 | - id: check-executables-have-shebangs 14 | # Check for files that contain merge conflict strings. 15 | - id: check-merge-conflict 16 | # Check for debugger imports and py37+ breakpoint() 17 | # calls in python source 18 | - id: debug-statements 19 | - id: check-yaml 20 | files: .*\.(yaml|yml)$ 21 | - repo: https://opendev.org/openstack/hacking 22 | rev: 7.0.0 23 | hooks: 24 | - id: hacking 25 | additional_dependencies: [] 26 | - repo: https://github.com/PyCQA/bandit 27 | rev: 1.7.10 28 | hooks: 29 | - id: bandit 30 | args: ['-x', 'tests', '-s', 'B314,B405'] 31 | - repo: https://github.com/asottile/pyupgrade 32 | rev: v3.18.0 33 | hooks: 34 | - id: pyupgrade 35 | args: [--py3-only] 36 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=./oslo_reports/tests 3 | top_path=./ 4 | -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - project: 2 | templates: 3 | - check-requirements 4 | - lib-forward-testing-python3 5 | - openstack-python3-jobs 6 | - periodic-stable-jobs 7 | - publish-openstack-docs-pti 8 | - release-notes-jobs-python3 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | If you would like to contribute to the development of oslo's libraries, 2 | first you must take a look to this page: 3 | 4 | https://specs.openstack.org/openstack/oslo-specs/specs/policy/contributing.html 5 | 6 | If you would like to contribute to the development of OpenStack, you must 7 | follow the steps in this page: 8 | 9 | https://docs.openstack.org/infra/manual/developers.html 10 | 11 | If you already have a good understanding of how the system works and your 12 | OpenStack accounts are set up, you can skip to the development workflow 13 | section of this documentation to learn how changes to OpenStack should be 14 | submitted for review via the Gerrit tool: 15 | 16 | https://docs.openstack.org/infra/manual/developers.html#development-workflow 17 | 18 | Pull requests submitted through GitHub will be ignored. 19 | 20 | Bugs should be filed on Launchpad, not GitHub: 21 | 22 | https://bugs.launchpad.net/oslo.reports 23 | -------------------------------------------------------------------------------- /HACKING.rst: -------------------------------------------------------------------------------- 1 | oslo.reports Style Commandments 2 | =============================== 3 | 4 | Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Team and repository tags 3 | ======================== 4 | 5 | .. image:: https://governance.openstack.org/tc/badges/oslo.reports.svg 6 | :target: https://governance.openstack.org/tc/reference/tags/index.html 7 | 8 | .. Change things from this point on 9 | 10 | =================================== 11 | oslo.reports 12 | =================================== 13 | 14 | .. image:: https://img.shields.io/pypi/v/oslo.reports.svg 15 | :target: https://pypi.org/project/oslo.reports/ 16 | :alt: Latest Version 17 | 18 | .. image:: https://img.shields.io/pypi/dm/oslo.reports.svg 19 | :target: https://pypi.org/project/oslo.reports/ 20 | :alt: Downloads 21 | 22 | When things go wrong in (production) deployments of OpenStack collecting debug 23 | data is a key first step in the process of triaging & ultimately resolving the 24 | problem. Projects like Nova has extensively used logging capabilities which 25 | produce a vast amount of data. This does not, however, enable an admin to 26 | obtain an accurate view on the current live state of the system. For example, 27 | what threads are running, what config parameters are in effect, and more. 28 | 29 | The project oslo.reports hosts a general purpose error report generation 30 | framework, known as the "guru meditation report" 31 | (cf http://en.wikipedia.org/wiki/Guru_Meditation) to address the issues 32 | described above. 33 | 34 | Models: These classes define structured data for a variety of interesting 35 | pieces of state. For example, stack traces, threads, config parameters, 36 | package version info, etc. They are capable of being serialized to XML / JSON 37 | or a plain text representation 38 | 39 | Generators: These classes are used to populate the model classes with the 40 | current runtime state of the system 41 | 42 | Views: views serialize models into say JSON, text or xml. There is also 43 | a predefined view that utilizes Jinja templating system. 44 | 45 | There will be a number of standard models / generators available for all 46 | OpenStack services 47 | 48 | StackTraceModel: a base class for any model which includes a stack trace 49 | ThreadModel: a class for information about a thread 50 | ExceptionModel: a class for information about a caught exception 51 | ConfigModel: a class for information about configuration file settings 52 | PackageModel: a class for information about vendor/product/version/package information 53 | 54 | Each OpenStack project will have the ability to register further generator 55 | classes to provide custom project specific data. 56 | 57 | * Free software: Apache license 58 | * Documentation: https://docs.openstack.org/oslo.reports/latest 59 | * Source: https://opendev.org/openstack/oslo.reports 60 | * Bugs: https://bugs.launchpad.net/oslo.reports 61 | * Release notes: https://docs.openstack.org/releasenotes/oslo.reports/ 62 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | openstackdocstheme>=2.2.0 # Apache-2.0 2 | sphinx>=2.0.0 # BSD 3 | reno>=3.1.0 # Apache-2.0 4 | sphinxcontrib-apidoc>=0.2.0 # BSD 5 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # -- General configuration ---------------------------------------------------- 17 | 18 | # Add any Sphinx extension module names here, as strings. They can be 19 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 20 | extensions = [ 21 | 'sphinx.ext.autodoc', 22 | 'sphinxcontrib.apidoc', 23 | 'openstackdocstheme', 24 | 'oslo_config.sphinxext', 25 | ] 26 | 27 | # openstackdocstheme options 28 | openstackdocs_repo_name = 'openstack/oslo.reports' 29 | openstackdocs_bug_project = 'oslo.reports' 30 | openstackdocs_bug_tag = '' 31 | 32 | # The master toctree document. 33 | master_doc = 'index' 34 | 35 | # General information about the project. 36 | copyright = '2014, OpenStack Foundation' 37 | 38 | # If true, '()' will be appended to :func: etc. cross-reference text. 39 | add_function_parentheses = True 40 | 41 | # If true, the current module name will be prepended to all description 42 | # unit titles (such as .. function::). 43 | add_module_names = True 44 | 45 | # The name of the Pygments (syntax highlighting) style to use. 46 | pygments_style = 'native' 47 | 48 | # A list of ignored prefixes for module index sorting. 49 | modindex_common_prefix = ['oslo_reports.'] 50 | 51 | # -- Options for HTML output ------------------------------------------------- 52 | 53 | html_theme = 'openstackdocs' 54 | 55 | # -- sphinxcontrib.apidoc configuration -------------------------------------- 56 | 57 | apidoc_module_dir = '../../oslo_reports' 58 | apidoc_output_dir = 'reference/api' 59 | apidoc_excluded_paths = [ 60 | 'tests', 61 | ] 62 | -------------------------------------------------------------------------------- /doc/source/contributor/index.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Contributing 3 | ============== 4 | 5 | .. include:: ../../../CONTRIBUTING.rst 6 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | oslo.reports 3 | ============== 4 | 5 | oslo.reports library 6 | 7 | Contents 8 | ======== 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | install/index 14 | contributor/index 15 | user/index 16 | reference/index 17 | 18 | Release Notes 19 | ============= 20 | 21 | Read also the `oslo.reports Release Notes 22 | `_. 23 | 24 | Indices and tables 25 | ================== 26 | 27 | * :ref:`genindex` 28 | * :ref:`modindex` 29 | * :ref:`search` 30 | 31 | -------------------------------------------------------------------------------- /doc/source/install/index.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | At the command line:: 6 | 7 | $ pip install oslo.reports 8 | -------------------------------------------------------------------------------- /doc/source/reference/index.rst: -------------------------------------------------------------------------------- 1 | .. _using: 2 | 3 | ========= 4 | Reference 5 | ========= 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | opts 11 | 12 | API 13 | === 14 | 15 | .. toctree:: 16 | :maxdepth: 1 17 | 18 | api/modules 19 | -------------------------------------------------------------------------------- /doc/source/reference/opts.rst: -------------------------------------------------------------------------------- 1 | .. _option-definitions: 2 | 3 | ===================== 4 | Configuration Options 5 | ===================== 6 | 7 | oslo.reports uses oslo.config to define and manage configuration options 8 | to allow the deployer to control where the GMR reports should be generated. 9 | 10 | .. show-options:: oslo.reports 11 | -------------------------------------------------------------------------------- /doc/source/user/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../ChangeLog 2 | -------------------------------------------------------------------------------- /doc/source/user/index.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Using oslo.reports 3 | =================== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | usage 9 | history 10 | -------------------------------------------------------------------------------- /doc/source/user/report.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | ==== Guru Meditation ==== 3 | ======================================================================== 4 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 5 | 6 | 7 | ======================================================================== 8 | ==== Package ==== 9 | ======================================================================== 10 | product = OpenStack Nova 11 | vendor = OpenStack Foundation 12 | version = 13.0.0 13 | ======================================================================== 14 | ==== Threads ==== 15 | ======================================================================== 16 | ------ Thread #140417215547200 ------ 17 | 18 | /usr/local/lib/python2.7/dist-packages/eventlet/hubs/hub.py:346 in run 19 | `self.wait(sleep_time)` 20 | 21 | /usr/local/lib/python2.7/dist-packages/eventlet/hubs/poll.py:82 in wait 22 | `sleep(seconds)` 23 | 24 | ======================================================================== 25 | ==== Green Threads ==== 26 | ======================================================================== 27 | ------ Green Thread ------ 28 | 29 | /usr/local/bin/nova-api:10 in 30 | `sys.exit(main())` 31 | 32 | /opt/stack/nova/nova/cmd/api.py:57 in main 33 | `launcher.wait()` 34 | 35 | /usr/local/lib/python2.7/dist-packages/oslo_service/service.py:511 in wait 36 | `self._respawn_children()` 37 | 38 | /usr/local/lib/python2.7/dist-packages/oslo_service/service.py:495 in _respawn_children 39 | `eventlet.greenthread.sleep(self.wait_interval)` 40 | 41 | /usr/local/lib/python2.7/dist-packages/eventlet/greenthread.py:34 in sleep 42 | `hub.switch()` 43 | 44 | /usr/local/lib/python2.7/dist-packages/eventlet/hubs/hub.py:294 in switch 45 | `return self.greenlet.switch()` 46 | 47 | ------ Green Thread ------ 48 | 49 | No Traceback! 50 | 51 | ======================================================================== 52 | ==== Processes ==== 53 | ======================================================================== 54 | Process 14756 (under 1) [ run by: stack (1001), state: running ] 55 | Process 14770 (under 14756) [ run by: stack (1001), state: sleeping ] 56 | Process 14771 (under 14756) [ run by: stack (1001), state: sleeping ] 57 | Process 14776 (under 14756) [ run by: stack (1001), state: sleeping ] 58 | Process 14777 (under 14756) [ run by: stack (1001), state: sleeping ] 59 | Process 14784 (under 14756) [ run by: stack (1001), state: sleeping ] 60 | Process 14785 (under 14756) [ run by: stack (1001), state: sleeping ] 61 | 62 | ======================================================================== 63 | ==== Configuration ==== 64 | ======================================================================== 65 | 66 | api_database: 67 | connection = *** 68 | connection_debug = 0 69 | connection_trace = False 70 | idle_timeout = 3600 71 | max_overflow = None 72 | max_pool_size = None 73 | max_retries = 10 74 | mysql_sql_mode = TRADITIONAL 75 | pool_timeout = None 76 | retry_interval = 10 77 | slave_connection = *** 78 | sqlite_synchronous = True 79 | 80 | cells: 81 | bandwidth_update_interval = 600 82 | call_timeout = 60 83 | capabilities = 84 | hypervisor=xenserver;kvm 85 | os=linux;windows 86 | cell_type = compute 87 | enable = False 88 | instance_update_sync_database_limit = 100 89 | manager = nova.cells.manager.CellsManager 90 | mute_child_interval = 300 91 | name = nova 92 | reserve_percent = 10.0 93 | topic = cells 94 | 95 | cinder: 96 | cafile = None 97 | catalog_info = volumev2:cinderv2:publicURL 98 | certfile = None 99 | cross_az_attach = True 100 | endpoint_template = None 101 | http_retries = 3 102 | insecure = False 103 | keyfile = None 104 | os_region_name = RegionOne 105 | timeout = None 106 | 107 | conductor: 108 | manager = nova.conductor.manager.ConductorManager 109 | topic = conductor 110 | use_local = False 111 | workers = 2 112 | 113 | database: 114 | backend = sqlalchemy 115 | connection = *** 116 | connection_debug = 0 117 | connection_trace = False 118 | db_inc_retry_interval = True 119 | db_max_retries = 20 120 | db_max_retry_interval = 10 121 | db_retry_interval = 1 122 | idle_timeout = 3600 123 | max_overflow = None 124 | max_pool_size = None 125 | max_retries = 10 126 | min_pool_size = 1 127 | mysql_sql_mode = TRADITIONAL 128 | pool_timeout = None 129 | retry_interval = 10 130 | slave_connection = *** 131 | sqlite_db = nova.sqlite 132 | sqlite_synchronous = True 133 | use_db_reconnect = False 134 | use_tpool = False 135 | 136 | default: 137 | allow_instance_snapshots = True 138 | allow_resize_to_same_host = True 139 | allow_same_net_traffic = True 140 | api_paste_config = /etc/nova/api-paste.ini 141 | api_rate_limit = False 142 | auth_strategy = keystone 143 | auto_assign_floating_ip = False 144 | bandwidth_poll_interval = 600 145 | bindir = /usr/local/bin 146 | block_device_allocate_retries = 60 147 | block_device_allocate_retries_interval = 3 148 | boot_script_template = /opt/stack/nova/nova/cloudpipe/bootscript.template 149 | ca_file = cacert.pem 150 | ca_path = /opt/stack/data/nova/CA 151 | cert_manager = nova.cert.manager.CertManager 152 | cert_topic = cert 153 | client_socket_timeout = 900 154 | cnt_vpn_clients = 0 155 | compute_available_monitors = None 156 | compute_driver = libvirt.LibvirtDriver 157 | compute_manager = nova.compute.manager.ComputeManager 158 | compute_monitors = 159 | compute_resources = 160 | vcpu 161 | compute_stats_class = nova.compute.stats.Stats 162 | compute_topic = compute 163 | config-dir = None 164 | config-file = 165 | /etc/nova/nova.conf 166 | config_drive_format = iso9660 167 | 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 168 | console_host = dims-ubuntu 169 | console_manager = nova.console.manager.ConsoleProxyManager 170 | console_topic = console 171 | consoleauth_manager = nova.consoleauth.manager.ConsoleAuthManager 172 | consoleauth_topic = consoleauth 173 | control_exchange = nova 174 | cpu_allocation_ratio = 0.0 175 | create_unique_mac_address_attempts = 5 176 | crl_file = crl.pem 177 | db_driver = nova.db 178 | debug = True 179 | default_access_ip_network_name = None 180 | default_availability_zone = nova 181 | default_ephemeral_format = ext4 182 | default_flavor = m1.small 183 | default_floating_pool = public 184 | default_log_levels = 185 | amqp=WARN 186 | amqplib=WARN 187 | boto=WARN 188 | glanceclient=WARN 189 | iso8601=WARN 190 | keystonemiddleware=WARN 191 | oslo_messaging=INFO 192 | qpid=WARN 193 | requests.packages.urllib3.connectionpool=WARN 194 | routes.middleware=WARN 195 | sqlalchemy=WARN 196 | stevedore=WARN 197 | suds=INFO 198 | urllib3.connectionpool=WARN 199 | websocket=WARN 200 | default_notification_level = INFO 201 | default_publisher_id = None 202 | default_schedule_zone = None 203 | defer_iptables_apply = False 204 | dhcp_domain = novalocal 205 | dhcp_lease_time = 86400 206 | dhcpbridge = /usr/local/bin/nova-dhcpbridge 207 | dhcpbridge_flagfile = 208 | /etc/nova/nova.conf 209 | dmz_cidr = 210 | dmz_mask = 255.255.255.0 211 | dmz_net = 10.0.0.0 212 | dns_server = 213 | dns_update_periodic_interval = -1 214 | dnsmasq_config_file = 215 | ebtables_exec_attempts = 3 216 | ebtables_retry_interval = 1.0 217 | ec2_dmz_host = 10.0.0.9 218 | ec2_host = 10.0.0.9 219 | ec2_listen = 0.0.0.0 220 | ec2_listen_port = 8773 221 | ec2_path = / 222 | ec2_port = 8773 223 | ec2_private_dns_show_ip = False 224 | ec2_scheme = http 225 | ec2_strict_validation = True 226 | ec2_timestamp_expiry = 300 227 | ec2_workers = 2 228 | enable_instance_password = True 229 | enable_network_quota = False 230 | enable_new_services = True 231 | enabled_apis = 232 | ec2 233 | metadata 234 | osapi_compute 235 | enabled_ssl_apis = 236 | fake_call = False 237 | fake_network = False 238 | fatal_deprecations = False 239 | fatal_exception_format_errors = False 240 | firewall_driver = nova.virt.firewall.NoopFirewallDriver 241 | fixed_ip_disassociate_timeout = 600 242 | fixed_range_v6 = fd00::/48 243 | flat_injected = False 244 | flat_interface = None 245 | flat_network_bridge = None 246 | flat_network_dns = 8.8.4.4 247 | floating_ip_dns_manager = nova.network.noop_dns_driver.NoopDNSDriver 248 | force_config_drive = True 249 | force_dhcp_release = True 250 | force_raw_images = True 251 | force_snat_range = 252 | forward_bridge_interface = 253 | all 254 | fping_path = /usr/sbin/fping 255 | gateway = None 256 | gateway_v6 = None 257 | heal_instance_info_cache_interval = 60 258 | host = dims-ubuntu 259 | image_cache_manager_interval = 2400 260 | image_cache_subdirectory_name = _base 261 | image_decryption_dir = /tmp 262 | injected_network_template = /opt/stack/nova/nova/virt/interfaces.template 263 | instance_build_timeout = 0 264 | instance_delete_interval = 300 265 | instance_dns_domain = 266 | instance_dns_manager = nova.network.noop_dns_driver.NoopDNSDriver 267 | instance_format = [instance: %(uuid)s] 268 | instance_name_template = instance-%08x 269 | instance_usage_audit = False 270 | instance_usage_audit_period = month 271 | instance_uuid_format = [instance: %(uuid)s] 272 | instances_path = /opt/stack/data/nova/instances 273 | internal_service_availability_zone = internal 274 | iptables_bottom_regex = 275 | iptables_drop_action = DROP 276 | iptables_top_regex = 277 | ipv6_backend = rfc2462 278 | key_file = private/cakey.pem 279 | keys_path = /opt/stack/data/nova/keys 280 | keystone_ec2_insecure = False 281 | keystone_ec2_url = http://10.0.0.9:5000/v2.0/ec2tokens 282 | l3_lib = nova.network.l3.LinuxNetL3 283 | linuxnet_interface_driver = 284 | linuxnet_ovs_integration_bridge = br-int 285 | live_migration_retry_count = 30 286 | lockout_attempts = 5 287 | lockout_minutes = 15 288 | lockout_window = 15 289 | log-config-append = None 290 | log-date-format = %Y-%m-%d %H:%M:%S 291 | log-dir = None 292 | log-file = None 293 | log-format = None 294 | log_options = True 295 | 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 296 | logging_debug_format_suffix = from (pid=%(process)d) %(funcName)s %(pathname)s:%(lineno)d 297 | logging_default_format_string = %(asctime)s.%(msecs)03d %(color)s%(levelname)s %(name)s [-%(color)s] %(instance)s%(color)s%(message)s 298 | logging_exception_prefix = %(color)s%(asctime)s.%(msecs)03d TRACE %(name)s %(instance)s 299 | max_age = 0 300 | max_concurrent_builds = 10 301 | max_concurrent_live_migrations = 1 302 | max_header_line = 16384 303 | max_local_block_devices = 3 304 | maximum_instance_delete_attempts = 5 305 | memcached_servers = None 306 | metadata_cache_expiration = 15 307 | metadata_host = 10.0.0.9 308 | metadata_listen = 0.0.0.0 309 | metadata_listen_port = 8775 310 | metadata_manager = nova.api.manager.MetadataManager 311 | metadata_port = 8775 312 | metadata_workers = 2 313 | migrate_max_retries = -1 314 | mkisofs_cmd = genisoimage 315 | monkey_patch = False 316 | monkey_patch_modules = 317 | nova.api.ec2.cloud:nova.notifications.notify_decorator 318 | nova.compute.api:nova.notifications.notify_decorator 319 | multi_host = False 320 | my_block_storage_ip = 10.0.0.9 321 | my_ip = 10.0.0.9 322 | network_allocate_retries = 0 323 | network_api_class = nova.network.neutronv2.api.API 324 | network_device_mtu = None 325 | network_driver = nova.network.linux_net 326 | network_manager = nova.network.manager.VlanManager 327 | network_size = 256 328 | network_topic = network 329 | networks_path = /opt/stack/data/nova/networks 330 | neutron_default_tenant_id = default 331 | non_inheritable_image_properties = 332 | bittorrent 333 | cache_in_nova 334 | notification_driver = 335 | notification_topics = 336 | notifications 337 | notify_api_faults = False 338 | notify_on_state_change = None 339 | null_kernel = nokernel 340 | num_networks = 1 341 | osapi_compute_ext_list = 342 | osapi_compute_extension = 343 | nova.api.openstack.compute.legacy_v2.contrib.standard_extensions 344 | osapi_compute_link_prefix = None 345 | osapi_compute_listen = 0.0.0.0 346 | osapi_compute_listen_port = 8774 347 | osapi_compute_unique_server_name_scope = 348 | osapi_compute_workers = 2 349 | osapi_glance_link_prefix = None 350 | osapi_hide_server_address_states = 351 | building 352 | osapi_max_limit = 1000 353 | ovs_vsctl_timeout = 120 354 | password_length = 12 355 | pci_alias = 356 | pci_passthrough_whitelist = 357 | periodic_enable = True 358 | periodic_fuzzy_delay = 60 359 | policy_default_rule = default 360 | policy_dirs = 361 | policy.d 362 | policy_file = policy.json 363 | preallocate_images = none 364 | project_cert_subject = /C=US/ST=California/O=OpenStack/OU=NovaDev/CN=project-ca-%.16s-%s 365 | public_interface = eth0 366 | publish_errors = False 367 | pybasedir = /opt/stack/nova 368 | quota_cores = 20 369 | quota_driver = nova.quota.DbQuotaDriver 370 | quota_fixed_ips = -1 371 | quota_floating_ips = 10 372 | quota_injected_file_content_bytes = 10240 373 | quota_injected_file_path_length = 255 374 | quota_injected_files = 5 375 | quota_instances = 10 376 | quota_key_pairs = 100 377 | quota_metadata_items = 128 378 | quota_networks = 3 379 | quota_ram = 51200 380 | quota_security_group_rules = 20 381 | quota_security_groups = 10 382 | quota_server_group_members = 10 383 | quota_server_groups = 10 384 | ram_allocation_ratio = 0.0 385 | reboot_timeout = 0 386 | reclaim_instance_interval = 0 387 | region_list = 388 | remove_unused_base_images = True 389 | remove_unused_original_minimum_age_seconds = 86400 390 | report_interval = 10 391 | rescue_timeout = 0 392 | reservation_expire = 86400 393 | reserved_host_disk_mb = 0 394 | reserved_host_memory_mb = 512 395 | resize_confirm_window = 0 396 | resize_fs_using_block_device = False 397 | resume_guests_state_on_host_boot = False 398 | rootwrap_config = /etc/nova/rootwrap.conf 399 | routing_source_ip = 10.0.0.9 400 | rpc_backend = rabbit 401 | rpc_response_timeout = 60 402 | run_external_periodic_tasks = True 403 | running_deleted_instance_action = reap 404 | running_deleted_instance_poll_interval = 1800 405 | running_deleted_instance_timeout = 0 406 | s3_access_key = notchecked 407 | s3_affix_tenant = False 408 | s3_host = 10.0.0.9 409 | s3_port = 3333 410 | s3_secret_key = notchecked 411 | s3_use_ssl = False 412 | scheduler_available_filters = 413 | nova.scheduler.filters.all_filters 414 | scheduler_default_filters = 415 | AvailabilityZoneFilter 416 | ComputeCapabilitiesFilter 417 | ComputeFilter 418 | DiskFilter 419 | ImagePropertiesFilter 420 | RamFilter 421 | RetryFilter 422 | ServerGroupAffinityFilter 423 | ServerGroupAntiAffinityFilter 424 | scheduler_instance_sync_interval = 120 425 | scheduler_manager = nova.scheduler.manager.SchedulerManager 426 | scheduler_max_attempts = 3 427 | scheduler_topic = scheduler 428 | scheduler_tracks_instance_changes = True 429 | scheduler_weight_classes = 430 | nova.scheduler.weights.all_weighers 431 | secure_proxy_ssl_header = None 432 | security_group_api = neutron 433 | send_arp_for_ha = False 434 | send_arp_for_ha_count = 3 435 | service_down_time = 60 436 | servicegroup_driver = db 437 | share_dhcp_address = False 438 | shelved_offload_time = 0 439 | shelved_poll_interval = 3600 440 | shutdown_timeout = 60 441 | snapshot_name_template = snapshot-%s 442 | ssl_ca_file = None 443 | ssl_cert_file = None 444 | ssl_key_file = None 445 | state_path = /opt/stack/data/nova 446 | sync_power_state_interval = 600 447 | syslog-log-facility = LOG_USER 448 | tcp_keepidle = 600 449 | teardown_unused_network_gateway = False 450 | tempdir = None 451 | transport_url = None 452 | until_refresh = 0 453 | update_dns_entries = False 454 | update_resources_interval = 0 455 | use-syslog = False 456 | use-syslog-rfc-format = True 457 | use_cow_images = True 458 | use_forwarded_for = False 459 | use_ipv6 = False 460 | use_network_dns_servers = False 461 | use_neutron_default_nets = False 462 | use_project_ca = False 463 | use_rootwrap_daemon = False 464 | use_single_default_gateway = False 465 | use_stderr = True 466 | user_cert_subject = /C=US/ST=California/O=OpenStack/OU=NovaDev/CN=%.16s-%.16s-%s 467 | vcpu_pin_set = None 468 | vendordata_driver = nova.api.metadata.vendordata_json.JsonFileVendorData 469 | verbose = True 470 | vif_plugging_is_fatal = True 471 | vif_plugging_timeout = 300 472 | virt_mkfs = 473 | vlan_interface = None 474 | vlan_start = 100 475 | volume_api_class = nova.volume.cinder.API 476 | volume_usage_poll_interval = 0 477 | vpn_flavor = m1.tiny 478 | vpn_image_id = 0 479 | vpn_ip = 10.0.0.9 480 | vpn_key_suffix = -vpn 481 | vpn_start = 1000 482 | wsgi_default_pool_size = 1000 483 | wsgi_keep_alive = True 484 | wsgi_log_format = %(client_ip)s "%(request_line)s" status: %(status_code)s len: %(body_length)s time: %(wall_seconds).7f 485 | 486 | ephemeral_storage_encryption: 487 | cipher = aes-xts-plain64 488 | enabled = False 489 | key_size = 512 490 | 491 | glance: 492 | allowed_direct_url_schemes = 493 | api_insecure = False 494 | api_servers = 495 | http://10.0.0.9:9292 496 | host = 10.0.0.9 497 | num_retries = 0 498 | port = 9292 499 | protocol = http 500 | 501 | guestfs: 502 | debug = False 503 | 504 | image_file_url: 505 | filesystems = 506 | 507 | ironic: 508 | admin_auth_token = *** 509 | admin_password = *** 510 | admin_tenant_name = None 511 | admin_url = None 512 | admin_username = None 513 | api_endpoint = None 514 | api_max_retries = 60 515 | api_retry_interval = 2 516 | api_version = 1 517 | client_log_level = None 518 | 519 | keymgr: 520 | api_class = nova.keymgr.conf_key_mgr.ConfKeyManager 521 | 522 | keystone_authtoken: 523 | admin_password = *** 524 | admin_tenant_name = admin 525 | admin_token = *** 526 | admin_user = None 527 | auth-url = http://10.0.0.9:35357 528 | auth_admin_prefix = 529 | auth_host = 127.0.0.1 530 | auth_plugin = password 531 | auth_port = 35357 532 | auth_protocol = https 533 | auth_section = None 534 | www_authenticate_uri = http://10.0.0.9:5000 535 | auth_version = None 536 | cache = None 537 | cafile = /opt/stack/data/ca-bundle.pem 538 | certfile = None 539 | check_revocations_for_cached = False 540 | delay_auth_decision = False 541 | domain-id = None 542 | domain-name = None 543 | enforce_token_bind = permissive 544 | hash_algorithms = 545 | md5 546 | http_connect_timeout = None 547 | http_request_max_retries = 3 548 | identity_uri = None 549 | include_service_catalog = True 550 | insecure = False 551 | keyfile = None 552 | memcache_pool_conn_get_timeout = 10 553 | memcache_pool_dead_retry = 300 554 | memcache_pool_maxsize = 10 555 | memcache_pool_socket_timeout = 3 556 | memcache_pool_unused_timeout = 60 557 | memcache_secret_key = *** 558 | memcache_security_strategy = None 559 | memcache_use_advanced_pool = False 560 | memcached_servers = None 561 | password = password 562 | project-domain-id = default 563 | project-domain-name = None 564 | project-id = None 565 | project-name = service 566 | region_name = None 567 | revocation_cache_time = 10 568 | signing_dir = /var/cache/nova 569 | tenant-id = None 570 | tenant-name = None 571 | token_cache_time = 300 572 | trust-id = None 573 | user-domain-id = default 574 | user-domain-name = None 575 | user-id = None 576 | user-name = nova 577 | 578 | libvirt: 579 | block_migration_flag = VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE, VIR_MIGRATE_TUNNELLED, VIR_MIGRATE_NON_SHARED_INC 580 | checksum_base_images = False 581 | checksum_interval_seconds = 3600 582 | connection_uri = 583 | cpu_mode = none 584 | cpu_model = None 585 | disk_cachemodes = 586 | disk_prefix = None 587 | gid_maps = 588 | hw_disk_discard = None 589 | hw_machine_type = None 590 | image_info_filename_pattern = /opt/stack/data/nova/instances/_base/%(image)s.info 591 | images_rbd_ceph_conf = 592 | images_rbd_pool = rbd 593 | images_type = default 594 | images_volume_group = None 595 | inject_key = False 596 | inject_partition = -2 597 | inject_password = False 598 | iscsi_iface = None 599 | iscsi_use_multipath = False 600 | live_migration_bandwidth = 0 601 | live_migration_completion_timeout = 800 602 | live_migration_downtime = 500 603 | live_migration_downtime_delay = 75 604 | live_migration_downtime_steps = 10 605 | live_migration_flag = VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE, VIR_MIGRATE_TUNNELLED 606 | live_migration_progress_timeout = 150 607 | live_migration_uri = qemu+ssh://stack@%s/system 608 | mem_stats_period_seconds = 10 609 | num_iscsi_scan_tries = 5 610 | qemu_allowed_storage_drivers = 611 | rbd_secret_uuid = None 612 | rbd_user = None 613 | remote_filesystem_transport = ssh 614 | remove_unused_kernels = True 615 | remove_unused_resized_minimum_age_seconds = 3600 616 | rescue_image_id = None 617 | rescue_kernel_id = None 618 | rescue_ramdisk_id = None 619 | rng_dev_path = None 620 | snapshot_compression = False 621 | snapshot_image_format = None 622 | snapshots_directory = /opt/stack/data/nova/instances/snapshots 623 | sparse_logical_volumes = False 624 | sysinfo_serial = auto 625 | uid_maps = 626 | use_usb_tablet = False 627 | use_virtio_for_bridges = True 628 | virt_type = kvm 629 | volume_clear = zero 630 | volume_clear_size = 0 631 | wait_soft_reboot_seconds = 120 632 | xen_hvmloader_path = /usr/lib/xen/boot/hvmloader 633 | 634 | mks: 635 | enabled = False 636 | mksproxy_base_url = http://127.0.0.1:6090/ 637 | 638 | neutron: 639 | admin_auth_url = http://10.0.0.9:35357/v2.0 640 | admin_password = *** 641 | admin_tenant_id = None 642 | admin_tenant_name = service 643 | admin_user_id = None 644 | admin_username = neutron 645 | auth_plugin = None 646 | auth_section = None 647 | auth_strategy = keystone 648 | cafile = None 649 | certfile = None 650 | extension_sync_interval = 600 651 | insecure = False 652 | keyfile = None 653 | metadata_proxy_shared_secret = *** 654 | ovs_bridge = br-int 655 | region_name = RegionOne 656 | service_metadata_proxy = True 657 | timeout = None 658 | url = http://10.0.0.9:9696 659 | 660 | osapi_v21: 661 | enabled = True 662 | extensions_blacklist = 663 | extensions_whitelist = 664 | 665 | oslo_concurrency: 666 | disable_process_locking = False 667 | lock_path = /opt/stack/data/nova 668 | 669 | oslo_messaging_rabbit: 670 | amqp_auto_delete = False 671 | amqp_durable_queues = False 672 | fake_rabbit = False 673 | heartbeat_rate = 2 674 | heartbeat_timeout_threshold = 60 675 | kombu_reconnect_delay = 1.0 676 | kombu_reconnect_timeout = 60 677 | kombu_ssl_ca_certs = 678 | kombu_ssl_certfile = 679 | kombu_ssl_keyfile = 680 | kombu_ssl_version = 681 | rabbit_ha_queues = False 682 | rabbit_host = localhost 683 | rabbit_hosts = 684 | 10.0.0.9 685 | rabbit_login_method = AMQPLAIN 686 | rabbit_max_retries = 0 687 | rabbit_password = *** 688 | rabbit_port = 5672 689 | rabbit_retry_backoff = 2 690 | rabbit_retry_interval = 1 691 | rabbit_use_ssl = False 692 | rabbit_userid = stackrabbit 693 | rabbit_virtual_host = / 694 | rpc_conn_pool_size = 30 695 | send_single_reply = False 696 | 697 | oslo_middleware: 698 | max_request_body_size = 114688 699 | 700 | oslo_versionedobjects: 701 | fatal_exception_format_errors = False 702 | 703 | rdp: 704 | enabled = False 705 | html5_proxy_base_url = http://127.0.0.1:6083/ 706 | 707 | remote_debug: 708 | host = None 709 | port = None 710 | 711 | serial_console: 712 | base_url = ws://127.0.0.1:6083/ 713 | enabled = False 714 | listen = 127.0.0.1 715 | port_range = 10000:20000 716 | proxyclient_address = 127.0.0.1 717 | 718 | spice: 719 | agent_enabled = True 720 | enabled = False 721 | html5proxy_base_url = http://10.0.0.9:6082/spice_auto.html 722 | keymap = en-us 723 | server_listen = 127.0.0.1 724 | server_proxyclient_address = 127.0.0.1 725 | 726 | ssl: 727 | ca_file = None 728 | cert_file = None 729 | key_file = None 730 | 731 | upgrade_levels: 732 | baseapi = None 733 | cells = None 734 | cert = None 735 | compute = None 736 | conductor = None 737 | console = None 738 | consoleauth = None 739 | network = None 740 | scheduler = None 741 | 742 | vnc: 743 | enabled = False 744 | keymap = en-us 745 | novncproxy_base_url = http://10.0.0.9:6080/vnc_auto.html 746 | vncserver_listen = 127.0.0.1 747 | vncserver_proxyclient_address = 127.0.0.1 748 | xvpvncproxy_base_url = http://10.0.0.9:6081/console 749 | 750 | workarounds: 751 | destroy_after_evacuate = True 752 | disable_libvirt_livesnapshot = True 753 | disable_rootwrap = False 754 | handle_virt_lifecycle_events = True 755 | -------------------------------------------------------------------------------- /doc/source/user/usage.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Usage 3 | ======= 4 | 5 | Every long running service process should have a call to install a 6 | signal handler which will trigger the guru meditation framework upon 7 | receipt of SIGUSR1/SIGUSR2. This will result in the process dumping a 8 | complete report of its current state to stderr. 9 | 10 | For RPC listeners, it may also be desirable to install some kind of hook in 11 | the RPC request dispatcher that will save a guru meditation report whenever 12 | the processing of a request results in an uncaught exception. It could save 13 | these reports to a well known directory 14 | (/var/log/openstack///) for later analysis by the sysadmin 15 | or automated bug analysis tools. 16 | 17 | To use oslo.reports in a project, you need to add the following call to 18 | :py:func:`~oslo_reports.TextGuruMeditation.setup_autorun` somewhere really 19 | early in the startup sequence of the process:: 20 | 21 | from oslo_reports import guru_meditation_report as gmr 22 | 23 | gmr.TextGuruMeditation.setup_autorun(version='13.0.0') 24 | 25 | Note that the version parameter is the version of the component itself. 26 | 27 | To trigger the report to be generated:: 28 | 29 | kill -SIGUSR2 30 | 31 | .. note:: 32 | 33 | On SELinux platforms the report process may fail with an AccessDenied 34 | exception. If this happens, temporarily disable SELinux enforcement 35 | by running ``sudo setenforce 0``, trigger the report, then turn SELinux 36 | back on by running ``sudo setenforce 1``. 37 | 38 | Here is a sample report: 39 | 40 | .. include:: report.txt 41 | :literal: 42 | -------------------------------------------------------------------------------- /oslo_reports/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides a way to generate serializable reports 16 | 17 | This package/module provides mechanisms for defining reports 18 | which may then be serialized into various data types. Each 19 | report ( :class:`oslo_reports.report.BasicReport` ) 20 | is composed of one or more report sections 21 | ( :class:`oslo_reports.report.BasicSection` ), 22 | which contain generators which generate data models 23 | ( :class:`oslo_reports.models.base.ReportModels` ), 24 | which are then serialized by views. 25 | """ 26 | -------------------------------------------------------------------------------- /oslo_reports/_i18n.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | """oslo.i18n integration module. 14 | 15 | See https://docs.openstack.org/oslo.i18n/latest/user/index.html . 16 | 17 | """ 18 | 19 | import oslo_i18n 20 | 21 | 22 | _translators = oslo_i18n.TranslatorFactory(domain='oslo_reports') 23 | 24 | # The primary translation function using the well-known name "_" 25 | _ = _translators.primary 26 | -------------------------------------------------------------------------------- /oslo_reports/_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Various utilities for report generation 16 | 17 | This module includes various utilities 18 | used in generating reports. 19 | """ 20 | 21 | 22 | class StringWithAttrs(str): 23 | """A String that can have arbitrary attributes""" 24 | 25 | pass 26 | -------------------------------------------------------------------------------- /oslo_reports/generators/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides Data Model Generators 16 | 17 | This module defines classes for generating data models 18 | ( :class:`oslo_reports.models.base.ReportModel` ). 19 | A generator is any object which is callable with no parameters 20 | and returns a data model. 21 | """ 22 | -------------------------------------------------------------------------------- /oslo_reports/generators/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides OpenStack config generators 16 | 17 | This module defines a class for configuration 18 | generators for generating the model in 19 | :mod:`oslo_reports.models.conf`. 20 | """ 21 | 22 | from oslo_config import cfg 23 | 24 | from oslo_reports.models import conf as cm 25 | 26 | 27 | class ConfigReportGenerator: 28 | """A Configuration Data Generator 29 | 30 | This generator returns 31 | :class:`oslo_reports.models.conf.ConfigModel`, 32 | by default using the configuration options stored 33 | in :attr:`oslo_config.cfg.CONF`, which is where 34 | OpenStack stores everything. 35 | 36 | :param cnf: the configuration option object 37 | :type cnf: :class:`oslo_config.cfg.ConfigOpts` 38 | """ 39 | 40 | def __init__(self, cnf=cfg.CONF): 41 | self.conf_obj = cnf 42 | 43 | def __call__(self): 44 | return cm.ConfigModel(self.conf_obj) 45 | -------------------------------------------------------------------------------- /oslo_reports/generators/process.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides process-data generators 16 | 17 | This modules defines a class for generating 18 | process data by way of the psutil package. 19 | """ 20 | 21 | import os 22 | 23 | import psutil 24 | 25 | from oslo_reports.models import process as pm 26 | 27 | 28 | class ProcessReportGenerator: 29 | """A Process Data Generator 30 | 31 | This generator returns a 32 | :class:`oslo_reports.models.process.ProcessModel` 33 | based on the current process (which will also include 34 | all subprocesses, recursively) using the :class:`psutil.Process` class`. 35 | """ 36 | 37 | def __call__(self): 38 | return pm.ProcessModel(psutil.Process(os.getpid())) 39 | -------------------------------------------------------------------------------- /oslo_reports/generators/threading.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides thread-related generators 16 | 17 | This module defines classes for threading-related 18 | generators for generating the models in 19 | :mod:`oslo_reports.models.threading`. 20 | """ 21 | 22 | import gc 23 | import sys 24 | import threading 25 | 26 | from oslo_reports.models import threading as tm 27 | from oslo_reports.models import with_default_views as mwdv 28 | from oslo_reports.views.text import generic as text_views 29 | 30 | 31 | def _find_objects(t): 32 | """Find Objects in the GC State 33 | 34 | This horribly hackish method locates objects of a 35 | given class in the current python instance's garbage 36 | collection state. In case you couldn't tell, this is 37 | horribly hackish, but is necessary for locating all 38 | green threads, since they don't keep track of themselves 39 | like normal threads do in python. 40 | 41 | :param class t: the class of object to locate 42 | :rtype: list 43 | :returns: a list of objects of the given type 44 | """ 45 | 46 | return [o for o in gc.get_objects() if isinstance(o, t)] 47 | 48 | 49 | class ThreadReportGenerator: 50 | """A Thread Data Generator 51 | 52 | This generator returns a collection of 53 | :class:`oslo_reports.models.threading.ThreadModel` 54 | objects by introspecting the current python state using 55 | :func:`sys._current_frames()` . Its constructor may optionally 56 | be passed a frame object. This frame object will be interpreted 57 | as the actual stack trace for the current thread, and, come generation 58 | time, will be used to replace the stack trace of the thread in which 59 | this code is running. 60 | """ 61 | 62 | def __init__(self, curr_thread_traceback=None): 63 | self.traceback = curr_thread_traceback 64 | 65 | def __call__(self): 66 | threadModels = { 67 | thread_id: tm.ThreadModel(thread_id, stack) 68 | for thread_id, stack in sys._current_frames().items() 69 | } 70 | 71 | if self.traceback is not None: 72 | curr_thread_id = threading.current_thread().ident 73 | threadModels[curr_thread_id] = tm.ThreadModel(curr_thread_id, 74 | self.traceback) 75 | 76 | return mwdv.ModelWithDefaultViews(threadModels, 77 | text_view=text_views.MultiView()) 78 | 79 | 80 | class GreenThreadReportGenerator: 81 | """A Green Thread Data Generator 82 | 83 | This generator returns a collection of 84 | :class:`oslo_reports.models.threading.GreenThreadModel` 85 | objects by introspecting the current python garbage collection 86 | state, and sifting through for :class:`greenlet.greenlet` objects. 87 | 88 | .. seealso:: 89 | 90 | Function :func:`_find_objects` 91 | """ 92 | 93 | def __call__(self): 94 | import greenlet 95 | 96 | threadModels = [ 97 | tm.GreenThreadModel(gr.gr_frame) 98 | for gr in _find_objects(greenlet.greenlet) 99 | ] 100 | 101 | return mwdv.ModelWithDefaultViews(threadModels, 102 | text_view=text_views.MultiView()) 103 | -------------------------------------------------------------------------------- /oslo_reports/generators/version.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides OpenStack version generators 16 | 17 | This module defines a class for OpenStack 18 | version and package information 19 | generators for generating the model in 20 | :mod:`oslo_reports.models.version`. 21 | """ 22 | 23 | from oslo_reports.models import version as vm 24 | 25 | 26 | class PackageReportGenerator: 27 | """A Package Information Data Generator 28 | 29 | This generator returns 30 | :class:`oslo_reports.models.version.PackageModel`, 31 | extracting data from the given version object, which should follow 32 | the general format defined in Nova's version information (i.e. it 33 | should contain the methods vendor_string, product_string, and 34 | version_string_with_package). 35 | 36 | :param version_object: the version information object 37 | """ 38 | 39 | def __init__(self, version_obj): 40 | self.version_obj = version_obj 41 | 42 | def __call__(self): 43 | if hasattr(self.version_obj, "vendor_string"): 44 | vendor_string = self.version_obj.vendor_string() 45 | else: 46 | vendor_string = None 47 | 48 | if hasattr(self.version_obj, "product_string"): 49 | product_string = self.version_obj.product_string() 50 | else: 51 | product_string = None 52 | 53 | if hasattr(self.version_obj, "version_string_with_package"): 54 | version_string_with_package = self.version_obj.\ 55 | version_string_with_package() 56 | else: 57 | version_string_with_package = None 58 | 59 | return vm.PackageModel(vendor_string, product_string, 60 | version_string_with_package) 61 | -------------------------------------------------------------------------------- /oslo_reports/guru_meditation_report.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides Guru Meditation Report 16 | 17 | This module defines the actual OpenStack Guru Meditation 18 | Report class. 19 | 20 | This can be used in the OpenStack command definition files. 21 | For example, in a nova command module (under nova/cmd): 22 | 23 | .. code-block:: python 24 | :emphasize-lines: 8,9,10 25 | 26 | from oslo_config import cfg 27 | from oslo_log import log as oslo_logging 28 | from oslo_reports import opts as gmr_opts 29 | from oslo_reports import guru_meditation_report as gmr 30 | 31 | CONF = cfg.CONF 32 | # maybe import some options here... 33 | 34 | def main(): 35 | oslo_logging.register_options(CONF) 36 | gmr_opts.set_defaults(CONF) 37 | 38 | CONF(sys.argv[1:], default_config_files=['myapp.conf']) 39 | oslo_logging.setup(CONF, 'myapp') 40 | 41 | gmr.TextGuruMeditation.register_section('Some Special Section', 42 | special_section_generator) 43 | gmr.TextGuruMeditation.setup_autorun(version_object, conf=CONF) 44 | 45 | server = service.Service.create(binary='some-service', 46 | topic=CONF.some_service_topic) 47 | service.serve(server) 48 | service.wait() 49 | 50 | Then, you can do 51 | 52 | .. code-block:: bash 53 | 54 | $ kill -USR2 $SERVICE_PID 55 | 56 | and get a Guru Meditation Report in the file or terminal 57 | where stderr is logged for that given service. 58 | """ 59 | 60 | import inspect 61 | import logging 62 | import os 63 | import signal 64 | import stat 65 | import sys 66 | import threading 67 | import time 68 | import traceback 69 | 70 | from oslo_utils import timeutils 71 | 72 | from oslo_reports.generators import conf as cgen 73 | from oslo_reports.generators import process as prgen 74 | from oslo_reports.generators import threading as tgen 75 | from oslo_reports.generators import version as pgen 76 | from oslo_reports import report 77 | 78 | try: 79 | import greenlet 80 | except ImportError: 81 | greenlet = None 82 | 83 | 84 | LOG = logging.getLogger(__name__) 85 | 86 | 87 | class GuruMeditation: 88 | """A Guru Meditation Report Mixin/Base Class 89 | 90 | This class is a base class for Guru Meditation Reports. 91 | It provides facilities for registering sections and 92 | setting up functionality to auto-run the report on 93 | a certain signal or use file modification events. 94 | 95 | This class should always be used in conjunction with 96 | a Report class via multiple inheritance. It should 97 | always come first in the class list to ensure the 98 | MRO is correct. 99 | """ 100 | 101 | timestamp_fmt = "%Y%m%d%H%M%S" 102 | 103 | def __init__(self, version_obj, sig_handler_tb=None, *args, **kwargs): 104 | self.version_obj = version_obj 105 | self.traceback = sig_handler_tb 106 | 107 | super().__init__(*args, **kwargs) 108 | self.start_section_index = len(self.sections) 109 | 110 | @classmethod 111 | def register_section(cls, section_title, generator): 112 | """Register a New Section 113 | 114 | This method registers a persistent section for the current 115 | class. 116 | 117 | :param str section_title: the title of the section 118 | :param generator: the generator for the section 119 | """ 120 | 121 | try: 122 | cls.persistent_sections.append([section_title, generator]) 123 | except AttributeError: 124 | cls.persistent_sections = [[section_title, generator]] 125 | 126 | @classmethod 127 | def setup_autorun(cls, version, service_name=None, 128 | log_dir=None, signum=None, conf=None, 129 | setup_signal=True): 130 | """Set Up Auto-Run 131 | 132 | This method sets up the Guru Meditation Report to automatically 133 | get dumped to stderr or a file in a given dir when the given signal 134 | is received. It can also use file modification events instead of 135 | signals. 136 | 137 | :param version: the version object for the current product 138 | :param service_name: this program name used to construct logfile name 139 | :param logdir: path to a log directory where to create a file 140 | :param signum: the signal to associate with running the report 141 | :param conf: Configuration object, managed by the caller. 142 | :param setup_signal: Set up a signal handler 143 | """ 144 | 145 | if log_dir is None and conf is not None: 146 | log_dir = conf.oslo_reports.log_dir 147 | 148 | if signum: 149 | if setup_signal: 150 | cls._setup_signal(signum, version, service_name, log_dir) 151 | return 152 | 153 | if conf and conf.oslo_reports.file_event_handler: 154 | cls._setup_file_watcher( 155 | conf.oslo_reports.file_event_handler, 156 | conf.oslo_reports.file_event_handler_interval, 157 | version, service_name, log_dir) 158 | else: 159 | if setup_signal and hasattr(signal, 'SIGUSR2'): 160 | cls._setup_signal(signal.SIGUSR2, 161 | version, service_name, log_dir) 162 | 163 | @classmethod 164 | def _setup_file_watcher(cls, filepath, interval, version, service_name, 165 | log_dir): 166 | 167 | st = os.stat(filepath) 168 | if not bool(st.st_mode & stat.S_IRGRP): 169 | LOG.error("Guru Meditation Report does not have read " 170 | "permissions to '%s' file.", filepath) 171 | 172 | def _handler(): 173 | mtime = time.time() 174 | while True: 175 | try: 176 | stat = os.stat(filepath) 177 | if stat.st_mtime > mtime: 178 | cls.handle_signal(version, service_name, log_dir, None) 179 | mtime = stat.st_mtime 180 | except OSError: 181 | msg = ("Guru Meditation Report cannot read " + 182 | "'{}' file".format(filepath)) 183 | raise OSError(msg) 184 | finally: 185 | time.sleep(interval) 186 | 187 | th = threading.Thread(target=_handler) 188 | th.daemon = True 189 | th.start() 190 | 191 | @classmethod 192 | def _setup_signal(cls, signum, version, service_name, log_dir): 193 | signal.signal(signum, 194 | lambda sn, f: cls.handle_signal( 195 | version, service_name, log_dir, f)) 196 | 197 | @classmethod 198 | def handle_signal(cls, version, service_name, log_dir, frame): 199 | """The Signal Handler 200 | 201 | This method (indirectly) handles receiving a registered signal and 202 | dumping the Guru Meditation Report to stderr or a file in a given dir. 203 | If service name and log dir are not None, the report will be dumped to 204 | a file named $service_name_gurumeditation_$current_time in the log_dir 205 | directory. 206 | This method is designed to be curried into a proper signal handler by 207 | currying out the version 208 | parameter. 209 | 210 | :param version: the version object for the current product 211 | :param service_name: this program name used to construct logfile name 212 | :param logdir: path to a log directory where to create a file 213 | :param frame: the frame object provided to the signal handler 214 | """ 215 | 216 | try: 217 | res = cls(version, frame).run() 218 | except Exception: 219 | traceback.print_exc(file=sys.stderr) 220 | print("Unable to run Guru Meditation Report!", 221 | file=sys.stderr) 222 | else: 223 | if log_dir: 224 | service_name = service_name or os.path.basename( 225 | inspect.stack()[-1][1]) 226 | filename = "{}_gurumeditation_{}".format( 227 | service_name, timeutils.utcnow().strftime( 228 | cls.timestamp_fmt)) 229 | filepath = os.path.join(log_dir, filename) 230 | try: 231 | with open(filepath, "w") as dumpfile: 232 | dumpfile.write(res) 233 | except Exception: 234 | print("Unable to dump Guru Meditation Report to file %s" % 235 | (filepath,), file=sys.stderr) 236 | else: 237 | print(res, file=sys.stderr) 238 | 239 | def _readd_sections(self): 240 | del self.sections[self.start_section_index:] 241 | 242 | self.add_section('Package', 243 | pgen.PackageReportGenerator(self.version_obj)) 244 | 245 | self.add_section('Threads', 246 | tgen.ThreadReportGenerator(self.traceback)) 247 | 248 | if greenlet: 249 | self.add_section('Green Threads', 250 | tgen.GreenThreadReportGenerator()) 251 | 252 | self.add_section('Processes', 253 | prgen.ProcessReportGenerator()) 254 | 255 | self.add_section('Configuration', 256 | cgen.ConfigReportGenerator()) 257 | 258 | try: 259 | for section_title, generator in self.persistent_sections: 260 | self.add_section(section_title, generator) 261 | except AttributeError: 262 | pass 263 | 264 | def run(self): 265 | self._readd_sections() 266 | return super().run() 267 | 268 | 269 | # GuruMeditation must come first to get the correct MRO 270 | class TextGuruMeditation(GuruMeditation, report.TextReport): 271 | """A Text Guru Meditation Report 272 | 273 | This report is the basic human-readable Guru Meditation Report 274 | 275 | It contains the following sections by default 276 | (in addition to any registered persistent sections): 277 | 278 | - Package Information 279 | 280 | - Threads List 281 | 282 | - Green Threads List 283 | 284 | - Process List 285 | 286 | - Configuration Options 287 | 288 | :param version_obj: the version object for the current product 289 | :param traceback: an (optional) frame object providing the actual 290 | traceback for the current thread 291 | """ 292 | 293 | def __init__(self, version_obj, traceback=None): 294 | super().__init__(version_obj, traceback, 'Guru Meditation') 295 | -------------------------------------------------------------------------------- /oslo_reports/locale/en_GB/LC_MESSAGES/oslo_reports.po: -------------------------------------------------------------------------------- 1 | # Andi Chandler , 2016. #zanata 2 | # Andi Chandler , 2017. #zanata 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: oslo.reports VERSION\n" 6 | "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" 7 | "POT-Creation-Date: 2018-01-29 04:42+0000\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "PO-Revision-Date: 2017-10-07 10:54+0000\n" 12 | "Last-Translator: Andi Chandler \n" 13 | "Language-Team: English (United Kingdom)\n" 14 | "Language: en_GB\n" 15 | "X-Generator: Zanata 4.3.3\n" 16 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 17 | 18 | msgid "How many seconds to wait between polls when file_event_handler is set" 19 | msgstr "How many seconds to wait between polls when file_event_handler is set" 20 | 21 | msgid "Path to a log directory where to create a file" 22 | msgstr "Path to a log directory where to create a file" 23 | 24 | msgid "" 25 | "The path to a file to watch for changes to trigger the reports, instead of " 26 | "signals. Setting this option disables the signal trigger for the reports. If " 27 | "application is running as a WSGI application it is recommended to use this " 28 | "instead of signals." 29 | msgstr "" 30 | "The path to a file to watch for changes to trigger the reports, instead of " 31 | "signals. Setting this option disables the signal trigger for the reports. If " 32 | "application is running as a WSGI application it is recommended to use this " 33 | "instead of signals." 34 | -------------------------------------------------------------------------------- /oslo_reports/models/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides data models 16 | 17 | This module provides both the base data model, 18 | as well as several predefined specific data models 19 | to be used in reports. 20 | """ 21 | -------------------------------------------------------------------------------- /oslo_reports/models/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides the base report model 16 | 17 | This module defines a class representing the basic report 18 | data model from which all data models should inherit (or 19 | at least implement similar functionality). Data models 20 | store unserialized data generated by generators during 21 | the report serialization process. 22 | """ 23 | 24 | from collections import abc 25 | import copy 26 | 27 | 28 | class ReportModel(abc.MutableMapping): 29 | """A Report Data Model 30 | 31 | A report data model contains data generated by some 32 | generator method or class. Data may be read or written 33 | using dictionary-style access, and may be read (but not 34 | written) using object-member-style access. Additionally, 35 | a data model may have an associated view. This view is 36 | used to serialize the model when str() is called on the 37 | model. An appropriate object for a view is callable with 38 | a single parameter: the model to be serialized. 39 | 40 | If present, the object passed in as data will be transformed 41 | into a standard python dict. For mappings, this is fairly 42 | straightforward. For sequences, the indices become keys 43 | and the items become values. 44 | 45 | :param data: a sequence or mapping of data to associate with the model 46 | :param attached_view: a view object to attach to this model 47 | """ 48 | 49 | def __init__(self, data=None, attached_view=None): 50 | self.attached_view = attached_view 51 | 52 | if data is not None: 53 | if isinstance(data, abc.Mapping): 54 | self.data = dict(data) 55 | elif isinstance(data, abc.Sequence): 56 | # convert a list [a, b, c] to a dict {0: a, 1: b, 2: c} 57 | self.data = dict(enumerate(data)) 58 | else: 59 | raise TypeError('Data for the model must be a sequence ' 60 | 'or mapping.') 61 | else: 62 | self.data = {} 63 | 64 | def __str__(self): 65 | self_cpy = copy.deepcopy(self) 66 | for key in self_cpy: 67 | if getattr(self_cpy[key], 'attached_view', None) is not None: 68 | self_cpy[key] = str(self_cpy[key]) 69 | 70 | if self.attached_view is not None: 71 | return self.attached_view(self_cpy) 72 | else: 73 | raise Exception("Cannot stringify model: no attached view") 74 | 75 | def __repr__(self): 76 | if self.attached_view is not None: 77 | return ("").format(cl=type(self), 80 | dt=self.data, 81 | vw=type(self.attached_view)) 82 | else: 83 | return ("").format(cl=type(self), 85 | dt=self.data) 86 | 87 | def __getitem__(self, attrname): 88 | return self.data[attrname] 89 | 90 | def __setitem__(self, attrname, attrval): 91 | self.data[attrname] = attrval 92 | 93 | def __delitem__(self, attrname): 94 | del self.data[attrname] 95 | 96 | def __contains__(self, key): 97 | return self.data.__contains__(key) 98 | 99 | def __getattr__(self, attrname): 100 | # Needed for deepcopy in Python3. That will avoid an infinite loop 101 | # in __getattr__ . 102 | if 'data' not in self.__dict__: 103 | self.data = {} 104 | 105 | try: 106 | return self.data[attrname] 107 | except KeyError: 108 | # we don't have that key in data, and the 109 | # model class doesn't have that attribute 110 | raise AttributeError( 111 | "'{cl}' object has no attribute '{an}'".format( 112 | cl=type(self).__name__, an=attrname 113 | ) 114 | ) 115 | 116 | def __len__(self): 117 | return len(self.data) 118 | 119 | def __iter__(self): 120 | return self.data.__iter__() 121 | 122 | def set_current_view_type(self, tp, visited=None): 123 | """Set the current view type 124 | 125 | This method attempts to set the current view 126 | type for this model and all submodels by calling 127 | itself recursively on all values, traversing 128 | intervening sequences and mappings when possible, 129 | and ignoring all other objects. 130 | 131 | :param tp: the type of the view ('text', 'json', 'xml', etc) 132 | :param visited: a set of object ids for which the corresponding objects 133 | have already had their view type set 134 | """ 135 | 136 | if visited is None: 137 | visited = set() 138 | 139 | def traverse_obj(obj): 140 | oid = id(obj) 141 | 142 | # don't die on recursive structures, 143 | # and don't treat strings like sequences 144 | if oid in visited or isinstance(obj, str): 145 | return 146 | 147 | visited.add(oid) 148 | 149 | if hasattr(obj, 'set_current_view_type'): 150 | obj.set_current_view_type(tp, visited=visited) 151 | 152 | if isinstance(obj, abc.Sequence): 153 | for item in obj: 154 | traverse_obj(item) 155 | 156 | elif isinstance(obj, abc.Mapping): 157 | for val in obj.values(): 158 | traverse_obj(val) 159 | 160 | traverse_obj(self) 161 | -------------------------------------------------------------------------------- /oslo_reports/models/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides OpenStack Configuration Model 16 | 17 | This module defines a class representing the data 18 | model for :mod:`oslo_config` configuration options 19 | """ 20 | 21 | from oslo_reports.models import with_default_views as mwdv 22 | from oslo_reports.views.text import generic as generic_text_views 23 | 24 | 25 | class ConfigModel(mwdv.ModelWithDefaultViews): 26 | """A Configuration Options Model 27 | 28 | This model holds data about a set of configuration options 29 | from :mod:`oslo_config`. It supports both the default group 30 | of options and named option groups. 31 | 32 | :param conf_obj: a configuration object 33 | :type conf_obj: :class:`oslo_config.cfg.ConfigOpts` 34 | """ 35 | 36 | def __init__(self, conf_obj): 37 | kv_view = generic_text_views.KeyValueView(dict_sep=": ", 38 | before_dict='') 39 | super().__init__(text_view=kv_view) 40 | 41 | def opt_title(optname, co): 42 | return co._opts[optname]['opt'].name 43 | 44 | def opt_value(opt_obj, value): 45 | if opt_obj['opt'].secret: 46 | return '***' 47 | else: 48 | return value 49 | 50 | self['default'] = { 51 | opt_title(optname, conf_obj): 52 | opt_value(conf_obj._opts[optname], conf_obj[optname]) 53 | for optname in conf_obj._opts 54 | } 55 | 56 | groups = {} 57 | for groupname in conf_obj._groups: 58 | group_obj = conf_obj._groups[groupname] 59 | curr_group_opts = { 60 | opt_title(optname, group_obj): 61 | opt_value(group_obj._opts[optname], 62 | conf_obj[groupname][optname]) 63 | for optname in group_obj._opts} 64 | groups[group_obj.name] = curr_group_opts 65 | 66 | self.update(groups) 67 | -------------------------------------------------------------------------------- /oslo_reports/models/process.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides a process model 16 | 17 | This module defines a class representing a process, 18 | potentially with subprocesses. 19 | """ 20 | 21 | import oslo_reports.models.with_default_views as mwdv 22 | import oslo_reports.views.text.process as text_views 23 | 24 | 25 | class ProcessModel(mwdv.ModelWithDefaultViews): 26 | """A Process Model 27 | 28 | This model holds data about a process, 29 | including references to any subprocesses 30 | 31 | :param process: a :class:`psutil.Process` object 32 | """ 33 | 34 | def __init__(self, process): 35 | super().__init__( 36 | text_view=text_views.ProcessView()) 37 | 38 | self['pid'] = process.pid 39 | self['parent_pid'] = process.ppid() 40 | if hasattr(process, 'uids'): 41 | self['uids'] = { 42 | 'real': process.uids().real, 43 | 'effective': process.uids().effective, 44 | 'saved': process.uids().saved 45 | } 46 | else: 47 | self['uids'] = {'real': None, 48 | 'effective': None, 49 | 'saved': None} 50 | 51 | if hasattr(process, 'gids'): 52 | self['gids'] = { 53 | 'real': process.gids().real, 54 | 'effective': process.gids().effective, 55 | 'saved': process.gids().saved 56 | } 57 | else: 58 | self['gids'] = {'real': None, 59 | 'effective': None, 60 | 'saved': None} 61 | 62 | self['username'] = process.username() 63 | self['command'] = process.cmdline() 64 | self['state'] = process.status() 65 | 66 | children = process.children() 67 | self['children'] = [ProcessModel(pr) for pr in children] 68 | -------------------------------------------------------------------------------- /oslo_reports/models/threading.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides threading and stack-trace models 16 | 17 | This module defines classes representing thread, green 18 | thread, and stack trace data models 19 | """ 20 | 21 | import traceback 22 | 23 | from oslo_reports.models import with_default_views as mwdv 24 | from oslo_reports.views.text import threading as text_views 25 | 26 | 27 | class StackTraceModel(mwdv.ModelWithDefaultViews): 28 | """A Stack Trace Model 29 | 30 | This model holds data from a python stack trace, 31 | commonly extracted from running thread information 32 | 33 | :param stack_state: the python stack_state object 34 | """ 35 | 36 | def __init__(self, stack_state): 37 | super().__init__( 38 | text_view=text_views.StackTraceView()) 39 | 40 | if (stack_state is not None): 41 | self['lines'] = [ 42 | {'filename': fn, 'line': ln, 'name': nm, 'code': cd} 43 | for fn, ln, nm, cd in traceback.extract_stack(stack_state) 44 | ] 45 | # FIXME(flepied): under Python3 f_exc_type doesn't exist 46 | # anymore so we lose information about exceptions 47 | if getattr(stack_state, 'f_exc_type', None) is not None: 48 | self['root_exception'] = { 49 | 'type': stack_state.f_exc_type, 50 | 'value': stack_state.f_exc_value} 51 | else: 52 | self['root_exception'] = None 53 | else: 54 | self['lines'] = [] 55 | self['root_exception'] = None 56 | 57 | 58 | class ThreadModel(mwdv.ModelWithDefaultViews): 59 | """A Thread Model 60 | 61 | This model holds data for information about an 62 | individual thread. It holds both a thread id, 63 | as well as a stack trace for the thread 64 | 65 | .. seealso:: 66 | 67 | Class :class:`StackTraceModel` 68 | 69 | :param int thread_id: the id of the thread 70 | :param stack: the python stack state for the current thread 71 | """ 72 | 73 | # threadId, stack in sys._current_frams().items() 74 | def __init__(self, thread_id, stack): 75 | super().__init__(text_view=text_views.ThreadView()) 76 | 77 | self['thread_id'] = thread_id 78 | self['stack_trace'] = StackTraceModel(stack) 79 | 80 | 81 | class GreenThreadModel(mwdv.ModelWithDefaultViews): 82 | """A Green Thread Model 83 | 84 | This model holds data for information about an 85 | individual thread. Unlike the thread model, 86 | it holds just a stack trace, since green threads 87 | do not have thread ids. 88 | 89 | .. seealso:: 90 | 91 | Class :class:`StackTraceModel` 92 | 93 | :param stack: the python stack state for the green thread 94 | """ 95 | 96 | # gr in greenpool.coroutines_running --> gr.gr_frame 97 | def __init__(self, stack): 98 | super().__init__( 99 | {'stack_trace': StackTraceModel(stack)}, 100 | text_view=text_views.GreenThreadView()) 101 | -------------------------------------------------------------------------------- /oslo_reports/models/version.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides OpenStack Version Info Model 16 | 17 | This module defines a class representing the data 18 | model for OpenStack package and version information 19 | """ 20 | 21 | from oslo_reports.models import with_default_views as mwdv 22 | from oslo_reports.views.text import generic as generic_text_views 23 | 24 | 25 | class PackageModel(mwdv.ModelWithDefaultViews): 26 | """A Package Information Model 27 | 28 | This model holds information about the current 29 | package. It contains vendor, product, and version 30 | information. 31 | 32 | :param str vendor: the product vendor 33 | :param str product: the product name 34 | :param str version: the product version 35 | """ 36 | 37 | def __init__(self, vendor, product, version): 38 | super().__init__( 39 | text_view=generic_text_views.KeyValueView() 40 | ) 41 | 42 | self['vendor'] = vendor 43 | self['product'] = product 44 | self['version'] = version 45 | -------------------------------------------------------------------------------- /oslo_reports/models/with_default_views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import copy 16 | 17 | from oslo_reports.models import base as base_model 18 | from oslo_reports.views.json import generic as jsonviews 19 | from oslo_reports.views.text import generic as textviews 20 | from oslo_reports.views.xml import generic as xmlviews 21 | 22 | 23 | class ModelWithDefaultViews(base_model.ReportModel): 24 | """A Model With Default Views of Various Types 25 | 26 | A model with default views has several predefined views, 27 | each associated with a given type. This is often used for 28 | when a submodel should have an attached view, but the view 29 | differs depending on the serialization format 30 | 31 | Parameters are as the superclass, except for any 32 | parameters ending in '_view': these parameters 33 | get stored as default views. 34 | 35 | The default 'default views' are 36 | 37 | text 38 | :class:`oslo_reports.views.text.generic.KeyValueView` 39 | xml 40 | :class:`oslo_reports.views.xml.generic.KeyValueView` 41 | json 42 | :class:`oslo_reports.views.json.generic.KeyValueView` 43 | 44 | .. function:: to_type() 45 | 46 | ('type' is one of the 'default views' defined for this model) 47 | Serializes this model using the default view for 'type' 48 | 49 | :rtype: str 50 | :returns: this model serialized as 'type' 51 | """ 52 | 53 | def __init__(self, *args, **kwargs): 54 | self.views = { 55 | 'text': textviews.KeyValueView(), 56 | 'json': jsonviews.KeyValueView(), 57 | 'xml': xmlviews.KeyValueView() 58 | } 59 | 60 | newargs = copy.copy(kwargs) 61 | for k in kwargs: 62 | if k.endswith('_view'): 63 | self.views[k[:-5]] = kwargs[k] 64 | del newargs[k] 65 | super().__init__(*args, **newargs) 66 | 67 | def set_current_view_type(self, tp, visited=None): 68 | self.attached_view = self.views[tp] 69 | super().set_current_view_type(tp, visited) 70 | 71 | def __getattr__(self, attrname): 72 | if attrname[:3] == 'to_': 73 | if self.views[attrname[3:]] is not None: 74 | return lambda: self.views[attrname[3:]](self) 75 | else: 76 | raise NotImplementedError(( 77 | "Model {cn.__module__}.{cn.__name__} does not have" + 78 | " a default view for " 79 | "{tp}").format(cn=type(self), tp=attrname[3:])) 80 | else: 81 | return super().__getattr__(attrname) 82 | -------------------------------------------------------------------------------- /oslo_reports/opts.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import copy 14 | 15 | from oslo_config import cfg 16 | 17 | from oslo_reports._i18n import _ 18 | 19 | __all__ = [ 20 | 'list_opts', 21 | 'set_defaults', 22 | ] 23 | 24 | 25 | _option_group = 'oslo_reports' 26 | 27 | _options = [ 28 | cfg.StrOpt('log_dir', 29 | help=_('Path to a log directory where to create a file')), 30 | cfg.StrOpt('file_event_handler', 31 | help=_('The path to a file to watch for changes to trigger ' 32 | 'the reports, instead of signals. Setting this option ' 33 | 'disables the signal trigger for the reports. If ' 34 | 'application is running as a WSGI application it is ' 35 | 'recommended to use this instead of signals.')), 36 | cfg.IntOpt('file_event_handler_interval', 37 | default=1, 38 | help=_('How many seconds to wait between polls when ' 39 | 'file_event_handler is set')) 40 | ] 41 | 42 | 43 | def list_opts(): 44 | """Return a list of oslo.config options available in the library. 45 | 46 | The returned list includes all oslo.config options which may be registered 47 | at runtime by the library. 48 | Each element of the list is a tuple. The first element is the name of the 49 | group under which the list of elements in the second element will be 50 | registered. A group name of None corresponds to the [DEFAULT] group in 51 | config files. 52 | This function is also discoverable via the 'oslo_messaging' entry point 53 | under the 'oslo.config.opts' namespace. 54 | The purpose of this is to allow tools like the Oslo sample config file 55 | generator to discover the options exposed to users by this library. 56 | 57 | :returns: a list of (group_name, opts) tuples 58 | """ 59 | 60 | return [(_option_group, copy.deepcopy(_options))] 61 | 62 | 63 | def set_defaults(conf): 64 | """Set defaults for configuration variables. 65 | 66 | Overrides default options values. 67 | 68 | :param conf: Configuration object, managed by the caller. 69 | :type conf: oslo.config.cfg.ConfigOpts 70 | """ 71 | conf.register_opts(_options, group=_option_group) 72 | -------------------------------------------------------------------------------- /oslo_reports/report.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides Report classes 16 | 17 | This module defines various classes representing reports and report sections. 18 | All reports take the form of a report class containing various report 19 | sections. 20 | """ 21 | 22 | 23 | from oslo_reports.views.text import header as header_views 24 | 25 | 26 | class BasicReport: 27 | """A Basic Report 28 | 29 | A Basic Report consists of a collection of :class:`ReportSection` 30 | objects, each of which contains a top-level model and generator. 31 | It collects these sections into a cohesive report which may then 32 | be serialized by calling :func:`run`. 33 | """ 34 | 35 | def __init__(self): 36 | self.sections = [] 37 | self._state = 0 38 | 39 | def add_section(self, view, generator, index=None): 40 | """Add a section to the report 41 | 42 | This method adds a section with the given view and 43 | generator to the report. An index may be specified to 44 | insert the section at a given location in the list; 45 | If no index is specified, the section is appended to the 46 | list. The view is called on the model which results from 47 | the generator when the report is run. A generator is simply 48 | a method or callable object which takes no arguments and 49 | returns a :class:`oslo_reports.models.base.ReportModel` 50 | or similar object. 51 | 52 | :param view: the top-level view for the section 53 | :param generator: the method or class which generates the model 54 | :param index: the index at which to insert the section 55 | (or None to append it) 56 | :type index: int or None 57 | """ 58 | 59 | if index is None: 60 | self.sections.append(ReportSection(view, generator)) 61 | else: 62 | self.sections.insert(index, ReportSection(view, generator)) 63 | 64 | def run(self): 65 | """Run the report 66 | 67 | This method runs the report, having each section generate 68 | its data and serialize itself before joining the sections 69 | together. The BasicReport accomplishes the joining 70 | by joining the serialized sections together with newlines. 71 | 72 | :rtype: str 73 | :returns: the serialized report 74 | """ 75 | 76 | return "\n".join(str(sect) for sect in self.sections) 77 | 78 | 79 | class ReportSection: 80 | """A Report Section 81 | 82 | A report section contains a generator and a top-level view. When something 83 | attempts to serialize the section by calling str() or unicode() on it, the 84 | section runs the generator and calls the view on the resulting model. 85 | 86 | .. seealso:: 87 | 88 | Class :class:`BasicReport` 89 | :func:`BasicReport.add_section` 90 | 91 | :param view: the top-level view for this section 92 | :param generator: the generator for this section 93 | (any callable object which takes no parameters and returns a data model) 94 | """ 95 | 96 | def __init__(self, view, generator): 97 | self.view = view 98 | self.generator = generator 99 | 100 | def __str__(self): 101 | return self.view(self.generator()) 102 | 103 | 104 | class ReportOfType(BasicReport): 105 | """A Report of a Certain Type 106 | 107 | A ReportOfType has a predefined type associated with it. 108 | This type is automatically propagated down to the each of 109 | the sections upon serialization by wrapping the generator 110 | for each section. 111 | 112 | .. seealso:: 113 | 114 | Class :class:`oslo_reports.models.with_default_view.ModelWithDefaultView` # noqa 115 | (the entire class) 116 | 117 | Class :class:`oslo_reports.models.base.ReportModel` 118 | :func:`oslo_reports.models.base.ReportModel.set_current_view_type` # noqa 119 | 120 | :param str tp: the type of the report 121 | """ 122 | 123 | def __init__(self, tp): 124 | self.output_type = tp 125 | super().__init__() 126 | 127 | def add_section(self, view, generator, index=None): 128 | def with_type(gen): 129 | def newgen(): 130 | res = gen() 131 | try: 132 | res.set_current_view_type(self.output_type) 133 | except AttributeError: 134 | pass 135 | 136 | return res 137 | return newgen 138 | 139 | super().add_section( 140 | view, 141 | with_type(generator), 142 | index 143 | ) 144 | 145 | 146 | class TextReport(ReportOfType): 147 | """A Human-Readable Text Report 148 | 149 | This class defines a report that is designed to be read by a human 150 | being. It has nice section headers, and a formatted title. 151 | 152 | :param str name: the title of the report 153 | """ 154 | 155 | def __init__(self, name): 156 | super().__init__('text') 157 | self.name = name 158 | # add a title with a generator that creates an empty result model 159 | self.add_section(name, lambda: ('|' * 72) + "\n\n") 160 | 161 | def add_section(self, heading, generator, index=None): 162 | """Add a section to the report 163 | 164 | This method adds a section with the given title, and 165 | generator to the report. An index may be specified to 166 | insert the section at a given location in the list; 167 | If no index is specified, the section is appended to the 168 | list. The view is called on the model which results from 169 | the generator when the report is run. A generator is simply 170 | a method or callable object which takes no arguments and 171 | returns a :class:`oslo_reports.models.base.ReportModel` 172 | or similar object. 173 | 174 | The model is told to serialize as text (if possible) at serialization 175 | time by wrapping the generator. The view model's attached view 176 | (if any) is wrapped in a 177 | :class:`oslo_reports.views.text.header.TitledView` 178 | 179 | :param str heading: the title for the section 180 | :param generator: the method or class which generates the model 181 | :param index: the index at which to insert the section 182 | (or None to append) 183 | :type index: int or None 184 | """ 185 | 186 | super().add_section(header_views.TitledView(heading), generator, index) 187 | -------------------------------------------------------------------------------- /oslo_reports/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/oslo.reports/172df1f77d7ff1c85fbd74f781501bb9514eff5f/oslo_reports/tests/__init__.py -------------------------------------------------------------------------------- /oslo_reports/tests/test_base_report.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from collections import abc 16 | import re 17 | 18 | from oslotest import base 19 | 20 | from oslo_reports.models import base as base_model 21 | from oslo_reports import report 22 | 23 | 24 | class BasicView: 25 | def __call__(self, model): 26 | res = "" 27 | for k in sorted(model.keys()): 28 | res += str(k) + ": " + str(model[k]) + ";" 29 | return res 30 | 31 | 32 | def basic_generator(): 33 | return base_model.ReportModel(data={'string': 'value', 'int': 1}) 34 | 35 | 36 | class TestBasicReport(base.BaseTestCase): 37 | def setUp(self): 38 | super().setUp() 39 | 40 | self.report = report.BasicReport() 41 | 42 | def test_add_section(self): 43 | self.report.add_section(BasicView(), basic_generator) 44 | self.assertEqual(len(self.report.sections), 1) 45 | 46 | def test_append_section(self): 47 | self.report.add_section(BasicView(), lambda: {'a': 1}) 48 | self.report.add_section(BasicView(), basic_generator) 49 | 50 | self.assertEqual(len(self.report.sections), 2) 51 | self.assertEqual(self.report.sections[1].generator, basic_generator) 52 | 53 | def test_insert_section(self): 54 | self.report.add_section(BasicView(), lambda: {'a': 1}) 55 | self.report.add_section(BasicView(), basic_generator, 0) 56 | 57 | self.assertEqual(len(self.report.sections), 2) 58 | self.assertEqual(self.report.sections[0].generator, basic_generator) 59 | 60 | def test_basic_render(self): 61 | self.report.add_section(BasicView(), basic_generator) 62 | self.assertEqual(self.report.run(), "int: 1;string: value;") 63 | 64 | 65 | class TestBaseModel(base.BaseTestCase): 66 | def test_submodel_attached_view(self): 67 | class TmpView: 68 | def __call__(self, model): 69 | return '{len: ' + str(len(model.c)) + '}' 70 | 71 | def generate_model_with_submodel(): 72 | base_m = basic_generator() 73 | tv = TmpView() 74 | base_m['submodel'] = base_model.ReportModel(data={'c': [1, 2, 3]}, 75 | attached_view=tv) 76 | return base_m 77 | 78 | self.assertEqual(BasicView()(generate_model_with_submodel()), 79 | 'int: 1;string: value;submodel: {len: 3};') 80 | 81 | def test_str_throws_error_with_no_attached_view(self): 82 | model = base_model.ReportModel(data={'c': [1, 2, 3]}) 83 | self.assertRaisesRegex(Exception, 84 | 'Cannot stringify model: no attached view', 85 | str, model) 86 | 87 | def test_str_returns_string_with_attached_view(self): 88 | model = base_model.ReportModel(data={'a': 1, 'b': 2}, 89 | attached_view=BasicView()) 90 | 91 | self.assertEqual(str(model), 'a: 1;b: 2;') 92 | 93 | def test_model_repr(self): 94 | model1 = base_model.ReportModel(data={'a': 1, 'b': 2}, 95 | attached_view=BasicView()) 96 | 97 | model2 = base_model.ReportModel(data={'a': 1, 'b': 2}) 98 | 99 | base_re = r"" 101 | without_view_re = base_re + r"no view>" 102 | 103 | self.assertTrue(re.match(with_view_re, repr(model1))) 104 | self.assertTrue(re.match(without_view_re, repr(model2))) 105 | 106 | def test_getattr(self): 107 | model = base_model.ReportModel(data={'a': 1}) 108 | 109 | self.assertEqual(model.a, 1) 110 | 111 | self.assertRaises(AttributeError, getattr, model, 'b') 112 | 113 | def test_data_as_sequence_is_handled_properly(self): 114 | model = base_model.ReportModel(data=['a', 'b']) 115 | model.attached_view = BasicView() 116 | 117 | # if we don't handle lists properly, we'll get a TypeError here 118 | self.assertEqual('0: a;1: b;', str(model)) 119 | 120 | def test_immutable_mappings_produce_mutable_models(self): 121 | class SomeImmutableMapping(abc.Mapping): 122 | def __init__(self): 123 | self.data = {'a': 2, 'b': 4, 'c': 8} 124 | 125 | def __getitem__(self, key): 126 | return self.data[key] 127 | 128 | def __len__(self): 129 | return len(self.data) 130 | 131 | def __iter__(self): 132 | return iter(self.data) 133 | 134 | mp = SomeImmutableMapping() 135 | model = base_model.ReportModel(data=mp) 136 | model.attached_view = BasicView() 137 | 138 | self.assertEqual('a: 2;b: 4;c: 8;', str(model)) 139 | 140 | model['d'] = 16 141 | 142 | self.assertEqual('a: 2;b: 4;c: 8;d: 16;', str(model)) 143 | self.assertEqual({'a': 2, 'b': 4, 'c': 8}, mp.data) 144 | -------------------------------------------------------------------------------- /oslo_reports/tests/test_guru_meditation_report.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import datetime 16 | import io 17 | import os 18 | import re 19 | import signal 20 | import sys 21 | import threading 22 | from unittest import mock 23 | 24 | # needed to get greenthreads 25 | import fixtures 26 | import greenlet 27 | from oslotest import base 28 | 29 | import oslo_config 30 | from oslo_config import fixture 31 | from oslo_reports import guru_meditation_report as gmr 32 | from oslo_reports.models import with_default_views as mwdv 33 | from oslo_reports import opts 34 | 35 | 36 | CONF = oslo_config.cfg.CONF 37 | opts.set_defaults(CONF) 38 | 39 | 40 | class FakeVersionObj: 41 | def vendor_string(self): 42 | return 'Cheese Shoppe' 43 | 44 | def product_string(self): 45 | return 'Sharp Cheddar' 46 | 47 | def version_string_with_package(self): 48 | return '1.0.0' 49 | 50 | 51 | def skip_body_lines(start_line, report_lines): 52 | curr_line = start_line 53 | while (len(report_lines[curr_line]) == 0 or 54 | report_lines[curr_line][0] != '='): 55 | curr_line += 1 56 | 57 | return curr_line 58 | 59 | 60 | class GmrConfigFixture(fixture.Config): 61 | def setUp(self): 62 | super().setUp() 63 | 64 | self.conf.set_override( 65 | 'file_event_handler', 66 | '/specific/file', 67 | group='oslo_reports') 68 | self.conf.set_override( 69 | 'file_event_handler_interval', 70 | 10, 71 | group='oslo_reports') 72 | self.conf.set_override( 73 | 'log_dir', 74 | '/var/fake_log', 75 | group='oslo_reports') 76 | 77 | 78 | class TestGuruMeditationReport(base.BaseTestCase): 79 | def setUp(self): 80 | super().setUp() 81 | 82 | self.curr_g = greenlet.getcurrent() 83 | 84 | self.report = gmr.TextGuruMeditation(FakeVersionObj()) 85 | 86 | self.old_stderr = None 87 | 88 | self.CONF = self.useFixture(GmrConfigFixture(CONF)).conf 89 | 90 | def test_basic_report(self): 91 | report_lines = self.report.run().split('\n') 92 | 93 | target_str_header = ['========================================================================', # noqa 94 | '==== Guru Meditation ====', # noqa 95 | '========================================================================', # noqa 96 | '||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||', # noqa 97 | '', 98 | '', 99 | '========================================================================', # noqa 100 | '==== Package ====', # noqa 101 | '========================================================================', # noqa 102 | 'product = Sharp Cheddar', 103 | 'vendor = Cheese Shoppe', 104 | 'version = 1.0.0', 105 | '========================================================================', # noqa 106 | '==== Threads ====', # noqa 107 | '========================================================================'] # noqa 108 | 109 | # first the header and version info... 110 | self.assertEqual(target_str_header, 111 | report_lines[0:len(target_str_header)]) 112 | 113 | # followed by at least one thread... 114 | # NOTE(zqfan): add an optional '-' because sys._current_frames() 115 | # may return a negative thread id on 32 bit operating system. 116 | self.assertTrue(re.match(r'------(\s+)Thread #-?\d+\1\s?------', 117 | report_lines[len(target_str_header)])) 118 | self.assertEqual('', report_lines[len(target_str_header) + 1]) 119 | 120 | # followed by more thread stuff stuff... 121 | curr_line = skip_body_lines(len(target_str_header) + 2, report_lines) 122 | 123 | # followed by at least one green thread 124 | target_str_gt = ['========================================================================', # noqa 125 | '==== Green Threads ====', # noqa 126 | '========================================================================', # noqa 127 | '------ Green Thread ------', # noqa 128 | ''] 129 | end_bound = curr_line + len(target_str_gt) 130 | self.assertEqual(target_str_gt, 131 | report_lines[curr_line:end_bound]) 132 | 133 | # followed by some more green thread stuff 134 | curr_line = skip_body_lines(curr_line + len(target_str_gt), 135 | report_lines) 136 | 137 | # followed by the processes header 138 | target_str_p_head = ['========================================================================', # noqa 139 | '==== Processes ====', # noqa 140 | '========================================================================'] # noqa 141 | end_bound = curr_line + len(target_str_p_head) 142 | self.assertEqual(target_str_p_head, 143 | report_lines[curr_line:end_bound]) 144 | 145 | curr_line += len(target_str_p_head) 146 | 147 | # followed by at least one process 148 | self.assertTrue(re.match(r"Process \d+ \(under \d+\)", 149 | report_lines[curr_line])) 150 | 151 | # followed by some more process stuff 152 | curr_line = skip_body_lines(curr_line + 1, report_lines) 153 | 154 | # followed finally by the configuration 155 | target_str_config = ['========================================================================', # noqa 156 | '==== Configuration ====', # noqa 157 | '========================================================================', # noqa 158 | ''] 159 | end_bound = curr_line + len(target_str_config) 160 | self.assertEqual(target_str_config, 161 | report_lines[curr_line:end_bound]) 162 | 163 | def test_reg_persistent_section(self): 164 | def fake_gen(): 165 | fake_data = {'cheddar': ['sharp', 'mild'], 166 | 'swiss': ['with holes', 'with lots of holes'], 167 | 'american': ['orange', 'yellow']} 168 | 169 | return mwdv.ModelWithDefaultViews(data=fake_data) 170 | 171 | gmr.TextGuruMeditation.register_section('Cheese Types', fake_gen) 172 | 173 | report_lines = self.report.run() 174 | target_lst = ['========================================================================', # noqa 175 | '==== Cheese Types ====', # noqa 176 | '========================================================================', # noqa 177 | 'american = ', 178 | ' orange', 179 | ' yellow', 180 | 'cheddar = ', 181 | ' mild', 182 | ' sharp', 183 | 'swiss = ', 184 | ' with holes', 185 | ' with lots of holes'] 186 | target_str = '\n'.join(target_lst) 187 | self.assertIn(target_str, report_lines) 188 | 189 | def test_register_autorun(self): 190 | gmr.TextGuruMeditation.setup_autorun(FakeVersionObj()) 191 | self.old_stderr = sys.stderr 192 | sys.stderr = io.StringIO() 193 | 194 | os.kill(os.getpid(), signal.SIGUSR2) 195 | self.assertIn('Guru Meditation', sys.stderr.getvalue()) 196 | 197 | @mock.patch.object(gmr.TextGuruMeditation, '_setup_file_watcher') 198 | def test_register_autorun_without_signals(self, mock_setup_fh): 199 | version = FakeVersionObj() 200 | gmr.TextGuruMeditation.setup_autorun(version, conf=self.CONF) 201 | mock_setup_fh.assert_called_once_with( 202 | '/specific/file', 10, version, None, '/var/fake_log') 203 | 204 | @mock.patch('os.stat') 205 | @mock.patch('time.sleep') 206 | @mock.patch.object(threading.Thread, 'start') 207 | def test_setup_file_watcher(self, mock_thread, mock_sleep, mock_stat): 208 | version = FakeVersionObj() 209 | mock_stat.return_value.st_mtime = 3 210 | 211 | gmr.TextGuruMeditation._setup_file_watcher( 212 | self.CONF.oslo_reports.file_event_handler, 213 | self.CONF.oslo_reports.file_event_handler_interval, 214 | version, None, self.CONF.oslo_reports.log_dir) 215 | 216 | mock_stat.assert_called_once_with('/specific/file') 217 | self.assertEqual(1, mock_thread.called) 218 | 219 | @mock.patch('oslo_utils.timeutils.utcnow', 220 | return_value=datetime.datetime(2014, 1, 1, 12, 0, 0)) 221 | def test_register_autorun_log_dir(self, mock_strtime): 222 | log_dir = self.useFixture(fixtures.TempDir()).path 223 | gmr.TextGuruMeditation.setup_autorun( 224 | FakeVersionObj(), "fake-service", log_dir) 225 | 226 | os.kill(os.getpid(), signal.SIGUSR2) 227 | with open(os.path.join( 228 | log_dir, "fake-service_gurumeditation_20140101120000")) as df: 229 | self.assertIn('Guru Meditation', df.read()) 230 | 231 | @mock.patch.object(gmr.TextGuruMeditation, 'run') 232 | def test_fail_prints_traceback(self, run_mock): 233 | class RunFail(Exception): 234 | pass 235 | 236 | run_mock.side_effect = RunFail() 237 | gmr.TextGuruMeditation.setup_autorun(FakeVersionObj()) 238 | self.old_stderr = sys.stderr 239 | sys.stderr = io.StringIO() 240 | 241 | os.kill(os.getpid(), signal.SIGUSR2) 242 | self.assertIn('RunFail', sys.stderr.getvalue()) 243 | 244 | def tearDown(self): 245 | super().tearDown() 246 | if self.old_stderr is not None: 247 | sys.stderr = self.old_stderr 248 | -------------------------------------------------------------------------------- /oslo_reports/tests/test_openstack_generators.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 OpenStack Foundation. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import threading 17 | from unittest import mock 18 | 19 | import greenlet 20 | from oslo_config import cfg 21 | from oslotest import base 22 | 23 | from oslo_reports.generators import conf as os_cgen 24 | from oslo_reports.generators import threading as os_tgen 25 | from oslo_reports.generators import version as os_pgen 26 | from oslo_reports.models import threading as os_tmod 27 | 28 | 29 | class TestOpenstackGenerators(base.BaseTestCase): 30 | def test_thread_generator(self): 31 | model = os_tgen.ThreadReportGenerator()() 32 | # self.assertGreaterEqual(len(model.keys()), 1) 33 | self.assertTrue(len(model.keys()) >= 1) 34 | was_ok = False 35 | for val in model.values(): 36 | self.assertIsInstance(val, os_tmod.ThreadModel) 37 | self.assertIsNotNone(val.stack_trace) 38 | if val.thread_id == threading.current_thread().ident: 39 | was_ok = True 40 | break 41 | 42 | self.assertTrue(was_ok) 43 | 44 | model.set_current_view_type('text') 45 | self.assertIsNotNone(str(model)) 46 | 47 | def test_thread_generator_tb(self): 48 | class FakeModel: 49 | def __init__(self, thread_id, tb): 50 | self.traceback = tb 51 | 52 | with mock.patch('oslo_reports.models' 53 | '.threading.ThreadModel', FakeModel): 54 | model = os_tgen.ThreadReportGenerator("fake traceback")() 55 | curr_thread = model.get(threading.current_thread().ident, None) 56 | self.assertIsNotNone(curr_thread, None) 57 | self.assertEqual("fake traceback", curr_thread.traceback) 58 | 59 | def test_green_thread_generator(self): 60 | curr_g = greenlet.getcurrent() 61 | 62 | model = os_tgen.GreenThreadReportGenerator()() 63 | 64 | # self.assertGreaterEqual(len(model.keys()), 1) 65 | self.assertTrue(len(model.keys()) >= 1) 66 | 67 | was_ok = False 68 | for tm in model.values(): 69 | if tm.stack_trace == os_tmod.StackTraceModel(curr_g.gr_frame): 70 | was_ok = True 71 | break 72 | self.assertTrue(was_ok) 73 | 74 | model.set_current_view_type('text') 75 | self.assertIsNotNone(str(model)) 76 | 77 | def test_config_model(self): 78 | conf = cfg.ConfigOpts() 79 | conf.register_opt(cfg.StrOpt('crackers', default='triscuit')) 80 | conf.register_opt(cfg.StrOpt('secrets', secret=True, 81 | default='should not show')) 82 | conf.register_group(cfg.OptGroup('cheese', title='Cheese Info')) 83 | conf.register_opt(cfg.IntOpt('sharpness', default=1), 84 | group='cheese') 85 | conf.register_opt(cfg.StrOpt('name', default='cheddar'), 86 | group='cheese') 87 | conf.register_opt(cfg.BoolOpt('from_cow', default=True), 88 | group='cheese') 89 | conf.register_opt(cfg.StrOpt('group_secrets', secret=True, 90 | default='should not show'), 91 | group='cheese') 92 | 93 | model = os_cgen.ConfigReportGenerator(conf)() 94 | model.set_current_view_type('text') 95 | 96 | # oslo.config added a default config_source opt which gets included 97 | # in our output, but we also need to support older versions where that 98 | # wasn't the case. This logic can be removed once the oslo.config 99 | # lower constraint becomes >=6.4.0. 100 | config_source_line = ' config_source = \n' 101 | try: 102 | conf.config_source 103 | except cfg.NoSuchOptError: 104 | config_source_line = '' 105 | 106 | target_str = ('\ncheese: \n' 107 | ' from_cow = True\n' 108 | ' group_secrets = ***\n' 109 | ' name = cheddar\n' 110 | ' sharpness = 1\n' 111 | '\n' 112 | 'default: \n' 113 | '%s' 114 | ' crackers = triscuit\n' 115 | ' secrets = ***\n' 116 | ' shell_completion = None') % config_source_line 117 | self.assertEqual(target_str, str(model)) 118 | 119 | def test_package_report_generator(self): 120 | class VersionObj: 121 | def vendor_string(self): 122 | return 'Cheese Shoppe' 123 | 124 | def product_string(self): 125 | return 'Sharp Cheddar' 126 | 127 | def version_string_with_package(self): 128 | return '1.0.0' 129 | 130 | model = os_pgen.PackageReportGenerator(VersionObj())() 131 | model.set_current_view_type('text') 132 | 133 | target_str = ('product = Sharp Cheddar\n' 134 | 'vendor = Cheese Shoppe\n' 135 | 'version = 1.0.0') 136 | self.assertEqual(target_str, str(model)) 137 | 138 | def test_package_report_generator_without_vendor_string(self): 139 | class VersionObj: 140 | def product_string(self): 141 | return 'Sharp Cheddar' 142 | 143 | def version_string_with_package(self): 144 | return '1.0.0' 145 | 146 | model = os_pgen.PackageReportGenerator(VersionObj())() 147 | model.set_current_view_type('text') 148 | 149 | target_str = ('product = Sharp Cheddar\n' 150 | 'vendor = None\n' 151 | 'version = 1.0.0') 152 | self.assertEqual(target_str, str(model)) 153 | -------------------------------------------------------------------------------- /oslo_reports/tests/test_views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import copy 16 | from unittest import mock 17 | 18 | from oslotest import base 19 | 20 | from oslo_reports.models import base as base_model 21 | from oslo_reports.models import with_default_views as mwdv 22 | from oslo_reports import report 23 | from oslo_reports.views import jinja_view as jv 24 | from oslo_reports.views.json import generic as json_generic 25 | from oslo_reports.views.text import generic as text_generic 26 | 27 | 28 | def mwdv_generator(): 29 | return mwdv.ModelWithDefaultViews(data={'string': 'value', 'int': 1}) 30 | 31 | 32 | class TestModelReportType(base.BaseTestCase): 33 | def test_model_with_default_views(self): 34 | model = mwdv_generator() 35 | 36 | model.set_current_view_type('text') 37 | self.assertEqual('int = 1\nstring = value', str(model)) 38 | 39 | model.set_current_view_type('json') 40 | self.assertEqual('{"int": 1, "string": "value"}', str(model)) 41 | 42 | model.set_current_view_type('xml') 43 | 44 | self.assertEqual('1value', 45 | str(model)) 46 | 47 | def test_recursive_type_propagation_with_nested_models(self): 48 | model = mwdv_generator() 49 | model['submodel'] = mwdv_generator() 50 | 51 | model.set_current_view_type('json') 52 | 53 | self.assertEqual(model.submodel.views['json'], 54 | model.submodel.attached_view) 55 | 56 | def test_recursive_type_propagation_with_nested_dicts(self): 57 | nested_model = mwdv.ModelWithDefaultViews(json_view='abc') 58 | data = {'a': 1, 'b': {'c': nested_model}} 59 | top_model = base_model.ReportModel(data=data) 60 | 61 | top_model.set_current_view_type('json') 62 | self.assertEqual(nested_model.attached_view, 63 | nested_model.views['json']) 64 | 65 | def test_recursive_type_propagation_with_nested_lists(self): 66 | nested_model = mwdv_generator() 67 | data = {'a': 1, 'b': [nested_model]} 68 | top_model = base_model.ReportModel(data=data) 69 | 70 | top_model.set_current_view_type('json') 71 | self.assertEqual(nested_model.attached_view, 72 | nested_model.views['json']) 73 | 74 | def test_recursive_type_propogation_on_recursive_structures(self): 75 | nested_model = mwdv_generator() 76 | data = {'a': 1, 'b': [nested_model]} 77 | nested_model['c'] = data 78 | top_model = base_model.ReportModel(data=data) 79 | 80 | top_model.set_current_view_type('json') 81 | self.assertEqual(nested_model.attached_view, 82 | nested_model.views['json']) 83 | del nested_model['c'] 84 | 85 | def test_report_of_type(self): 86 | rep = report.ReportOfType('json') 87 | rep.add_section(lambda x: str(x), mwdv_generator) 88 | 89 | self.assertEqual('{"int": 1, "string": "value"}', rep.run()) 90 | 91 | # NOTE: this also tests views.text.header 92 | def test_text_report(self): 93 | rep = report.TextReport('Test Report') 94 | rep.add_section('An Important Section', mwdv_generator) 95 | rep.add_section('Another Important Section', mwdv_generator) 96 | 97 | target_str = ('========================================================================\n' # noqa 98 | '==== Test Report ====\n' # noqa 99 | '========================================================================\n' # noqa 100 | '||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||\n' # noqa 101 | '\n' # noqa 102 | '\n' # noqa 103 | '========================================================================\n' # noqa 104 | '==== An Important Section ====\n' # noqa 105 | '========================================================================\n' # noqa 106 | 'int = 1\n' # noqa 107 | 'string = value\n' # noqa 108 | '========================================================================\n' # noqa 109 | '==== Another Important Section ====\n' # noqa 110 | '========================================================================\n' # noqa 111 | 'int = 1\n' # noqa 112 | 'string = value') # noqa 113 | self.assertEqual(target_str, rep.run()) 114 | 115 | def test_to_type(self): 116 | model = mwdv_generator() 117 | 118 | self.assertEqual('1value', 119 | model.to_xml()) 120 | 121 | 122 | class TestGenericXMLView(base.BaseTestCase): 123 | def setUp(self): 124 | super().setUp() 125 | 126 | self.model = mwdv_generator() 127 | self.model.set_current_view_type('xml') 128 | 129 | def test_dict_serialization(self): 130 | self.model['dt'] = {'a': 1, 'b': 2} 131 | 132 | target_str = ('' 133 | '
12
' 134 | '1' 135 | 'value
') 136 | self.assertEqual(target_str, str(self.model)) 137 | 138 | def test_list_serialization(self): 139 | self.model['lt'] = ['a', 'b'] 140 | 141 | target_str = ('' 142 | '1' 143 | 'ab' 144 | 'value') 145 | self.assertEqual(target_str, str(self.model)) 146 | 147 | def test_list_in_dict_serialization(self): 148 | self.model['dt'] = {'a': 1, 'b': [2, 3]} 149 | 150 | target_str = ('' 151 | '
1' 152 | '23
' 153 | '1' 154 | 'value
') 155 | self.assertEqual(target_str, str(self.model)) 156 | 157 | def test_dict_in_list_serialization(self): 158 | self.model['lt'] = [1, {'b': 2, 'c': 3}] 159 | 160 | target_str = ('' 161 | '1' 162 | '1' 163 | '23' 164 | 'value') 165 | self.assertEqual(target_str, str(self.model)) 166 | 167 | def test_submodel_serialization(self): 168 | sm = mwdv_generator() 169 | sm.set_current_view_type('xml') 170 | 171 | self.model['submodel'] = sm 172 | 173 | target_str = ('' 174 | '1' 175 | 'value' 176 | '' 177 | '1value' 178 | '' 179 | '') 180 | self.assertEqual(target_str, str(self.model)) 181 | 182 | def test_wrapper_name(self): 183 | self.model.attached_view.wrapper_name = 'cheese' 184 | 185 | target_str = ('' 186 | '1' 187 | 'value' 188 | '') 189 | self.assertEqual(target_str, str(self.model)) 190 | 191 | 192 | class TestGenericJSONViews(base.BaseTestCase): 193 | def setUp(self): 194 | super().setUp() 195 | 196 | self.model = mwdv_generator() 197 | self.model.set_current_view_type('json') 198 | 199 | def test_basic_kv_view(self): 200 | attached_view = json_generic.BasicKeyValueView() 201 | self.model = base_model.ReportModel(data={'string': 'value', 'int': 1}, 202 | attached_view=attached_view) 203 | 204 | self.assertEqual('{"int": 1, "string": "value"}', 205 | str(self.model)) 206 | 207 | def test_dict_serialization(self): 208 | self.model['dt'] = {'a': 1, 'b': 2} 209 | 210 | target_str = ('{' 211 | '"dt": {"a": 1, "b": 2}, ' 212 | '"int": 1, ' 213 | '"string": "value"' 214 | '}') 215 | self.assertEqual(target_str, str(self.model)) 216 | 217 | def test_list_serialization(self): 218 | self.model['lt'] = ['a', 'b'] 219 | 220 | target_str = ('{' 221 | '"int": 1, ' 222 | '"lt": ["a", "b"], ' 223 | '"string": "value"' 224 | '}') 225 | self.assertEqual(target_str, str(self.model)) 226 | 227 | def test_list_in_dict_serialization(self): 228 | self.model['dt'] = {'a': 1, 'b': [2, 3]} 229 | 230 | target_str = ('{' 231 | '"dt": {"a": 1, "b": [2, 3]}, ' 232 | '"int": 1, ' 233 | '"string": "value"' 234 | '}') 235 | self.assertEqual(target_str, str(self.model)) 236 | 237 | def test_dict_in_list_serialization(self): 238 | self.model['lt'] = [1, {'b': 2, 'c': 3}] 239 | 240 | target_str = ('{' 241 | '"int": 1, ' 242 | '"lt": [1, {"b": 2, "c": 3}], ' 243 | '"string": "value"' 244 | '}') 245 | self.assertEqual(target_str, str(self.model)) 246 | 247 | def test_submodel_serialization(self): 248 | sm = mwdv_generator() 249 | sm.set_current_view_type('json') 250 | 251 | self.model['submodel'] = sm 252 | 253 | target_str = ('{' 254 | '"int": 1, ' 255 | '"string": "value", ' 256 | '"submodel": {"int": 1, "string": "value"}' 257 | '}') 258 | self.assertEqual(target_str, str(self.model)) 259 | 260 | 261 | class TestGenericTextViews(base.BaseTestCase): 262 | def setUp(self): 263 | super().setUp() 264 | 265 | self.model = mwdv_generator() 266 | self.model.set_current_view_type('text') 267 | 268 | def test_multi_view(self): 269 | attached_view = text_generic.MultiView() 270 | self.model = base_model.ReportModel(data={}, 271 | attached_view=attached_view) 272 | 273 | self.model['1'] = mwdv_generator() 274 | self.model['2'] = mwdv_generator() 275 | self.model['2']['int'] = 2 276 | self.model.set_current_view_type('text') 277 | 278 | target_str = ('int = 1\n' 279 | 'string = value\n' 280 | 'int = 2\n' 281 | 'string = value') 282 | self.assertEqual(target_str, str(self.model)) 283 | 284 | def test_basic_kv_view(self): 285 | attached_view = text_generic.BasicKeyValueView() 286 | self.model = base_model.ReportModel(data={'string': 'value', 'int': 1}, 287 | attached_view=attached_view) 288 | 289 | self.assertEqual('int = 1\nstring = value\n', 290 | str(self.model)) 291 | 292 | def test_table_view(self): 293 | column_names = ['Column A', 'Column B'] 294 | column_values = ['a', 'b'] 295 | attached_view = text_generic.TableView(column_names, column_values, 296 | 'table') 297 | self.model = base_model.ReportModel(data={}, 298 | attached_view=attached_view) 299 | 300 | self.model['table'] = [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}] 301 | 302 | target_str = (' Column A | Column B \n' # noqa 303 | '------------------------------------------------------------------------\n' # noqa 304 | ' 1 | 2 \n' # noqa 305 | ' 3 | 4 \n') # noqa 306 | 307 | self.assertEqual(target_str, str(self.model)) 308 | 309 | def test_dict_serialization(self): 310 | self.model['dt'] = {'a': 1, 'b': 2} 311 | 312 | target_str = ('dt = \n' 313 | ' a = 1\n' 314 | ' b = 2\n' 315 | 'int = 1\n' 316 | 'string = value') 317 | self.assertEqual(target_str, str(self.model)) 318 | 319 | def test_dict_serialization_integer_keys(self): 320 | self.model['dt'] = {3: 4, 5: 6} 321 | 322 | target_str = ('dt = \n' 323 | ' 3 = 4\n' 324 | ' 5 = 6\n' 325 | 'int = 1\n' 326 | 'string = value') 327 | self.assertEqual(target_str, str(self.model)) 328 | 329 | def test_dict_serialization_mixed_keys(self): 330 | self.model['dt'] = {'3': 4, 5: 6} 331 | 332 | target_str = ('dt = \n' 333 | ' 3 = 4\n' 334 | ' 5 = 6\n' 335 | 'int = 1\n' 336 | 'string = value') 337 | self.assertEqual(target_str, str(self.model)) 338 | 339 | def test_list_serialization(self): 340 | self.model['lt'] = ['a', 'b'] 341 | 342 | target_str = ('int = 1\n' 343 | 'lt = \n' 344 | ' a\n' 345 | ' b\n' 346 | 'string = value') 347 | self.assertEqual(target_str, str(self.model)) 348 | 349 | def test_list_in_dict_serialization(self): 350 | self.model['dt'] = {'a': 1, 'b': [2, 3]} 351 | 352 | target_str = ('dt = \n' 353 | ' a = 1\n' 354 | ' b = \n' 355 | ' 2\n' 356 | ' 3\n' 357 | 'int = 1\n' 358 | 'string = value') 359 | 360 | self.assertEqual(target_str, str(self.model)) 361 | 362 | def test_dict_in_list_serialization(self): 363 | self.model['lt'] = [1, {'b': 2, 'c': 3}] 364 | 365 | target_str = ('int = 1\n' 366 | 'lt = \n' 367 | ' 1\n' 368 | ' [dict]\n' 369 | ' b = 2\n' 370 | ' c = 3\n' 371 | 'string = value') 372 | self.assertEqual(target_str, str(self.model)) 373 | 374 | def test_submodel_serialization(self): 375 | sm = mwdv_generator() 376 | sm.set_current_view_type('text') 377 | 378 | self.model['submodel'] = sm 379 | 380 | target_str = ('int = 1\n' 381 | 'string = value\n' 382 | 'submodel = \n' 383 | ' int = 1\n' 384 | ' string = value') 385 | self.assertEqual(target_str, str(self.model)) 386 | 387 | def test_custom_indent_string(self): 388 | view = text_generic.KeyValueView(indent_str='~~') 389 | 390 | self.model['lt'] = ['a', 'b'] 391 | self.model.attached_view = view 392 | 393 | target_str = ('int = 1\n' 394 | 'lt = \n' 395 | '~~a\n' 396 | '~~b\n' 397 | 'string = value') 398 | self.assertEqual(target_str, str(self.model)) 399 | 400 | 401 | def get_open_mocks(rv): 402 | file_mock = mock.MagicMock(name='file_obj') 403 | file_mock.read.return_value = rv 404 | open_mock = mock.MagicMock(name='open') 405 | open_mock().__enter__.return_value = file_mock 406 | return (open_mock, file_mock) 407 | 408 | 409 | class TestJinjaView(base.BaseTestCase): 410 | 411 | TEMPL_STR = "int is {{ int }}, string is {{ string }}" 412 | MM_OPEN, MM_FILE = get_open_mocks(TEMPL_STR) 413 | 414 | def setUp(self): 415 | super().setUp() 416 | self.model = base_model.ReportModel(data={'int': 1, 'string': 'value'}) 417 | 418 | @mock.mock_open(MM_OPEN) 419 | def test_load_from_file(self): 420 | self.model.attached_view = jv.JinjaView(path='a/b/c/d.jinja.txt') 421 | 422 | self.assertEqual('int is 1, string is value', 423 | str(self.model)) 424 | self.MM_FILE.assert_called_with_once('a/b/c/d.jinja.txt') 425 | 426 | def test_direct_pass(self): 427 | self.model.attached_view = jv.JinjaView(text=self.TEMPL_STR) 428 | 429 | self.assertEqual('int is 1, string is value', 430 | str(self.model)) 431 | 432 | def test_load_from_class(self): 433 | class TmpJinjaView(jv.JinjaView): 434 | VIEW_TEXT = TestJinjaView.TEMPL_STR 435 | 436 | self.model.attached_view = TmpJinjaView() 437 | 438 | self.assertEqual('int is 1, string is value', 439 | str(self.model)) 440 | 441 | def test_is_deepcopiable(self): 442 | view_orig = jv.JinjaView(text=self.TEMPL_STR) 443 | view_cpy = copy.deepcopy(view_orig) 444 | 445 | self.assertIsNot(view_orig, view_cpy) 446 | -------------------------------------------------------------------------------- /oslo_reports/views/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides predefined views 16 | 17 | This module provides a collection of predefined views 18 | for use in reports. It is separated by type (xml, json, or text). 19 | Each type contains a submodule called 'generic' containing 20 | several basic, universal views for that type. There is also 21 | a predefined view that utilizes Jinja. 22 | """ 23 | -------------------------------------------------------------------------------- /oslo_reports/views/jinja_view.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides Jinja Views 16 | 17 | This module provides views that utilize the Jinja templating 18 | system for serialization. For more information on Jinja, please 19 | see http://jinja.pocoo.org/ . 20 | """ 21 | 22 | import copy 23 | 24 | import jinja2 25 | 26 | 27 | class JinjaView: 28 | """A Jinja View 29 | 30 | This view renders the given model using the provided Jinja 31 | template. The template can be given in various ways. 32 | If the `VIEw_TEXT` property is defined, that is used as template. 33 | Othewise, if a `path` parameter is passed to the constructor, that 34 | is used to load a file containing the template. If the `path` 35 | parameter is None, the `text` parameter is used as the template. 36 | 37 | The leading newline character and trailing newline character are stripped 38 | from the template (provided they exist). Baseline indentation is 39 | also stripped from each line. The baseline indentation is determined by 40 | checking the indentation of the first line, after stripping off the leading 41 | newline (if any). 42 | 43 | :param str path: the path to the Jinja template 44 | :param str text: the text of the Jinja template 45 | """ 46 | 47 | def __init__(self, path=None, text=None): 48 | try: 49 | self._text = self.VIEW_TEXT 50 | except AttributeError: 51 | if path is not None: 52 | with open(path) as f: 53 | self._text = f.read() 54 | elif text is not None: 55 | self._text = text 56 | else: 57 | self._text = "" 58 | 59 | if self._text[0] == "\n": 60 | self._text = self._text[1:] 61 | 62 | newtext = self._text.lstrip() 63 | amt = len(self._text) - len(newtext) 64 | if (amt > 0): 65 | base_indent = self._text[0:amt] 66 | lines = self._text.splitlines() 67 | newlines = [] 68 | for line in lines: 69 | if line.startswith(base_indent): 70 | newlines.append(line[amt:]) 71 | else: 72 | newlines.append(line) 73 | self._text = "\n".join(newlines) 74 | 75 | if self._text[-1] == "\n": 76 | self._text = self._text[:-1] 77 | 78 | self._regentemplate = True 79 | self._templatecache = None 80 | 81 | def __call__(self, model): 82 | return self.template.render(**model) 83 | 84 | def __deepcopy__(self, memodict): 85 | res = object.__new__(JinjaView) 86 | res._text = copy.deepcopy(self._text, memodict) 87 | 88 | # regenerate the template on a deepcopy 89 | res._regentemplate = True 90 | res._templatecache = None 91 | 92 | return res 93 | 94 | @property 95 | def template(self): 96 | """Get the Compiled Template 97 | 98 | Gets the compiled template, using a cached copy if possible 99 | (stored in attr:`_templatecache`) or otherwise recompiling 100 | the template if the compiled template is not present or is 101 | invalid (due to attr:`_regentemplate` being set to True). 102 | 103 | :returns: the compiled Jinja template 104 | :rtype: :class:`jinja2.Template` 105 | """ 106 | 107 | if self._templatecache is None or self._regentemplate: 108 | self._templatecache = jinja2.Template(self._text) 109 | self._regentemplate = False 110 | 111 | return self._templatecache 112 | 113 | def _gettext(self): 114 | """Get the Template Text 115 | 116 | Gets the text of the current template 117 | 118 | :returns: the text of the Jinja template 119 | :rtype: str 120 | """ 121 | 122 | return self._text 123 | 124 | def _settext(self, textval): 125 | """Set the Template Text 126 | 127 | Sets the text of the current template, marking it 128 | for recompilation next time the compiled template 129 | is retrieved via attr:`template` . 130 | 131 | :param str textval: the new text of the Jinja template 132 | """ 133 | 134 | self._text = textval 135 | self.regentemplate = True 136 | 137 | text = property(_gettext, _settext) 138 | -------------------------------------------------------------------------------- /oslo_reports/views/json/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides basic JSON views 16 | 17 | This module provides several basic views which serialize 18 | models into JSON. 19 | """ 20 | -------------------------------------------------------------------------------- /oslo_reports/views/json/generic.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides generic JSON views 16 | 17 | This modules defines several basic views for serializing 18 | data to JSON. Submodels that have already been serialized 19 | as JSON may have their string values marked with `__is_json__ 20 | = True` using :class:`oslo_reports._utils.StringWithAttrs` 21 | (each of the classes within this module does this automatically, 22 | and non-naive serializers check for this attribute and handle 23 | such strings specially) 24 | """ 25 | 26 | import copy 27 | 28 | from oslo_serialization import jsonutils as json 29 | 30 | from oslo_reports import _utils as utils 31 | 32 | 33 | class BasicKeyValueView: 34 | """A Basic Key-Value JSON View 35 | 36 | This view performs a naive serialization of a model 37 | into JSON by simply calling :func:`json.dumps` on the model 38 | """ 39 | 40 | def __call__(self, model): 41 | res = utils.StringWithAttrs(json.dumps(model.data, sort_keys=True)) 42 | res.__is_json__ = True 43 | return res 44 | 45 | 46 | class KeyValueView: 47 | """A Key-Value JSON View 48 | 49 | This view performs advanced serialization to a model 50 | into JSON. It does so by first checking all values to 51 | see if they are marked as JSON. If so, they are deserialized 52 | using :func:`json.loads`. Then, the copy of the model with all 53 | JSON deserialized is reserialized into proper nested JSON using 54 | :func:`json.dumps`. 55 | """ 56 | 57 | def __call__(self, model): 58 | # this part deals with subviews that were already serialized 59 | cpy = copy.deepcopy(model) 60 | for key in model.keys(): 61 | if getattr(model[key], '__is_json__', False): 62 | cpy[key] = json.loads(model[key]) 63 | 64 | res = utils.StringWithAttrs(json.dumps(cpy.data, sort_keys=True)) 65 | res.__is_json__ = True 66 | return res 67 | -------------------------------------------------------------------------------- /oslo_reports/views/text/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides basic text views 16 | 17 | This module provides several basic views which serialize 18 | models into human-readable text. 19 | """ 20 | -------------------------------------------------------------------------------- /oslo_reports/views/text/generic.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides generic text views 16 | 17 | This modules provides several generic views for 18 | serializing models into human-readable text. 19 | """ 20 | 21 | from collections import abc 22 | 23 | 24 | class MultiView: 25 | """A Text View Containing Multiple Views 26 | 27 | This view simply serializes each 28 | value in the data model, and then 29 | joins them with newlines (ignoring 30 | the key values altogether). This is 31 | useful for serializing lists of models 32 | (as array-like dicts). 33 | """ 34 | 35 | def __call__(self, model): 36 | res = sorted([str(model[key]) for key in model]) 37 | return "\n".join(res) 38 | 39 | 40 | class BasicKeyValueView: 41 | """A Basic Key-Value Text View 42 | 43 | This view performs a naive serialization of a model into 44 | text using a basic key-value method, where each 45 | key-value pair is rendered as "key = str(value)" 46 | """ 47 | 48 | def __call__(self, model): 49 | res = "" 50 | for key in sorted(model): 51 | res += "{key} = {value}\n".format(key=key, value=model[key]) 52 | 53 | return res 54 | 55 | 56 | class KeyValueView: 57 | """A Key-Value Text View 58 | 59 | This view performs an advanced serialization of a model 60 | into text by following the following set of rules: 61 | 62 | key : text 63 | key = text 64 | 65 | rootkey : Mapping 66 | :: 67 | 68 | rootkey = 69 | serialize(key, value) 70 | 71 | key : Sequence 72 | :: 73 | 74 | key = 75 | serialize(item) 76 | 77 | :param str indent_str: the string used to represent one "indent" 78 | :param str key_sep: the separator to use between keys and values 79 | :param str dict_sep: the separator to use after a dictionary root key 80 | :param str list_sep: the separator to use after a list root key 81 | :param str anon_dict: the "key" to use when there is a dict in a list 82 | (does not automatically use the dict separator) 83 | :param before_dict: content to place on the line(s) before the a dict 84 | root key (use None to avoid inserting an extra line) 85 | :type before_dict: str or None 86 | :param before_list: content to place on the line(s) before the a list 87 | root key (use None to avoid inserting an extra line) 88 | :type before_list: str or None 89 | """ 90 | 91 | def __init__(self, 92 | indent_str=' ', 93 | key_sep=' = ', 94 | dict_sep=' = ', 95 | list_sep=' = ', 96 | anon_dict='[dict]', 97 | before_dict=None, 98 | before_list=None): 99 | self.indent_str = indent_str 100 | self.key_sep = key_sep 101 | self.dict_sep = dict_sep 102 | self.list_sep = list_sep 103 | self.anon_dict = anon_dict 104 | self.before_dict = before_dict 105 | self.before_list = before_list 106 | 107 | def __call__(self, model): 108 | def serialize(root, rootkey, indent): 109 | res = [] 110 | if rootkey is not None: 111 | res.append((self.indent_str * indent) + str(rootkey)) 112 | 113 | if isinstance(root, abc.Mapping): 114 | if rootkey is None and indent > 0: 115 | res.append((self.indent_str * indent) + self.anon_dict) 116 | elif rootkey is not None: 117 | res[0] += self.dict_sep 118 | if self.before_dict is not None: 119 | res.insert(0, self.before_dict) 120 | 121 | for key in sorted(root, key=str): 122 | res.extend(serialize(root[key], key, indent + 1)) 123 | elif (isinstance(root, abc.Sequence) and 124 | not isinstance(root, str)): 125 | if rootkey is not None: 126 | res[0] += self.list_sep 127 | if self.before_list is not None: 128 | res.insert(0, self.before_list) 129 | 130 | for val in sorted(root, key=str): 131 | res.extend(serialize(val, None, indent + 1)) 132 | else: 133 | str_root = str(root) 134 | if '\n' in str_root: 135 | # we are in a submodel 136 | if rootkey is not None: 137 | res[0] += self.dict_sep 138 | 139 | list_root = [(self.indent_str * (indent + 1)) + line 140 | for line in str_root.split('\n')] 141 | res.extend(list_root) 142 | else: 143 | # just a normal key or list entry 144 | try: 145 | res[0] += self.key_sep + str_root 146 | except IndexError: 147 | res = [(self.indent_str * indent) + str_root] 148 | 149 | return res 150 | 151 | return "\n".join(serialize(model, None, -1)) 152 | 153 | 154 | class TableView: 155 | """A Basic Table Text View 156 | 157 | This view performs serialization of data into a basic table with 158 | predefined column names and mappings. Column width is auto-calculated 159 | evenly, column values are automatically truncated accordingly. Values 160 | are centered in the columns. 161 | 162 | :param [str] column_names: the headers for each of the columns 163 | :param [str] column_values: the item name to match each column to in 164 | each row 165 | :param str table_prop_name: the name of the property within the model 166 | containing the row models 167 | """ 168 | 169 | def __init__(self, column_names, column_values, table_prop_name): 170 | self.table_prop_name = table_prop_name 171 | self.column_names = column_names 172 | self.column_values = column_values 173 | self.column_width = (72 - len(column_names) + 1) // len(column_names) 174 | 175 | column_headers = "|".join( 176 | "{{ch[{n}]: ^{width}}}".format(n=n, width=self.column_width) 177 | for n in range(len(column_names)) 178 | ) 179 | 180 | # correct for float-to-int roundoff error 181 | test_fmt = column_headers.format(ch=column_names) 182 | if len(test_fmt) < 72: 183 | column_headers += ' ' * (72 - len(test_fmt)) 184 | 185 | vert_divider = '-' * 72 186 | self.header_fmt_str = column_headers + "\n" + vert_divider + "\n" 187 | 188 | self.row_fmt_str = "|".join( 189 | "{{cv[{n}]: ^{width}}}".format(n=n, width=self.column_width) 190 | for n in range(len(column_values)) 191 | ) 192 | 193 | def __call__(self, model): 194 | res = self.header_fmt_str.format(ch=self.column_names) 195 | for raw_row in model[self.table_prop_name]: 196 | row = [str(raw_row[prop_name]) 197 | for prop_name in self.column_values] 198 | # double format is in case we have roundoff error 199 | res += '{: <72}\n'.format(self.row_fmt_str.format(cv=row)) 200 | 201 | return res 202 | -------------------------------------------------------------------------------- /oslo_reports/views/text/header.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Text Views With Headers 16 | 17 | This package defines several text views with headers 18 | """ 19 | 20 | 21 | class HeaderView: 22 | """A Text View With a Header 23 | 24 | This view simply serializes the model and places the given 25 | header on top. 26 | 27 | :param header: the header (can be anything on which str() can be called) 28 | """ 29 | 30 | def __init__(self, header): 31 | self.header = header 32 | 33 | def __call__(self, model): 34 | return f'{self.header}\n{model}' 35 | 36 | 37 | class TitledView(HeaderView): 38 | """A Text View With a Title 39 | 40 | This view simply serializes the model, and places 41 | a preformatted header containing the given title 42 | text on top. The title text can be up to 64 characters 43 | long. 44 | 45 | :param str title: the title of the view 46 | """ 47 | 48 | FORMAT_STR = ('=' * 72) + "\n===={0: ^64}====\n" + ('=' * 72) 49 | 50 | def __init__(self, title): 51 | super().__init__(self.FORMAT_STR.format(title)) 52 | -------------------------------------------------------------------------------- /oslo_reports/views/text/process.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides process view 16 | 17 | This module provides a view for 18 | visualizing processes in human-readable form 19 | """ 20 | 21 | import oslo_reports.views.jinja_view as jv 22 | 23 | 24 | class ProcessView(jv.JinjaView): 25 | """A Process View 26 | 27 | This view displays process models defined by 28 | :class:`oslo_reports.models.process.ProcessModel` 29 | """ 30 | 31 | VIEW_TEXT = ( 32 | "Process {{ pid }} (under {{ parent_pid }}) " 33 | "[ run by: {{ username }} ({{ uids.real|default('unknown uid') }})," 34 | " state: {{ state }} ]\n" 35 | "{% for child in children %}" 36 | " {{ child }}" 37 | "{% endfor %}" 38 | ) 39 | -------------------------------------------------------------------------------- /oslo_reports/views/text/threading.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides thread and stack-trace views 16 | 17 | This module provides a collection of views for 18 | visualizing threads, green threads, and stack traces 19 | in human-readable form. 20 | """ 21 | 22 | from oslo_reports.views import jinja_view as jv 23 | 24 | 25 | class StackTraceView(jv.JinjaView): 26 | """A Stack Trace View 27 | 28 | This view displays stack trace models defined by 29 | :class:`oslo_reports.models.threading.StackTraceModel` 30 | """ 31 | 32 | VIEW_TEXT = ( 33 | "{% if root_exception is not none %}" 34 | "Exception: {{ root_exception }}\n" 35 | "------------------------------------\n" 36 | "\n" 37 | "{% endif %}" 38 | "{% for line in lines %}\n" 39 | "{{ line.filename }}:{{ line.line }} in {{ line.name }}\n" 40 | " {% if line.code is not none %}" 41 | "`{{ line.code }}`" 42 | "{% else %}" 43 | "(source not found)" 44 | "{% endif %}\n" 45 | "{% else %}\n" 46 | "No Traceback!\n" 47 | "{% endfor %}" 48 | ) 49 | 50 | 51 | class GreenThreadView: 52 | """A Green Thread View 53 | 54 | This view displays a green thread provided by the data 55 | model :class:`oslo_reports.models.threading.GreenThreadModel` 56 | """ 57 | 58 | FORMAT_STR = "------{thread_str: ^60}------" + "\n" + "{stack_trace}" 59 | 60 | def __call__(self, model): 61 | return self.FORMAT_STR.format( 62 | thread_str=" Green Thread ", 63 | stack_trace=model.stack_trace 64 | ) 65 | 66 | 67 | class ThreadView: 68 | """A Thread Collection View 69 | 70 | This view displays a python thread provided by the data 71 | model :class:`oslo_reports.models.threading.ThreadModel` # noqa 72 | """ 73 | 74 | FORMAT_STR = "------{thread_str: ^60}------" + "\n" + "{stack_trace}" 75 | 76 | def __call__(self, model): 77 | return self.FORMAT_STR.format( 78 | thread_str=" Thread #{} ".format(model.thread_id), 79 | stack_trace=model.stack_trace 80 | ) 81 | -------------------------------------------------------------------------------- /oslo_reports/views/xml/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides basic XML views 16 | 17 | This module provides several basic views which serialize 18 | models into XML. 19 | """ 20 | -------------------------------------------------------------------------------- /oslo_reports/views/xml/generic.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """Provides generic XML views 16 | 17 | This modules defines several basic views for serializing 18 | data to XML. Submodels that have already been serialized 19 | as XML may have their string values marked with `__is_xml__ 20 | = True` using :class:`oslo_reports._utils.StringWithAttrs` 21 | (each of the classes within this module does this automatically, 22 | and non-naive serializers check for this attribute and handle 23 | such strings specially) 24 | """ 25 | 26 | from collections import abc 27 | import copy 28 | import xml.etree.ElementTree as ET 29 | 30 | from oslo_reports import _utils as utils 31 | 32 | 33 | class KeyValueView: 34 | """A Key-Value XML View 35 | 36 | This view performs advanced serialization of a data model 37 | into XML. It first deserializes any values marked as XML so 38 | that they can be properly reserialized later. It then follows 39 | the following rules to perform serialization: 40 | 41 | key : text/xml 42 | The tag name is the key name, and the contents are the text or xml 43 | key : Sequence 44 | A wrapper tag is created with the key name, and each item is placed 45 | in an 'item' tag 46 | key : Mapping 47 | A wrapper tag is created with the key name, and the serialize is called 48 | on each key-value pair (such that each key gets its own tag) 49 | 50 | :param str wrapper_name: the name of the top-level element 51 | """ 52 | 53 | def __init__(self, wrapper_name="model"): 54 | self.wrapper_name = wrapper_name 55 | 56 | def __call__(self, model): 57 | # this part deals with subviews that were already serialized 58 | cpy = copy.deepcopy(model) 59 | for key, valstr in model.items(): 60 | if getattr(valstr, '__is_xml__', False): 61 | cpy[key] = ET.fromstring(valstr) 62 | 63 | def serialize(rootmodel, rootkeyname): 64 | res = ET.Element(rootkeyname) 65 | 66 | if isinstance(rootmodel, abc.Mapping): 67 | for key in sorted(rootmodel): 68 | res.append(serialize(rootmodel[key], key)) 69 | elif (isinstance(rootmodel, abc.Sequence) and 70 | not isinstance(rootmodel, str)): 71 | for val in sorted(rootmodel, key=str): 72 | res.append(serialize(val, 'item')) 73 | elif ET.iselement(rootmodel): 74 | res.append(rootmodel) 75 | else: 76 | res.text = str(rootmodel) 77 | 78 | return res 79 | 80 | str_ = ET.tostring(serialize(cpy, 81 | self.wrapper_name), 82 | encoding="utf-8").decode("utf-8") 83 | res = utils.StringWithAttrs(str_) 84 | res.__is_xml__ = True 85 | return res 86 | -------------------------------------------------------------------------------- /releasenotes/notes/add-reno-996dd44974d53238.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | other: 3 | - Introduce reno for deployer release notes. 4 | -------------------------------------------------------------------------------- /releasenotes/notes/drop-python27-support-26fad37c3f7a3d28.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Support for Python 2.7 has been dropped. The minimum version of Python now 5 | supported is Python 3.6. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/remove-py38-9a3e6702c3d23445.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Support for Python 3.8 has been removed. Now the minimum python version 5 | supported is 3.9 . 6 | -------------------------------------------------------------------------------- /releasenotes/source/2023.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2023.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/2023.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/2023.2.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2023.2 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2023.2 7 | -------------------------------------------------------------------------------- /releasenotes/source/2024.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2024.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2024.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/2024.2.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2024.2 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2024.2 7 | -------------------------------------------------------------------------------- /releasenotes/source/2025.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2025.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2025.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/_static/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/oslo.reports/172df1f77d7ff1c85fbd74f781501bb9514eff5f/releasenotes/source/_static/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/_templates/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/oslo.reports/172df1f77d7ff1c85fbd74f781501bb9514eff5f/releasenotes/source/_templates/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/conf.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 10 | # implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | # This file is execfile()d with the current directory set to its 15 | # containing dir. 16 | # 17 | # Note that not all possible configuration values are present in this 18 | # autogenerated file. 19 | # 20 | # All configuration values have a default; values that are commented out 21 | # serve to show the default. 22 | 23 | # If extensions (or modules to document with autodoc) are in another directory, 24 | # add these directories to sys.path here. If the directory is relative to the 25 | # documentation root, use os.path.abspath to make it absolute, like shown here. 26 | # sys.path.insert(0, os.path.abspath('.')) 27 | 28 | # -- General configuration ------------------------------------------------ 29 | 30 | # If your documentation needs a minimal Sphinx version, state it here. 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | 'openstackdocstheme', 38 | 'reno.sphinxext', 39 | ] 40 | 41 | # openstackdocstheme options 42 | openstackdocs_repo_name = 'openstack/oslo.reports' 43 | openstackdocs_bug_project = 'oslo.reports' 44 | openstackdocs_bug_tag = '' 45 | openstackdocs_auto_name = False 46 | 47 | # Add any paths that contain templates here, relative to this directory. 48 | templates_path = ['_templates'] 49 | 50 | # The suffix of source filenames. 51 | source_suffix = '.rst' 52 | 53 | # The encoding of source files. 54 | # source_encoding = 'utf-8-sig' 55 | 56 | # The master toctree document. 57 | master_doc = 'index' 58 | 59 | # General information about the project. 60 | project = 'oslo.reports Release Notes' 61 | copyright = '2016, oslo.reports Developers' 62 | 63 | # Release notes do not need a version in the title, they span 64 | # multiple versions. 65 | release = '' 66 | # The short X.Y version. 67 | version = '' 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | # language = None 72 | 73 | # There are two options for replacing |today|: either, you set today to some 74 | # non-false value, then it is used: 75 | # today = '' 76 | # Else, today_fmt is used as the format for a strftime call. 77 | # today_fmt = '%B %d, %Y' 78 | 79 | # List of patterns, relative to source directory, that match files and 80 | # directories to ignore when looking for source files. 81 | exclude_patterns = [] 82 | 83 | # The reST default role (used for this markup: `text`) to use for all 84 | # documents. 85 | # default_role = None 86 | 87 | # If true, '()' will be appended to :func: etc. cross-reference text. 88 | # add_function_parentheses = True 89 | 90 | # If true, the current module name will be prepended to all description 91 | # unit titles (such as .. function::). 92 | # add_module_names = True 93 | 94 | # If true, sectionauthor and moduleauthor directives will be shown in the 95 | # output. They are ignored by default. 96 | # show_authors = False 97 | 98 | # The name of the Pygments (syntax highlighting) style to use. 99 | pygments_style = 'native' 100 | 101 | # A list of ignored prefixes for module index sorting. 102 | # modindex_common_prefix = [] 103 | 104 | # If true, keep warnings as "system message" paragraphs in the built documents. 105 | # keep_warnings = False 106 | 107 | 108 | # -- Options for HTML output ---------------------------------------------- 109 | 110 | # The theme to use for HTML and HTML Help pages. See the documentation for 111 | # a list of builtin themes. 112 | html_theme = 'openstackdocs' 113 | 114 | # Theme options are theme-specific and customize the look and feel of a theme 115 | # further. For a list of options available for each theme, see the 116 | # documentation. 117 | # html_theme_options = {} 118 | 119 | # Add any paths that contain custom themes here, relative to this directory. 120 | # html_theme_path = [] 121 | 122 | # The name for this set of Sphinx documents. If None, it defaults to 123 | # " v documentation". 124 | # html_title = None 125 | 126 | # A shorter title for the navigation bar. Default is the same as html_title. 127 | # html_short_title = None 128 | 129 | # The name of an image file (relative to this directory) to place at the top 130 | # of the sidebar. 131 | # html_logo = None 132 | 133 | # The name of an image file (within the static path) to use as favicon of the 134 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 135 | # pixels large. 136 | # html_favicon = None 137 | 138 | # Add any paths that contain custom static files (such as style sheets) here, 139 | # relative to this directory. They are copied after the builtin static files, 140 | # so a file named "default.css" will overwrite the builtin "default.css". 141 | html_static_path = ['_static'] 142 | 143 | # Add any extra paths that contain custom files (such as robots.txt or 144 | # .htaccess) here, relative to this directory. These files are copied 145 | # directly to the root of the documentation. 146 | # html_extra_path = [] 147 | 148 | # If true, SmartyPants will be used to convert quotes and dashes to 149 | # typographically correct entities. 150 | # html_use_smartypants = True 151 | 152 | # Custom sidebar templates, maps document names to template names. 153 | # html_sidebars = {} 154 | 155 | # Additional templates that should be rendered to pages, maps page names to 156 | # template names. 157 | # html_additional_pages = {} 158 | 159 | # If false, no module index is generated. 160 | # html_domain_indices = True 161 | 162 | # If false, no index is generated. 163 | # html_use_index = True 164 | 165 | # If true, the index is split into individual pages for each letter. 166 | # html_split_index = False 167 | 168 | # If true, links to the reST sources are added to the pages. 169 | # html_show_sourcelink = True 170 | 171 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 172 | # html_show_sphinx = True 173 | 174 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 175 | # html_show_copyright = True 176 | 177 | # If true, an OpenSearch description file will be output, and all pages will 178 | # contain a tag referring to it. The value of this option must be the 179 | # base URL from which the finished HTML is served. 180 | # html_use_opensearch = '' 181 | 182 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 183 | # html_file_suffix = None 184 | 185 | # Output file base name for HTML help builder. 186 | htmlhelp_basename = 'oslo.reportsReleaseNotesDoc' 187 | 188 | 189 | # -- Options for LaTeX output --------------------------------------------- 190 | 191 | latex_elements = { 192 | # The paper size ('letterpaper' or 'a4paper'). 193 | # 'papersize': 'letterpaper', 194 | 195 | # The font size ('10pt', '11pt' or '12pt'). 196 | # 'pointsize': '10pt', 197 | 198 | # Additional stuff for the LaTeX preamble. 199 | # 'preamble': '', 200 | } 201 | 202 | # Grouping the document tree into LaTeX files. List of tuples 203 | # (source start file, target name, title, 204 | # author, documentclass [howto, manual, or own class]). 205 | latex_documents = [ 206 | ('index', 'oslo.reportsReleaseNotes.tex', 207 | 'oslo.reports Release Notes Documentation', 208 | 'oslo.reports Developers', 'manual'), 209 | ] 210 | 211 | # The name of an image file (relative to this directory) to place at the top of 212 | # the title page. 213 | # latex_logo = None 214 | 215 | # For "manual" documents, if this is true, then toplevel headings are parts, 216 | # not chapters. 217 | # latex_use_parts = False 218 | 219 | # If true, show page references after internal links. 220 | # latex_show_pagerefs = False 221 | 222 | # If true, show URL addresses after external links. 223 | # latex_show_urls = False 224 | 225 | # Documents to append as an appendix to all manuals. 226 | # latex_appendices = [] 227 | 228 | # If false, no module index is generated. 229 | # latex_domain_indices = True 230 | 231 | 232 | # -- Options for manual page output --------------------------------------- 233 | 234 | # One entry per manual page. List of tuples 235 | # (source start file, name, description, authors, manual section). 236 | man_pages = [ 237 | ('index', 'oslo.reportsReleaseNotes', 238 | 'oslo.reports Release Notes Documentation', 239 | ['oslo.reports Developers'], 1) 240 | ] 241 | 242 | # If true, show URL addresses after external links. 243 | # man_show_urls = False 244 | 245 | 246 | # -- Options for Texinfo output ------------------------------------------- 247 | 248 | # Grouping the document tree into Texinfo files. List of tuples 249 | # (source start file, target name, title, author, 250 | # dir menu entry, description, category) 251 | texinfo_documents = [ 252 | ('index', 'oslo.reportsReleaseNotes', 253 | 'oslo.reports Release Notes Documentation', 254 | 'oslo.reports Developers', 'oslo.reportsReleaseNotes', 255 | 'One line description of project.', 256 | 'Miscellaneous'), 257 | ] 258 | 259 | # Documents to append as an appendix to all manuals. 260 | # texinfo_appendices = [] 261 | 262 | # If false, no module index is generated. 263 | # texinfo_domain_indices = True 264 | 265 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 266 | # texinfo_show_urls = 'footnote' 267 | 268 | # If true, do not generate a @detailmenu in the "Top" node's menu. 269 | # texinfo_no_detailmenu = False 270 | 271 | # -- Options for Internationalization output ------------------------------ 272 | locale_dirs = ['locale/'] 273 | -------------------------------------------------------------------------------- /releasenotes/source/index.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | oslo.reports Release Notes 3 | ============================ 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | unreleased 9 | 2025.1 10 | 2024.2 11 | 2024.1 12 | 2023.2 13 | 2023.1 14 | victoria 15 | ussuri 16 | train 17 | stein 18 | rocky 19 | queens 20 | pike 21 | ocata 22 | -------------------------------------------------------------------------------- /releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po: -------------------------------------------------------------------------------- 1 | # Andi Chandler , 2017. #zanata 2 | # Andi Chandler , 2018. #zanata 3 | # Andi Chandler , 2022. #zanata 4 | # Andi Chandler , 2023. #zanata 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: oslo.reports Release Notes\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2023-05-05 13:27+0000\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "PO-Revision-Date: 2023-06-21 08:13+0000\n" 14 | "Last-Translator: Andi Chandler \n" 15 | "Language-Team: English (United Kingdom)\n" 16 | "Language: en_GB\n" 17 | "X-Generator: Zanata 4.3.3\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 19 | 20 | msgid "1.16.0" 21 | msgstr "1.16.0" 22 | 23 | msgid "2.0.0" 24 | msgstr "2.0.0" 25 | 26 | msgid "2023.1 Series Release Notes" 27 | msgstr "2023.1 Series Release Notes" 28 | 29 | msgid "Introduce reno for deployer release notes." 30 | msgstr "Introduce Reno for developer release notes." 31 | 32 | msgid "Ocata Series Release Notes" 33 | msgstr "Ocata Series Release Notes" 34 | 35 | msgid "Other Notes" 36 | msgstr "Other Notes" 37 | 38 | msgid "Pike Series Release Notes" 39 | msgstr "Pike Series Release Notes" 40 | 41 | msgid "Queens Series Release Notes" 42 | msgstr "Queens Series Release Notes" 43 | 44 | msgid "Rocky Series Release Notes" 45 | msgstr "Rocky Series Release Notes" 46 | 47 | msgid "Stein Series Release Notes" 48 | msgstr "Stein Series Release Notes" 49 | 50 | msgid "" 51 | "Support for Python 2.7 has been dropped. The minimum version of Python now " 52 | "supported is Python 3.6." 53 | msgstr "" 54 | "Support for Python 2.7 has been dropped. The minimum version of Python now " 55 | "supported is Python 3.6." 56 | 57 | msgid "Train Series Release Notes" 58 | msgstr "Train Series Release Notes" 59 | 60 | msgid "Unreleased Release Notes" 61 | msgstr "Unreleased Release Notes" 62 | 63 | msgid "Upgrade Notes" 64 | msgstr "Upgrade Notes" 65 | 66 | msgid "Ussuri Series Release Notes" 67 | msgstr "Ussuri Series Release Notes" 68 | 69 | msgid "Victoria Series Release Notes" 70 | msgstr "Victoria Series Release Notes" 71 | 72 | msgid "oslo.reports Release Notes" 73 | msgstr "oslo.reports Release Notes" 74 | -------------------------------------------------------------------------------- /releasenotes/source/ocata.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Ocata Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: origin/stable/ocata 7 | -------------------------------------------------------------------------------- /releasenotes/source/pike.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Pike Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/pike 7 | -------------------------------------------------------------------------------- /releasenotes/source/queens.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Queens Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/queens 7 | -------------------------------------------------------------------------------- /releasenotes/source/rocky.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Rocky Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/rocky 7 | -------------------------------------------------------------------------------- /releasenotes/source/stein.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Stein Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/stein 7 | -------------------------------------------------------------------------------- /releasenotes/source/train.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Train Series Release Notes 3 | ========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/train 7 | -------------------------------------------------------------------------------- /releasenotes/source/unreleased.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Unreleased Release Notes 3 | ========================== 4 | 5 | .. release-notes:: 6 | -------------------------------------------------------------------------------- /releasenotes/source/ussuri.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Ussuri Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/ussuri 7 | -------------------------------------------------------------------------------- /releasenotes/source/victoria.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Victoria Series Release Notes 3 | ============================= 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/victoria 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Requirements lower bounds listed here are our best effort to keep them up to 2 | # date but we do not test them so no guarantee of having them all correct. If 3 | # you find any incorrect lower bounds, let us know or propose a fix. 4 | 5 | pbr>=2.0.0 # Apache-2.0 6 | Jinja2>=2.10 # BSD License (3 clause) 7 | oslo.serialization>=2.18.0 # Apache-2.0 8 | psutil>=3.2.2 # BSD 9 | oslo.i18n>=3.15.3 # Apache-2.0 10 | oslo.utils>=3.33.0 # Apache-2.0 11 | oslo.config>=5.1.0 # Apache-2.0 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = oslo.reports 3 | summary = oslo.reports library 4 | description_file = 5 | README.rst 6 | author = OpenStack 7 | author_email = openstack-discuss@lists.openstack.org 8 | home_page = https://docs.openstack.org/oslo.reports/latest 9 | python_requires = >=3.9 10 | classifier = 11 | Environment :: OpenStack 12 | Intended Audience :: Information Technology 13 | Intended Audience :: System Administrators 14 | License :: OSI Approved :: Apache Software License 15 | Operating System :: POSIX :: Linux 16 | Programming Language :: Python 17 | Programming Language :: Python :: 3 18 | Programming Language :: Python :: 3.9 19 | Programming Language :: Python :: 3.10 20 | Programming Language :: Python :: 3.11 21 | Programming Language :: Python :: 3.12 22 | Programming Language :: Python :: 3 :: Only 23 | Programming Language :: Python :: Implementation :: CPython 24 | 25 | [extras] 26 | greenlet = 27 | greenlet>=0.4.15 # MIT 28 | 29 | [files] 30 | packages = 31 | oslo_reports 32 | 33 | [entry_points] 34 | oslo.config.opts = 35 | oslo.reports = oslo_reports.opts:list_opts 36 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import setuptools 17 | 18 | setuptools.setup( 19 | setup_requires=['pbr>=2.0.0'], 20 | pbr=True) 21 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | oslotest>=3.2.0 # Apache-2.0 2 | stestr>=2.0.0 # Apache-2.0 3 | 4 | # for testing optional parts 5 | greenlet>=0.4.15 # MIT 6 | 7 | coverage>=4.0 # Apache-2.0 8 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.18.0 3 | envlist = py3,pep8 4 | 5 | [testenv] 6 | deps = 7 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 8 | -r{toxinidir}/test-requirements.txt 9 | -r{toxinidir}/requirements.txt 10 | commands = stestr run --slowest {posargs} 11 | 12 | [testenv:pep8] 13 | skip_install = true 14 | deps = 15 | pre-commit 16 | commands = 17 | pre-commit run -a 18 | 19 | [testenv:venv] 20 | commands = {posargs} 21 | 22 | [testenv:docs] 23 | allowlist_externals = 24 | rm 25 | deps = 26 | {[testenv]deps} 27 | -r{toxinidir}/doc/requirements.txt 28 | commands = 29 | rm -rf doc/build doc/source/reference/api 30 | sphinx-build -W --keep-going -b html doc/source doc/build/html 31 | 32 | [testenv:cover] 33 | setenv = 34 | PYTHON=coverage run --source oslo_reports --parallel-mode 35 | commands = 36 | coverage erase 37 | stestr run {posargs} 38 | coverage combine 39 | coverage html -d cover 40 | coverage xml -o cover/coverage.xml 41 | coverage report --show-missing 42 | 43 | [flake8] 44 | # E123, E125 skipped as they are invalid PEP-8. 45 | # W504 line break after binary operator 46 | show-source = True 47 | ignore = E123,E125,W504 48 | builtins = _ 49 | exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build 50 | 51 | [hacking] 52 | import_exceptions = 53 | 54 | [testenv:releasenotes] 55 | allowlist_externals = 56 | rm 57 | deps = 58 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 59 | -r{toxinidir}/doc/requirements.txt 60 | commands = 61 | rm -rf releasenotes/build 62 | sphinx-build -a -E -W -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html 63 | 64 | --------------------------------------------------------------------------------