python-ceilometerclient-2.9.0/0000775000175000017500000000000013117513272017520 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/AUTHORS0000664000175000017500000001127713117513271020577 0ustar jenkinsjenkins00000000000000Aleksey Ripinen Alex Gaynor Andreas Jaeger Andreas Jaeger Andrey Kurilin Angus Salkeld Anh Tran Artur Svechnikov Atsushi SAKAI Balazs Gibizer Bartosz Górski Boris Pavlovic Brian Waldon Chaozhe.Chen ChenZheng Chmouel Boudjnah Chris Dent Christian Berendt Chuck Short Clenimar Filemon Cyril Roelandt Dan Florea Davanum Srinivas Dina Belova Dirk Mueller Doug Hellmann Ekaterina Khripunova Enol Fernandez Eoghan Glynn Eric Pendergrass Eugeniya Kudryashova Fabio Giannetti ForestLee Gordon Chung Guangyu Suo Gábor Antal Hanxi Liu Igor Degtiarov Ildiko Vancsa Ilya Tyaptin Jake Yip James E. Blair Jason Zhang Jeremy Liu Jeremy Stanley Jia Dong Jiří Suchomel John Herndon Juan Antonio Osorio Robles Julien Danjou Kieran Spear Kui Shi Lan Qi song Lena Novokshonova Lianhao Lu LiuNanke Luong Anh Tuan Marc Solanas Martin Geisler Matthew Edmonds Mehdi Abaakouk Mehdi Abaakouk Michał Jastrzębski Monty Taylor Nejc Saje Nejc Saje Noorul Islam K M Ondřej Nový OpenStack Release Bot Pavlo Shchelokovskyy Pradeep Kilambi Pradeep Kilambi Prateek Khushalani Rafael Rivero Rohit Jaiswal Roman Vasilets Rui Chen Ryota MIBU Sanjana Pai Sascha Peilicke Shuquan Huang Srinivas Sakhamuri Stefano Zilli Steve Martinelli Steve Martinelli Steve Wilkerson Stéphane Albert Sushil Kumar Swapnil Kulkarni (coolsvap) Thomas Herve Tong Li Tony Breeds Uros Jovanovic Victor Morales Wu Wenxiang Zhi Kun Liu ZhiQiang Fan ZhiQiang Fan akanksha april aviau chenxiao ekudryashova fujioka yuuichi gecong1973 gord chung gordon chung hexin joey5678 liu-sheng liuqing liusheng llg8212 ls1175 luqitao rabi raiesmh08 ricolin sanjana shu-mutou tuathail venkatamahesh xialinjuan xiangjun li xiaozhuangqing yolanda.robla yuyafei zhangguoqing zhangjianfeng python-ceilometerclient-2.9.0/README.rst0000664000175000017500000000223513117513017021206 0ustar jenkinsjenkins00000000000000Python bindings to the Ceilometer API ===================================== .. image:: https://img.shields.io/pypi/v/python-ceilometerclient.svg :target: https://pypi.python.org/pypi/python-ceilometerclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-ceilometerclient.svg :target: https://pypi.python.org/pypi/python-ceilometerclient/ :alt: Downloads This is a client library for Ceilometer built on the Ceilometer API. It provides a Python API (the ``ceilometerclient`` module) and a command-line tool (``ceilometer``). * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ .. _PyPi: https://pypi.python.org/pypi/python-ceilometerclient .. _Online Documentation: http://docs.openstack.org/developer/python-ceilometerclient .. _Launchpad project: https://launchpad.net/python-ceilometerclient .. _Blueprints: https://blueprints.launchpad.net/python-ceilometerclient .. _Bugs: https://bugs.launchpad.net/python-ceilometerclient .. _Source: https://git.openstack.org/cgit/openstack/python-ceilometerclient python-ceilometerclient-2.9.0/tools/0000775000175000017500000000000013117513272020660 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/tools/ceilometer.bash_completion0000664000175000017500000000167313117513017026104 0ustar jenkinsjenkins00000000000000# bash completion for openstack ceilometer _ceilometer_opts="" # lazy init _ceilometer_flags="" # lazy init _ceilometer_opts_exp="" # lazy init _ceilometer() { local cur prev kbc COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_ceilometer_opts" == "x" ] ; then kbc="`ceilometer bash-completion | sed -e "s/ -h / /"`" _ceilometer_opts="`echo "$kbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`" _ceilometer_flags="`echo " $kbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`" _ceilometer_opts_exp="`echo $_ceilometer_opts | sed -e "s/[ ]/|/g"`" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_ceilometer_opts_exp)" " && "$prev" != "help" ]] ; then COMPREPLY=($(compgen -W "${_ceilometer_flags}" -- ${cur})) else COMPREPLY=($(compgen -W "${_ceilometer_opts}" -- ${cur})) fi return 0 } complete -F _ceilometer ceilometer python-ceilometerclient-2.9.0/setup.cfg0000664000175000017500000000176213117513272021347 0ustar jenkinsjenkins00000000000000[metadata] name = python-ceilometerclient summary = OpenStack Telemetry API Client Library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://docs.openstack.org/developer/python-ceilometerclient classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = ceilometerclient [global] setup-hooks = pbr.hooks.setup_hook [entry_points] console_scripts = ceilometer = ceilometerclient.shell:main [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 python-ceilometerclient-2.9.0/LICENSE0000664000175000017500000002363613117513017020534 0ustar jenkinsjenkins00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. python-ceilometerclient-2.9.0/releasenotes/0000775000175000017500000000000013117513272022211 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/releasenotes/notes/0000775000175000017500000000000013117513272023341 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/releasenotes/notes/alarm_deprecated-74363d70d48a20e2.yaml0000664000175000017500000000011413117513017031543 0ustar jenkinsjenkins00000000000000--- deprecations: - Alarm commands are deprecated in favor of aodhclient. python-ceilometerclient-2.9.0/releasenotes/notes/panko-redirect-9d03598dbf51f8fd.yaml0000664000175000017500000000031013117513017031440 0ustar jenkinsjenkins00000000000000--- prelude: > Panko replaces the API and storage of events previously in Ceilometer features: - | Similar to aodh redirect support, specify `panko_endpoint` as a redirect to Panko API. python-ceilometerclient-2.9.0/releasenotes/notes/.placeholder0000664000175000017500000000000013117513017025607 0ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/releasenotes/notes/deprecation-44ae455f4ef3a81e.yaml0000664000175000017500000000030113117513017031011 0ustar jenkinsjenkins00000000000000--- deprecations: - | As the Ceilometer API has been deprecated, this client is also now marked as deprecated and will print a warning when used as a command-line interface tool. python-ceilometerclient-2.9.0/releasenotes/source/0000775000175000017500000000000013117513272023511 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/releasenotes/source/liberty.rst0000664000175000017500000000022213117513017025706 0ustar jenkinsjenkins00000000000000============================== Liberty Series Release Notes ============================== .. release-notes:: :branch: origin/stable/liberty python-ceilometerclient-2.9.0/releasenotes/source/ocata.rst0000664000175000017500000000023013117513017025322 0ustar jenkinsjenkins00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata python-ceilometerclient-2.9.0/releasenotes/source/index.rst0000664000175000017500000000044613117513017025353 0ustar jenkinsjenkins00000000000000Welcome to Ceilometer Client Release Notes documentation! ========================================================= Contents ======== .. toctree:: :maxdepth: 2 unreleased ocata newton mitaka liberty Indices and tables ================== * :ref:`genindex` * :ref:`search` python-ceilometerclient-2.9.0/releasenotes/source/conf.py0000664000175000017500000002213413117513017025007 0ustar jenkinsjenkins00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # # Ceilometer Client Release Notes documentation build configuration file, # created by sphinx-quickstart on Mon Nov 23 20:38:38 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'oslosphinx', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Ceilometer Client Release Notes' copyright = u'2015-present, Ceilometer developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # import pbr.version ceilometer_version = pbr.version.VersionInfo('python-ceilometerclient') # The short X.Y version. version = ceilometer_version.canonical_version_string() # The full version, including alpha/beta/rc tags. release = ceilometer_version.version_string_with_vcs() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'CeilometerClientReleaseNotestdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'PythonCeilometerClient.tex', u'Ceilometer Client Release Notes Documentation', u'Ceilometer developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pythonceilometerclient', u'Ceilometer Client Release Notes Documentation', [u'Ceilometer developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'PythonCeilometerClient', u'Ceilometer Client Release Notes Documentation', u'Ceilometer developers', 'PythonCeilometerClient', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] python-ceilometerclient-2.9.0/releasenotes/source/newton.rst0000664000175000017500000000023213117513017025547 0ustar jenkinsjenkins00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton python-ceilometerclient-2.9.0/releasenotes/source/_templates/0000775000175000017500000000000013117513272025646 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013117513017030114 0ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/releasenotes/source/mitaka.rst0000664000175000017500000000023213117513017025503 0ustar jenkinsjenkins00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka python-ceilometerclient-2.9.0/releasenotes/source/_static/0000775000175000017500000000000013117513272025137 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000013117513017027405 0ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/releasenotes/source/unreleased.rst0000664000175000017500000000015313117513017026366 0ustar jenkinsjenkins00000000000000============================ Current Series Release Notes ============================ .. release-notes:: python-ceilometerclient-2.9.0/requirements.txt0000664000175000017500000000076713117513017023013 0ustar jenkinsjenkins00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=1.6 # Apache-2.0 iso8601>=0.1.11 # MIT keystoneauth1>=2.1.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.17.0 # Apache-2.0 PrettyTable<0.8,>=0.7 # BSD requests!=2.9.0,>=2.8.1 # Apache-2.0 six>=1.9.0 # MIT stevedore>=1.10.0 # Apache-2.0 python-ceilometerclient-2.9.0/setup.py0000664000175000017500000000200413117513017021223 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=1.8'], pbr=True) python-ceilometerclient-2.9.0/test-requirements.txt0000664000175000017500000000110313117513017023751 0ustar jenkinsjenkins00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 coverage>=3.6 # Apache-2.0 fixtures<2.0,>=1.3.1 # Apache-2.0/BSD mock>=1.2 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 reno>=1.6.2 # Apache2 python-subunit>=0.0.18 # Apache-2.0/BSD sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD tempest>=11.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT python-ceilometerclient-2.9.0/python_ceilometerclient.egg-info/0000775000175000017500000000000013117513272026142 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/python_ceilometerclient.egg-info/pbr.json0000664000175000017500000000005613117513271027620 0ustar jenkinsjenkins00000000000000{"git_version": "3b4e35a", "is_release": true}python-ceilometerclient-2.9.0/python_ceilometerclient.egg-info/not-zip-safe0000664000175000017500000000000113117513263030370 0ustar jenkinsjenkins00000000000000 python-ceilometerclient-2.9.0/python_ceilometerclient.egg-info/PKG-INFO0000664000175000017500000000424413117513271027242 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-ceilometerclient Version: 2.9.0 Summary: OpenStack Telemetry API Client Library Home-page: http://docs.openstack.org/developer/python-ceilometerclient Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the Ceilometer API ===================================== .. image:: https://img.shields.io/pypi/v/python-ceilometerclient.svg :target: https://pypi.python.org/pypi/python-ceilometerclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-ceilometerclient.svg :target: https://pypi.python.org/pypi/python-ceilometerclient/ :alt: Downloads This is a client library for Ceilometer built on the Ceilometer API. It provides a Python API (the ``ceilometerclient`` module) and a command-line tool (``ceilometer``). * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ .. _PyPi: https://pypi.python.org/pypi/python-ceilometerclient .. _Online Documentation: http://docs.openstack.org/developer/python-ceilometerclient .. _Launchpad project: https://launchpad.net/python-ceilometerclient .. _Blueprints: https://blueprints.launchpad.net/python-ceilometerclient .. _Bugs: https://bugs.launchpad.net/python-ceilometerclient .. _Source: https://git.openstack.org/cgit/openstack/python-ceilometerclient Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 python-ceilometerclient-2.9.0/python_ceilometerclient.egg-info/dependency_links.txt0000664000175000017500000000000113117513271032207 0ustar jenkinsjenkins00000000000000 python-ceilometerclient-2.9.0/python_ceilometerclient.egg-info/top_level.txt0000664000175000017500000000002113117513271030664 0ustar jenkinsjenkins00000000000000ceilometerclient python-ceilometerclient-2.9.0/python_ceilometerclient.egg-info/SOURCES.txt0000664000175000017500000000650713117513272030036 0ustar jenkinsjenkins00000000000000.testr.conf AUTHORS CONTRIBUTING.rst ChangeLog LICENSE README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini ceilometerclient/__init__.py ceilometerclient/client.py ceilometerclient/exc.py ceilometerclient/i18n.py ceilometerclient/shell.py ceilometerclient/apiclient/__init__.py ceilometerclient/apiclient/auth.py ceilometerclient/apiclient/base.py ceilometerclient/apiclient/client.py ceilometerclient/apiclient/exceptions.py ceilometerclient/apiclient/fake_client.py ceilometerclient/apiclient/utils.py ceilometerclient/common/__init__.py ceilometerclient/common/base.py ceilometerclient/common/utils.py ceilometerclient/tests/__init__.py ceilometerclient/tests/functional/__init__.py ceilometerclient/tests/functional/base.py ceilometerclient/tests/functional/test_readonly_ceilometer.py ceilometerclient/tests/functional/hooks/post_test_hook.sh ceilometerclient/tests/unit/__init__.py ceilometerclient/tests/unit/test_client.py ceilometerclient/tests/unit/test_exc.py ceilometerclient/tests/unit/test_openstack_common.py ceilometerclient/tests/unit/test_shell.py ceilometerclient/tests/unit/test_utils.py ceilometerclient/tests/unit/utils.py ceilometerclient/tests/unit/v2/__init__.py ceilometerclient/tests/unit/v2/test_alarms.py ceilometerclient/tests/unit/v2/test_capabilities.py ceilometerclient/tests/unit/v2/test_event_types.py ceilometerclient/tests/unit/v2/test_events.py ceilometerclient/tests/unit/v2/test_options.py ceilometerclient/tests/unit/v2/test_query_alarm_history.py ceilometerclient/tests/unit/v2/test_query_alarms.py ceilometerclient/tests/unit/v2/test_query_samples.py ceilometerclient/tests/unit/v2/test_resources.py ceilometerclient/tests/unit/v2/test_samples.py ceilometerclient/tests/unit/v2/test_shell.py ceilometerclient/tests/unit/v2/test_statistics.py ceilometerclient/tests/unit/v2/test_trait_descriptions.py ceilometerclient/tests/unit/v2/test_traits.py ceilometerclient/v2/__init__.py ceilometerclient/v2/alarms.py ceilometerclient/v2/capabilities.py ceilometerclient/v2/client.py ceilometerclient/v2/event_types.py ceilometerclient/v2/events.py ceilometerclient/v2/meters.py ceilometerclient/v2/options.py ceilometerclient/v2/query.py ceilometerclient/v2/resources.py ceilometerclient/v2/samples.py ceilometerclient/v2/shell.py ceilometerclient/v2/statistics.py ceilometerclient/v2/trait_descriptions.py ceilometerclient/v2/traits.py doc/ext/gen_ref.py doc/source/api.rst doc/source/conf.py doc/source/index.rst doc/source/shell.rst python_ceilometerclient.egg-info/PKG-INFO python_ceilometerclient.egg-info/SOURCES.txt python_ceilometerclient.egg-info/dependency_links.txt python_ceilometerclient.egg-info/entry_points.txt python_ceilometerclient.egg-info/not-zip-safe python_ceilometerclient.egg-info/pbr.json python_ceilometerclient.egg-info/requires.txt python_ceilometerclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/alarm_deprecated-74363d70d48a20e2.yaml releasenotes/notes/deprecation-44ae455f4ef3a81e.yaml releasenotes/notes/panko-redirect-9d03598dbf51f8fd.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/ceilometer.bash_completionpython-ceilometerclient-2.9.0/python_ceilometerclient.egg-info/requires.txt0000664000175000017500000000027013117513271030540 0ustar jenkinsjenkins00000000000000pbr>=1.6 iso8601>=0.1.11 keystoneauth1>=2.1.0 oslo.i18n>=2.1.0 oslo.serialization>=1.10.0 oslo.utils>=3.17.0 PrettyTable<0.8,>=0.7 requests!=2.9.0,>=2.8.1 six>=1.9.0 stevedore>=1.10.0 python-ceilometerclient-2.9.0/python_ceilometerclient.egg-info/entry_points.txt0000664000175000017500000000007413117513271031440 0ustar jenkinsjenkins00000000000000[console_scripts] ceilometer = ceilometerclient.shell:main python-ceilometerclient-2.9.0/ChangeLog0000664000175000017500000004522413117513271021300 0ustar jenkinsjenkins00000000000000CHANGES ======= 2.9.0 ----- * Deprecate ceilometerclient * [bugfix]with keystone v3 Could not find domain: default * delete bash\_completion in subcommand * Remove log translations * Remove support for py34 * Handle log message interpolation by the logger * Adjust parameters order of tenant\_xxx and project\_xxx * Update reno for stable/ocata * shell: do not force domain to be present 2.8.0 ----- * Enable coverage report in console output * cleanup aodh redirect * panko redirect * cleanup aodh mocks * [doc] Note lack of constraints is a choice * Adding default project and domain if nothing is specified * Don't include openstack/common in flake8 exclude list * Add \_\_ne\_\_ built-in function * Bump hacking to 0.12 * move old oslo-incubator code out of openstack/common * Make method import\_versioned\_module work 2.7.0 ----- * Fixes SSLError during cclient.meters.list() by https * Set code and details on HTTPException * Enable release notes translation * Using assertIsNone() instead of assertEqual(None) * Replace assertEqual(None, ...) with assertIsNone(...) * Deprecate alarms commands * Update reno for stable/newton * Fix from\_response method to process response from requests 2.6.0 ----- * [trivial] add a blank character * Correct the parameters's position of assertEqual * Remove keystoneclient dependency * Add support for Python 3.5 * Remove discover from test-requirements 2.5.0 ----- * tools: remove unused scripts * base.Resource not define \_\_ne\_\_() built-in function * Ignore aodh\_endpoint argument * Switch to keystoneauth * Correct the usage of the method assertEqual() * Update the home-page with developer documentation * Update to hacking 0.11.0 * Only install hacking in pep8 * Updated from global requirements * Enable releasenotes documentation * Updated from global requirements * Switch from deprecated tempest-lib to tempest * Updated from global requirements * Fix the disorder of items of Traits in the output of event-list * remove default value of repeat-actions * Decouple ceilometerclient without aodh services running * Trivial: remove \`None\` as a redundant argument to dict.get() * Updated from global requirements * Updated from global requirements * Enhances client to support unique meter retrieval * Fixing a word spelling * Updated from global requirements * make aggregation-method argument as a mandatory field 2.3.0 ----- * improve readme contents * Remove argparse from requirements * drop oslo-incubator modules: cliutils and uuidutils * Updated from global requirements * remove unused code * fix project\_id and user\_id fields not set when create gnocchi alarm * Updated from global requirements * improve help docs for cli commands * change the dict output format to make consistency under py27 and py34 2.2.1 ----- * Only token or creds are required 2.2.0 ----- * Fix to disable meter-links from CLI * Use the oslo.utils.reflection to extract the class name * remove py24 compatible code * Don't copy the auth\_plugin for aodh * Remove openstack-common.conf * Use assertTrue/False instead of assertEqual(T/F) * Improve ceilometer alarm enabled input parameter validation * Updated from global requirements * Replace assertEqual(None, \*) with assertIsNone in tests * remove MANIFEST.in * Updated from global requirements * Restructuring a comment in Python-Ceilometerclient * Deprecated tox -downloadcache option removed * Revert "Delete python bytecode before every test run" * Remove duplicated assert * Delete python bytecode before every test run * Fix Python 3 version announced in metadata * Remove py26 support * Fix the parameter order of assertEqual in ceilometerclient * Fix Resource.\_\_eq\_\_ mismatch semantics of object equal 2.1.0 ----- * Updated from global requirements * Token will be lost when constructing a ceilometer client * Correct an api reference error * Ensure metering is the default service\_type * Ensure keystoneauth1 exception are raised * Ignores endpoint\_type if interface is provided * Remove double API requests * Catch exception raised by keystoneauth1 * Updated from global requirements * Updated from global requirements 2.0.1 ----- * Last sync from oslo-incubator * fix gnocchi alarm create to use right field name 2.0.0 ----- * Update help message for alarm-gnocchi-resources-threshold-create * Updated from global requirements * Fix to enable meter-links on resource-list CLI * drop v1 client * Updated from global requirements * fix typos in docstring * print sample id for query-samples * Updated from global requirements * Updated from global requirements * Updated from global requirements * Move to keystone session object * Add statistic in rule information * Updated from global requirements * Change the word for clarity 1.5.0 ----- * add limit support * do not generate meter links on resource-list * refactor: move 'repeat\_actions' to common args * Support alarm-event-{create,update} * Use new location of subunit2html * Updated from global requirements * Don't try to get aodh endpoint if auth\_url didn't provided * Updated from global requirements 1.4.0 ----- * tenant\_id not required with keystone v3 * Updated from global requirements * Add support for client redirecting to aodh endpoint if available * Updated from global requirements * Updated from global requirements * Fix unit tests failing caused by new Mock release * Updated from global requirements * Add ceilometerclient support for api-no-pipeline * Change default values from [] to None * Typo: Show an sample => Show a sample * Add alarm severity in alarm-history output * Updated from global requirements 1.3.0 ----- * Explicit error for wrong resource metadata format * Removes whitespace from CLI queries * Add capability for creating array of samples * Updated from global requirements * Drop use of 'oslo' namespace package * Pass OS\_\* env vars fix for tox 2.0 * Catch missing 404 exceptions in client * Use oslo\_utils instead of deprecated oslo.utils * [unittest] Increase client and shell modules cover * Fix alarm-evaluator can't start when ssl was enabled * Updated from global requirements * Updated from global requirements * remove useless event unit test code * Allow changing project-id and user-id by alarm-threshold-update * move capabilities ut code to unit dir * show raw details of event 1.2.0 ----- * Uncap library requirements for liberty * update README.rst to help release process 1.1.0 ----- * Add timeout for keystoneclient session * add region\_name to auth plugin parameters * Added missing ceilometer CLI tests * Updates examples to reference new sample format * ceilometerclient insecure argument no longer works * fix client docstring * Add CLI for Capabilities REST API * print user friendly error message for alarm update time constraints * ceilometerclient fails with keystone v3 auth * Updated from global requirements * Set auth\_plugin in \_\_init\_\_ * support specify user-id when create sample and alarm * add in missing options * Add a post\_test\_hook for gate-run functional tests * Add cli functional tests from tempest * alarm: Use new gnocchi aggregation API * Move unit tests into their own subdirectory * Add a py34 target for tox * Fixes bug with Client function not setting up SSL params * Updated from global requirements * Enable specified project\_id in CLI commands 1.0.13 ------ * Updated from global requirements * Corrected the errors in sample-show and sample-create * Allow create/update gnocchi alarm rule * Don't enforce a kind of alarm rules * Support unicode for alarm * Update get\_client() parameters with correct variable names * Add Sample API support * add --no-traits for event-list * Updated from global requirements * Add severity field to alarm CLI * Updated from global requirements * Remove trailing space before , in the help string of --time-constraint * Fix improper parameter setup for cacert and client certs * Upgrade to hacking 0.10 * event-list should sort by timestamp, not id * Allow all pep8 checks * Fix H105 pep8 error * Update hacking to global requirements * Triple double-quoted strings should be used for docstrings * Updated from global requirements * Support ceilometer-url and os-endpoint * sync to latest oslo-incubator code * Add apiclient to openstack-common.conf * Add client property for common.base.Manager * Allow graceful shutdown on Ctrl+C * Updated from global requirements * Make methods static where it's possible * Fix old-style classes declaration * Remove redundant parentheses (except openstack.common) * Enable --os-insecure CLI option * sync with oslo and use oslo.i18n * Workflow documentation is now in infra-manual * Updated from global requirements * Support os-endpoint-type * Alarm TimeConstraint display incorrect * Add \`requests\` to requirements * Fix timeout argument not treated as integer * Refactor tests/test\_shell.py * Add --slowest option for testr * Fix wrong initialization of AuthPlugin for keystone v3 * Updated from global requirements * Add CONTRIBUTING.rst * Updated from global requirements * Updated from global requirements 1.0.12 ------ * sync oslo code * switch to oslo.utils 1.0.11 ------ * Reduce redundant parameter of some commands in CLI * Typo "authtenticated" instead of "authenticated" * Fix AuthPlugin authentification * Stop using intersphinx * Updated from global requirements * Fix sample-create in v2 api calls * keystone discovery fallback support * Fix a help string nit for statistics command * Verify alarm found before modifying * Revamp documentation, add module references * Check if the alarm has time constraints field before displaying * Updated from global requirements * Removed undefined method in install\_env.py file * Replace assertTrue with assertIsNotNone for check an object * Add endpoint opt into auth\_plugin * Updated from global requirements * Use HTTPClient from common Oslo code * Update developer docs template * Add doc/build to .gitignore * Add docs job to tox.ini * Don't expose X-Auth-Token in ceilometer CLI * Calculate a suitable column width for positional arguments * Use suitable assert * Improve --debug logging output * Update python-ceilometerclient to support Keystone V3 API * Updated from global requirements * Fix the alarm history order shown to user * Provide explicit help string of resource-metadata * Add methods to resource classes * Improve a bit query API * Fix hacking rules: H302,H305,H307,H402 * Updated from global requirements * Fix alarm-threshold-update --query option * Refactor split\_by\_op and split\_by\_datatype * Remove © and remove unnecessary encoding lines * use mock instead of try...finally * Avoid unnecessary stderr message when run test * Avoid empty entity field in uri path * replace dict.iteritems() with six.iteritems(dict) * Fix exception handling of CLI * fixed several pep8 issues * Remove out-dated exceptions * extraneous vim editor configuration comments * Correct help string about insufficient\_data * Avoid dead loop when token is string format * Fix some help strings * Updated from global requirements * Revert "Fix temporary pypy gate issue with setuptools" * Display message on HTTPException * Update v2.options docstring * Correct testcase content 1.0.10 ------ * Ensure statistics aggregates are ordered with parameterized first * Statistics groupby handling improvement * Implementation of statistics aggregators * Adds alarm time constraint support to ceilometer CLI * Updated from global requirements * Fix temporary pypy gate issue with setuptools * Add complex query support for alarm history * Add complex query support for alarms * Checking the type of auth\_token, and fixing it if necessary * Deprecate 'alarm-update' * Add complex query support for samples * test created virtual env directory should git ignored * Updated from global requirements * Fix the ceilometer trait-description-list command * Updated from global requirements * py3kcompat: remove in python-ceilometerclient * Python 3 compatibility * Fix a typo in a comment in v2 client * test\_url\_generation\_with\_proxy: do not use contextlib.nested() * Ensure url sent to proxy don't have redundant / * Remove unused mock in v2/test\_shell.py * fix help message of deprecated command * Sync with Oslo * Python 3: fix format\_nested\_list\_of\_dict() * Remove tox locale overrides * Improve help strings 1.0.9 ----- * Modify ceilometer client cmd line help info * Remove unused import for print\_function * Remove ununsed httplib2 requirement * Updated from global requirements * Update client to display data type of traits * Using common methods from oslo cliutils * Avoid discarding alarm-threshold-create --query option * Fix typos picked up by misspellings * return sample info when creating sample with CLI * Enable hacking H233 rule * Using common method 'bool\_from\_string' from oslo strutils * Raise traceback on error when using CLI and -debug * Remove print debugs statements * replace assertTrue(isinstance) to assertIsInstance * Add support for groupby in statistics for API v2 * Remove dependencies on pep8, pyflakes and flake8 * Replace inheritance hierarchy with composition * fix optional parameter of creating sample * abbreviating --meter-name to -m in alarm commands * Remove unused imports * Support the Event API * Python 3: fix test\_sample\_list * Use Resource() class from common Oslo code * client looking at wrong cacert argument name * Supports bash\_completion for ceilometerclient * Fix the ceilometerlient log curl request incorrectly * Python 3: use six.moves.zip() rather than itertools.izip() * Display message on HTTP 400 * Fix alarm-combination-update operator argument * Improve description of some commands * Updates tox.ini to use new features * Updated from global requirements 1.0.8 ----- * sync with oslo-incubator * Change OpenStack Metering to OpenStack Telemetry * Update .gitignore * Add HTTP proxy support to ceilometer client * Encode exception on ceilometer-client for UnicodeDecodeError 1.0.7 ----- * Allow alarm-threshold-update to upate generic attributes * Enable pep8 E711/E712/E721/H302 checking * Enable pep8 E128 checking * Enable pep8 E121/E122/E123 checking * Allow specifying a timestamp when creating a sample * Avoid reset of repeat\_actions attribute on alarm update * Ensure basic logging config is applied * Support building wheels (PEP-427) * Add six to requirements.txt * Updated from global requirements * Ceilometer UnicodeEncodeError when update or show alarm fix * Adds the 'limit' parameter to sample list command in V2 API * Fix order of sample list * add cliutils from oslo-incubator * update oslo libraries * Updated from global requirements * Fix cacert argument to HTTPS connection * Updated from global requirements * Replace mox3 with mock in unit test * Updated from global requirements * Fix missed Pep8 error with 1.4.6 * Updated from global requirements * Replace mox with mox3 * align the order of parameters for urlencode() * replace basetring/xrange * Replace unicode() with six.u() * replace dict.keys() with list(dict) * Import urlutils to substitute urllib * Use six.iteritems() for dict * Translate print statement to print function * Fix module importing issues for Python 3 * Import six.StringIO 1.0.6 ----- * Add support for new alarm-history command * Use standard CLI object-verb ordering for alarm-{g|s}set-state * Fix shell.do\_alarm\_get\_state to get as opposed to set * Updated from global requirements * Allow to update an alarm partially * Added support to --os-cacert * Help messages: specify which options are required * Improve the CM shell client alarm visualisation 1.0.5 ----- * Use the new alarm format * Replace OpenStack LLC with OpenStack Foundation * Pass region\_name argument to keystone client * Adding missing 'statistic' field to alarm-show * Use global openstack requirements * Fix a typo in "sample-create" help message * Added support for running the tests under PyPy with tox * alarm: rename counter\_name to meter\_name 1.0.3 ----- * Add support for new alarm repeat\_actions attribute * Updated from global requirements * Handle case where os\_auth\_token is set to '' * Ensure keystoneclient.auth\_token is re-evaluated * Fix typo in help text * Enhance ceilometer statistics command with --period 1.0.2 ----- * Allow to set matching\_metadata with the cli * Add support for creating samples * Rename README.md to README.rst * Relax OpenStack upper capping of client versions * Allow Keystoneclient 0.3.x * Sync install\_venv\_common from oslo * Add matching\_metadata to the allowed attributes * Move tests to ceilometerclient 1.0.1 ----- * Avoid unnecessary GET of existing alarm for update * Make authenticated client easier to consume * Add support for specifying statistics period * requirements.txt is not configured properly * Drop unnecessary arg when instantiating HTTP class * Remove explicit distribute depend * Start using pyflakes * Use Python 3.x compatible except construct * Add client support for creating new alarms * Add client support for updating alarms * Fix install\_venv.py requirements file * Enable more pep8 checks * Migrate to pbr * Rename tools/pip-requires to requirements.txt * Fix pep H306 (import order) * Fix pep H402 and H401 errors * Migrate to flake8 * Add support for deleting alarms * Add support for getting individual alarms * Add support for listing alarms * Fix mis-scoped Client class breaking CLI * Use testr to run tests * Add install\_venv\_common from oslo * Update oslo code and split the module lines * Use the utils.BaseTestCase for all tests * Fix pep8 errors in test code * Remove unused test code in test\_util.py * Fix manifest (README.rst -> README.md) * client does not show version * Sync requirements with openstack-common/requirements * Fix for Bug #1167521, ceilometer client crashes with missing content type response pep8 compliance * Restore compatibility with PrettyTable < 0.7.2 * Change the default API version used by the cli to v2 * v2 API: added resource-show 1.0.0 ----- * Make it possible to pass -q "metadata.field=value" * Corrected help strings * Don't log unneccessarly on each http request * Catch KeyError exception as early as possible when there is no matching data on the server * Properly removing start and ending slashes * Remove warlock from pip-requires as it is not used * Add resources and meters to the v2 shell * Correct the help info of resource-list sub-command * Add shell.py so we can do v2 shell commands * Support --os-auth-token * v1-api: Added timestamp support * v1-api: Adapted resouce/user api * Add support for v2 API * Update to latest oslo-version * Add tests for samples * Add a test for list by source * fix the fields in v1 do\_meter\_list * Add missing dependencies * Pin pep8 to 1.3.3 * Add support for metadata query * Fix tests * Move repository to openstack org * Revert "Remove the event class and use Meter instead." * Remove the event class and use Meter instead * Make sure the version is prepended * Fix the default service\_type * Add basic functionality * Initial Commit python-ceilometerclient-2.9.0/PKG-INFO0000664000175000017500000000424413117513272020621 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-ceilometerclient Version: 2.9.0 Summary: OpenStack Telemetry API Client Library Home-page: http://docs.openstack.org/developer/python-ceilometerclient Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the Ceilometer API ===================================== .. image:: https://img.shields.io/pypi/v/python-ceilometerclient.svg :target: https://pypi.python.org/pypi/python-ceilometerclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-ceilometerclient.svg :target: https://pypi.python.org/pypi/python-ceilometerclient/ :alt: Downloads This is a client library for Ceilometer built on the Ceilometer API. It provides a Python API (the ``ceilometerclient`` module) and a command-line tool (``ceilometer``). * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ .. _PyPi: https://pypi.python.org/pypi/python-ceilometerclient .. _Online Documentation: http://docs.openstack.org/developer/python-ceilometerclient .. _Launchpad project: https://launchpad.net/python-ceilometerclient .. _Blueprints: https://blueprints.launchpad.net/python-ceilometerclient .. _Bugs: https://bugs.launchpad.net/python-ceilometerclient .. _Source: https://git.openstack.org/cgit/openstack/python-ceilometerclient Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 python-ceilometerclient-2.9.0/tox.ini0000664000175000017500000000204013117513017021024 0ustar jenkinsjenkins00000000000000[tox] envlist = py35,py27,pypy,pep8 minversion = 1.6 skipsdist = True [testenv] usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt # NOTE(tonyb): This project has chosen to *NOT* consume upper-constraints.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] deps = hacking<0.13,>=0.12 commands = flake8 [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' coverage report [testenv:venv] commands = {posargs} [testenv:functional] setenv = OS_TEST_PATH=./ceilometerclient/tests/functional passenv = OS_* [testenv:docs] commands= python setup.py build_sphinx [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] show-source = True exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools [hacking] import_exceptions = ceilometerclient.i18n python-ceilometerclient-2.9.0/ceilometerclient/0000775000175000017500000000000013117513272023047 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/common/0000775000175000017500000000000013117513272024337 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/common/utils.py0000664000175000017500000001620013117513017026045 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from __future__ import print_function import os import textwrap from oslo_serialization import jsonutils from oslo_utils import encodeutils import prettytable import six from ceilometerclient import exc # Decorator for cli-args def arg(*args, **kwargs): def _decorator(func): if 'help' in kwargs: if 'default' in kwargs: kwargs['help'] += " Defaults to %s." % kwargs['default'] required = kwargs.get('required', False) if required: kwargs['help'] += " Required." # Because of the sematics of decorator composition if we just append # to the options list positional options will appear to be backwards. func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs)) return func return _decorator def print_list(objs, fields, field_labels, formatters=None, sortby=0): """Print a list of objects as a table, one row per object. :param objs: Iterable of :class:`Resource` :param fields: Attributes that correspond to columns, in order :param field_labels: Labels to use in the heading of the table, default to fields. :param formatters: `dict` of callables for field formatting :param sortby: Index of the field for sorting table rows """ formatters = formatters or {} if len(field_labels) != len(fields): raise ValueError(("Field labels list %(labels)s has different number " "of elements than fields list %(fields)s"), {'labels': field_labels, 'fields': fields}) def _make_default_formatter(field): return lambda o: getattr(o, field, '') new_formatters = {} for field, field_label in six.moves.zip(fields, field_labels): if field in formatters: new_formatters[field_label] = formatters[field] else: new_formatters[field_label] = _make_default_formatter(field) kwargs = {} if sortby is None else {'sortby': field_labels[sortby]} pt = prettytable.PrettyTable(field_labels) pt.align = 'l' for o in objs: row = [] for field in field_labels: if field in new_formatters: row.append(new_formatters[field](o)) else: field_name = field.lower().replace(' ', '_') data = getattr(o, field_name, '') row.append(data) pt.add_row(row) if six.PY3: print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) else: print(encodeutils.safe_encode(pt.get_string(**kwargs))) def nested_list_of_dict_formatter(field, column_names): # (TMaddox) Because the formatting scheme actually drops the whole object # into the formatter, rather than just the specified field, we have to # extract it and then pass the value. return lambda o: format_nested_list_of_dict(getattr(o, field), column_names) def format_nested_list_of_dict(l, column_names): pt = prettytable.PrettyTable(caching=False, print_empty=False, header=True, hrules=prettytable.FRAME, field_names=column_names) # Sort by values of first column if l is not None: l.sort(key=lambda k: k.get(column_names[0])) for d in l: pt.add_row(list(map(lambda k: d[k], column_names))) return pt.get_string() def print_dict(d, dict_property="Property", wrap=0): pt = prettytable.PrettyTable([dict_property, 'Value'], print_empty=False) pt.align = 'l' for k, v in sorted(six.iteritems(d)): # convert dict to str to check length if isinstance(v, (list, dict)): v = jsonutils.dumps(v) # if value has a newline, add in multiple rows # e.g. fault with stacktrace if v and isinstance(v, six.string_types) and r'\n' in v: lines = v.strip().split(r'\n') col1 = k for line in lines: if wrap > 0: line = textwrap.fill(six.text_type(line), wrap) pt.add_row([col1, line]) col1 = '' else: if wrap > 0: v = textwrap.fill(six.text_type(v), wrap) pt.add_row([k, v]) encoded = encodeutils.safe_encode(pt.get_string()) # FIXME(gordc): https://bugs.launchpad.net/oslo-incubator/+bug/1370710 if six.PY3: encoded = encoded.decode() print(encoded) def args_array_to_dict(kwargs, key_to_convert): values_to_convert = kwargs.get(key_to_convert) if values_to_convert: try: kwargs[key_to_convert] = dict(v.split("=", 1) for v in values_to_convert) except ValueError: raise exc.CommandError( '%s must be a list of key=value not "%s"' % ( key_to_convert, values_to_convert)) return kwargs def args_array_to_list_of_dicts(kwargs, key_to_convert): """Converts ['a=1;b=2','c=3;d=4'] to [{a:1,b:2},{c:3,d:4}].""" values_to_convert = kwargs.get(key_to_convert) if values_to_convert: try: kwargs[key_to_convert] = [] for lst in values_to_convert: pairs = lst.split(";") dct = dict() for pair in pairs: kv = pair.split("=", 1) dct[kv[0]] = kv[1].strip(" \"'") # strip spaces and quotes kwargs[key_to_convert].append(dct) except Exception: raise exc.CommandError( '%s must be a list of key1=value1;key2=value2;... not "%s"' % ( key_to_convert, values_to_convert)) return kwargs def key_with_slash_to_nested_dict(kwargs): nested_kwargs = {} for k in list(kwargs): keys = k.split('/', 1) if len(keys) == 2: nested_kwargs.setdefault(keys[0], {})[keys[1]] = kwargs[k] del kwargs[k] kwargs.update(nested_kwargs) return kwargs def merge_nested_dict(dest, source, depth=0): for (key, value) in six.iteritems(source): if isinstance(value, dict) and depth: merge_nested_dict(dest[key], value, depth=(depth - 1)) else: dest[key] = value def env(*args, **kwargs): """Returns the first environment variable set. If all are empty, defaults to '' or keyword arg `default`. """ for arg in args: value = os.environ.get(arg) if value: return value return kwargs.get('default', '') python-ceilometerclient-2.9.0/ceilometerclient/common/__init__.py0000664000175000017500000000000013117513017026433 0ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/common/base.py0000664000175000017500000000576113117513017025631 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base utilities to build API operation managers and objects on top of. """ import copy from ceilometerclient.apiclient import base from ceilometerclient.apiclient import exceptions from ceilometerclient import exc def getid(obj): """Extracts object ID. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ try: return obj.id except AttributeError: return obj class Manager(object): """Managers interact with a particular type of API. It works with samples, meters, alarms, etc. and provide CRUD operations for them. """ resource_class = None def __init__(self, api): self.api = api @property def client(self): """Compatible with latest oslo-incubator.apiclient code.""" return self.api def _create(self, url, body): body = self.api.post(url, json=body).json() if body: return self.resource_class(self, body) def _list(self, url, response_key=None, obj_class=None, body=None, expect_single=False): try: resp = self.api.get(url) except exceptions.NotFound: raise exc.HTTPNotFound if not resp.content: raise exc.HTTPNotFound body = resp.json() if obj_class is None: obj_class = self.resource_class if response_key: try: data = body[response_key] except KeyError: return [] else: data = body if expect_single: data = [data] return [obj_class(self, res, loaded=True) for res in data if res] def _update(self, url, body, response_key=None): body = self.api.put(url, json=body).json() # PUT requests may not return a body if body: return self.resource_class(self, body) def _delete(self, url): self.api.delete(url) class Resource(base.Resource): """A resource represents a particular instance of an object. Resource might be tenant, user, etc. This is pretty much just a bag for attributes. :param manager: Manager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ def to_dict(self): return copy.deepcopy(self._info) python-ceilometerclient-2.9.0/ceilometerclient/client.py0000664000175000017500000004606513117513017024707 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import time from keystoneauth1 import adapter from keystoneauth1 import discover from keystoneauth1 import exceptions as ka_exc from keystoneauth1.identity import v2 as v2_auth from keystoneauth1.identity import v3 as v3_auth from keystoneauth1 import session from oslo_utils import importutils from oslo_utils import strutils import six.moves.urllib.parse as urlparse from ceilometerclient.apiclient import auth from ceilometerclient.apiclient import client from ceilometerclient.apiclient import exceptions from ceilometerclient import exc def _discover_auth_versions(session, auth_url): # discover the API versions the server is supporting based on the # given URL v2_auth_url = None v3_auth_url = None try: ks_discover = discover.Discover(session=session, url=auth_url) v2_auth_url = ks_discover.url_for('2.0') v3_auth_url = ks_discover.url_for('3.0') except ka_exc.DiscoveryFailure: raise except exceptions.ClientException: # Identity service may not support discovery. In that case, # try to determine version from auth_url url_parts = urlparse.urlparse(auth_url) (scheme, netloc, path, params, query, fragment) = url_parts path = path.lower() if path.startswith('/v3'): v3_auth_url = auth_url elif path.startswith('/v2'): v2_auth_url = auth_url else: raise exc.CommandError('Unable to determine the Keystone ' 'version to authenticate with ' 'using the given auth_url.') return v2_auth_url, v3_auth_url def _get_keystone_session(**kwargs): # TODO(fabgia): the heavy lifting here should be really done by Keystone. # Unfortunately Keystone does not support a richer method to perform # discovery and return a single viable URL. A bug against Keystone has # been filed: https://bugs.launchpad.net/python-keystoneclient/+bug/1330677 # first create a Keystone session cacert = kwargs.pop('cacert', None) cert = kwargs.pop('cert', None) key = kwargs.pop('key', None) insecure = kwargs.pop('insecure', False) auth_url = kwargs.pop('auth_url', None) project_id = kwargs.pop('project_id', None) project_name = kwargs.pop('project_name', None) token = kwargs['token'] timeout = kwargs.get('timeout') if insecure: verify = False else: verify = cacert or True if cert and key: # passing cert and key together is deprecated in favour of the # requests lib form of having the cert and key as a tuple cert = (cert, key) # create the keystone client session ks_session = session.Session(verify=verify, cert=cert, timeout=timeout) v2_auth_url, v3_auth_url = _discover_auth_versions(ks_session, auth_url) username = kwargs.pop('username', None) user_id = kwargs.pop('user_id', None) user_domain_name = kwargs.pop('user_domain_name', None) user_domain_id = kwargs.pop('user_domain_id', None) project_domain_name = kwargs.pop('project_domain_name', None) project_domain_id = kwargs.pop('project_domain_id', None) if v3_auth_url: if not user_domain_name: user_domain_name = 'Default' if not project_domain_name: project_domain_name = 'Default' auth = None use_domain = (user_domain_id or user_domain_name or project_domain_id or project_domain_name) use_v3 = v3_auth_url and (use_domain or (not v2_auth_url)) use_v2 = v2_auth_url and not use_domain if use_v3 and token: auth = v3_auth.Token( v3_auth_url, token=token, project_name=project_name, project_id=project_id, project_domain_name=project_domain_name, project_domain_id=project_domain_id) elif use_v2 and token: auth = v2_auth.Token( v2_auth_url, token=token, tenant_id=project_id, tenant_name=project_name) elif use_v3: # the auth_url as v3 specified # e.g. http://no.where:5000/v3 # Keystone will return only v3 as viable option auth = v3_auth.Password( v3_auth_url, username=username, password=kwargs.pop('password', None), user_id=user_id, user_domain_name=user_domain_name, user_domain_id=user_domain_id, project_name=project_name, project_id=project_id, project_domain_name=project_domain_name, project_domain_id=project_domain_id) elif use_v2: # the auth_url as v2 specified # e.g. http://no.where:5000/v2.0 # Keystone will return only v2 as viable option auth = v2_auth.Password( v2_auth_url, username, kwargs.pop('password', None), tenant_id=project_id, tenant_name=project_name) else: raise exc.CommandError('Unable to determine the Keystone version ' 'to authenticate with using the given ' 'auth_url.') ks_session.auth = auth return ks_session def _get_endpoint(ks_session, **kwargs): """Get an endpoint using the provided keystone session.""" # set service specific endpoint types endpoint_type = kwargs.get('endpoint_type') or 'publicURL' service_type = kwargs.get('service_type') or 'metering' endpoint = ks_session.get_endpoint(service_type=service_type, interface=endpoint_type, region_name=kwargs.get('region_name')) return endpoint class AuthPlugin(auth.BaseAuthPlugin): opt_names = ['tenant_id', 'region_name', 'auth_token', 'service_type', 'endpoint_type', 'cacert', 'auth_url', 'insecure', 'cert_file', 'key_file', 'cert', 'key', 'tenant_name', 'project_name', 'project_id', 'project_domain_id', 'project_domain_name', 'user_id', 'user_domain_id', 'user_domain_name', 'password', 'username', 'endpoint'] def __init__(self, auth_system=None, **kwargs): self.opt_names.extend(self.common_opt_names) super(AuthPlugin, self).__init__(auth_system, **kwargs) # NOTE(sileht): backward compat if self.opts.get('auth_token') and not self.opts.get('token'): self.opts['token'] = self.opts.get('auth_token') def _do_authenticate(self, http_client): token = self.opts.get('token') endpoint = self.opts.get('endpoint') if not (endpoint and token): ks_kwargs = self._get_ks_kwargs(http_timeout=http_client.timeout) ks_session = _get_keystone_session(**ks_kwargs) if not token: token = lambda: ks_session.get_token() if not endpoint: endpoint = _get_endpoint(ks_session, **ks_kwargs) self.opts['token'] = token self.opts['endpoint'] = endpoint def _get_ks_kwargs(self, http_timeout): project_id = (self.opts.get('project_id') or self.opts.get('tenant_id')) project_name = (self.opts.get('project_name') or self.opts.get('tenant_name')) token = self.opts.get('token') ks_kwargs = { 'username': self.opts.get('username'), 'password': self.opts.get('password'), 'user_id': self.opts.get('user_id'), 'user_domain_id': self.opts.get('user_domain_id'), 'user_domain_name': self.opts.get('user_domain_name'), 'project_id': project_id, 'project_name': project_name, 'project_domain_name': self.opts.get('project_domain_name'), 'project_domain_id': self.opts.get('project_domain_id'), 'auth_url': self.opts.get('auth_url'), 'cacert': self.opts.get('cacert'), 'cert': self.opts.get('cert'), 'key': self.opts.get('key'), 'insecure': strutils.bool_from_string( self.opts.get('insecure')), 'endpoint_type': self.opts.get('endpoint_type'), 'service_type': self.opts.get('service_type'), 'region_name': self.opts.get('region_name'), 'timeout': http_timeout, 'token': token() if callable(token) else token, } return ks_kwargs def token_and_endpoint(self, endpoint_type, service_type): token = self.opts.get('token') if callable(token): token = token() return token, self.opts.get('endpoint') def sufficient_options(self): """Check if all required options are present. :raises: AuthPluginOptionsMissing """ has_token = self.opts.get('token') has_project_domain_or_tenant = (self.opts.get('project_id') or (self.opts.get('project_name') and (self.opts.get('user_domain_name') or self.opts.get('user_domain_id'))) or (self.opts.get('tenant_id') or self.opts.get('tenant_name'))) has_credential = (self.opts.get('username') and has_project_domain_or_tenant and self.opts.get('password') and self.opts.get('auth_url')) missing = not (has_token or has_credential) if missing: missing_opts = [] opts = ['token', 'endpoint', 'username', 'password', 'auth_url', 'tenant_id', 'tenant_name'] for opt in opts: if not self.opts.get(opt): missing_opts.append(opt) raise exceptions.AuthPluginOptionsMissing(missing_opts) def _adjust_kwargs(kwargs): client_kwargs = { 'username': kwargs.get('os_username'), 'password': kwargs.get('os_password'), 'tenant_id': kwargs.get('os_tenant_id'), 'tenant_name': kwargs.get('os_tenant_name'), 'auth_url': kwargs.get('os_auth_url'), 'region_name': kwargs.get('os_region_name'), 'service_type': kwargs.get('os_service_type'), 'endpoint_type': kwargs.get('os_endpoint_type'), 'insecure': kwargs.get('os_insecure'), 'cacert': kwargs.get('os_cacert'), 'cert_file': kwargs.get('os_cert'), 'key_file': kwargs.get('os_key'), 'token': kwargs.get('os_token') or kwargs.get('os_auth_token'), 'user_domain_name': kwargs.get('os_user_domain_name'), 'user_domain_id': kwargs.get('os_user_domain_id'), 'project_domain_name': kwargs.get('os_project_domain_name'), 'project_domain_id': kwargs.get('os_project_domain_id'), } client_kwargs.update(kwargs) client_kwargs['token'] = (client_kwargs.get('token') or kwargs.get('token') or kwargs.get('auth_token')) timeout = kwargs.get('timeout') if timeout is not None: timeout = int(timeout) if timeout <= 0: timeout = None insecure = strutils.bool_from_string(client_kwargs.get('insecure')) verify = kwargs.get('verify') if verify is None: if insecure: verify = False else: verify = client_kwargs.get('cacert') or True cert = client_kwargs.get('cert_file') key = client_kwargs.get('key_file') if cert and key: cert = cert, key client_kwargs.update({'verify': verify, 'cert': cert, 'timeout': timeout}) return client_kwargs def Client(version, *args, **kwargs): client_kwargs = _adjust_kwargs(kwargs) module = importutils.import_versioned_module('ceilometerclient', version, 'client') client_class = getattr(module, 'Client') return client_class(*args, **client_kwargs) def get_client(version, **kwargs): """Get an authenticated client, based on the credentials in the kwargs. :param version: the API version to use ('1' or '2') :param kwargs: keyword args containing credentials, either: * session: a keystoneauth/keystoneclient session object * service_type: The default service_type for URL discovery * service_name: The default service_name for URL discovery * interface: The default interface for URL discovery (Default: public) * region_name: The default region_name for URL discovery * endpoint_override: Always use this endpoint URL for requests for this ceiloclient * auth: An auth plugin to use instead of the session one * user_agent: The User-Agent string to set (Default is python-ceilometer-client) * connect_retries: the maximum number of retries that should be attempted for connection errors * logger: A logging object or (DEPRECATED): * os_auth_token: (DEPRECATED) pre-existing token to re-use, use os_token instead * os_token: pre-existing token to re-use * ceilometer_url: (DEPRECATED) Ceilometer API endpoint, use os_endpoint instead * os_endpoint: Ceilometer API endpoint or (DEPRECATED): * os_username: name of user * os_password: user's password * os_user_id: user's id * os_user_domain_id: the domain id of the user * os_user_domain_name: the domain name of the user * os_project_id: the user project id * os_tenant_id: V2 alternative to os_project_id * os_project_name: the user project name * os_tenant_name: V2 alternative to os_project_name * os_project_domain_name: domain name for the user project * os_project_domain_id: domain id for the user project * os_auth_url: endpoint to authenticate against * os_cert|os_cacert: path of CA TLS certificate * os_key: SSL private key * os_insecure: allow insecure SSL (no cert verification) """ endpoint = kwargs.get('os_endpoint') or kwargs.get('ceilometer_url') return Client(version, endpoint, **kwargs) def get_auth_plugin(endpoint, **kwargs): auth_plugin = AuthPlugin( auth_url=kwargs.get('auth_url'), service_type=kwargs.get('service_type'), token=kwargs.get('token'), endpoint_type=kwargs.get('endpoint_type'), insecure=kwargs.get('insecure'), region_name=kwargs.get('region_name'), cacert=kwargs.get('cacert'), tenant_id=kwargs.get('project_id') or kwargs.get('tenant_id'), endpoint=endpoint, username=kwargs.get('username'), password=kwargs.get('password'), tenant_name=kwargs.get('project_name') or kwargs.get('tenant_name'), user_domain_name=kwargs.get('user_domain_name'), user_domain_id=kwargs.get('user_domain_id'), project_domain_name=kwargs.get('project_domain_name'), project_domain_id=kwargs.get('project_domain_id') ) return auth_plugin LEGACY_OPTS = ('auth_plugin', 'auth_url', 'token', 'insecure', 'cacert', 'tenant_id', 'project_id', 'username', 'password', 'project_name', 'tenant_name', 'user_domain_name', 'user_domain_id', 'project_domain_name', 'project_domain_id', 'key_file', 'cert_file', 'verify', 'timeout', 'cert') def _construct_http_client(**kwargs): kwargs = kwargs.copy() if kwargs.get('session') is not None: # Drop legacy options for opt in LEGACY_OPTS: kwargs.pop(opt, None) # Drop redirect endpoints from kwargs kwargs.pop('aodh_endpoint', None) kwargs.pop('panko_endpoint', None) return SessionClient( session=kwargs.pop('session'), service_type=kwargs.pop('service_type', 'metering') or 'metering', interface=kwargs.pop('interface', kwargs.pop('endpoint_type', 'publicURL')), region_name=kwargs.pop('region_name', None), user_agent=kwargs.pop('user_agent', 'python-ceilometerclient'), auth=kwargs.get('auth'), timings=kwargs.pop('timings', None), **kwargs) else: return client.BaseClient(client.HTTPClient( auth_plugin=kwargs.get('auth_plugin'), region_name=kwargs.get('region_name'), endpoint_type=kwargs.get('endpoint_type'), original_ip=kwargs.get('original_ip'), verify=kwargs.get('verify'), cert=kwargs.get('cert'), timeout=kwargs.get('timeout'), timings=kwargs.get('timings'), keyring_saver=kwargs.get('keyring_saver'), debug=kwargs.get('debug'), user_agent=kwargs.get('user_agent'), http=kwargs.get('http') )) @contextlib.contextmanager def record_time(times, enabled, *args): """Record the time of a specific action. :param times: A list of tuples holds time data. :type times: list :param enabled: Whether timing is enabled. :type enabled: bool :param args: Other data to be stored besides time data, these args will be joined to a string. """ if not enabled: yield else: start = time.time() yield end = time.time() times.append((' '.join(args), start, end)) class SessionClient(adapter.LegacyJsonAdapter): def __init__(self, *args, **kwargs): self.times = [] self.timings = kwargs.pop('timings', False) super(SessionClient, self).__init__(*args, **kwargs) def request(self, url, method, **kwargs): kwargs.setdefault('headers', kwargs.get('headers', {})) # NOTE(sileht): The standard call raises errors from # keystoneauth, where we need to raise the ceilometerclient errors. raise_exc = kwargs.pop('raise_exc', True) with record_time(self.times, self.timings, method, url): resp, body = super(SessionClient, self).request(url, method, raise_exc=False, **kwargs) if raise_exc and resp.status_code >= 400: raise exc.from_response(resp, body) return resp python-ceilometerclient-2.9.0/ceilometerclient/tests/0000775000175000017500000000000013117513272024211 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/0000775000175000017500000000000013117513272025170 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/utils.py0000664000175000017500000000146613117513017026706 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures import testtools class BaseTestCase(testtools.TestCase): def setUp(self): super(BaseTestCase, self).setUp() self.useFixture(fixtures.FakeLogger()) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/test_shell.py0000664000175000017500000002665413117513017027722 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import sys import fixtures from keystoneauth1 import session as ks_session import mock import six from testtools import matchers from ceilometerclient.apiclient import client as api_client from ceilometerclient import client from ceilometerclient import exc from ceilometerclient import shell as ceilometer_shell from ceilometerclient.tests.unit import utils FAKE_V2_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://localhost:5000/v2.0'} FAKE_V3_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_USER_DOMAIN_NAME': 'domain_name', 'OS_PROJECT_ID': '1234567890', 'OS_AUTH_URL': 'http://localhost:5000/v3'} class ShellTestBase(utils.BaseTestCase): @mock.patch('sys.stdout', new=six.StringIO()) @mock.patch.object(ks_session, 'Session', mock.MagicMock()) @mock.patch.object(client.client.HTTPClient, 'client_request', mock.MagicMock()) def shell(self, argstr): try: _shell = ceilometer_shell.CeilometerShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) return sys.stdout.getvalue() # Patch os.environ to avoid required auth info. def make_env(self, env_version, exclude=None): env = dict((k, v) for k, v in env_version.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) class ShellHelpTest(ShellTestBase): RE_OPTIONS = re.DOTALL | re.MULTILINE def test_help_unknown_command(self): self.assertRaises(exc.CommandError, self.shell, 'help foofoo') def test_help(self): required = [ '.*?^usage: ceilometer', '.*?^See "ceilometer help COMMAND" ' 'for help on a specific command', ] for argstr in ['--help', 'help']: help_text = self.shell(argstr) for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, self.RE_OPTIONS)) def test_help_on_subcommand(self): required = [ '.*?^usage: ceilometer meter-list', ".*?^List the user's meter", ] argstrings = [ 'help meter-list', ] for argstr in argstrings: help_text = self.shell(argstr) for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, self.RE_OPTIONS)) def test_get_base_parser(self): standalone_shell = ceilometer_shell.CeilometerShell() parser = standalone_shell.get_base_parser() self.assertEqual(600, parser.get_default('timeout')) class ShellBashCompletionTest(ShellTestBase): def test_bash_completion(self): completion_commands = self.shell("bash-completion") options = completion_commands.split(' ') self.assertNotIn('bash_completion', options) for option in options: self.assertThat(option, matchers.MatchesRegex(r'[a-z0-9-]')) class ShellKeystoneV2Test(ShellTestBase): @mock.patch.object(ks_session, 'Session') @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', mock.Mock(return_value=None)) def test_debug_switch_raises_error(self, mock_ksclient): mock_ksclient.side_effect = exc.HTTPUnauthorized self.make_env(FAKE_V2_ENV) args = ['--debug', 'event-list'] self.assertRaises(exc.CommandError, ceilometer_shell.main, args) @mock.patch.object(ks_session, 'Session') @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', mock.Mock(return_value=None)) def test_dash_d_switch_raises_error(self, mock_ksclient): mock_ksclient.side_effect = exc.CommandError("FAIL") self.make_env(FAKE_V2_ENV) args = ['-d', 'event-list'] self.assertRaises(exc.CommandError, ceilometer_shell.main, args) @mock.patch('sys.stderr') @mock.patch.object(ks_session, 'Session') def test_no_debug_switch_no_raises_errors(self, mock_ksclient, __): mock_ksclient.side_effect = exc.HTTPUnauthorized("FAIL") self.make_env(FAKE_V2_ENV) args = ['event-list'] self.assertRaises(SystemExit, ceilometer_shell.main, args) class ShellKeystoneV3Test(ShellTestBase): @mock.patch.object(ks_session, 'Session') @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', mock.Mock(return_value=None)) def test_debug_switch_raises_error(self, mock_ksclient): mock_ksclient.side_effect = exc.HTTPUnauthorized self.make_env(FAKE_V3_ENV) args = ['--debug', 'event-list'] self.assertRaises(exc.CommandError, ceilometer_shell.main, args) @mock.patch.object(ks_session, 'Session') def test_dash_d_switch_raises_error(self, mock_ksclient): mock_ksclient.side_effect = exc.CommandError("FAIL") self.make_env(FAKE_V3_ENV) args = ['-d', 'event-list'] self.assertRaises(exc.CommandError, ceilometer_shell.main, args) @mock.patch('sys.stderr') @mock.patch.object(ks_session, 'Session') def test_no_debug_switch_no_raises_errors(self, mock_ksclient, __): mock_ksclient.side_effect = exc.HTTPUnauthorized("FAIL") self.make_env(FAKE_V3_ENV) args = ['event-list'] self.assertRaises(SystemExit, ceilometer_shell.main, args) class ShellTimeoutTest(ShellTestBase): @mock.patch('sys.stderr', new=six.StringIO()) def _test_timeout(self, timeout, expected_msg): args = ['--timeout', timeout, 'alarm-list'] self.assertRaises(SystemExit, ceilometer_shell.main, args) self.assertEqual(expected_msg, sys.stderr.getvalue().splitlines()[-1]) def test_timeout_invalid_value(self): expected_msg = ('ceilometer: error: argument --timeout: ' 'abc must be an integer') self._test_timeout('abc', expected_msg) def test_timeout_negative_value(self): expected_msg = ('ceilometer: error: argument --timeout: ' '-1 must be greater than 0') self._test_timeout('-1', expected_msg) def test_timeout_float_value(self): expected_msg = ('ceilometer: error: argument --timeout: ' '1.5 must be an integer') self._test_timeout('1.5', expected_msg) def test_timeout_zero(self): expected_msg = ('ceilometer: error: argument --timeout: ' '0 must be greater than 0') self._test_timeout('0', expected_msg) @mock.patch.object(ks_session, 'Session') @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', mock.Mock(return_value=None)) def test_timeout_keystone_session(self, mocked_session): mocked_session.side_effect = exc.HTTPUnauthorized("FAIL") self.make_env(FAKE_V2_ENV) args = ['--debug', '--timeout', '5', 'alarm-list'] self.assertRaises(exc.CommandError, ceilometer_shell.main, args) args, kwargs = mocked_session.call_args self.assertEqual(5, kwargs.get('timeout')) class ShellInsecureTest(ShellTestBase): @mock.patch.object(api_client, 'HTTPClient') @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', mock.Mock(return_value=None)) def test_insecure_true_ceilometer(self, mocked_client): self.make_env(FAKE_V2_ENV) args = ['--debug', '--os-insecure', 'true', 'alarm-list'] self.assertIsNone(ceilometer_shell.main(args)) args, kwargs = mocked_client.call_args self.assertFalse(kwargs.get('verify')) @mock.patch.object(ks_session, 'Session') @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', mock.Mock(return_value=None)) def test_insecure_true_keystone(self, mocked_session): mocked_session.side_effect = exc.HTTPUnauthorized("FAIL") self.make_env(FAKE_V2_ENV) args = ['--debug', '--os-insecure', 'true', 'alarm-list'] self.assertRaises(exc.CommandError, ceilometer_shell.main, args) args, kwargs = mocked_session.call_args self.assertFalse(kwargs.get('verify')) @mock.patch.object(api_client, 'HTTPClient') @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', mock.Mock(return_value=None)) def test_insecure_false_ceilometer(self, mocked_client): self.make_env(FAKE_V2_ENV) args = ['--debug', '--os-insecure', 'false', 'alarm-list'] self.assertIsNone(ceilometer_shell.main(args)) args, kwargs = mocked_client.call_args self.assertTrue(kwargs.get('verify')) @mock.patch.object(ks_session, 'Session') @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', mock.Mock(return_value=None)) def test_insecure_false_keystone(self, mocked_session): mocked_session.side_effect = exc.HTTPUnauthorized("FAIL") self.make_env(FAKE_V2_ENV) args = ['--debug', '--os-insecure', 'false', 'alarm-list'] self.assertRaises(exc.CommandError, ceilometer_shell.main, args) args, kwargs = mocked_session.call_args self.assertTrue(kwargs.get('verify')) class ShellEndpointTest(ShellTestBase): @mock.patch('ceilometerclient.v2.client.Client') def _test_endpoint_and_token(self, token_name, endpoint_name, mocked): args = ['--debug', token_name, 'fake-token', endpoint_name, 'http://fake-url', 'alarm-list'] self.assertIsNone(ceilometer_shell.main(args)) args, kwargs = mocked.call_args self.assertEqual('http://fake-url', kwargs.get('endpoint')) self.assertEqual('fake-token', kwargs.get('token')) def test_endpoint_and_token(self): self._test_endpoint_and_token('--os-auth-token', '--ceilometer-url') self._test_endpoint_and_token('--os-auth-token', '--os-endpoint') self._test_endpoint_and_token('--os-token', '--ceilometer-url') self._test_endpoint_and_token('--os-token', '--os-endpoint') class ShellAlarmUpdateRepeatAction(ShellTestBase): @mock.patch('ceilometerclient.v2.alarms.AlarmManager.update') @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', mock.Mock()) def test_repeat_action_not_specified(self, mocked): self.make_env(FAKE_V2_ENV) def _test(method): args = ['--debug', method, '--state', 'alarm', '123'] ceilometer_shell.main(args) args, kwargs = mocked.call_args self.assertIsNone(kwargs.get('repeat_actions')) _test('alarm-update') _test('alarm-threshold-update') _test('alarm-combination-update') _test('alarm-event-update') python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/test_openstack_common.py0000664000175000017500000000354113117513017032140 0ustar jenkinsjenkins00000000000000# Copyright 2015 Huawei. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.common import base from ceilometerclient.tests.unit import utils from ceilometerclient.v2 import events class BaseTest(utils.BaseTestCase): def test_two_resources_with_same_id_are_not_equal(self): # Two resources with same ID: never equal if their info is not equal r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertNotEqual(r1, r2) def test_two_resources_with_same_id_and_info_are_equal(self): # Two resources with same ID: equal if their info is equal r1 = base.Resource(None, {'id': 1, 'name': 'hello'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertEqual(r1, r2) def test_two_resources_with_diff_type_are_not_equal(self): # Two resources of different types: never equal r1 = base.Resource(None, {'id': 1}) r2 = events.Event(None, {'id': 1}) self.assertNotEqual(r1, r2) def test_two_resources_with_no_id_are_equal(self): # Two resources with no ID: equal if their info is equal r1 = base.Resource(None, {'name': 'joe', 'age': 12}) r2 = base.Resource(None, {'name': 'joe', 'age': 12}) self.assertEqual(r1, r2) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/test_exc.py0000664000175000017500000000714113117513017027360 0ustar jenkinsjenkins00000000000000# Copyright 2013 eNovance # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from ceilometerclient import exc from ceilometerclient.tests.unit import utils HTTPEXCEPTIONS = {'HTTPBadRequest': exc.HTTPBadRequest, 'HTTPUnauthorized': exc.HTTPUnauthorized, 'HTTPForbidden': exc.HTTPForbidden, 'HTTPNotFound': exc.HTTPNotFound, 'HTTPMethodNotAllowed': exc.HTTPMethodNotAllowed, 'HTTPConflict': exc.HTTPConflict, 'HTTPOverLimit': exc.HTTPOverLimit, 'HTTPInternalServerError': exc.HTTPInternalServerError, 'HTTPNotImplemented': exc.HTTPNotImplemented, 'HTTPBadGateway': exc.HTTPBadGateway, 'HTTPServiceUnavailable': exc.HTTPServiceUnavailable} class HTTPExceptionsTest(utils.BaseTestCase): def test_str_no_details(self): for k, v in HTTPEXCEPTIONS.items(): exception = v() ret_str = k + " (HTTP " + str(exception.code) + ")" self.assertEqual(ret_str, str(exception)) def test_str_no_json(self): for k, v in HTTPEXCEPTIONS.items(): exception = v(details="foo") ret_str = k + " (HTTP " + str(exception.code) + ") foo" self.assertEqual(ret_str, str(exception)) def test_str_no_error_message(self): for k, v in HTTPEXCEPTIONS.items(): exception = v(details=json.dumps({})) ret_str = k + " (HTTP " + str(exception.code) + ")" self.assertEqual(ret_str, str(exception)) def test_str_no_faultstring(self): for k, v in HTTPEXCEPTIONS.items(): exception = v( details=json.dumps({"error_message": {"foo": "bar"}})) ret_str = (k + " (HTTP " + str(exception.code) + ") " + str({u'foo': u'bar'})) self.assertEqual(ret_str, str(exception)) def test_str_error_message_unknown_format(self): for k, v in HTTPEXCEPTIONS.items(): exception = v(details=json.dumps({"error_message": "oops"})) ret_str = k + " (HTTP " + str(exception.code) + ") oops" self.assertEqual(ret_str, str(exception)) def test_str_faultstring(self): for k, v in HTTPEXCEPTIONS.items(): exception = v(details=json.dumps( {"error_message": {"faultstring": "oops"}})) ret_str = k + " (HTTP " + str(exception.code) + ") ERROR oops" self.assertEqual(ret_str, str(exception)) def test_from_response(self): class HTTPLibLikeResponse(object): status = 400 class RequestsLikeResponse(object): status_code = 401 class UnexpectedResponse(object): code = 200 self.assertEqual(HTTPLibLikeResponse.status, exc.from_response(HTTPLibLikeResponse).code) self.assertEqual(RequestsLikeResponse.status_code, exc.from_response(RequestsLikeResponse).code) self.assertRaises(TypeError, exc.from_response, UnexpectedResponse) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/test_client.py0000664000175000017500000004145313117513017030063 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import types from keystoneauth1 import exceptions as ks_exc from keystoneauth1.identity import v2 as v2_auth from keystoneauth1.identity import v3 as v3_auth from keystoneauth1 import session as ks_session import mock import requests from ceilometerclient.apiclient import exceptions from ceilometerclient import client from ceilometerclient import exc from ceilometerclient.tests.unit import utils from ceilometerclient.v2 import client as v2client FAKE_ENV = { 'username': 'username', 'password': 'password', 'tenant_name': 'tenant_name', 'auth_url': 'http://no.where', 'auth_plugin': mock.Mock(), 'ceilometer_url': 'http://no.where', 'token': '1234', 'user_domain_name': 'default', 'project_domain_name': 'default', } class ClientTest(utils.BaseTestCase): @staticmethod def create_client(env, api_version=2, endpoint=None, exclude=[]): env = dict((k, v) for k, v in env.items() if k not in exclude) with mock.patch( 'ceilometerclient.v2.client.Client._get_redirect_client', return_value=None): return client.get_client(api_version, **env) def test_client_v2_with_session(self): resp = mock.Mock(status_code=200, text=b'') resp.json.return_value = [] session = mock.Mock() session.request.return_value = resp c = client.get_client(2, session=session) c.resources.list() self.assertTrue(session.request.called) self.assertTrue(resp.json.called) def test_client_version(self): c2 = self.create_client(env=FAKE_ENV, api_version=2) self.assertIsInstance(c2, v2client.Client) def test_client_auth_lambda(self): env = FAKE_ENV.copy() env['token'] = lambda: env['token'] self.assertIsInstance(env['token'], types.FunctionType) c2 = self.create_client(env) self.assertIsInstance(c2, v2client.Client) def test_client_auth_non_lambda(self): env = FAKE_ENV.copy() env['token'] = "1234" self.assertIsInstance(env['token'], str) c2 = self.create_client(env) self.assertIsInstance(c2, v2client.Client) def test_client_without_auth_plugin(self): env = FAKE_ENV.copy() del env['auth_plugin'] c = self.create_client(env, api_version=2, endpoint='fake_endpoint') self.assertIsInstance(c.auth_plugin, client.AuthPlugin) def test_client_without_auth_plugin_keystone_v3(self): env = FAKE_ENV.copy() del env['auth_plugin'] expected = { 'username': 'username', 'endpoint': 'http://no.where', 'tenant_name': 'tenant_name', 'service_type': None, 'token': '1234', 'endpoint_type': None, 'region_name': None, 'auth_url': 'http://no.where', 'tenant_id': None, 'insecure': None, 'cacert': None, 'password': 'password', 'user_domain_name': 'default', 'user_domain_id': None, 'project_domain_name': 'default', 'project_domain_id': None, } with mock.patch('ceilometerclient.client.AuthPlugin') as auth_plugin: self.create_client(env, api_version=2, endpoint='http://no.where') self.assertEqual(mock.call(**expected), auth_plugin.mock_calls[0]) def test_v2_client_timeout_invalid_value(self): env = FAKE_ENV.copy() env['timeout'] = 'abc' self.assertRaises(ValueError, self.create_client, env) env['timeout'] = '1.5' self.assertRaises(ValueError, self.create_client, env) def _test_v2_client_timeout_integer(self, timeout, expected_value): env = FAKE_ENV.copy() env['timeout'] = timeout expected = { 'auth_plugin': mock.ANY, 'timeout': expected_value, 'original_ip': None, 'http': None, 'region_name': None, 'verify': True, 'timings': None, 'keyring_saver': None, 'cert': None, 'endpoint_type': None, 'user_agent': None, 'debug': None, } cls = 'ceilometerclient.apiclient.client.HTTPClient' with mock.patch(cls) as mocked: self.create_client(env) mocked.assert_called_with(**expected) def test_v2_client_timeout_zero(self): self._test_v2_client_timeout_integer(0, None) def test_v2_client_timeout_valid_value(self): self._test_v2_client_timeout_integer(30, 30) @mock.patch.object(ks_session, 'Session') def test_v2_client_timeout_keystone_session(self, mocked_session): mocked_session.side_effect = RuntimeError('Stop!') env = FAKE_ENV.copy() env['timeout'] = 5 del env['auth_plugin'] del env['token'] client = self.create_client(env) self.assertRaises(RuntimeError, client.alarms.list) args, kwargs = mocked_session.call_args self.assertEqual(5, kwargs['timeout']) def test_v2_client_cacert_in_verify(self): env = FAKE_ENV.copy() env['cacert'] = '/path/to/cacert' client = self.create_client(env) self.assertEqual('/path/to/cacert', client.http_client.http_client.verify) def test_v2_client_certfile_and_keyfile(self): env = FAKE_ENV.copy() env['cert_file'] = '/path/to/cert' env['key_file'] = '/path/to/keycert' client = self.create_client(env) self.assertEqual(('/path/to/cert', '/path/to/keycert'), client.http_client.http_client.cert) def test_v2_client_insecure(self): env = FAKE_ENV.copy() env.pop('auth_plugin') env['os_insecure'] = 'True' client = self.create_client(env) self.assertIn('insecure', client.auth_plugin.opts) self.assertEqual('True', client.auth_plugin.opts['insecure']) class ClientTest2(ClientTest): @staticmethod def create_client(env, api_version=2, endpoint=None, exclude=[]): env = dict((k, v) for k, v in env.items() if k not in exclude) with mock.patch( 'ceilometerclient.v2.client.Client._get_redirect_client', return_value=None): return client.Client(api_version, endpoint, **env) class ClientTestWithAodh(ClientTest): @staticmethod def create_client(env, api_version=2, endpoint=None, exclude=[]): env = dict((k, v) for k, v in env.items() if k not in exclude) with mock.patch('ceilometerclient.apiclient.client.' 'HTTPClient.client_request', return_value=mock.MagicMock()): return client.get_client(api_version, **env) def test_client_without_auth_plugin(self): env = FAKE_ENV.copy() del env['auth_plugin'] c = self.create_client(env, api_version=2, endpoint='fake_endpoint') self.assertIsInstance(c.alarm_client.http_client.auth_plugin, client.AuthPlugin) def test_v2_client_insecure(self): env = FAKE_ENV.copy() env.pop('auth_plugin') env['insecure'] = 'True' client = self.create_client(env) self.assertIn('insecure', client.alarm_client.http_client.auth_plugin.opts) self.assertEqual('True', (client.alarm_client.http_client. auth_plugin.opts['insecure'])) def test_ceilometerclient_available_without_aodh_services_running(self): env = FAKE_ENV.copy() env.pop('auth_plugin', None) with mock.patch('ceilometerclient.apiclient.client.' 'HTTPClient.client_request') as mocked_request: mocked_request.side_effect = requests.exceptions.ConnectionError ceiloclient = client.get_client(2, **env) self.assertIsInstance(ceiloclient, v2client.Client) @mock.patch('ceilometerclient.client.SessionClient') def test_http_client_with_session_and_aodh(self, mock_sc): session = mock.Mock() kwargs = {"session": session, "service_type": "metering", "user_agent": "python-ceilometerclient"} expected = { "auth": None, "interface": 'publicURL', "region_name": None, "timings": None, "session": session, "service_type": "metering", "user_agent": "python-ceilometerclient"} kwargs['aodh_endpoint'] = 'http://aodh.where' client._construct_http_client(**kwargs) mock_sc.assert_called_with(**expected) class ClientAuthTest(utils.BaseTestCase): @staticmethod def create_client(env, api_version=2, endpoint=None, exclude=[]): env = dict((k, v) for k, v in env.items() if k not in exclude) with mock.patch('ceilometerclient.apiclient.client.' 'HTTPClient.client_request', return_value=mock.MagicMock()): return client.get_client(api_version, **env) @mock.patch('keystoneauth1.discover.Discover') @mock.patch('keystoneauth1.session.Session') def test_discover_auth_versions(self, session, discover_mock): env = FAKE_ENV.copy() env.pop('auth_plugin', None) mock_session_instance = mock.MagicMock() session.return_value = mock_session_instance client = self.create_client(env) client.auth_plugin.opts.pop('token', None) client.auth_plugin._do_authenticate(mock.MagicMock()) self.assertEqual([mock.call(url='http://no.where', session=mock_session_instance)], discover_mock.call_args_list) self.assertIsInstance(mock_session_instance.auth, v3_auth.Password) @mock.patch('keystoneauth1.discover.Discover') @mock.patch('keystoneauth1.session.Session') def test_discover_auth_versions_v2_only(self, session, discover): env = FAKE_ENV.copy() env.pop('auth_plugin', None) env.pop('user_domain_name', None) env.pop('user_domain_id', None) env.pop('project_domain_name', None) env.pop('project_domain_id', None) session_instance_mock = mock.MagicMock() session.return_value = session_instance_mock discover_instance_mock = mock.MagicMock() discover_instance_mock.url_for.side_effect = (lambda v: v if v == '2.0' else None) discover.return_value = discover_instance_mock client = self.create_client(env) client.auth_plugin.opts.pop('token', None) client.auth_plugin._do_authenticate(mock.MagicMock()) self.assertEqual([mock.call(url='http://no.where', session=session_instance_mock)], discover.call_args_list) self.assertIsInstance(session_instance_mock.auth, v2_auth.Password) @mock.patch('keystoneauth1.discover.Discover') @mock.patch('keystoneauth1.session.Session') def test_discover_auth_versions_raise_discovery_failure(self, session, discover): env = FAKE_ENV.copy() env.pop('auth_plugin', None) env.pop('token', None) session_instance_mock = mock.MagicMock() session.return_value = session_instance_mock discover_instance_mock = mock.MagicMock() discover_instance_mock.url_for.side_effect = (lambda v: v if v == '2.0' else None) discover.side_effect = ks_exc.DiscoveryFailure client = self.create_client(env) self.assertRaises(ks_exc.DiscoveryFailure, client.auth_plugin._do_authenticate, mock.Mock()) discover.side_effect = mock.MagicMock() client = self.create_client(env) discover.side_effect = ks_exc.DiscoveryFailure client.auth_plugin.opts.pop('token', None) self.assertRaises(ks_exc.DiscoveryFailure, client.auth_plugin._do_authenticate, mock.Mock()) self.assertEqual([mock.call(url='http://no.where', session=session_instance_mock), mock.call(url='http://no.where', session=session_instance_mock)], discover.call_args_list) @mock.patch('ceilometerclient.client._get_keystone_session') def test_get_endpoint(self, session): env = FAKE_ENV.copy() env.pop('auth_plugin', None) env.pop('endpoint', None) session_instance_mock = mock.MagicMock() session.return_value = session_instance_mock client = self.create_client(env) client.auth_plugin.opts.pop('endpoint') client.auth_plugin.opts.pop('token', None) alarm_auth_plugin = client.alarm_client.http_client.auth_plugin alarm_auth_plugin.opts.pop('endpoint') alarm_auth_plugin.opts.pop('token', None) self.assertNotEqual(client.auth_plugin, alarm_auth_plugin) client.auth_plugin._do_authenticate(mock.MagicMock()) alarm_auth_plugin._do_authenticate(mock.MagicMock()) self.assertEqual([ mock.call(interface='publicURL', region_name=None, service_type='metering'), mock.call(interface='publicURL', region_name=None, service_type='alarming'), ], session_instance_mock.get_endpoint.mock_calls) def test_http_client_with_session(self): session = mock.Mock() session.request.return_value = mock.Mock(status_code=404, text=b'') env = {"session": session, "service_type": "metering", "user_agent": "python-ceilometerclient"} c = client.SessionClient(**env) self.assertRaises(exc.HTTPException, c.get, "/") def test_get_aodh_endpoint_without_auth_url(self): env = FAKE_ENV.copy() env.pop('auth_plugin', None) env.pop('endpoint', None) env.pop('auth_url', None) client = self.create_client(env, endpoint='fake_endpoint') self.assertEqual(client.alarm_client.http_client.auth_plugin.opts, client.auth_plugin.opts) @mock.patch('ceilometerclient.client._get_keystone_session') def test_get_different_endpoint_type(self, session): env = FAKE_ENV.copy() env.pop('auth_plugin', None) env.pop('endpoint', None) env['endpoint_type'] = 'internal' session_instance_mock = mock.MagicMock() session.return_value = session_instance_mock client = self.create_client(env) client.auth_plugin.opts.pop('endpoint') client.auth_plugin.opts.pop('token', None) alarm_auth_plugin = client.alarm_client.http_client.auth_plugin alarm_auth_plugin.opts.pop('endpoint') alarm_auth_plugin.opts.pop('token', None) self.assertNotEqual(client.auth_plugin, alarm_auth_plugin) client.auth_plugin._do_authenticate(mock.MagicMock()) alarm_auth_plugin._do_authenticate(mock.MagicMock()) self.assertEqual([ mock.call(interface='internal', region_name=None, service_type='metering'), mock.call(interface='internal', region_name=None, service_type='alarming'), ], session_instance_mock.get_endpoint.mock_calls) @mock.patch('ceilometerclient.client._get_keystone_session') def test_get_sufficient_options_missing(self, session): env = FAKE_ENV.copy() env.pop('auth_plugin', None) env.pop('password', None) env.pop('endpoint', None) env.pop('auth_token', None) env.pop('tenant_name', None) env.pop('username', None) session_instance_mock = mock.MagicMock() session.return_value = session_instance_mock client = self.create_client(env) client.auth_plugin.opts.pop('token', None) self.assertRaises(exceptions.AuthPluginOptionsMissing, client.auth_plugin.sufficient_options) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/test_utils.py0000664000175000017500000002673013117513017027746 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import itertools import mock import six from ceilometerclient.common import utils from ceilometerclient.tests.unit import utils as test_utils class UtilsTest(test_utils.BaseTestCase): def test_prettytable(self): class Struct(object): def __init__(self, **entries): self.__dict__.update(entries) # test that the prettytable output is wellformatted (left-aligned) with mock.patch('sys.stdout', new=six.StringIO()) as stdout: utils.print_dict({'K': 'k', 'Key': 'Value'}) self.assertEqual('''\ +----------+-------+ | Property | Value | +----------+-------+ | K | k | | Key | Value | +----------+-------+ ''', stdout.getvalue()) with mock.patch('sys.stdout', new=six.StringIO()) as stdout: utils.print_dict({'alarm_id': '262567fd-d79a-4bbb-a9d0-59d879b6', 'name': u'\u6d4b\u8bd5', 'description': u'\u6d4b\u8bd5', 'state': 'insufficient data', 'repeat_actions': 'False', 'type': 'threshold', 'threshold': '1.0', 'statistic': 'avg', 'alarm_actions': [u'http://something/alarm1', u'http://something/alarm2'], 'ok_actions': [{"get_attr1": [u"web_server_scaleup_policy1", u"alarm_url1"]}, {"get_attr2": [u"web_server_scaleup_policy2", u"alarm_url2"]}], 'time_constraints': '[{name: c1,' '\\n description: test,' '\\n start: 0 18 * * *,' '\\n duration: 1,' '\\n timezone: US}]'}, wrap=72) expected = u'''\ +------------------+-------------------------------------------------------\ --------+ | Property | Value \ | +------------------+-------------------------------------------------------\ --------+ | alarm_actions | ["http://something/alarm1", "http://something/alarm2"]\ | | alarm_id | 262567fd-d79a-4bbb-a9d0-59d879b6 \ | | description | \u6d4b\u8bd5 \ | | name | \u6d4b\u8bd5 \ | | ok_actions | [{"get_attr1": ["web_server_scaleup_policy1", "alarm_u\ rl1"]}, | | | {"get_attr2": ["web_server_scaleup_policy2", "alarm_ur\ l2"]}] | | repeat_actions | False \ | | state | insufficient data \ | | statistic | avg \ | | threshold | 1.0 \ | | time_constraints | [{name: c1, \ | | | description: test, \ | | | start: 0 18 * * *, \ | | | duration: 1, \ | | | timezone: US}] \ | | type | threshold \ | +------------------+-------------------------------------------------------\ --------+ ''' # py2 prints str type, py3 prints unicode type if six.PY2: expected = expected.encode('utf-8') self.assertEqual(expected, stdout.getvalue()) def test_print_list(self): class Foo(object): def __init__(self, one, two, three): self.one = one self.two = two self.three = three foo_list = [ Foo(10, 'a', 'B'), Foo(8, 'c', 'c'), Foo(12, '0', 'Z')] def do_print_list(sortby): with mock.patch('sys.stdout', new=six.StringIO()) as stdout: utils.print_list(foo_list, ['one', 'two', 'three'], ['1st', '2nd', '3rd'], {'one': lambda o: o.one * 10}, sortby) return stdout.getvalue() printed = do_print_list(None) self.assertEqual('''\ +-----+-----+-----+ | 1st | 2nd | 3rd | +-----+-----+-----+ | 100 | a | B | | 80 | c | c | | 120 | 0 | Z | +-----+-----+-----+ ''', printed) printed = do_print_list(0) self.assertEqual('''\ +-----+-----+-----+ | 1st | 2nd | 3rd | +-----+-----+-----+ | 80 | c | c | | 100 | a | B | | 120 | 0 | Z | +-----+-----+-----+ ''', printed) printed = do_print_list(1) self.assertEqual('''\ +-----+-----+-----+ | 1st | 2nd | 3rd | +-----+-----+-----+ | 120 | 0 | Z | | 100 | a | B | | 80 | c | c | +-----+-----+-----+ ''', printed) def test_args_array_to_dict(self): my_args = { 'matching_metadata': ['metadata.key=metadata_value'], 'other': 'value' } cleaned_dict = utils.args_array_to_dict(my_args, "matching_metadata") self.assertEqual({ 'matching_metadata': {'metadata.key': 'metadata_value'}, 'other': 'value' }, cleaned_dict) def test_args_array_to_list_of_dicts(self): starts = ['0 11 * * *', '"0 11 * * *"', '\'0 11 * * *\''] timezones = [None, 'US/Eastern', '"US/Eastern"', '\'US/Eastern\''] descs = [None, 'de sc', '"de sc"', '\'de sc\''] for start, tz, desc in itertools.product(starts, timezones, descs): my_args = { 'time_constraints': ['name=const1;start=%s;duration=1' % start], 'other': 'value' } expected = { 'time_constraints': [dict(name='const1', start='0 11 * * *', duration='1')], 'other': 'value' } if tz: my_args['time_constraints'][0] += ';timezone=%s' % tz expected['time_constraints'][0]['timezone'] = 'US/Eastern' if desc: my_args['time_constraints'][0] += ';description=%s' % desc expected['time_constraints'][0]['description'] = 'de sc' cleaned = utils.args_array_to_list_of_dicts(my_args, 'time_constraints') self.assertEqual(expected, cleaned) def test_key_with_slash_to_nested_dict(self): my_args = { 'combination_rule/alarm_ids': ['id1', 'id2'], 'combination_rule/operator': 'and', 'threshold_rule/threshold': 400, 'threshold_rule/statictic': 'avg', 'threshold_rule/comparison_operator': 'or', } nested_dict = utils.key_with_slash_to_nested_dict(my_args) self.assertEqual({ 'combination_rule': {'alarm_ids': ['id1', 'id2'], 'operator': 'and'}, 'threshold_rule': {'threshold': 400, 'statictic': 'avg', 'comparison_operator': 'or'}, }, nested_dict) def test_arg(self): @utils.arg(help="not_required_no_default.") def not_required_no_default(): pass _, args = not_required_no_default.__dict__['arguments'][0] self.assertEqual("not_required_no_default.", args['help']) @utils.arg(required=True, help="required_no_default.") def required_no_default(): pass _, args = required_no_default.__dict__['arguments'][0] self.assertEqual("required_no_default. Required.", args['help']) @utils.arg(default=42, help="not_required_default.") def not_required_default(): pass _, args = not_required_default.__dict__['arguments'][0] self.assertEqual("not_required_default. Defaults to 42.", args['help']) def test_merge_nested_dict(self): dest = {'key': 'value', 'nested': {'key2': 'value2', 'key3': 'value3', 'nested2': {'key': 'value', 'some': 'thing'}}} source = {'key': 'modified', 'nested': {'key3': 'modified3', 'nested2': {'key5': 'value5'}}} utils.merge_nested_dict(dest, source, depth=1) self.assertEqual({'key': 'modified', 'nested': {'key2': 'value2', 'key3': 'modified3', 'nested2': {'key5': 'value5'}}}, dest) def test_merge_nested_dict_no_depth(self): dest = {'key': 'value', 'nested': {'key2': 'value2', 'key3': 'value3', 'nested2': {'key': 'value', 'some': 'thing'}}} source = {'key': 'modified', 'nested': {'key3': 'modified3', 'nested2': {'key5': 'value5'}}} utils.merge_nested_dict(dest, source) self.assertEqual({'key': 'modified', 'nested': {'key3': 'modified3', 'nested2': {'key5': 'value5'}}}, dest) @mock.patch('prettytable.PrettyTable') def test_format_nested_list_of_dict(self, pt_mock): actual_rows = [] def mock_add_row(row): actual_rows.append(row) table = mock.Mock() table.add_row = mock_add_row table.get_string.return_value = "the table" test_data = [ {'column_1': 'value_c', 'column_2': 'value_23'}, {'column_1': 'value_b', 'column_2': 'value_22'}, {'column_1': 'value_a', 'column_2': 'value_21'} ] columns = ['column_1', 'column_2'] pt_mock.return_value = table rval = utils.format_nested_list_of_dict(test_data, columns) self.assertEqual("the table", rval) self.assertEqual([['value_a', 'value_21'], ['value_b', 'value_22'], ['value_c', 'value_23']], actual_rows) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/__init__.py0000664000175000017500000000000013117513017027264 0ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/0000775000175000017500000000000013117513272025517 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_shell.py0000664000175000017500000025026513117513017030246 0ustar jenkinsjenkins00000000000000# Copyright Ericsson AB 2014. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import re import sys import mock import six from testtools import matchers from ceilometerclient import exc from ceilometerclient import shell as base_shell from ceilometerclient.tests.unit import test_shell from ceilometerclient.tests.unit import utils from ceilometerclient.v2 import alarms from ceilometerclient.v2 import capabilities from ceilometerclient.v2 import event_types from ceilometerclient.v2 import events from ceilometerclient.v2 import meters from ceilometerclient.v2 import resources from ceilometerclient.v2 import samples from ceilometerclient.v2 import shell as ceilometer_shell from ceilometerclient.v2 import statistics from ceilometerclient.v2 import trait_descriptions from ceilometerclient.v2 import traits from keystoneauth1 import exceptions class ShellAlarmStateCommandsTest(utils.BaseTestCase): ALARM_ID = 'foobar' def setUp(self): super(ShellAlarmStateCommandsTest, self).setUp() self.cc = mock.Mock() self.cc.alarms = mock.Mock() self.args = mock.Mock() self.args.alarm_id = self.ALARM_ID def test_alarm_state_get(self): ceilometer_shell.do_alarm_state_get(self.cc, self.args) self.cc.alarms.get_state.assert_called_once_with(self.ALARM_ID) self.assertFalse(self.cc.alarms.set_state.called) def test_alarm_state_set(self): self.args.state = 'ok' ceilometer_shell.do_alarm_state_set(self.cc, self.args) self.cc.alarms.set_state.assert_called_once_with(self.ALARM_ID, 'ok') self.assertFalse(self.cc.alarms.get_state.called) class ShellAlarmHistoryCommandTest(utils.BaseTestCase): ALARM_ID = '768ff714-8cfb-4db9-9753-d484cb33a1cc' FULL_DETAIL = ('{"alarm_actions": [], ' '"user_id": "8185aa72421a4fd396d4122cba50e1b5", ' '"name": "scombo", ' '"timestamp": "2013-10-03T08:58:33.647912", ' '"enabled": true, ' '"state_timestamp": "2013-10-03T08:58:33.647912", ' '"rule": {"operator": "or", "alarm_ids": ' '["062cc907-3a9f-4867-ab3b-fa83212b39f7"]}, ' '"alarm_id": "768ff714-8cfb-4db9-9753-d484cb33a1cc", ' '"state": "insufficient data", ' '"insufficient_data_actions": [], ' '"repeat_actions": false, ' '"ok_actions": [], ' '"project_id": "57d04f24d0824b78b1ea9bcecedbda8f", ' '"type": "combination", ' '"severity": "low", ' '"description": "Combined state of alarms ' '062cc907-3a9f-4867-ab3b-fa83212b39f7"}') ALARM_HISTORY = [{'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', 'user_id': '8185aa72421a4fd396d4122cba50e1b5', 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', 'timestamp': '2013-10-03T08:59:28.326000', 'detail': '{"state": "alarm"}', 'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', 'type': 'state transition'}, {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', 'user_id': '8185aa72421a4fd396d4122cba50e1b5', 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', 'timestamp': '2013-10-03T08:59:28.326000', 'detail': '{"description": "combination of one"}', 'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', 'type': 'rule change'}, {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', 'user_id': '8185aa72421a4fd396d4122cba50e1b5', 'event_id': '4fd7df9e-190d-4471-8884-dc5a33d5d4bb', 'timestamp': '2013-10-03T08:58:33.647000', 'detail': FULL_DETAIL, 'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', 'type': 'creation'}] TIMESTAMP_RE = (' +\| (\d{4})-(\d{2})-(\d{2})T' '(\d{2})\:(\d{2})\:(\d{2})\.(\d{6}) \| +') def setUp(self): super(ShellAlarmHistoryCommandTest, self).setUp() self.cc = mock.Mock() self.cc.alarms = mock.Mock() self.args = mock.Mock() self.args.alarm_id = self.ALARM_ID @mock.patch('sys.stdout', new=six.StringIO()) def _do_test_alarm_history(self, raw_query=None, parsed_query=None): self.args.query = raw_query history = [alarms.AlarmChange(mock.Mock(), change) for change in self.ALARM_HISTORY] self.cc.alarms.get_history.return_value = history ceilometer_shell.do_alarm_history(self.cc, self.args) self.cc.alarms.get_history.assert_called_once_with( q=parsed_query, alarm_id=self.ALARM_ID ) out = sys.stdout.getvalue() required = [ '.*creation%sname: scombo.*' % self.TIMESTAMP_RE, '.*rule change%sdescription: combination of one.*' % self.TIMESTAMP_RE, '.*state transition%sstate: alarm.*' % self.TIMESTAMP_RE, ] for r in required: self.assertThat(out, matchers.MatchesRegex(r, re.DOTALL)) def test_alarm_all_history(self): self._do_test_alarm_history() def test_alarm_constrained_history(self): parsed_query = [dict(field='timestamp', value='2013-10-03T08:59:28', op='gt', type='')] self._do_test_alarm_history(raw_query='timestamp>2013-10-03T08:59:28', parsed_query=parsed_query) class ShellAlarmCommandTest(utils.BaseTestCase): ALARM_ID = '768ff714-8cfb-4db9-9753-d484cb33a1cc' ALARM = {"alarm_actions": ["log://"], "ok_actions": [], "description": "instance running hot", "timestamp": "2013-11-20T10:38:42.206952", "enabled": True, "state_timestamp": "2013-11-19T17:20:44", "threshold_rule": {"meter_name": "cpu_util", "evaluation_periods": 3, "period": 600, "statistic": "avg", "threshold": 99.0, "query": [{"field": "resource_id", "value": "INSTANCE_ID", "op": "eq"}], "comparison_operator": "gt"}, "time_constraints": [{"name": "cons1", "description": "desc1", "start": "0 11 * * *", "duration": 300, "timezone": ""}, {"name": "cons2", "description": "desc2", "start": "0 23 * * *", "duration": 600, "timezone": ""}], "alarm_id": ALARM_ID, "state": "insufficient data", "severity": "low", "insufficient_data_actions": [], "repeat_actions": True, "user_id": "528d9b68fa774689834b5c04b4564f8a", "project_id": "ed9d4e2be2a748bc80108053cf4598f5", "type": "threshold", "name": "cpu_high"} THRESHOLD_ALARM_CLI_ARGS = [ '--name', 'cpu_high', '--description', 'instance running hot', '--meter-name', 'cpu_util', '--threshold', '70.0', '--comparison-operator', 'gt', '--statistic', 'avg', '--period', '600', '--evaluation-periods', '3', '--alarm-action', 'log://', '--alarm-action', 'http://example.com/alarm/state', '--query', 'resource_id=INSTANCE_ID' ] def setUp(self): super(ShellAlarmCommandTest, self).setUp() self.cc = mock.Mock() self.cc.alarms = mock.Mock() self.args = mock.Mock() self.args.alarm_id = self.ALARM_ID @mock.patch('sys.stdout', new=six.StringIO()) def _do_test_alarm_update_repeat_actions(self, method, repeat_actions): self.args.threshold = 42.0 if repeat_actions is not None: self.args.repeat_actions = repeat_actions alarm = [alarms.Alarm(mock.Mock(), self.ALARM)] self.cc.alarms.get.return_value = alarm self.cc.alarms.update.return_value = alarm[0] method(self.cc, self.args) args, kwargs = self.cc.alarms.update.call_args self.assertEqual(self.ALARM_ID, args[0]) self.assertEqual(42.0, kwargs.get('threshold')) if repeat_actions is not None: self.assertEqual(repeat_actions, kwargs.get('repeat_actions')) else: self.assertNotIn('repeat_actions', kwargs) def test_alarm_update_repeat_actions_untouched(self): method = ceilometer_shell.do_alarm_update self._do_test_alarm_update_repeat_actions(method, None) def test_alarm_update_repeat_actions_set(self): method = ceilometer_shell.do_alarm_update self._do_test_alarm_update_repeat_actions(method, True) def test_alarm_update_repeat_actions_clear(self): method = ceilometer_shell.do_alarm_update self._do_test_alarm_update_repeat_actions(method, False) def test_alarm_combination_update_repeat_actions_untouched(self): method = ceilometer_shell.do_alarm_combination_update self._do_test_alarm_update_repeat_actions(method, None) def test_alarm_combination_update_repeat_actions_set(self): method = ceilometer_shell.do_alarm_combination_update self._do_test_alarm_update_repeat_actions(method, True) def test_alarm_combination_update_repeat_actions_clear(self): method = ceilometer_shell.do_alarm_combination_update self._do_test_alarm_update_repeat_actions(method, False) def test_alarm_threshold_update_repeat_actions_untouched(self): method = ceilometer_shell.do_alarm_threshold_update self._do_test_alarm_update_repeat_actions(method, None) def test_alarm_threshold_update_repeat_actions_set(self): method = ceilometer_shell.do_alarm_threshold_update self._do_test_alarm_update_repeat_actions(method, True) def test_alarm_threshold_update_repeat_actions_clear(self): method = ceilometer_shell.do_alarm_threshold_update self._do_test_alarm_update_repeat_actions(method, False) def test_alarm_event_upadte_repeat_action_untouched(self): method = ceilometer_shell.do_alarm_event_update self._do_test_alarm_update_repeat_actions(method, None) def test_alarm_event_upadte_repeat_action_set(self): method = ceilometer_shell.do_alarm_event_update self._do_test_alarm_update_repeat_actions(method, True) def test_alarm_event_upadte_repeat_action_clear(self): method = ceilometer_shell.do_alarm_event_update self._do_test_alarm_update_repeat_actions(method, False) @mock.patch('sys.stdout', new=six.StringIO()) def test_alarm_threshold_create_args(self): argv = ['alarm-threshold-create'] + self.THRESHOLD_ALARM_CLI_ARGS self._test_alarm_threshold_action_args('create', argv) def test_alarm_threshold_update_args(self): argv = ['alarm-threshold-update', 'x'] + self.THRESHOLD_ALARM_CLI_ARGS self._test_alarm_threshold_action_args('update', argv) @mock.patch('sys.stdout', new=six.StringIO()) def _test_alarm_threshold_action_args(self, action, argv): shell = base_shell.CeilometerShell() _, args = shell.parse_args(argv) alarm = alarms.Alarm(mock.Mock(), self.ALARM) getattr(self.cc.alarms, action).return_value = alarm func = getattr(ceilometer_shell, 'do_alarm_threshold_' + action) func(self.cc, args) _, kwargs = getattr(self.cc.alarms, action).call_args self._check_alarm_threshold_args(kwargs) def _check_alarm_threshold_args(self, kwargs): self.assertEqual('cpu_high', kwargs.get('name')) self.assertEqual('instance running hot', kwargs.get('description')) actions = ['log://', 'http://example.com/alarm/state'] self.assertEqual(actions, kwargs.get('alarm_actions')) self.assertIn('threshold_rule', kwargs) rule = kwargs['threshold_rule'] self.assertEqual('cpu_util', rule.get('meter_name')) self.assertEqual(70.0, rule.get('threshold')) self.assertEqual('gt', rule.get('comparison_operator')) self.assertEqual('avg', rule.get('statistic')) self.assertEqual(600, rule.get('period')) self.assertEqual(3, rule.get('evaluation_periods')) query = dict(field='resource_id', type='', value='INSTANCE_ID', op='eq') self.assertEqual([query], rule['query']) @mock.patch('sys.stdout', new=six.StringIO()) def test_alarm_create_time_constraints(self): shell = base_shell.CeilometerShell() argv = ['alarm-threshold-create', '--name', 'cpu_high', '--meter-name', 'cpu_util', '--threshold', '70.0', '--time-constraint', 'name=cons1;start="0 11 * * *";duration=300', '--time-constraint', 'name=cons2;start="0 23 * * *";duration=600', ] _, args = shell.parse_args(argv) alarm = alarms.Alarm(mock.Mock(), self.ALARM) self.cc.alarms.create.return_value = alarm ceilometer_shell.do_alarm_threshold_create(self.cc, args) _, kwargs = self.cc.alarms.create.call_args time_constraints = [dict(name='cons1', start='0 11 * * *', duration='300'), dict(name='cons2', start='0 23 * * *', duration='600')] self.assertEqual(time_constraints, kwargs['time_constraints']) class ShellAlarmGnocchiCommandTest(test_shell.ShellTestBase): ALARM_ID = 'b69ecdb9-f19b-4fb5-950f-5eb53938b718' TIME_CONSTRAINTS = [{ u'duration': 300, u'start': u'0 11 * * *', u'description': u'desc1', u'name': u'cons1', u'timezone': u''}, { u'duration': 600, u'start': u'0 23 * * *', u'name': u'cons2', u'description': u'desc2', u'timezone': u''}] ALARM1 = { u'name': u'name_gnocchi_alarm', u'description': u'description_gnocchi_alarm', u'enabled': True, u'ok_actions': [u'http://something/ok'], u'alarm_actions': [u'http://something/alarm'], u'timestamp': u'2015-12-21T03:10:32.305133', u'state_timestamp': u'2015-12-21T03:10:32.305133', u'gnocchi_resources_threshold_rule': { u'evaluation_periods': 3, u'metric': u'cpu_util', u'resource_id': u'768ff714-8cfb-4db9-9753-d484cb33a1cc', u'threshold': 70.0, u'granularity': 60, u'aggregation_method': u'count', u'comparison_operator': u'le', u'resource_type': u'instance', }, u'time_constraints': TIME_CONSTRAINTS, u'alarm_id': ALARM_ID, u'state': u'ok', u'insufficient_data_actions': [u'http://something/insufficient'], u'repeat_actions': True, u'user_id': u'f28735621ee84f329144eb467c91fce6', u'project_id': u'97fcad0402ce4f65ac3bd42a0c6a7e74', u'type': u'gnocchi_resources_threshold', u'severity': u'critical', } ALARM2 = { u'name': u'name_gnocchi_alarm', u'description': u'description_gnocchi_alarm', u'enabled': True, u'ok_actions': [u'http://something/ok'], u'alarm_actions': [u'http://something/alarm'], u'timestamp': u'2015-12-21T03:10:32.305133', u'state_timestamp': u'2015-12-21T03:10:32.305133', u'gnocchi_aggregation_by_metrics_threshold_rule': { u'evaluation_periods': 3, u'metrics': [u'b3d9d8ab-05e8-439f-89ad-5e978dd2a5eb', u'009d4faf-c275-46f0-8f2d-670b15bac2b0'], u'threshold': 70.0, u'granularity': 60, u'aggregation_method': u'count', u'comparison_operator': u'le', }, u'time_constraints': TIME_CONSTRAINTS, u'alarm_id': ALARM_ID, u'state': u'ok', u'insufficient_data_actions': [u'http://something/insufficient'], u'repeat_actions': True, u'user_id': u'f28735621ee84f329144eb467c91fce6', u'project_id': u'97fcad0402ce4f65ac3bd42a0c6a7e74', u'type': u'gnocchi_aggregation_by_metrics_threshold', u'severity': u'critical', } ALARM3 = { u'name': u'name_gnocchi_alarm', u'description': u'description_gnocchi_alarm', u'enabled': True, u'ok_actions': [u'http://something/ok'], u'alarm_actions': [u'http://something/alarm'], u'timestamp': u'2015-12-21T03:10:32.305133', u'state_timestamp': u'2015-12-21T03:10:32.305133', u'gnocchi_aggregation_by_resources_threshold_rule': { u'evaluation_periods': 3, u'metric': u'cpu_util', u'threshold': 70.0, u'granularity': 60, u'aggregation_method': u'count', u'comparison_operator': u'le', u'resource_type': u'instance', u'query': u'{"=": {"server_group":"my_autoscaling_group"}}', }, u'time_constraints': TIME_CONSTRAINTS, u'alarm_id': ALARM_ID, u'state': u'ok', u'insufficient_data_actions': [u'http://something/insufficient'], u'repeat_actions': True, u'user_id': u'f28735621ee84f329144eb467c91fce6', u'project_id': u'97fcad0402ce4f65ac3bd42a0c6a7e74', u'type': u'gnocchi_aggregation_by_resources_threshold', u'severity': u'critical', } COMMON_CLI_ARGS = [ '--name', 'name_gnocchi_alarm', '--description', 'description_gnocchi_alarm', '--enabled', 'True', '--state', 'ok', '--severity', 'critical', '--ok-action', 'http://something/ok', '--alarm-action', 'http://something/alarm', '--insufficient-data-action', 'http://something/insufficient', '--repeat-actions', 'True', '--comparison-operator', 'le', '--aggregation-method', 'count', '--threshold', '70', '--evaluation-periods', '3', '--granularity', '60', '--time-constraint', 'name=cons1;start="0 11 * * *";duration=300;description="desc1"', '--time-constraint', 'name=cons2;start="0 23 * * *";duration=600;description="desc2"', '--user-id', 'f28735621ee84f329144eb467c91fce6', '--project-id', '97fcad0402ce4f65ac3bd42a0c6a7e74', ] GNOCCHI_RESOURCES_CLI_ARGS = COMMON_CLI_ARGS + [ '--metric', 'cpu_util', '--resource-type', 'instance', '--resource-id', '768ff714-8cfb-4db9-9753-d484cb33a1cc', ] GNOCCHI_AGGR_BY_METRICS_CLI_ARGS = COMMON_CLI_ARGS + [ '-m', 'b3d9d8ab-05e8-439f-89ad-5e978dd2a5eb', '-m', '009d4faf-c275-46f0-8f2d-670b15bac2b0', ] GNOCCHI_AGGR_BY_RESOURCES_CLI_ARGS = COMMON_CLI_ARGS + [ '--metric', 'cpu_util', '--resource-type', 'instance', '--query', '{"=": {"server_group":"my_autoscaling_group"}}' ] def setUp(self): super(ShellAlarmGnocchiCommandTest, self).setUp() self.cc = mock.Mock() self.cc.alarms = mock.Mock() self.args = mock.Mock() @mock.patch('sys.stdout', new=six.StringIO()) def test_do_alarm_gnocchi_resources_threshold_create(self): alarm = alarms.Alarm(mock.Mock(), self.ALARM1) self.cc.alarms.create.return_value = alarm ceilometer_shell.do_alarm_gnocchi_resources_threshold_create(self.cc, self.args) self.assertEqual('''\ +---------------------------+--------------------------------------+ | Property | Value | +---------------------------+--------------------------------------+ | aggregation_method | count | | alarm_actions | ["http://something/alarm"] | | alarm_id | b69ecdb9-f19b-4fb5-950f-5eb53938b718 | | comparison_operator | le | | description | description_gnocchi_alarm | | enabled | True | | evaluation_periods | 3 | | granularity | 60 | | insufficient_data_actions | ["http://something/insufficient"] | | metric | cpu_util | | name | name_gnocchi_alarm | | ok_actions | ["http://something/ok"] | | project_id | 97fcad0402ce4f65ac3bd42a0c6a7e74 | | repeat_actions | True | | resource_id | 768ff714-8cfb-4db9-9753-d484cb33a1cc | | resource_type | instance | | severity | critical | | state | ok | | threshold | 70.0 | | time_constraints | [{name: cons1, | | | description: desc1, | | | start: 0 11 * * *, | | | duration: 300}, | | | {name: cons2, | | | description: desc2, | | | start: 0 23 * * *, | | | duration: 600}] | | type | gnocchi_resources_threshold | | user_id | f28735621ee84f329144eb467c91fce6 | +---------------------------+--------------------------------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_do_alarm_gnocchi_aggr_by_metrics_threshold_create(self): alarm = alarms.Alarm(mock.Mock(), self.ALARM2) self.cc.alarms.create.return_value = alarm ceilometer_shell.\ do_alarm_gnocchi_aggregation_by_metrics_threshold_create( self.cc, self.args) stdout = sys.stdout.getvalue() self.assertIn("b69ecdb9-f19b-4fb5-950f-5eb53938b718", stdout) self.assertIn("[\"http://something/alarm\"]", stdout) self.assertIn("description_gnocchi_alarm", stdout) self.assertIn("gnocchi_aggregation_by_metrics_threshold", stdout) @mock.patch('sys.stdout', new=six.StringIO()) def test_do_alarm_gnocchi_aggr_by_resources_threshold_create(self): alarm = alarms.Alarm(mock.Mock(), self.ALARM3) self.cc.alarms.create.return_value = alarm ceilometer_shell.\ do_alarm_gnocchi_aggregation_by_resources_threshold_create( self.cc, self.args) self.assertEqual('''\ +---------------------------+------------------------------------------------+ | Property | Value | +---------------------------+------------------------------------------------+ | aggregation_method | count | | alarm_actions | ["http://something/alarm"] | | alarm_id | b69ecdb9-f19b-4fb5-950f-5eb53938b718 | | comparison_operator | le | | description | description_gnocchi_alarm | | enabled | True | | evaluation_periods | 3 | | granularity | 60 | | insufficient_data_actions | ["http://something/insufficient"] | | metric | cpu_util | | name | name_gnocchi_alarm | | ok_actions | ["http://something/ok"] | | project_id | 97fcad0402ce4f65ac3bd42a0c6a7e74 | | query | {"=": {"server_group":"my_autoscaling_group"}} | | repeat_actions | True | | resource_type | instance | | severity | critical | | state | ok | | threshold | 70.0 | | time_constraints | [{name: cons1, | | | description: desc1, | | | start: 0 11 * * *, | | | duration: 300}, | | | {name: cons2, | | | description: desc2, | | | start: 0 23 * * *, | | | duration: 600}] | | type | gnocchi_aggregation_by_resources_threshold | | user_id | f28735621ee84f329144eb467c91fce6 | +---------------------------+------------------------------------------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_do_alarm_gnocchi_resources_threshold_create_args(self): argv = ['alarm-gnocchi-resources-threshold-create'] argv.extend(self.GNOCCHI_RESOURCES_CLI_ARGS) self._test_alarm_gnocchi_resources_arguments('create', argv) def test_do_alarm_gnocchi_resources_threshold_update_args(self): argv = ['alarm-gnocchi-resources-threshold-update'] argv.extend(self.GNOCCHI_RESOURCES_CLI_ARGS) argv.append(self.ALARM_ID) self._test_alarm_gnocchi_resources_arguments('update', argv) @mock.patch('sys.stdout', new=six.StringIO()) def test_do_alarm_gnocchi_aggr_by_metrics_threshold_create_args(self): argv = ['alarm-gnocchi-aggregation-by-metrics-threshold-create'] argv.extend(self.GNOCCHI_AGGR_BY_METRICS_CLI_ARGS) self._test_alarm_gnocchi_aggr_by_metrics_arguments('create', argv) def test_do_alarm_gnocchi_aggr_by_metrics_threshold_update_args(self): argv = ['alarm-gnocchi-aggregation-by-metrics-threshold-update'] argv.extend(self.GNOCCHI_AGGR_BY_METRICS_CLI_ARGS) argv.append(self.ALARM_ID) self._test_alarm_gnocchi_aggr_by_metrics_arguments('update', argv) @mock.patch('sys.stdout', new=six.StringIO()) def test_do_alarm_gnocchi_aggr_by_resources_threshold_create_args(self): argv = ['alarm-gnocchi-aggregation-by-resources-threshold-create'] argv.extend(self.GNOCCHI_AGGR_BY_RESOURCES_CLI_ARGS) self._test_alarm_gnocchi_aggr_by_resources_arguments('create', argv) def test_do_alarm_gnocchi_aggr_by_resources_threshold_update_args(self): argv = ['alarm-gnocchi-aggregation-by-resources-threshold-update'] argv.extend(self.GNOCCHI_AGGR_BY_RESOURCES_CLI_ARGS) argv.append(self.ALARM_ID) self._test_alarm_gnocchi_aggr_by_resources_arguments('update', argv) @mock.patch('sys.stdout', new=six.StringIO()) def _test_common_alarm_gnocchi_arguments(self, kwargs): self.assertEqual('97fcad0402ce4f65ac3bd42a0c6a7e74', kwargs.get('project_id')) self.assertEqual('f28735621ee84f329144eb467c91fce6', kwargs.get('user_id')) self.assertEqual('name_gnocchi_alarm', kwargs.get('name')) self.assertEqual('description_gnocchi_alarm', kwargs.get('description')) self.assertEqual(['http://something/alarm'], kwargs.get('alarm_actions')) self.assertEqual(['http://something/ok'], kwargs.get('ok_actions')) self.assertEqual(['http://something/insufficient'], kwargs.get('insufficient_data_actions')) self.assertEqual('critical', kwargs.get('severity')) self.assertEqual('ok', kwargs.get('state')) self.assertEqual(True, kwargs.get('enabled')) self.assertEqual(True, kwargs.get('repeat_actions')) time_constraints = [dict(name='cons1', start='0 11 * * *', duration='300', description='desc1'), dict(name='cons2', start='0 23 * * *', duration='600', description='desc2')] self.assertEqual(time_constraints, kwargs['time_constraints']) def _test_alarm_gnocchi_resources_arguments(self, action, argv): self.make_env(test_shell.FAKE_V2_ENV) with mock.patch.object(alarms.AlarmManager, action) as mocked: with mock.patch('ceilometerclient.apiclient.' 'client.HTTPClient.client_request') as request: request.site_effect = exceptions.EndpointNotFound base_shell.main(argv) args, kwargs = mocked.call_args self.assertEqual('gnocchi_resources_threshold', kwargs.get('type')) self.assertIn('gnocchi_resources_threshold_rule', kwargs) rule = kwargs['gnocchi_resources_threshold_rule'] self.assertEqual('cpu_util', rule.get('metric')) self.assertEqual(70.0, rule.get('threshold')) self.assertEqual(60, rule.get('granularity')) self.assertEqual('count', rule.get('aggregation_method')) self.assertEqual('le', rule.get('comparison_operator')) self.assertEqual(3, rule.get('evaluation_periods')) self.assertEqual('768ff714-8cfb-4db9-9753-d484cb33a1cc', rule.get('resource_id')) self.assertEqual('instance', rule.get('resource_type')) self._test_common_alarm_gnocchi_arguments(kwargs) def _test_alarm_gnocchi_aggr_by_metrics_arguments(self, action, argv): self.make_env(test_shell.FAKE_V2_ENV) with mock.patch.object(alarms.AlarmManager, action) as mocked: with mock.patch('ceilometerclient.apiclient.' 'client.HTTPClient.client_request') as request: request.site_effect = exceptions.EndpointNotFound base_shell.main(argv) args, kwargs = mocked.call_args self.assertEqual('gnocchi_aggregation_by_metrics_threshold', kwargs.get('type')) self.assertIn('gnocchi_aggregation_by_metrics_threshold_rule', kwargs) rule = kwargs['gnocchi_aggregation_by_metrics_threshold_rule'] self.assertEqual(['b3d9d8ab-05e8-439f-89ad-5e978dd2a5eb', '009d4faf-c275-46f0-8f2d-670b15bac2b0'], rule.get('metrics')) self.assertEqual(70.0, rule.get('threshold')) self.assertEqual(60, rule.get('granularity')) self.assertEqual('count', rule.get('aggregation_method')) self.assertEqual('le', rule.get('comparison_operator')) self.assertEqual(3, rule.get('evaluation_periods')) self._test_common_alarm_gnocchi_arguments(kwargs) def _test_alarm_gnocchi_aggr_by_resources_arguments(self, action, argv): self.make_env(test_shell.FAKE_V2_ENV) with mock.patch.object(alarms.AlarmManager, action) as mocked: with mock.patch('ceilometerclient.apiclient.' 'client.HTTPClient.client_request') as request: request.site_effect = exceptions.EndpointNotFound base_shell.main(argv) args, kwargs = mocked.call_args self.assertEqual('gnocchi_aggregation_by_resources_threshold', kwargs.get('type')) self.assertIn('gnocchi_aggregation_by_resources_threshold_rule', kwargs) rule = kwargs['gnocchi_aggregation_by_resources_threshold_rule'] self.assertEqual('cpu_util', rule.get('metric')) self.assertEqual(70.0, rule.get('threshold')) self.assertEqual(60, rule.get('granularity')) self.assertEqual('count', rule.get('aggregation_method')) self.assertEqual('le', rule.get('comparison_operator')) self.assertEqual(3, rule.get('evaluation_periods')) self.assertEqual('instance', rule.get('resource_type')) self.assertEqual('{"=": {"server_group":"my_autoscaling_group"}}', rule.get('query')) self._test_common_alarm_gnocchi_arguments(kwargs) class ShellSampleListCommandTest(utils.BaseTestCase): METER = 'cpu_util' SAMPLE_VALUES = ( ("cpu_util", "5dcf5537-3161-4e25-9235-407e1385bd35", "2013-10-15T05:50:30", "%", 0.261666666667, "gauge", "86536501-b2c9-48f6-9c6a-7a5b14ba7482"), ("cpu_util", "87d197e9-9cf6-4c25-bc66-1b1f4cedb52f", "2013-10-15T05:50:29", "%", 0.261666666667, "gauge", "fe2a91ec-602b-4b55-8cba-5302ce3b916e",), ("cpu_util", "5dcf5537-3161-4e25-9235-407e1385bd35", "2013-10-15T05:40:30", "%", 0.251247920133, "gauge", "52768bcb-b4e9-4db9-a30c-738c758b6f43"), ("cpu_util", "87d197e9-9cf6-4c25-bc66-1b1f4cedb52f", "2013-10-15T05:40:29", "%", 0.26, "gauge", "31ae614a-ac6b-4fb9-b106-4667bae03308"), ) OLD_SAMPLES = [ dict(counter_name=s[0], resource_id=s[1], timestamp=s[2], counter_unit=s[3], counter_volume=s[4], counter_type=s[5]) for s in SAMPLE_VALUES ] SAMPLES = [ dict(meter=s[0], resource_id=s[1], timestamp=s[2], unit=s[3], volume=s[4], type=s[5], id=s[6]) for s in SAMPLE_VALUES ] def setUp(self): super(ShellSampleListCommandTest, self).setUp() self.cc = mock.Mock() self.cc.samples = mock.Mock() self.cc.new_samples = mock.Mock() self.args = mock.Mock() self.args.query = None self.args.limit = None @mock.patch('sys.stdout', new=six.StringIO()) def test_old_sample_list(self): self.args.meter = self.METER sample_list = [samples.OldSample(mock.Mock(), sample) for sample in self.OLD_SAMPLES] self.cc.samples.list.return_value = sample_list ceilometer_shell.do_sample_list(self.cc, self.args) self.cc.samples.list.assert_called_once_with( meter_name=self.METER, q=None, limit=None) self.assertEqual('''\ +--------------------------------------+----------+-------+----------------\ +------+---------------------+ | Resource ID | Name | Type | Volume \ | Unit | Timestamp | +--------------------------------------+----------+-------+----------------\ +------+---------------------+ | 5dcf5537-3161-4e25-9235-407e1385bd35 | cpu_util | gauge | 0.261666666667 \ | % | 2013-10-15T05:50:30 | | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f | cpu_util | gauge | 0.261666666667 \ | % | 2013-10-15T05:50:29 | | 5dcf5537-3161-4e25-9235-407e1385bd35 | cpu_util | gauge | 0.251247920133 \ | % | 2013-10-15T05:40:30 | | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f | cpu_util | gauge | 0.26 \ | % | 2013-10-15T05:40:29 | +--------------------------------------+----------+-------+----------------\ +------+---------------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_sample_list(self): self.args.meter = None sample_list = [samples.Sample(mock.Mock(), sample) for sample in self.SAMPLES] self.cc.new_samples.list.return_value = sample_list ceilometer_shell.do_sample_list(self.cc, self.args) self.cc.new_samples.list.assert_called_once_with( q=None, limit=None) self.assertEqual('''\ +--------------------------------------+--------------------------------------\ +----------+-------+----------------+------+---------------------+ | ID | Resource ID \ | Name | Type | Volume | Unit | Timestamp | +--------------------------------------+--------------------------------------\ +----------+-------+----------------+------+---------------------+ | 86536501-b2c9-48f6-9c6a-7a5b14ba7482 | 5dcf5537-3161-4e25-9235-407e1385bd35 \ | cpu_util | gauge | 0.261666666667 | % | 2013-10-15T05:50:30 | | fe2a91ec-602b-4b55-8cba-5302ce3b916e | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f \ | cpu_util | gauge | 0.261666666667 | % | 2013-10-15T05:50:29 | | 52768bcb-b4e9-4db9-a30c-738c758b6f43 | 5dcf5537-3161-4e25-9235-407e1385bd35 \ | cpu_util | gauge | 0.251247920133 | % | 2013-10-15T05:40:30 | | 31ae614a-ac6b-4fb9-b106-4667bae03308 | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f \ | cpu_util | gauge | 0.26 | % | 2013-10-15T05:40:29 | +--------------------------------------+--------------------------------------\ +----------+-------+----------------+------+---------------------+ ''', sys.stdout.getvalue()) class ShellSampleShowCommandTest(utils.BaseTestCase): SAMPLE = { "user_id": None, "resource_id": "9b651dfd-7d30-402b-972e-212b2c4bfb05", "timestamp": "2014-11-03T13:37:46", "meter": "image", "volume": 1.0, "source": "openstack", "recorded_at": "2014-11-03T13:37:46.994458", "project_id": "2cc3a7bb859b4bacbeab0aa9ca673033", "type": "gauge", "id": "98b5f258-635e-11e4-8bdd-0025647390c1", "unit": "image", "metadata": { "name": "cirros-0.3.2-x86_64-uec", } } def setUp(self): super(ShellSampleShowCommandTest, self).setUp() self.cc = mock.Mock() self.cc.new_samples = mock.Mock() self.args = mock.Mock() self.args.sample_id = "98b5f258-635e-11e4-8bdd-0025647390c1" @mock.patch('sys.stdout', new=six.StringIO()) def test_sample_show(self): sample = samples.Sample(mock.Mock(), self.SAMPLE) self.cc.new_samples.get.return_value = sample ceilometer_shell.do_sample_show(self.cc, self.args) self.cc.new_samples.get.assert_called_once_with( "98b5f258-635e-11e4-8bdd-0025647390c1") self.assertEqual('''\ +-------------+--------------------------------------+ | Property | Value | +-------------+--------------------------------------+ | id | 98b5f258-635e-11e4-8bdd-0025647390c1 | | metadata | {"name": "cirros-0.3.2-x86_64-uec"} | | meter | image | | project_id | 2cc3a7bb859b4bacbeab0aa9ca673033 | | recorded_at | 2014-11-03T13:37:46.994458 | | resource_id | 9b651dfd-7d30-402b-972e-212b2c4bfb05 | | source | openstack | | timestamp | 2014-11-03T13:37:46 | | type | gauge | | unit | image | | user_id | None | | volume | 1.0 | +-------------+--------------------------------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_sample_show_raises_command_err(self): self.cc.new_samples.get.side_effect = exc.HTTPNotFound self.assertRaises(exc.CommandError, ceilometer_shell.do_sample_show, self.cc, self.args) class ShellSampleCreateCommandTest(utils.BaseTestCase): METER = 'instance' METER_TYPE = 'gauge' RESOURCE_ID = '0564c64c-3545-4e34-abfb-9d18e5f2f2f9' SAMPLE_VOLUME = '1' METER_UNIT = 'instance' SAMPLE = [{ u'counter_name': u'instance', u'user_id': u'21b442b8101d407d8242b6610e0ed0eb', u'resource_id': u'0564c64c-3545-4e34-abfb-9d18e5f2f2f9', u'timestamp': u'2014-01-10T03: 05: 33.951170', u'message_id': u'1247cbe6-79a4-11e3-a296-000c294c58e2', u'source': u'384260c6987b451d8290e66e1f108082: openstack', u'counter_unit': u'instance', u'counter_volume': 1.0, u'project_id': u'384260c6987b451d8290e66e1f108082', u'counter_type': u'gauge', u'resource_metadata': {u'display_name': u'test_name'} }] def setUp(self): super(ShellSampleCreateCommandTest, self).setUp() self.cc = mock.Mock() self.cc.samples = mock.Mock() self.args = mock.Mock() self.args.meter_name = self.METER self.args.meter_type = self.METER_TYPE self.args.meter_unit = self.METER_UNIT self.args.resource_id = self.RESOURCE_ID self.args.sample_volume = self.SAMPLE_VOLUME @mock.patch('sys.stdout', new=six.StringIO()) def test_sample_create(self): ret_sample = [samples.OldSample(mock.Mock(), sample) for sample in self.SAMPLE] self.cc.samples.create.return_value = ret_sample ceilometer_shell.do_sample_create(self.cc, self.args) self.assertEqual('''\ +-------------------+---------------------------------------------+ | Property | Value | +-------------------+---------------------------------------------+ | message_id | 1247cbe6-79a4-11e3-a296-000c294c58e2 | | name | instance | | project_id | 384260c6987b451d8290e66e1f108082 | | resource_id | 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | | resource_metadata | {"display_name": "test_name"} | | source | 384260c6987b451d8290e66e1f108082: openstack | | timestamp | 2014-01-10T03: 05: 33.951170 | | type | gauge | | unit | instance | | user_id | 21b442b8101d407d8242b6610e0ed0eb | | volume | 1.0 | +-------------------+---------------------------------------------+ ''', sys.stdout.getvalue()) def test_sample_create_with_invalid_resource_metadata(self): self.args.resource_metadata = 'foo=bar' with mock.patch('ceilometerclient.exc.CommandError') as e: e.return_value = exc.BaseException() self.assertRaises(exc.BaseException, ceilometer_shell.do_sample_create, self.cc, self.args) e.assert_called_with('Invalid resource metadata, it should be a' ' json string, like: \'{"foo":"bar"}\'') class ShellSampleCreateListCommandTest(utils.BaseTestCase): SAMPLE = { u'counter_name': u'image', u'user_id': u'21b442b8101d407d8242b6610e0ed0eb', u'resource_id': u'0564c64c-3545-4e34-abfb-9d18e5f2f2f9', u'timestamp': u'2015-05-19T12:00:08.368574', u'source': u'384260c6987b451d8290e66e1f108082: openstack', u'counter_unit': u'image', u'counter_volume': 1.0, u'project_id': u'384260c6987b451d8290e66e1f108082', u'resource_metadata': {}, u'counter_type': u'cumulative' } def setUp(self): super(ShellSampleCreateListCommandTest, self).setUp() self.cc = mock.Mock() self.cc.samples = mock.Mock() self.cc.samples.create_list = mock.Mock() self.args = mock.Mock() self.samples = [self.SAMPLE] * 5 self.args.samples_list = json.dumps(self.samples) @mock.patch('sys.stdout', new=six.StringIO()) def test_sample_create_list(self): ret_samples = [samples.OldSample(mock.Mock(), sample) for sample in self.samples] self.cc.samples.create_list.return_value = ret_samples ceilometer_shell.do_sample_create_list(self.cc, self.args) self.cc.samples.create_list.assert_called_with(self.samples, direct=mock.ANY) self.assertEqual('''\ +--------------------------------------+-------+------------+--------+-------\ +----------------------------+ | Resource ID | Name | Type | Volume | Unit \ | Timestamp | +--------------------------------------+-------+------------+--------+-------\ +----------------------------+ | 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ | 2015-05-19T12:00:08.368574 | | 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ | 2015-05-19T12:00:08.368574 | | 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ | 2015-05-19T12:00:08.368574 | | 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ | 2015-05-19T12:00:08.368574 | | 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ | 2015-05-19T12:00:08.368574 | +--------------------------------------+-------+------------+--------+-------\ +----------------------------+ ''', sys.stdout.getvalue()) class ShellQuerySamplesCommandTest(utils.BaseTestCase): SAMPLE = [{u'id': u'b55d1526-9929-11e3-a3f6-02163e5df1e6', u'metadata': { u'name1': u'value1', u'name2': u'value2'}, u'meter': 'instance', u'project_id': u'35b17138-b364-4e6a-a131-8f3099c5be68', u'resource_id': u'bd9431c1-8d69-4ad3-803a-8d4a6b89fd36', u'source': u'openstack', u'timestamp': u'2014-02-19T05:50:16.673604', u'type': u'gauge', u'unit': u'instance', u'volume': 1, u'user_id': 'efd87807-12d2-4b38-9c70-5f5c2ac427ff'}] QUERY = {"filter": {"and": [{"=": {"source": "openstack"}}, {">": {"timestamp": "2014-02-19T05:50:16"}}]}, "orderby": [{"timestamp": "desc"}, {"volume": "asc"}], "limit": 10} def setUp(self): super(ShellQuerySamplesCommandTest, self).setUp() self.cc = mock.Mock() self.args = mock.Mock() self.args.filter = self.QUERY["filter"] self.args.orderby = self.QUERY["orderby"] self.args.limit = self.QUERY["limit"] @mock.patch('sys.stdout', new=six.StringIO()) def test_query(self): ret_sample = [samples.Sample(mock.Mock(), sample) for sample in self.SAMPLE] self.cc.query_samples.query.return_value = ret_sample ceilometer_shell.do_query_samples(self.cc, self.args) self.assertEqual('''\ +--------------------------------------+--------------------------------------\ +----------+-------+--------+----------+----------------------------+ | ID | Resource ID \ | Meter | Type | Volume | Unit | Timestamp | +--------------------------------------+--------------------------------------\ +----------+-------+--------+----------+----------------------------+ | b55d1526-9929-11e3-a3f6-02163e5df1e6 | bd9431c1-8d69-4ad3-803a-8d4a6b89fd36 \ | instance | gauge | 1 | instance | 2014-02-19T05:50:16.673604 | +--------------------------------------+--------------------------------------\ +----------+-------+--------+----------+----------------------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_query_raises_command_error(self): self.cc.query_samples.query.side_effect = exc.HTTPNotFound self.assertRaises(exc.CommandError, ceilometer_shell.do_query_samples, self.cc, self.args) class ShellQueryAlarmsCommandTest(utils.BaseTestCase): ALARM = [{"alarm_actions": ["http://site:8000/alarm"], "alarm_id": "768ff714-8cfb-4db9-9753-d484cb33a1cc", "combination_rule": { "alarm_ids": [ "739e99cb-c2ec-4718-b900-332502355f38", "153462d0-a9b8-4b5b-8175-9e4b05e9b856"], "operator": "or"}, "description": "An alarm", "enabled": True, "insufficient_data_actions": ["http://site:8000/nodata"], "name": "SwiftObjectAlarm", "ok_actions": ["http://site:8000/ok"], "project_id": "c96c887c216949acbdfbd8b494863567", "repeat_actions": False, "state": "ok", "severity": "critical", "state_timestamp": "2014-02-20T10:37:15.589860", "threshold_rule": None, "timestamp": "2014-02-20T10:37:15.589856", "time_constraints": [{"name": "test", "start": "0 23 * * *", "duration": 10800}], "type": "combination", "user_id": "c96c887c216949acbdfbd8b494863567"}] QUERY = {"filter": {"and": [{"!=": {"state": "ok"}}, {"=": {"type": "combination"}}]}, "orderby": [{"state_timestamp": "desc"}], "limit": 10} def setUp(self): super(ShellQueryAlarmsCommandTest, self).setUp() self.cc = mock.Mock() self.args = mock.Mock() self.args.filter = self.QUERY["filter"] self.args.orderby = self.QUERY["orderby"] self.args.limit = self.QUERY["limit"] @mock.patch('sys.stdout', new=six.StringIO()) def test_query(self): ret_alarm = [alarms.Alarm(mock.Mock(), alarm) for alarm in self.ALARM] self.cc.query_alarms.query.return_value = ret_alarm ceilometer_shell.do_query_alarms(self.cc, self.args) self.assertEqual('''\ +--------------------------------------+------------------+-------+----------+\ ---------+------------+-------------------------------------------------------\ -----------------------------------------------+-------------------------------\ -+ | Alarm ID | Name | State | Severity \ | Enabled | Continuous | Alarm condition \ | Time constraints \ | +--------------------------------------+------------------+-------+----------+\ ---------+------------+-------------------------------------------------------\ -----------------------------------------------+--------------------------------+ | 768ff714-8cfb-4db9-9753-d484cb33a1cc | SwiftObjectAlarm | ok | critical \ | True | False | combinated states (OR) of \ 739e99cb-c2ec-4718-b900-332502355f38, 153462d0-a9b8-4b5b-8175-9e4b05e9b856 |\ test at 0 23 * * * for 10800s | +--------------------------------------+------------------+-------+----------+\ ---------+------------+-------------------------------------------------------\ -----------------------------------------------+------------------------------\ --+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_time_constraints_compatibility(self): # client should be backwards compatible alarm_without_tc = dict(self.ALARM[0]) del alarm_without_tc['time_constraints'] # NOTE(nsaje): Since we're accessing a nonexisting key in the resource, # the resource is looking it up with the manager (which is a mock). manager_mock = mock.Mock() del manager_mock.get ret_alarm = [alarms.Alarm(manager_mock, alarm_without_tc)] self.cc.query_alarms.query.return_value = ret_alarm ceilometer_shell.do_query_alarms(self.cc, self.args) self.assertEqual('''\ +--------------------------------------+------------------+-------+----------+\ ---------+------------+-------------------------------------------------------\ -----------------------------------------------+------------------+ | Alarm ID | Name | State | Severity \ | Enabled | Continuous | Alarm condition \ | Time constraints | +--------------------------------------+------------------+-------+----------+\ ---------+------------+-------------------------------------------------------\ -----------------------------------------------+------------------+ | 768ff714-8cfb-4db9-9753-d484cb33a1cc | SwiftObjectAlarm | ok | critical \ | True | False | combinated states (OR) of \ 739e99cb-c2ec-4718-b900-332502355f38, 153462d0-a9b8-4b5b-8175-9e4b05e9b856 \ | None | +--------------------------------------+------------------+-------+----------+\ ---------+------------+-------------------------------------------------------\ -----------------------------------------------+------------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_query_raises_command_err(self): self.cc.query_alarms.query.side_effect = exc.HTTPNotFound self.assertRaises(exc.CommandError, ceilometer_shell.do_query_alarms, self.cc, self.args) class ShellQueryAlarmHistoryCommandTest(utils.BaseTestCase): ALARM_HISTORY = [{"alarm_id": "e8ff32f772a44a478182c3fe1f7cad6a", "event_id": "c74a8611-6553-4764-a860-c15a6aabb5d0", "detail": "{\"threshold\": 42.0, \"evaluation_periods\": 4}", "on_behalf_of": "92159030020611e3b26dde429e99ee8c", "project_id": "b6f16144010811e387e4de429e99ee8c", "timestamp": "2014-03-11T16:02:58.376261", "type": "rule change", "user_id": "3e5d11fda79448ac99ccefb20be187ca" }] QUERY = {"filter": {"and": [{">": {"timestamp": "2014-03-11T16:02:58"}}, {"=": {"type": "rule change"}}]}, "orderby": [{"timestamp": "desc"}], "limit": 10} def setUp(self): super(ShellQueryAlarmHistoryCommandTest, self).setUp() self.cc = mock.Mock() self.args = mock.Mock() self.args.filter = self.QUERY["filter"] self.args.orderby = self.QUERY["orderby"] self.args.limit = self.QUERY["limit"] @mock.patch('sys.stdout', new=six.StringIO()) def test_query(self): ret_alarm_history = [alarms.AlarmChange(mock.Mock(), alarm_history) for alarm_history in self.ALARM_HISTORY] self.cc.query_alarm_history.query.return_value = ret_alarm_history ceilometer_shell.do_query_alarm_history(self.cc, self.args) self.assertEqual('''\ +----------------------------------+--------------------------------------+-\ ------------+----------------------------------------------+----------------\ ------------+ | Alarm ID | Event ID | \ Type | Detail | Timestamp \ | +----------------------------------+--------------------------------------+-\ ------------+----------------------------------------------+----------------\ ------------+ | e8ff32f772a44a478182c3fe1f7cad6a | c74a8611-6553-4764-a860-c15a6aabb5d0 | \ rule change | {"threshold": 42.0, "evaluation_periods": 4} | 2014-03-11T16:0\ 2:58.376261 | +----------------------------------+--------------------------------------+-\ ------------+----------------------------------------------+----------------\ ------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_query_raises_command_err(self): self.cc.query_alarm_history.query.side_effect = exc.HTTPNotFound self.assertRaises(exc.CommandError, ceilometer_shell.do_query_alarm_history, self.cc, self.args) class ShellStatisticsTest(utils.BaseTestCase): def setUp(self): super(ShellStatisticsTest, self).setUp() self.cc = mock.Mock() self.displays = { 'duration': 'Duration', 'duration_end': 'Duration End', 'duration_start': 'Duration Start', 'period': 'Period', 'period_end': 'Period End', 'period_start': 'Period Start', 'groupby': 'Group By', 'avg': 'Avg', 'count': 'Count', 'max': 'Max', 'min': 'Min', 'sum': 'Sum', 'stddev': 'Standard deviation', 'cardinality': 'Cardinality' } self.args = mock.Mock() self.args.meter_name = 'instance' self.args.aggregate = [] self.args.groupby = None self.args.query = None def test_statistics_list_simple(self): samples = [ {u'count': 135, u'duration_start': u'2013-02-04T10:51:42', u'min': 1.0, u'max': 1.0, u'duration_end': u'2013-02-05T15:46:09', u'duration': 1734.0, u'avg': 1.0, u'sum': 135.0}, ] fields = [ 'period', 'period_start', 'period_end', 'max', 'min', 'avg', 'sum', 'count', 'duration', 'duration_start', 'duration_end', ] statistics_ret = [ statistics.Statistics(mock.Mock(), sample) for sample in samples ] self.cc.statistics.list.return_value = statistics_ret with mock.patch('ceilometerclient.v2.shell.utils.print_list') as pmock: ceilometer_shell.do_statistics(self.cc, self.args) pmock.assert_called_with( statistics_ret, fields, [self.displays[f] for f in fields] ) def test_statistics_list_groupby(self): samples = [ {u'count': 135, u'duration_start': u'2013-02-04T10:51:42', u'min': 1.0, u'max': 1.0, u'duration_end': u'2013-02-05T15:46:09', u'duration': 1734.0, u'avg': 1.0, u'sum': 135.0, u'groupby': {u'resource_id': u'foo'} }, {u'count': 12, u'duration_start': u'2013-02-04T10:51:42', u'min': 1.0, u'max': 1.0, u'duration_end': u'2013-02-05T15:46:09', u'duration': 1734.0, u'avg': 1.0, u'sum': 12.0, u'groupby': {u'resource_id': u'bar'} }, ] fields = [ 'period', 'period_start', 'period_end', 'groupby', 'max', 'min', 'avg', 'sum', 'count', 'duration', 'duration_start', 'duration_end', ] self.args.groupby = 'resource_id' statistics_ret = [ statistics.Statistics(mock.Mock(), sample) for sample in samples ] self.cc.statistics.list.return_value = statistics_ret with mock.patch('ceilometerclient.v2.shell.utils.print_list') as pmock: ceilometer_shell.do_statistics(self.cc, self.args) pmock.assert_called_with( statistics_ret, fields, [self.displays[f] for f in fields], ) def test_statistics_list_aggregates(self): samples = [ {u'aggregate': {u'cardinality/resource_id': 4.0, u'count': 2.0}, u'count': 2, u'duration': 0.442451, u'duration_end': u'2014-03-12T14:00:21.774154', u'duration_start': u'2014-03-12T14:00:21.331703', u'groupby': None, u'period': 0, u'period_end': u'2014-03-12T14:00:21.774154', u'period_start': u'2014-03-12T14:00:21.331703', u'unit': u'instance', }, ] fields = [ 'period', 'period_start', 'period_end', 'count', 'cardinality/resource_id', 'duration', 'duration_start', 'duration_end', ] self.args.aggregate = ['count', 'cardinality<-resource_id'] statistics_ret = [ statistics.Statistics(mock.Mock(), sample) for sample in samples ] self.cc.statistics.list.return_value = statistics_ret with mock.patch('ceilometerclient.v2.shell.utils.print_list') as pmock: ceilometer_shell.do_statistics(self.cc, self.args) pmock.assert_called_with( statistics_ret, fields, [self.displays.get(f, f) for f in fields], ) class ShellEmptyIdTest(utils.BaseTestCase): """Test empty field which will cause calling incorrect rest uri.""" def _test_entity_action_with_empty_values(self, entity, *args, **kwargs): positional = kwargs.pop('positional', False) for value in ('', ' ', ' ', '\t'): self._test_entity_action_with_empty_value(entity, value, positional, *args) def _test_entity_action_with_empty_value(self, entity, value, positional, *args): new_args = [value] if positional else ['--%s' % entity, value] argv = list(args) + new_args shell = base_shell.CeilometerShell() with mock.patch('ceilometerclient.exc.CommandError') as e: e.return_value = exc.BaseException() self.assertRaises(exc.BaseException, shell.parse_args, argv) entity = entity.replace('-', '_') e.assert_called_with('%s should not be empty' % entity) def _test_alarm_action_with_empty_ids(self, method, *args): args = [method] + list(args) self._test_entity_action_with_empty_values('alarm_id', positional=True, *args) def test_alarm_show_with_empty_id(self): self._test_alarm_action_with_empty_ids('alarm-show') def test_alarm_update_with_empty_id(self): self._test_alarm_action_with_empty_ids('alarm-update') def test_alarm_threshold_update_with_empty_id(self): self._test_alarm_action_with_empty_ids('alarm-threshold-update') def test_alarm_combination_update_with_empty_id(self): self._test_alarm_action_with_empty_ids('alarm-combination-update') def test_alarm_gnocchi_resources_update_with_empty_id(self): self._test_alarm_action_with_empty_ids( 'alarm-gnocchi-resources-threshold-update') def test_alarm_gnocchi_aggr_by_resources_update_with_empty_id(self): self._test_alarm_action_with_empty_ids( 'alarm-gnocchi-aggregation-by-resources-threshold-update') def test_alarm_gnocchi_aggr_by_metrics_update_with_empty_id(self): self._test_alarm_action_with_empty_ids( 'alarm-gnocchi-aggregation-by-metrics-threshold-update') def test_alarm_delete_with_empty_id(self): self._test_alarm_action_with_empty_ids('alarm-delete') def test_alarm_state_get_with_empty_id(self): self._test_alarm_action_with_empty_ids('alarm-state-get') def test_alarm_state_set_with_empty_id(self): args = ['alarm-state-set', '--state', 'ok'] self._test_alarm_action_with_empty_ids(*args) def test_alarm_history_with_empty_id(self): self._test_alarm_action_with_empty_ids('alarm-history') def test_event_show_with_empty_message_id(self): args = ['event-show'] self._test_entity_action_with_empty_values('message_id', *args) def test_resource_show_with_empty_id(self): args = ['resource-show'] self._test_entity_action_with_empty_values('resource_id', *args) def test_sample_list_with_empty_meter(self): args = ['sample-list'] self._test_entity_action_with_empty_values('meter', *args) def test_sample_create_with_empty_meter(self): args = ['sample-create', '-r', 'x', '--meter-type', 'gauge', '--meter-unit', 'B', '--sample-volume', '1'] self._test_entity_action_with_empty_values('meter-name', *args) def test_statistics_with_empty_meter(self): args = ['statistics'] self._test_entity_action_with_empty_values('meter', *args) def test_trait_description_list_with_empty_event_type(self): args = ['trait-description-list'] self._test_entity_action_with_empty_values('event_type', *args) def test_trait_list_with_empty_event_type(self): args = ['trait-list', '--trait_name', 'x'] self._test_entity_action_with_empty_values('event_type', *args) def test_trait_list_with_empty_trait_name(self): args = ['trait-list', '--event_type', 'x'] self._test_entity_action_with_empty_values('trait_name', *args) class ShellObsoletedArgsTest(utils.BaseTestCase): """Test arguments that have been obsoleted.""" def _test_entity_obsoleted(self, entity, value, positional, *args): new_args = [value] if positional else ['--%s' % entity, value] argv = list(args) + new_args shell = base_shell.CeilometerShell() with mock.patch('sys.stdout', new_callable=six.StringIO) as stdout: shell.parse_args(argv) self.assertIn('obsolete', stdout.getvalue()) def test_obsolete_alarm_id(self): for method in ['alarm-show', 'alarm-update', 'alarm-threshold-update', 'alarm-combination-update', 'alarm-delete', 'alarm-state-get', 'alarm-history']: self._test_entity_obsoleted('alarm_id', 'abcde', False, method) class ShellEventListCommandTest(utils.BaseTestCase): EVENTS = [ { "generated": "2015-01-12T04:03:25.741471", "message_id": "fb2bef58-88af-4380-8698-e0f18fcf452d", "event_type": "compute.instance.create.start", "traits": [{ "name": "state", "type": "string", "value": "building", }], }, { "generated": "2015-01-12T04:03:28.452495", "message_id": "9b20509a-576b-4995-acfa-1a24ee5cf49f", "event_type": "compute.instance.create.end", "traits": [{ "name": "state", "type": "string", "value": "active", }], }, ] def setUp(self): super(ShellEventListCommandTest, self).setUp() self.cc = mock.Mock() self.args = mock.Mock() self.args.query = None self.args.no_traits = None self.args.limit = None @mock.patch('sys.stdout', new=six.StringIO()) def test_event_list(self): ret_events = [events.Event(mock.Mock(), event) for event in self.EVENTS] self.cc.events.list.return_value = ret_events ceilometer_shell.do_event_list(self.cc, self.args) self.assertEqual('''\ +--------------------------------------+-------------------------------+\ ----------------------------+-------------------------------+ | Message ID | Event Type |\ Generated | Traits | +--------------------------------------+-------------------------------+\ ----------------------------+-------------------------------+ | fb2bef58-88af-4380-8698-e0f18fcf452d | compute.instance.create.start |\ 2015-01-12T04:03:25.741471 | +-------+--------+----------+ | | | |\ | | name | type | value | | | | |\ | +-------+--------+----------+ | | | |\ | | state | string | building | | | | |\ | +-------+--------+----------+ | | 9b20509a-576b-4995-acfa-1a24ee5cf49f | compute.instance.create.end |\ 2015-01-12T04:03:28.452495 | +-------+--------+--------+ | | | |\ | | name | type | value | | | | |\ | +-------+--------+--------+ | | | |\ | | state | string | active | | | | |\ | +-------+--------+--------+ | +--------------------------------------+-------------------------------+\ ----------------------------+-------------------------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_event_list_no_traits(self): self.args.no_traits = True ret_events = [events.Event(mock.Mock(), event) for event in self.EVENTS] self.cc.events.list.return_value = ret_events ceilometer_shell.do_event_list(self.cc, self.args) self.assertEqual('''\ +--------------------------------------+-------------------------------\ +----------------------------+ | Message ID | Event Type \ | Generated | +--------------------------------------+-------------------------------\ +----------------------------+ | fb2bef58-88af-4380-8698-e0f18fcf452d | compute.instance.create.start \ | 2015-01-12T04:03:25.741471 | | 9b20509a-576b-4995-acfa-1a24ee5cf49f | compute.instance.create.end \ | 2015-01-12T04:03:28.452495 | +--------------------------------------+-------------------------------\ +----------------------------+ ''', sys.stdout.getvalue()) class ShellShadowedArgsTest(test_shell.ShellTestBase): def _test_shadowed_args_alarm(self, command, args, method): self.make_env(test_shell.FAKE_V2_ENV) cli_args = [ '--os-project-id', '0ba30185ddf44834914a0b859d244c56', '--os-user-id', '85f59b3b17484ccb974c50596023bf8c', '--debug', command, '--project-id', 'the-project-id-i-want-to-set', '--user-id', 'the-user-id-i-want-to-set', '--name', 'project-id-test'] + args with mock.patch.object(alarms.AlarmManager, method) as mocked: with mock.patch('ceilometerclient.apiclient.' 'client.HTTPClient.client_request') as request: request.site_effect = exceptions.EndpointNotFound base_shell.main(cli_args) args, kwargs = mocked.call_args self.assertEqual('the-project-id-i-want-to-set', kwargs.get('project_id')) self.assertEqual('the-user-id-i-want-to-set', kwargs.get('user_id')) def test_shadowed_args_threshold_alarm(self): cli_args = ['--meter-name', 'cpu', '--threshold', '90'] self._test_shadowed_args_alarm('alarm-create', cli_args, 'create') self._test_shadowed_args_alarm('alarm-threshold-create', cli_args, 'create') cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] self._test_shadowed_args_alarm('alarm-update', cli_args, 'update') self._test_shadowed_args_alarm('alarm-threshold-update', cli_args, 'update') def test_shadowed_args_combination_alarm(self): cli_args = ['--alarm_ids', 'fb16a05a-669d-414e-8bbe-93aa381df6a8', '--alarm_ids', 'b189bcca-0a7b-49a9-a244-a927ac291881'] self._test_shadowed_args_alarm('alarm-combination-create', cli_args, 'create') cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] self._test_shadowed_args_alarm('alarm-combination-update', cli_args, 'update') def test_shadowed_args_gnocchi_resources_threshold_alarm(self): cli_args = [ '--metric', 'cpu', '--threshold', '80', '--resource-type', 'instance', '--resource-id', 'fb16a05a-669d-414e-8bbe-93aa381df6a8', '--aggregation-method', 'last', ] self._test_shadowed_args_alarm('alarm-gnocchi-resources-' 'threshold-create', cli_args, 'create') cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] self._test_shadowed_args_alarm('alarm-gnocchi-resources-' 'threshold-update', cli_args, 'update') def test_shadowed_args_gnocchi_aggr_by_resources_threshold_alarm(self): cli_args = [ '--metric', 'cpu', '--threshold', '80', '--resource-type', 'instance', '--aggregation-method', 'last', '--query', '"server_group":"my_autoscaling_group"', ] self._test_shadowed_args_alarm('alarm-gnocchi-aggregation-' 'by-resources-threshold-create', cli_args, 'create') cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] self._test_shadowed_args_alarm('alarm-gnocchi-aggregation-' 'by-resources-threshold-update', cli_args, 'update') def test_shadowed_args_gnocchi_aggr_by_metrics_threshold_alarm(self): cli_args = [ '-m', 'b3d9d8ab-05e8-439f-89ad-5e978dd2a5eb', '-m', '009d4faf-c275-46f0-8f2d-670b15bac2b0', '--threshold', '80', '--aggregation-method', 'last', ] self._test_shadowed_args_alarm('alarm-gnocchi-aggregation-' 'by-metrics-threshold-create', cli_args, 'create') cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] self._test_shadowed_args_alarm('alarm-gnocchi-aggregation-' 'by-metrics-threshold-update', cli_args, 'update') @mock.patch.object(samples.OldSampleManager, 'create') def test_shadowed_args_sample_create(self, mocked): self.make_env(test_shell.FAKE_V2_ENV) cli_args = [ '--os-project-id', '0ba30185ddf44834914a0b859d244c56', '--os-user-id', '85f59b3b17484ccb974c50596023bf8c', '--debug', 'sample-create', '--project-id', 'the-project-id-i-want-to-set', '--user-id', 'the-user-id-i-want-to-set', '--resource-id', 'b666633d-9bb6-4e05-89c0-ee5a8752fb0b', '--meter-name', 'cpu', '--meter-type', 'cumulative', '--meter-unit', 'ns', '--sample-volume', '10086', ] with mock.patch('ceilometerclient.apiclient.client.' 'HTTPClient.client_request') as client_request: client_request.site_effect = exceptions.EndpointNotFound base_shell.main(cli_args) args, kwargs = mocked.call_args self.assertEqual('the-project-id-i-want-to-set', kwargs.get('project_id')) self.assertEqual('the-user-id-i-want-to-set', kwargs.get('user_id')) class ShellCapabilityShowTest(utils.BaseTestCase): CAPABILITIES = { "alarm_storage": { "storage:production_ready": True }, "api": { "alarms:query:complex": True, "alarms:query:simple": True }, "event_storage": { "storage:production_ready": True }, "storage": { "storage:production_ready": True }, } def setUp(self): super(ShellCapabilityShowTest, self).setUp() self.cc = mock.Mock() self.args = mock.Mock() @mock.patch('sys.stdout', new=six.StringIO()) def test_capability_show(self): _cap = capabilities.Capabilities(mock.Mock, self.CAPABILITIES) self.cc.capabilities.get.return_value = _cap ceilometer_shell.do_capabilities(self.cc, self.args) self.assertEqual('''\ +---------------+----------------------------------+ | Property | Value | +---------------+----------------------------------+ | alarm_storage | "storage:production_ready": true | | api | "alarms:query:complex": true, | | | "alarms:query:simple": true | | event_storage | "storage:production_ready": true | | storage | "storage:production_ready": true | +---------------+----------------------------------+ ''', sys.stdout.getvalue()) class ShellMeterListCommandTest(utils.BaseTestCase): METER = { "name": 'image', "resource_id": "resource-id", "meter": "image", "project_id": "project", "type": "gauge", "unit": "image", } def setUp(self): super(ShellMeterListCommandTest, self).setUp() self.cc = mock.Mock() self.cc.meters.list = mock.Mock() self.args = mock.MagicMock() self.args.limit = None self.args.unique = False @mock.patch('sys.stdout', new=six.StringIO()) def test_meter_list(self): meter = meters.Meter(mock.Mock(), self.METER) self.cc.meters.list.return_value = [meter] ceilometer_shell.do_meter_list(self.cc, self.args) self.cc.meters.list.assert_called_once_with(q=[], limit=None, unique=False) self.assertEqual('''\ +-------+-------+-------+-------------+---------+------------+ | Name | Type | Unit | Resource ID | User ID | Project ID | +-------+-------+-------+-------------+---------+------------+ | image | gauge | image | resource-id | | project | +-------+-------+-------+-------------+---------+------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_unique_meter_list(self): self.args.unique = True meter = meters.Meter(mock.Mock(), self.METER) self.cc.meters.list.return_value = [meter] ceilometer_shell.do_meter_list(self.cc, self.args) self.cc.meters.list.assert_called_once_with(q=[], limit=None, unique=True) self.assertEqual('''\ +-------+-------+-------+-------------+---------+------------+ | Name | Type | Unit | Resource ID | User ID | Project ID | +-------+-------+-------+-------------+---------+------------+ | image | gauge | image | resource-id | | project | +-------+-------+-------+-------------+---------+------------+ ''', sys.stdout.getvalue()) class ShellResourceListCommandTest(utils.BaseTestCase): RESOURCE = { "source": "openstack", "resource_id": "resource-id", "project_id": "project", "user_id": "user" } def setUp(self): super(ShellResourceListCommandTest, self).setUp() self.cc = mock.Mock() self.cc.resources.list = mock.Mock() self.args = mock.MagicMock() self.args.limit = None self.args.meter_links = None @mock.patch('sys.stdout', new=six.StringIO()) def test_resource_list(self): resource = resources.Resource(mock.Mock(), self.RESOURCE) self.cc.resources.list.return_value = [resource] ceilometer_shell.do_resource_list(self.cc, self.args) self.cc.resources.list.assert_called_once_with(q=[], limit=None) self.assertEqual('''\ +-------------+-----------+---------+------------+ | Resource ID | Source | User ID | Project ID | +-------------+-----------+---------+------------+ | resource-id | openstack | user | project | +-------------+-----------+---------+------------+ ''', sys.stdout.getvalue()) @mock.patch('sys.stdout', new=six.StringIO()) def test_resource_list_with_links(self): resource = resources.Resource(mock.Mock(), self.RESOURCE) self.cc.resources.list.return_value = [resource] ceilometer_shell.do_resource_list(self.cc, self.args) self.cc.resources.list.assert_called_once_with(q=[], limit=None) self.assertEqual('''\ +-------------+-----------+---------+------------+ | Resource ID | Source | User ID | Project ID | +-------------+-----------+---------+------------+ | resource-id | openstack | user | project | +-------------+-----------+---------+------------+ ''', sys.stdout.getvalue()) class ShellEventTypeListCommandTest(utils.BaseTestCase): EVENT_TYPE = { "event_type": "test_event" } def setUp(self): super(ShellEventTypeListCommandTest, self).setUp() self.cc = mock.Mock() self.cc.event_types.list = mock.Mock() self.args = mock.Mock() @mock.patch('sys.stdout', new=six.StringIO()) def test_sample_show(self): event_type = event_types.EventType(mock.Mock(), self.EVENT_TYPE) self.cc.event_types.list.return_value = [event_type] ceilometer_shell.do_event_type_list(self.cc, self.args) self.cc.event_types.list.assert_called_once_with() self.assertEqual('''\ +------------+ | Event Type | +------------+ | test_event | +------------+ ''', sys.stdout.getvalue()) class ShellTraitsListCommandTest(utils.BaseTestCase): TRAIT = { "name": "test", "value": "test", "type": "string", } def setUp(self): super(ShellTraitsListCommandTest, self).setUp() self.cc = mock.Mock() self.cc.traits.list = mock.Mock() self.args = mock.Mock() self.args.event_type = "test" self.args.trait_name = "test" @mock.patch('sys.stdout', new=six.StringIO()) def test_trait_list(self): trait = traits.Trait(mock.Mock(), self.TRAIT) self.cc.traits.list.return_value = [trait] ceilometer_shell.do_trait_list(self.cc, self.args) self.cc.traits.list.assert_called_once_with(self.args.event_type, self.args.trait_name) self.assertEqual('''\ +------------+-------+-----------+ | Trait Name | Value | Data Type | +------------+-------+-----------+ | test | test | string | +------------+-------+-----------+ ''', sys.stdout.getvalue()) class ShellTraitsDescriptionListCommandTest(utils.BaseTestCase): TRAIT_DESCRIPTION = { "name": "test", "type": "string", } def setUp(self): super(ShellTraitsDescriptionListCommandTest, self).setUp() self.cc = mock.Mock() self.cc.trait_descriptions.list = mock.Mock() self.args = mock.Mock() self.args.event_type = "test" @mock.patch('sys.stdout', new=six.StringIO()) def test_traits_description_list(self): trait_desc = trait_descriptions.TraitDescription( mock.Mock(), self.TRAIT_DESCRIPTION) self.cc.trait_descriptions.list.return_value = [trait_desc] ceilometer_shell.do_trait_description_list(self.cc, self.args) self.cc.trait_descriptions.list.assert_called_once_with( self.args.event_type) self.assertEqual('''\ +------------+-----------+ | Trait Name | Data Type | +------------+-----------+ | test | string | +------------+-----------+ ''', sys.stdout.getvalue()) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_query_alarms.py0000664000175000017500000000473713117513017031644 0ustar jenkinsjenkins00000000000000# Copyright Ericsson AB 2014. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils from ceilometerclient.v2 import query ALARM = {"alarm_actions": ["http://site:8000/alarm"], "alarm_id": None, "combination_rule": { "alarm_ids": [ "739e99cb-c2ec-4718-b900-332502355f38", "153462d0-a9b8-4b5b-8175-9e4b05e9b856"], "operator": "or"}, "description": "An alarm", "enabled": True, "insufficient_data_actions": ["http://site:8000/nodata"], "name": "SwiftObjectAlarm", "ok_actions": ["http://site:8000/ok"], "project_id": "c96c887c216949acbdfbd8b494863567", "repeat_actions": False, "state": "ok", "state_timestamp": "2014-02-20T10:37:15.589860", "threshold_rule": None, "timestamp": "2014-02-20T10:37:15.589856", "type": "combination", "user_id": "c96c887c216949acbdfbd8b494863567"} QUERY = {"filter": {"and": [{"!=": {"state": "ok"}}, {"=": {"type": "combination"}}]}, "orderby": [{"state_timestamp": "desc"}], "limit": 10} base_url = '/v2/query/alarms' fixtures = { base_url: { 'POST': ( {}, [ALARM], ), }, } class QueryAlarmsManagerTest(utils.BaseTestCase): def setUp(self): super(QueryAlarmsManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = query.QueryAlarmsManager(self.api) def test_query(self): alarms = self.mgr.query(**QUERY) expect = [ 'POST', '/v2/query/alarms', QUERY, ] self.http_client.assert_called(*expect) self.assertEqual(1, len(alarms)) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_resources.py0000664000175000017500000001131713117513017031142 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils import ceilometerclient.v2.resources fixtures = { '/v2/resources?meter_links=0': { 'GET': ( {}, [ { 'resource_id': 'a', 'project_id': 'project_bla', 'user_id': 'freddy', 'metadata': {'zxc_id': 'bla'}, }, { 'resource_id': 'b', 'project_id': 'dig_the_ditch', 'user_id': 'joey', 'metadata': {'zxc_id': 'foo'}, }, ] ), }, '/v2/resources?q.field=resource_id&q.op=&q.type=&q.value=a&meter_links=0': { 'GET': ( {}, [ { 'resource_id': 'a', 'project_id': 'project_bla', 'user_id': 'freddy', 'metadata': {'zxc_id': 'bla'}, }, ] ), }, '/v2/resources?meter_links=1': { 'GET': ( {}, [ { 'resource_id': 'c', 'project_id': 'project_blah', 'user_id': 'fred', 'metadata': {'zxc_id': 'blah'}, }, { 'resource_id': 'd', 'project_id': 'bury_the_ditch', 'user_id': 'jack', 'metadata': {'zxc_id': 'foobar'}, }, ] ), }, '/v2/resources/a': { 'GET': ( {}, { 'resource_id': 'a', 'project_id': 'project_bla', 'user_id': 'freddy', 'metadata': {'zxc_id': 'bla'}, }, ), }, } class ResourceManagerTest(utils.BaseTestCase): def setUp(self): super(ResourceManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = ceilometerclient.v2.resources.ResourceManager(self.api) def test_list_all(self): resources = list(self.mgr.list()) expect = [ 'GET', '/v2/resources?meter_links=0' ] self.http_client.assert_called(*expect) self.assertEqual(2, len(resources)) self.assertEqual('a', resources[0].resource_id) self.assertEqual('b', resources[1].resource_id) def test_list_all_with_links_enabled(self): resources = list(self.mgr.list(links=True)) expect = [ 'GET', '/v2/resources?meter_links=1' ] self.http_client.assert_called(*expect) self.assertEqual(2, len(resources)) self.assertEqual('c', resources[0].resource_id) self.assertEqual('d', resources[1].resource_id) def test_list_one(self): resource = self.mgr.get(resource_id='a') expect = [ 'GET', '/v2/resources/a' ] self.http_client.assert_called(*expect) self.assertIsNotNone(resource) self.assertEqual('a', resource.resource_id) def test_list_by_query(self): resources = list(self.mgr.list(q=[{"field": "resource_id", "value": "a"}, ])) expect = [ 'GET', '/v2/resources?q.field=resource_id&q.op=' '&q.type=&q.value=a&meter_links=0' ] self.http_client.assert_called(*expect) self.assertEqual(1, len(resources)) self.assertEqual('a', resources[0].resource_id) def test_get_from_resource_class(self): resource = self.mgr.get(resource_id='a') self.assertIsNotNone(resource) resource.get() expect = [ 'GET', '/v2/resources/a' ] self.http_client.assert_called(*expect, pos=0) self.http_client.assert_called(*expect, pos=1) self.assertEqual('a', resource.resource_id) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_samples.py0000664000175000017500000001734213117513017030600 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils import ceilometerclient.v2.samples GET_OLD_SAMPLE = {u'counter_name': u'instance', u'user_id': u'user-id', u'resource_id': u'resource-id', u'timestamp': u'2012-07-02T10:40:00', u'source': u'test_source', u'message_id': u'54558a1c-6ef3-11e2-9875-5453ed1bbb5f', u'counter_unit': u'', u'counter_volume': 1.0, u'project_id': u'project1', u'resource_metadata': {u'tag': u'self.counter', u'display_name': u'test-server'}, u'counter_type': u'cumulative'} CREATE_SAMPLE = copy.deepcopy(GET_OLD_SAMPLE) del CREATE_SAMPLE['message_id'] del CREATE_SAMPLE['source'] CREATE_LIST_SAMPLE = copy.deepcopy(CREATE_SAMPLE) CREATE_LIST_SAMPLE['counter_name'] = 'image' GET_SAMPLE = { "user_id": None, "resource_id": "9b651dfd-7d30-402b-972e-212b2c4bfb05", "timestamp": "2014-11-03T13:37:46", "meter": "image", "volume": 1.0, "source": "openstack", "recorded_at": "2014-11-03T13:37:46.994458", "project_id": "2cc3a7bb859b4bacbeab0aa9ca673033", "type": "gauge", "id": "98b5f258-635e-11e4-8bdd-0025647390c1", "unit": "image", "resource_metadata": {}, } METER_URL = '/v2/meters/instance' METER_URL_DIRECT = '/v2/meters/instance?direct=True' SECOND_METER_URL = '/v2/meters/image' SECOND_METER_URL_DIRECT = '/v2/meters/image?direct=True' SAMPLE_URL = '/v2/samples' QUERIES = ('q.field=resource_id&q.field=source&q.op=&q.op=' '&q.type=&q.type=&q.value=foo&q.value=bar') LIMIT = 'limit=1' OLD_SAMPLE_FIXTURES = { METER_URL: { 'GET': ( {}, [GET_OLD_SAMPLE] ), 'POST': ( {}, [CREATE_SAMPLE], ), }, METER_URL_DIRECT: { 'POST': ( {}, [CREATE_SAMPLE], ) }, SECOND_METER_URL: { 'POST': ( {}, [CREATE_LIST_SAMPLE] * 10, ), }, SECOND_METER_URL_DIRECT: { 'POST': ( {}, [CREATE_LIST_SAMPLE] * 10, ) }, '%s?%s' % (METER_URL, QUERIES): { 'GET': ( {}, [], ), }, '%s?%s' % (METER_URL, LIMIT): { 'GET': ( {}, [GET_OLD_SAMPLE] ), } } SAMPLE_FIXTURES = { SAMPLE_URL: { 'GET': ( (), [GET_SAMPLE] ), }, '%s?%s' % (SAMPLE_URL, QUERIES): { 'GET': ( {}, [], ), }, '%s?%s' % (SAMPLE_URL, LIMIT): { 'GET': ( {}, [GET_SAMPLE], ), }, '%s/%s' % (SAMPLE_URL, GET_SAMPLE['id']): { 'GET': ( {}, GET_SAMPLE, ), }, } class OldSampleManagerTest(utils.BaseTestCase): def setUp(self): super(OldSampleManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient( fixtures=OLD_SAMPLE_FIXTURES) self.api = client.BaseClient(self.http_client) self.mgr = ceilometerclient.v2.samples.OldSampleManager(self.api) def test_list_by_meter_name(self): samples = list(self.mgr.list(meter_name='instance')) expect = [ 'GET', '/v2/meters/instance' ] self.http_client.assert_called(*expect) self.assertEqual(1, len(samples)) self.assertEqual('resource-id', samples[0].resource_id) def test_list_by_meter_name_extended(self): samples = list(self.mgr.list(meter_name='instance', q=[ {"field": "resource_id", "value": "foo"}, {"field": "source", "value": "bar"}, ])) expect = ['GET', '%s?%s' % (METER_URL, QUERIES)] self.http_client.assert_called(*expect) self.assertEqual(0, len(samples)) def test_create(self): sample = self.mgr.create(**CREATE_SAMPLE) expect = [ 'POST', '/v2/meters/instance' ] self.http_client.assert_called(*expect, body=[CREATE_SAMPLE]) self.assertIsNotNone(sample) def test_create_directly(self): sample = self.mgr.create(direct=True, **CREATE_SAMPLE) expect = [ 'POST', '/v2/meters/instance?direct=True' ] self.http_client.assert_called(*expect, body=[CREATE_SAMPLE]) self.assertIsNotNone(sample) def test_create_list(self): test_samples = [CREATE_LIST_SAMPLE] * 10 samples = self.mgr.create_list(test_samples) expect = [ 'POST', '/v2/meters/image' ] self.http_client.assert_called(*expect, body=test_samples) self.assertEqual(10, len(samples)) def test_create_list_directly(self): test_samples = [CREATE_LIST_SAMPLE] * 10 samples = self.mgr.create_list(test_samples, direct=True) expect = [ 'POST', '/v2/meters/image?direct=True' ] self.http_client.assert_called(*expect, body=test_samples) self.assertEqual(10, len(samples)) def test_limit(self): samples = list(self.mgr.list(meter_name='instance', limit=1)) expect = ['GET', '/v2/meters/instance?limit=1'] self.http_client.assert_called(*expect) self.assertEqual(1, len(samples)) class SampleManagerTest(utils.BaseTestCase): def setUp(self): super(SampleManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient( fixtures=SAMPLE_FIXTURES) self.api = client.BaseClient(self.http_client) self.mgr = ceilometerclient.v2.samples.SampleManager(self.api) def test_sample_list(self): samples = list(self.mgr.list()) expect = [ 'GET', '/v2/samples' ] self.http_client.assert_called(*expect) self.assertEqual(1, len(samples)) self.assertEqual('9b651dfd-7d30-402b-972e-212b2c4bfb05', samples[0].resource_id) def test_sample_list_with_queries(self): queries = [ {"field": "resource_id", "value": "foo"}, {"field": "source", "value": "bar"}, ] samples = list(self.mgr.list(q=queries)) expect = ['GET', '%s?%s' % (SAMPLE_URL, QUERIES)] self.http_client.assert_called(*expect) self.assertEqual(0, len(samples)) def test_sample_list_with_limit(self): samples = list(self.mgr.list(limit=1)) expect = ['GET', '/v2/samples?limit=1'] self.http_client.assert_called(*expect) self.assertEqual(1, len(samples)) def test_sample_get(self): sample = self.mgr.get(GET_SAMPLE['id']) expect = ['GET', '/v2/samples/' + GET_SAMPLE['id']] self.http_client.assert_called(*expect) self.assertEqual(GET_SAMPLE, sample.to_dict()) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_events.py0000664000175000017500000001512313117513017030433 0ustar jenkinsjenkins00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils import ceilometerclient.v2.events fixtures = { '/v2/events': { 'GET': ( {}, [ { 'message_id': '1', 'event_type': 'Foo', 'generated': '1970-01-01T00:00:00', 'traits': {'trait_A': 'abc'}, }, { 'message_id': '2', 'event_type': 'Foo', 'generated': '1970-01-01T00:00:00', 'traits': {'trait_A': 'def'}, }, { 'message_id': '3', 'event_type': 'Bar', 'generated': '1970-01-01T00:00:00', 'traits': {'trait_B': 'bartrait'}, }, ] ), }, '/v2/events?q.field=hostname&q.op=&q.type=string&q.value=localhost': { 'GET': ( {}, [ { 'message_id': '1', 'event_type': 'Foo', 'generated': '1970-01-01T00:00:00', 'traits': {'trait_A': 'abc', 'hostname': 'localhost'}, }, { 'message_id': '2', 'event_type': 'Foo', 'generated': '1970-01-01T00:00:00', 'traits': {'trait_A': 'def', 'hostname': 'localhost'}, } ] ), }, '/v2/events?q.field=hostname&q.op=&q.type=&q.value=foreignhost': { 'GET': ( {}, [ { 'message_id': '1', 'event_type': 'Foo', 'generated': '1970-01-01T00:00:00', 'traits': {'trait_A': 'abc', 'hostname': 'foreignhost'}, }, { 'message_id': '2', 'event_type': 'Foo', 'generated': '1970-01-01T00:00:00', 'traits': {'trait_A': 'def', 'hostname': 'foreignhost'}, } ] ), }, '/v2/events?q.field=hostname&q.field=num_cpus&q.op=&q.op=' '&q.type=&q.type=integer&q.value=localhost&q.value=5': { 'GET': ( {}, [ { 'message_id': '1', 'event_type': 'Bar', 'generated': '1970-01-01T00:00:00', 'traits': {'trait_A': 'abc', 'hostname': 'localhost', 'num_cpus': '5'}, }, ] ), }, '/v2/events/2': { 'GET': ( {}, { 'message_id': '2', 'event_type': 'Foo', 'generated': '1970-01-01T00:00:00', 'traits': {'trait_A': 'def', 'intTrait': '42'}, } ), }, } class EventManagerTest(utils.BaseTestCase): def setUp(self): super(EventManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = ceilometerclient.v2.events.EventManager(self.api) def test_list_all(self): events = list(self.mgr.list()) expect = [ 'GET', '/v2/events' ] self.http_client.assert_called(*expect) self.assertEqual(3, len(events)) self.assertEqual('Foo', events[0].event_type) self.assertEqual('Foo', events[1].event_type) self.assertEqual('Bar', events[2].event_type) def test_list_one(self): event = self.mgr.get(2) expect = [ 'GET', '/v2/events/2' ] self.http_client.assert_called(*expect) self.assertIsNotNone(event) self.assertEqual('Foo', event.event_type) def test_list_with_query(self): events = list(self.mgr.list(q=[{"field": "hostname", "value": "localhost", "type": "string"}])) expect = [ 'GET', '/v2/events?q.field=hostname&q.op=&q.type=string' '&q.value=localhost' ] self.http_client.assert_called(*expect) self.assertEqual(2, len(events)) self.assertEqual('Foo', events[0].event_type) def test_list_with_query_no_type(self): events = list(self.mgr.list(q=[{"field": "hostname", "value": "foreignhost"}])) expect = [ 'GET', '/v2/events?q.field=hostname&q.op=' '&q.type=&q.value=foreignhost' ] self.http_client.assert_called(*expect) self.assertEqual(2, len(events)) self.assertEqual('Foo', events[0].event_type) def test_list_with_multiple_filters(self): events = list(self.mgr.list(q=[{"field": "hostname", "value": "localhost"}, {"field": "num_cpus", "value": "5", "type": "integer"}])) expect = [ 'GET', '/v2/events?q.field=hostname&q.field=num_cpus&q.op=&q.op=' '&q.type=&q.type=integer&q.value=localhost&q.value=5' ] self.http_client.assert_called(*expect) self.assertEqual(1, len(events)) def test_get_from_event_class(self): event = self.mgr.get(2) self.assertIsNotNone(event) event.get() expect = [ 'GET', '/v2/events/2' ] self.http_client.assert_called(*expect, pos=0) self.http_client.assert_called(*expect, pos=1) self.assertEqual('Foo', event.event_type) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_event_types.py0000664000175000017500000000332213117513017031472 0ustar jenkinsjenkins00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils import ceilometerclient.v2.event_types fixtures = { '/v2/event_types/': { 'GET': ( {}, ['Foo', 'Bar', 'Sna', 'Fu'] ), } } class EventTypesManagerTest(utils.BaseTestCase): def setUp(self): super(EventTypesManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = ceilometerclient.v2.event_types.EventTypeManager(self.api) def test_list(self): event_types = list(self.mgr.list()) expect = [ 'GET', '/v2/event_types/' ] self.http_client.assert_called(*expect) self.assertEqual(4, len(event_types)) self.assertEqual("Foo", event_types[0].event_type) self.assertEqual("Bar", event_types[1].event_type) self.assertEqual("Sna", event_types[2].event_type) self.assertEqual("Fu", event_types[3].event_type) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_query_alarm_history.py0000664000175000017500000000431013117513017033225 0ustar jenkinsjenkins00000000000000# Copyright Ericsson AB 2014. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils from ceilometerclient.v2 import query ALARMCHANGE = {"alarm_id": "e8ff32f772a44a478182c3fe1f7cad6a", "event_id": "c74a8611-6553-4764-a860-c15a6aabb5d0", "detail": "{\"threshold\": 42.0, \"evaluation_periods\": 4}", "on_behalf_of": "92159030020611e3b26dde429e99ee8c", "project_id": "b6f16144010811e387e4de429e99ee8c", "timestamp": "2014-03-11T16:02:58.376261", "type": "rule change", "user_id": "3e5d11fda79448ac99ccefb20be187ca" } QUERY = {"filter": {"and": [{">": {"timestamp": "2014-03-11T16:02:58"}}, {"=": {"type": "rule change"}}]}, "orderby": [{"timestamp": "desc"}], "limit": 10} base_url = '/v2/query/alarms/history' fixtures = { base_url: { 'POST': ( {}, [ALARMCHANGE], ), }, } class QueryAlarmsManagerTest(utils.BaseTestCase): def setUp(self): super(QueryAlarmsManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = query.QueryAlarmHistoryManager(self.api) def test_query(self): alarm_history = self.mgr.query(**QUERY) expect = [ 'POST', '/v2/query/alarms/history', QUERY, ] self.http_client.assert_called(*expect) self.assertEqual(1, len(alarm_history)) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_query_samples.py0000664000175000017500000000434313117513017032022 0ustar jenkinsjenkins00000000000000# Copyright Ericsson AB 2014. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils from ceilometerclient.v2 import query SAMPLE = {u'id': u'b55d1526-9929-11e3-a3f6-02163e5df1e6', u'metadata': { u'name1': u'value1', u'name2': u'value2'}, u'meter': 'instance', u'project_id': u'35b17138-b364-4e6a-a131-8f3099c5be68', u'resource_id': u'bd9431c1-8d69-4ad3-803a-8d4a6b89fd36', u'source': u'openstack', u'timestamp': u'2014-02-19T05:50:16.673604', u'type': u'gauge', u'unit': u'instance', u'volume': 1, u'user_id': 'efd87807-12d2-4b38-9c70-5f5c2ac427ff'} QUERY = {"filter": {"and": [{"=": {"source": "openstack"}}, {">": {"timestamp": "2014-02-19T05:50:16"}}]}, "orderby": [{"timestamp": "desc"}, {"volume": "asc"}], "limit": 10} base_url = '/v2/query/samples' fixtures = { base_url: { 'POST': ( {}, [SAMPLE], ), }, } class QuerySamplesManagerTest(utils.BaseTestCase): def setUp(self): super(QuerySamplesManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = query.QuerySamplesManager(self.api) def test_query(self): samples = self.mgr.query(**QUERY) expect = [ 'POST', '/v2/query/samples', QUERY, ] self.http_client.assert_called(*expect) self.assertEqual(1, len(samples)) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_traits.py0000664000175000017500000000435713117513017030444 0ustar jenkinsjenkins00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils import ceilometerclient.v2.traits fixtures = { '/v2/event_types/Foo/traits/trait_1': { 'GET': ( {}, [ {'name': 'trait_1', 'type': 'datetime', 'value': '2014-01-07T17:22:10.925553'}, {'name': 'trait_1', 'type': 'datetime', 'value': '2014-01-07T17:23:10.925553'} ] ), } } class TraitManagerTest(utils.BaseTestCase): def setUp(self): super(TraitManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = ceilometerclient.v2.traits.TraitManager(self.api) def test_list(self): traits = list(self.mgr.list('Foo', 'trait_1')) expect = [ 'GET', '/v2/event_types/Foo/traits/trait_1' ] self.http_client.assert_called(*expect) self.assertEqual(2, len(traits)) for i, vals in enumerate([('trait_1', 'datetime', '2014-01-07T17:22:10.925553'), ('trait_1', 'datetime', '2014-01-07T17:23:10.925553')]): name, type, value = vals self.assertEqual(traits[i].name, name) self.assertEqual(traits[i].type, type) self.assertEqual(traits[i].value, value) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_options.py0000664000175000017500000002377113117513017030632 0ustar jenkinsjenkins00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.tests.unit import utils from ceilometerclient.v2 import options class BuildUrlTest(utils.BaseTestCase): def test_one(self): url = options.build_url('/', [{'field': 'this', 'op': 'gt', 'value': 43}]) self.assertEqual(url, '/?q.field=this&q.op=gt&q.type=&q.value=43') def test_two(self): url = options.build_url('/', [{'field': 'this', 'op': 'gt', 'value': 43}, {'field': 'that', 'op': 'lt', 'value': 88}]) ops = 'q.op=gt&q.op=lt' vals = 'q.value=43&q.value=88' types = 'q.type=&q.type=' fields = 'q.field=this&q.field=that' self.assertEqual(url, '/?%s&%s&%s&%s' % (fields, ops, types, vals)) def test_default_op(self): url = options.build_url('/', [{'field': 'this', 'value': 43}]) self.assertEqual(url, '/?q.field=this&q.op=&q.type=&q.value=43') def test_one_param(self): url = options.build_url('/', None, ['period=60']) self.assertEqual(url, '/?period=60') def test_two_params(self): url = options.build_url('/', None, ['period=60', 'others=value']) self.assertEqual(url, '/?period=60&others=value') def test_with_data_type(self): url = options.build_url('/', [{'field': 'f1', 'value': '10', 'type': 'integer'}]) self.assertEqual('/?q.field=f1&q.op=&q.type=integer&q.value=10', url) class CliTest(utils.BaseTestCase): def test_one(self): ar = options.cli_to_array('this<=34') self.assertEqual(ar, [{'field': 'this', 'op': 'le', 'value': '34', 'type': ''}]) def test_two(self): ar = options.cli_to_array('this<=34;that!=foo') self.assertEqual(ar, [{'field': 'this', 'op': 'le', 'value': '34', 'type': ''}, {'field': 'that', 'op': 'ne', 'value': 'foo', 'type': ''}]) def test_negative(self): ar = options.cli_to_array('this>=-783') self.assertEqual(ar, [{'field': 'this', 'op': 'ge', 'value': '-783', 'type': ''}]) def test_float(self): ar = options.cli_to_array('this<=283.347') self.assertEqual(ar, [{'field': 'this', 'op': 'le', 'value': '283.347', 'type': ''}]) def test_comma(self): ar = options.cli_to_array('this=2.4,fooo=doof') self.assertEqual([{'field': 'this', 'op': 'eq', 'value': '2.4,fooo=doof', 'type': ''}], ar) def test_special_character(self): ar = options.cli_to_array('key~123=value!123') self.assertEqual([{'field': 'key~123', 'op': 'eq', 'value': 'value!123', 'type': ''}], ar) def _do_test_typed_float_op(self, op, op_str): ar = options.cli_to_array('that%sfloat::283.347' % op) self.assertEqual([{'field': 'that', 'type': 'float', 'value': '283.347', 'op': op_str}], ar) def test_typed_float_eq(self): self._do_test_typed_float_op('<', 'lt') def test_typed_float_le(self): self._do_test_typed_float_op('<=', 'le') def test_typed_string_whitespace(self): ar = options.cli_to_array('state=string::insufficient data') self.assertEqual([{'field': 'state', 'op': 'eq', 'type': 'string', 'value': 'insufficient data'}], ar) def test_typed_string_whitespace_complex(self): ar = options.cli_to_array( 'that>=float::99.9999;state=string::insufficient data' ) self.assertEqual([{'field': 'that', 'op': 'ge', 'type': 'float', 'value': '99.9999'}, {'field': 'state', 'op': 'eq', 'type': 'string', 'value': 'insufficient data'}], ar) def test_invalid_operator(self): self.assertRaises(ValueError, options.cli_to_array, 'this=2.4;fooo-doof') def test_with_dot(self): ar = options.cli_to_array('metadata.this<=34') self.assertEqual(ar, [{'field': 'metadata.this', 'op': 'le', 'value': '34', 'type': ''}]) def test_single_char_field_or_value(self): ar = options.cli_to_array('m<=34;large.thing>s;x!=y') self.assertEqual([{'field': 'm', 'op': 'le', 'value': '34', 'type': ''}, {'field': 'large.thing', 'op': 'gt', 'value': 's', 'type': ''}, {'field': 'x', 'op': 'ne', 'value': 'y', 'type': ''}], ar) def test_without_data_type(self): ar = options.cli_to_array('hostname=localhost') self.assertEqual(ar, [{'field': 'hostname', 'op': 'eq', 'value': 'localhost', 'type': ''}]) def test_with_string_data_type(self): ar = options.cli_to_array('hostname=string::localhost') self.assertEqual(ar, [{'field': 'hostname', 'op': 'eq', 'type': 'string', 'value': 'localhost'}]) def test_with_int_data_type(self): ar = options.cli_to_array('port=integer::1234') self.assertEqual(ar, [{'field': 'port', 'op': 'eq', 'type': 'integer', 'value': '1234'}]) def test_with_bool_data_type(self): ar = options.cli_to_array('port=boolean::true') self.assertEqual(ar, [{'field': 'port', 'op': 'eq', 'type': 'boolean', 'value': 'true'}]) def test_with_float_data_type(self): ar = options.cli_to_array('average=float::1234.5678') self.assertEqual(ar, [{'field': 'average', 'op': 'eq', 'type': 'float', 'value': '1234.5678'}]) def test_with_datetime_data_type(self): ar = options.cli_to_array('timestamp=datetime::sometimestamp') self.assertEqual(ar, [{'field': 'timestamp', 'op': 'eq', 'type': 'datetime', 'value': 'sometimestamp'}]) def test_with_incorrect_type(self): ar = options.cli_to_array('timestamp=invalid::sometimestamp') self.assertEqual(ar, [{'field': 'timestamp', 'op': 'eq', 'type': '', 'value': 'invalid::sometimestamp'}]) def test_with_single_colon(self): ar = options.cli_to_array('timestamp=datetime:sometimestamp') self.assertEqual(ar, [{'field': 'timestamp', 'op': 'eq', 'type': '', 'value': 'datetime:sometimestamp'}]) def test_missing_key(self): self.assertRaises(ValueError, options.cli_to_array, 'average=float::1234.0;>=string::hello') def test_missing_value(self): self.assertRaises(ValueError, options.cli_to_array, 'average=float::1234.0;house>=') def test_timestamp_value(self): ar = options.cli_to_array( 'project=cow;timestamp>=datetime::2014-03-11T16:02:58' ) self.assertEqual([{'field': 'project', 'op': 'eq', 'type': '', 'value': 'cow'}, {'field': 'timestamp', 'op': 'ge', 'type': 'datetime', 'value': '2014-03-11T16:02:58'}], ar) def test_with_whitespace(self): ar = options.cli_to_array('start_timestamp= 2015-01-01T00:00:00;' ' end_timestamp =2015-06-20T14:01:59 ') self.assertEqual([{'field': 'start_timestamp', 'op': 'eq', 'type': '', 'value': '2015-01-01T00:00:00'}, {'field': 'end_timestamp', 'op': 'eq', 'type': '', 'value': '2015-06-20T14:01:59'}], ar) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_trait_descriptions.py0000664000175000017500000000405413117513017033041 0ustar jenkinsjenkins00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils import ceilometerclient.v2.trait_descriptions fixtures = { '/v2/event_types/Foo/traits': { 'GET': ( {}, [ {'name': 'trait_1', 'type': 'string'}, {'name': 'trait_2', 'type': 'integer'}, {'name': 'trait_3', 'type': 'datetime'} ] ), } } class TraitDescriptionManagerTest(utils.BaseTestCase): def setUp(self): super(TraitDescriptionManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = (ceilometerclient.v2.trait_descriptions. TraitDescriptionManager(self.api)) def test_list(self): trait_descriptions = list(self.mgr.list('Foo')) expect = [ 'GET', '/v2/event_types/Foo/traits' ] self.http_client.assert_called(*expect) self.assertEqual(3, len(trait_descriptions)) for i, vals in enumerate([('trait_1', 'string'), ('trait_2', 'integer'), ('trait_3', 'datetime')]): name, type = vals self.assertEqual(trait_descriptions[i].name, name) self.assertEqual(trait_descriptions[i].type, type) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/__init__.py0000664000175000017500000000000013117513017027613 0ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_capabilities.py0000664000175000017500000000327013117513017031560 0ustar jenkinsjenkins00000000000000# Copyright 2014 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.v2 import capabilities CAPABILITIES = { "alarm_storage": { "storage:production_ready": True }, "api": { "alarms:query:complex": True, "alarms:query:simple": True }, "event_storage": { "storage:production_ready": True }, "storage": { "storage:production_ready": True }, } FIXTURES = { '/v2/capabilities': { 'GET': ( {}, CAPABILITIES ), }, } class CapabilitiesManagerTest(testtools.TestCase): def setUp(self): super(CapabilitiesManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=FIXTURES) self.api = client.BaseClient(self.http_client) self.mgr = capabilities.CapabilitiesManager(self.api) def test_capabilities_get(self): capabilities = self.mgr.get() self.http_client.assert_called('GET', '/v2/capabilities') self.assertTrue(capabilities.api['alarms:query:complex']) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_alarms.py0000664000175000017500000004773113117513017030420 0ustar jenkinsjenkins00000000000000# # Copyright 2013 Red Hat, Inc # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import six from six.moves import xrange # noqa import testtools from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient import exc from ceilometerclient.v2 import alarms AN_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], u'ok_actions': [u'http://site:8000/ok'], u'description': u'An alarm', u'type': u'threshold', u'severity': 'low', u'threshold_rule': { u'meter_name': u'storage.objects', u'query': [{u'field': u'key_name', u'op': u'eq', u'value': u'key_value'}], u'evaluation_periods': 2, u'period': 240.0, u'statistic': u'avg', u'threshold': 200.0, u'comparison_operator': 'gt'}, u'time_constraints': [ { u'name': u'cons1', u'description': u'desc1', u'start': u'0 11 * * *', u'duration': 300, u'timezone': u''}, { u'name': u'cons2', u'description': u'desc2', u'start': u'0 23 * * *', u'duration': 600, u'timezone': ''}], u'timestamp': u'2013-05-09T13:41:23.085000', u'enabled': True, u'alarm_id': u'alarm-id', u'state': u'ok', u'insufficient_data_actions': [u'http://site:8000/nodata'], u'user_id': u'user-id', u'project_id': u'project-id', u'state_timestamp': u'2013-05-09T13:41:23.085000', u'repeat_actions': False, u'name': 'SwiftObjectAlarm'} CREATE_ALARM = copy.deepcopy(AN_ALARM) del CREATE_ALARM['timestamp'] del CREATE_ALARM['state_timestamp'] del CREATE_ALARM['alarm_id'] CREATE_ALARM_WITHOUT_TC = copy.deepcopy(CREATE_ALARM) del CREATE_ALARM_WITHOUT_TC['time_constraints'] DELTA_ALARM = {u'alarm_actions': ['url1', 'url2']} DELTA_ALARM_RULE = {u'comparison_operator': u'lt', u'threshold': 42.1, u'meter_name': u'foobar', u'query': [{u'field': u'key_name', u'op': u'eq', u'value': u'key_value'}]} DELTA_ALARM_TC = [{u'name': u'cons1', u'duration': 500}] DELTA_ALARM['time_constraints'] = DELTA_ALARM_TC DELTA_ALARM['user_id'] = u'new-user-id' UPDATED_ALARM = copy.deepcopy(AN_ALARM) UPDATED_ALARM.update(DELTA_ALARM) UPDATED_ALARM['threshold_rule'].update(DELTA_ALARM_RULE) DELTA_ALARM['remove_time_constraints'] = 'cons2' UPDATED_ALARM['time_constraints'] = [{u'name': u'cons1', u'description': u'desc1', u'start': u'0 11 * * *', u'duration': 500, u'timezone': u''}] DELTA_ALARM['threshold_rule'] = DELTA_ALARM_RULE UPDATE_ALARM = copy.deepcopy(UPDATED_ALARM) UPDATE_ALARM['remove_time_constraints'] = 'cons2' UPDATE_ALARM['user_id'] = u'new-user-id' del UPDATE_ALARM['project_id'] del UPDATE_ALARM['name'] del UPDATE_ALARM['alarm_id'] del UPDATE_ALARM['timestamp'] del UPDATE_ALARM['state_timestamp'] AN_LEGACY_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], u'ok_actions': [u'http://site:8000/ok'], u'description': u'An alarm', u'matching_metadata': {u'key_name': u'key_value'}, u'evaluation_periods': 2, u'timestamp': u'2013-05-09T13:41:23.085000', u'enabled': True, u'meter_name': u'storage.objects', u'period': 240.0, u'alarm_id': u'alarm-id', u'state': u'ok', u'severity': u'low', u'insufficient_data_actions': [u'http://site:8000/nodata'], u'statistic': u'avg', u'threshold': 200.0, u'user_id': u'user-id', u'project_id': u'project-id', u'state_timestamp': u'2013-05-09T13:41:23.085000', u'comparison_operator': 'gt', u'repeat_actions': False, u'name': 'SwiftObjectAlarm'} CREATE_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM) del CREATE_LEGACY_ALARM['timestamp'] del CREATE_LEGACY_ALARM['state_timestamp'] del CREATE_LEGACY_ALARM['alarm_id'] DELTA_LEGACY_ALARM = {u'alarm_actions': ['url1', 'url2'], u'comparison_operator': u'lt', u'meter_name': u'foobar', u'threshold': 42.1} DELTA_LEGACY_ALARM['time_constraints'] = [{u'name': u'cons1', u'duration': 500}] DELTA_LEGACY_ALARM['user_id'] = u'new-user-id' DELTA_LEGACY_ALARM['remove_time_constraints'] = 'cons2' UPDATED_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM) UPDATED_LEGACY_ALARM.update(DELTA_LEGACY_ALARM) UPDATE_LEGACY_ALARM = copy.deepcopy(UPDATED_LEGACY_ALARM) UPDATE_LEGACY_ALARM['user_id'] = u'new-user-id' del UPDATE_LEGACY_ALARM['project_id'] del UPDATE_LEGACY_ALARM['name'] del UPDATE_LEGACY_ALARM['alarm_id'] del UPDATE_LEGACY_ALARM['timestamp'] del UPDATE_LEGACY_ALARM['state_timestamp'] FULL_DETAIL = ('{"alarm_actions": [], ' '"user_id": "8185aa72421a4fd396d4122cba50e1b5", ' '"name": "scombo", ' '"timestamp": "2013-10-03T08:58:33.647912", ' '"enabled": true, ' '"state_timestamp": "2013-10-03T08:58:33.647912", ' '"rule": {"operator": "or", "alarm_ids": ' '["062cc907-3a9f-4867-ab3b-fa83212b39f7"]}, ' '"alarm_id": "alarm-id, ' '"state": "insufficient data", ' '"insufficient_data_actions": [], ' '"repeat_actions": false, ' '"ok_actions": [], ' '"project_id": "57d04f24d0824b78b1ea9bcecedbda8f", ' '"type": "combination", ' '"description": "Combined state of alarms ' '062cc907-3a9f-4867-ab3b-fa83212b39f7"}') ALARM_HISTORY = [{'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', 'user_id': '8185aa72421a4fd396d4122cba50e1b5', 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', 'timestamp': '2013-10-03T08:59:28.326000', 'detail': '{"state": "alarm"}', 'alarm_id': 'alarm-id', 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', 'type': 'state transition'}, {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', 'user_id': '8185aa72421a4fd396d4122cba50e1b5', 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', 'timestamp': '2013-10-03T08:59:28.326000', 'detail': '{"description": "combination of one"}', 'alarm_id': 'alarm-id', 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', 'type': 'rule change'}, {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', 'user_id': '8185aa72421a4fd396d4122cba50e1b5', 'event_id': '4fd7df9e-190d-4471-8884-dc5a33d5d4bb', 'timestamp': '2013-10-03T08:58:33.647000', 'detail': FULL_DETAIL, 'alarm_id': 'alarm-id', 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', 'type': 'creation'}] fixtures = { '/v2/alarms': { 'GET': ( {}, [AN_ALARM], ), 'POST': ( {}, CREATE_ALARM, ), }, '/v2/alarms/alarm-id': { 'GET': ( {}, AN_ALARM, ), 'PUT': ( {}, UPDATED_ALARM, ), 'DELETE': ( {}, None, ), }, '/v2/alarms/unk-alarm-id': { 'GET': ( {}, None, ), 'PUT': ( {}, None, ), }, '/v2/alarms/alarm-id/state': { 'PUT': ( {}, {'alarm': 'alarm'} ), 'GET': ( {}, {'alarm': 'alarm'} ), }, '/v2/alarms?q.field=project_id&q.field=name&q.op=&q.op=' '&q.type=&q.type=&q.value=project-id&q.value=SwiftObjectAlarm': { 'GET': ( {}, [AN_ALARM], ), }, '/v2/alarms/victim-id': { 'DELETE': ( {}, None, ), }, '/v2/alarms/alarm-id/history': { 'GET': ( {}, ALARM_HISTORY, ), }, '/v2/alarms/alarm-id/history?q.field=timestamp&q.op=&q.type=&q.value=NOW': { 'GET': ( {}, ALARM_HISTORY, ), }, } class AlarmManagerTest(testtools.TestCase): def setUp(self): super(AlarmManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = alarms.AlarmManager(self.api) def test_list_all(self): alarms = list(self.mgr.list()) expect = [ 'GET', '/v2/alarms' ] self.http_client.assert_called(*expect) self.assertEqual(1, len(alarms)) self.assertEqual('alarm-id', alarms[0].alarm_id) def test_list_with_query(self): alarms = list(self.mgr.list(q=[{"field": "project_id", "value": "project-id"}, {"field": "name", "value": "SwiftObjectAlarm"}])) expect = [ 'GET', '/v2/alarms?q.field=project_id&q.field=name&q.op=&q.op=' '&q.type=&q.type=&q.value=project-id&q.value=SwiftObjectAlarm', ] self.http_client.assert_called(*expect) self.assertEqual(1, len(alarms)) self.assertEqual('alarm-id', alarms[0].alarm_id) def test_get(self): alarm = self.mgr.get(alarm_id='alarm-id') expect = [ 'GET', '/v2/alarms/alarm-id' ] self.http_client.assert_called(*expect) self.assertIsNotNone(alarm) self.assertEqual('alarm-id', alarm.alarm_id) self.assertEqual(alarm.rule, alarm.threshold_rule) def test_create(self): alarm = self.mgr.create(**CREATE_ALARM) expect = [ 'POST', '/v2/alarms' ] self.http_client.assert_called(*expect, body=CREATE_ALARM) self.assertIsNotNone(alarm) def test_update(self): alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_ALARM) expect_get = [ 'GET', '/v2/alarms/alarm-id' ] expect_put = [ 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM ] self.http_client.assert_called(*expect_get, pos=0) self.http_client.assert_called(*expect_put, pos=1) self.assertIsNotNone(alarm) self.assertEqual('alarm-id', alarm.alarm_id) for (key, value) in six.iteritems(UPDATED_ALARM): self.assertEqual(getattr(alarm, key), value) def test_update_delta(self): alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_ALARM) expect_get = [ 'GET', '/v2/alarms/alarm-id' ] expect_put = [ 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM ] self.http_client.assert_called(*expect_get, pos=0) self.http_client.assert_called(*expect_put, pos=1) self.assertIsNotNone(alarm) self.assertEqual('alarm-id', alarm.alarm_id) for (key, value) in six.iteritems(UPDATED_ALARM): self.assertEqual(getattr(alarm, key), value) def test_set_state(self): state = self.mgr.set_state(alarm_id='alarm-id', state='alarm') expect = [ 'PUT', '/v2/alarms/alarm-id/state' ] self.http_client.assert_called(*expect, body='alarm') self.assertEqual({'alarm': 'alarm'}, state) def test_get_state(self): state = self.mgr.get_state(alarm_id='alarm-id') expect = [ 'GET', '/v2/alarms/alarm-id/state' ] self.http_client.assert_called(*expect) self.assertEqual({'alarm': 'alarm'}, state) def test_delete(self): deleted = self.mgr.delete(alarm_id='victim-id') expect = [ 'DELETE', '/v2/alarms/victim-id' ] self.http_client.assert_called(*expect) self.assertIsNone(deleted) def test_get_from_alarm_class(self): alarm = self.mgr.get(alarm_id='alarm-id') self.assertIsNotNone(alarm) alarm.get() expect = [ 'GET', '/v2/alarms/alarm-id' ] self.http_client.assert_called(*expect, pos=0) self.http_client.assert_called(*expect, pos=1) self.assertEqual('alarm-id', alarm.alarm_id) self.assertEqual(alarm.threshold_rule, alarm.rule) def test_get_state_from_alarm_class(self): alarm = self.mgr.get(alarm_id='alarm-id') self.assertIsNotNone(alarm) state = alarm.get_state() expect_get_1 = [ 'GET', '/v2/alarms/alarm-id' ] expect_get_2 = [ 'GET', '/v2/alarms/alarm-id/state' ] self.http_client.assert_called(*expect_get_1, pos=0) self.http_client.assert_called(*expect_get_2, pos=1) self.assertEqual('alarm', state) def test_update_missing(self): alarm = None try: alarm = self.mgr.update(alarm_id='unk-alarm-id', **UPDATE_ALARM) except exc.CommandError: pass self.assertIsNone(alarm) def test_delete_from_alarm_class(self): alarm = self.mgr.get(alarm_id='alarm-id') self.assertIsNotNone(alarm) deleted = alarm.delete() expect_get = [ 'GET', '/v2/alarms/alarm-id' ] expect_delete = [ 'DELETE', '/v2/alarms/alarm-id' ] self.http_client.assert_called(*expect_get, pos=0) self.http_client.assert_called(*expect_delete, pos=1) self.assertIsNone(deleted) def _do_test_get_history(self, q, url): history = self.mgr.get_history(q=q, alarm_id='alarm-id') expect = ['GET', url] self.http_client.assert_called(*expect) for i in xrange(len(history)): change = history[i] self.assertIsInstance(change, alarms.AlarmChange) for k, v in six.iteritems(ALARM_HISTORY[i]): self.assertEqual(getattr(change, k), v) def test_get_all_history(self): url = '/v2/alarms/alarm-id/history' self._do_test_get_history(None, url) def test_get_constrained_history(self): q = [dict(field='timestamp', value='NOW')] url = ('/v2/alarms/alarm-id/history?q.field=timestamp' '&q.op=&q.type=&q.value=NOW') self._do_test_get_history(q, url) class AlarmLegacyManagerTest(testtools.TestCase): def setUp(self): super(AlarmLegacyManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = alarms.AlarmManager(self.api) def test_create(self): alarm = self.mgr.create(**CREATE_LEGACY_ALARM) expect = [ 'POST', '/v2/alarms', CREATE_ALARM_WITHOUT_TC, ] self.http_client.assert_called(*expect) self.assertIsNotNone(alarm) def test_create_counter_name(self): create = {} create.update(CREATE_LEGACY_ALARM) create['counter_name'] = CREATE_LEGACY_ALARM['meter_name'] del create['meter_name'] alarm = self.mgr.create(**create) expect = [ 'POST', '/v2/alarms', CREATE_ALARM_WITHOUT_TC, ] self.http_client.assert_called(*expect) self.assertIsNotNone(alarm) def test_update(self): alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_LEGACY_ALARM) expect_put = [ 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM ] self.http_client.assert_called(*expect_put) self.assertIsNotNone(alarm) self.assertEqual('alarm-id', alarm.alarm_id) for (key, value) in six.iteritems(UPDATED_ALARM): self.assertEqual(getattr(alarm, key), value) def test_update_counter_name(self): updated = {} updated.update(UPDATE_LEGACY_ALARM) updated['counter_name'] = UPDATED_LEGACY_ALARM['meter_name'] del updated['meter_name'] alarm = self.mgr.update(alarm_id='alarm-id', **updated) expect_put = [ 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM ] self.http_client.assert_called(*expect_put) self.assertIsNotNone(alarm) self.assertEqual('alarm-id', alarm.alarm_id) for (key, value) in six.iteritems(UPDATED_ALARM): self.assertEqual(getattr(alarm, key), value) class AlarmTimeConstraintTest(testtools.TestCase): def setUp(self): super(AlarmTimeConstraintTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = alarms.AlarmManager(self.api) def test_add_new(self): new_constraint = dict(name='cons3', start='0 0 * * *', duration=500) kwargs = dict(time_constraints=[new_constraint]) self.mgr.update(alarm_id='alarm-id', **kwargs) body = copy.deepcopy(AN_ALARM) body[u'time_constraints'] = \ AN_ALARM[u'time_constraints'] + [new_constraint] expect = [ 'PUT', '/v2/alarms/alarm-id', body ] self.http_client.assert_called(*expect) def test_update_existing(self): updated_constraint = dict(name='cons2', duration=500) kwargs = dict(time_constraints=[updated_constraint]) self.mgr.update(alarm_id='alarm-id', **kwargs) body = copy.deepcopy(AN_ALARM) body[u'time_constraints'][1] = dict(name='cons2', description='desc2', start='0 23 * * *', duration=500, timezone='') expect = [ 'PUT', '/v2/alarms/alarm-id', body ] self.http_client.assert_called(*expect) def test_update_time_constraint_no_name(self): updated_constraint = { 'start': '0 23 * * *', 'duration': 500 } kwargs = dict(time_constraints=[updated_constraint]) self.mgr.update(alarm_id='alarm-id', **kwargs) body = copy.deepcopy(AN_ALARM) body[u'time_constraints'].append({ 'start': '0 23 * * *', 'duration': 500, }) expect = [ 'PUT', '/v2/alarms/alarm-id', body ] self.http_client.assert_called(*expect) def test_remove(self): kwargs = dict(remove_time_constraints=['cons2']) self.mgr.update(alarm_id='alarm-id', **kwargs) body = copy.deepcopy(AN_ALARM) body[u'time_constraints'] = AN_ALARM[u'time_constraints'][:1] expect = [ 'PUT', '/v2/alarms/alarm-id', body ] self.http_client.assert_called(*expect) python-ceilometerclient-2.9.0/ceilometerclient/tests/unit/v2/test_statistics.py0000664000175000017500000001650413117513017031325 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.apiclient import client from ceilometerclient.apiclient import fake_client from ceilometerclient.tests.unit import utils import ceilometerclient.v2.statistics base_url = '/v2/meters/instance/statistics' qry = ('q.field=resource_id&q.field=source&q.op=&q.op=' '&q.type=&q.type=&q.value=foo&q.value=bar') period = '&period=60' groupby = '&groupby=resource_id' aggregate_query = ("aggregate.func=cardinality&aggregate.param=resource_id" "&aggregate.func=count") samples = [ {u'count': 135, u'duration_start': u'2013-02-04T10:51:42', u'min': 1.0, u'max': 1.0, u'duration_end': u'2013-02-05T15:46:09', u'duration': 1734.0, u'avg': 1.0, u'sum': 135.0}, ] groupby_samples = [ {u'count': 135, u'duration_start': u'2013-02-04T10:51:42', u'min': 1.0, u'max': 1.0, u'duration_end': u'2013-02-05T15:46:09', u'duration': 1734.0, u'avg': 1.0, u'sum': 135.0, u'groupby': {u'resource_id': u'foo'} }, {u'count': 12, u'duration_start': u'2013-02-04T10:51:42', u'min': 1.0, u'max': 1.0, u'duration_end': u'2013-02-05T15:46:09', u'duration': 1734.0, u'avg': 1.0, u'sum': 12.0, u'groupby': {u'resource_id': u'bar'} }, ] aggregate_samples = [ {u'aggregate': {u'cardinality/resource_id': 4.0, u'count': 2.0}, u'count': 2, u'duration': 0.442451, u'duration_end': u'2014-03-12T14:00:21.774154', u'duration_start': u'2014-03-12T14:00:21.331703', u'groupby': None, u'period': 0, u'period_end': u'2014-03-12T14:00:21.774154', u'period_start': u'2014-03-12T14:00:21.331703', u'unit': u'instance', }, ] fixtures = { base_url: { 'GET': ( {}, samples ), }, '%s?%s' % (base_url, qry): { 'GET': ( {}, samples ), }, '%s?%s%s' % (base_url, qry, period): { 'GET': ( {}, samples ), }, '%s?%s%s' % (base_url, qry, groupby): { 'GET': ( {}, groupby_samples ), }, '%s?%s' % (base_url, aggregate_query): { 'GET': ( {}, aggregate_samples ), } } class StatisticsManagerTest(utils.BaseTestCase): def setUp(self): super(StatisticsManagerTest, self).setUp() self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) self.api = client.BaseClient(self.http_client) self.mgr = ceilometerclient.v2.statistics.StatisticsManager(self.api) def test_list_by_meter_name(self): stats = list(self.mgr.list(meter_name='instance')) expect = [ 'GET', '/v2/meters/instance/statistics' ] self.http_client.assert_called(*expect) self.assertEqual(1, len(stats)) self.assertEqual(135, stats[0].count) def test_list_by_meter_name_extended(self): stats = list(self.mgr.list(meter_name='instance', q=[ {"field": "resource_id", "value": "foo"}, {"field": "source", "value": "bar"}, ])) expect = [ 'GET', '%s?%s' % (base_url, qry) ] self.http_client.assert_called(*expect) self.assertEqual(1, len(stats)) self.assertEqual(135, stats[0].count) def test_list_by_meter_name_with_period(self): stats = list(self.mgr.list(meter_name='instance', q=[ {"field": "resource_id", "value": "foo"}, {"field": "source", "value": "bar"}, ], period=60)) expect = [ 'GET', '%s?%s%s' % (base_url, qry, period) ] self.http_client.assert_called(*expect) self.assertEqual(1, len(stats)) self.assertEqual(135, stats[0].count) def test_list_by_meter_name_with_groupby(self): stats = list(self.mgr.list(meter_name='instance', q=[ {"field": "resource_id", "value": "foo"}, {"field": "source", "value": "bar"}, ], groupby=['resource_id'])) expect = [ 'GET', '%s?%s%s' % (base_url, qry, groupby) ] self.http_client.assert_called(*expect) self.assertEqual(2, len(stats)) self.assertEqual(135, stats[0].count) self.assertEqual(12, stats[1].count) self.assertEqual('foo', stats[0].groupby.get('resource_id')) self.assertEqual('bar', stats[1].groupby.get('resource_id')) def test_list_by_meter_name_with_groupby_as_str(self): stats = list(self.mgr.list(meter_name='instance', q=[ {"field": "resource_id", "value": "foo"}, {"field": "source", "value": "bar"}, ], groupby='resource_id')) expect = [ 'GET', '%s?%s%s' % (base_url, qry, groupby) ] self.http_client.assert_called(*expect) self.assertEqual(2, len(stats)) self.assertEqual(135, stats[0].count) self.assertEqual(12, stats[1].count) self.assertEqual('foo', stats[0].groupby.get('resource_id')) self.assertEqual('bar', stats[1].groupby.get('resource_id')) def test_list_by_meter_name_with_aggregates(self): aggregates = [ { 'func': 'count', }, { 'func': 'cardinality', 'param': 'resource_id', }, ] stats = list(self.mgr.list(meter_name='instance', aggregates=aggregates)) expect = [ 'GET', '%s?%s' % (base_url, aggregate_query) ] self.http_client.assert_called(*expect) self.assertEqual(1, len(stats)) self.assertEqual(2, stats[0].count) self.assertEqual(2.0, stats[0].aggregate.get('count')) self.assertEqual(4.0, stats[0].aggregate.get( 'cardinality/resource_id', )) python-ceilometerclient-2.9.0/ceilometerclient/tests/functional/0000775000175000017500000000000013117513272026353 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/tests/functional/test_readonly_ceilometer.py0000664000175000017500000000531113117513017034006 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.tests.functional import base import re class SimpleReadOnlyCeilometerClientTest(base.ClientTestBase): """Basic, read-only tests for Ceilometer CLI client. Checks return values and output of read-only commands. These tests do not presume any content, nor do they create their own. They only verify the structure of output if present. """ def test_ceilometer_meter_list(self): result = self.ceilometer('meter-list') meters = self.parser.listing(result) self.assertTableStruct(meters, ['Name', 'Type', 'Unit', 'Resource ID', 'Project ID']) def test_ceilometer_resource_list(self): result = self.ceilometer('resource-list') resources = self.parser.listing(result) self.assertTableStruct(resources, ['Resource ID', 'Source', 'User ID', 'Project ID']) def test_ceilometer_alarm_list(self): result = self.ceilometer('alarm-list') alarm = self.parser.listing(result) self.assertTableStruct(alarm, ['Alarm ID', 'Name', 'State', 'Enabled', 'Continuous']) def test_admin_help(self): help_text = self.ceilometer('help') lines = help_text.split('\n') self.assertFirstLineStartsWith(lines, 'usage: ceilometer') commands = [] cmds_start = lines.index('Positional arguments:') cmds_end = lines.index('Optional arguments:') command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) if match: commands.append(match.group(1)) commands = set(commands) wanted_commands = set(('alarm-combination-create', 'alarm-create', 'help', 'alarm-delete', 'event-list')) self.assertFalse(wanted_commands - commands) def test_ceilometer_bash_completion(self): self.ceilometer('bash-completion') # Optional arguments def test_ceilometer_debug_list(self): self.ceilometer('meter-list', flags='--debug') python-ceilometerclient-2.9.0/ceilometerclient/tests/functional/hooks/0000775000175000017500000000000013117513272027476 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/tests/functional/hooks/post_test_hook.sh0000775000175000017500000000337413117513017033105 0ustar jenkinsjenkins00000000000000#!/bin/bash -xe # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This script is executed inside post_test_hook function in devstack gate. function generate_testr_results { if [ -f .testrepository/0 ]; then sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz fi } export CEILOMETERCLIENT_DIR="$BASE/new/python-ceilometerclient" # Get admin credentials cd $BASE/new/devstack source openrc admin admin # Go to the ceilometerclient dir cd $CEILOMETERCLIENT_DIR sudo chown -R jenkins:stack $CEILOMETERCLIENT_DIR # Run tests echo "Running ceilometerclient functional test suite" set +e # Preserve env for OS_ credentials sudo -E -H -u jenkins tox -efunctional EXIT_CODE=$? set -e # Collect and parse result generate_testr_results exit $EXIT_CODE python-ceilometerclient-2.9.0/ceilometerclient/tests/functional/__init__.py0000664000175000017500000000000013117513017030447 0ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/tests/functional/base.py0000664000175000017500000000252113117513017027634 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from tempest.lib.cli import base class ClientTestBase(base.ClientTestBase): """Base class for ceilometerclient tests. Establishes the ceilometer client and retrieves the essential environment information. """ def _get_clients(self): cli_dir = os.environ.get( 'OS_CEILOMETER_CLIENT_EXEC_DIR', os.path.join(os.path.abspath('.'), '.tox/functional/bin')) return base.CLIClient( username=os.environ.get('OS_USERNAME'), password=os.environ.get('OS_PASSWORD'), tenant_name=os.environ.get('OS_TENANT_NAME'), uri=os.environ.get('OS_AUTH_URL'), cli_dir=cli_dir) def ceilometer(self, *args, **kwargs): return self.clients.ceilometer(*args, **kwargs) python-ceilometerclient-2.9.0/ceilometerclient/tests/__init__.py0000664000175000017500000000000013117513017026305 0ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/shell.py0000664000175000017500000002534713117513017024540 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Command-line interface to the OpenStack Telemetry API. """ from __future__ import print_function import argparse import logging import sys import warnings from oslo_utils import encodeutils from oslo_utils import importutils import six import ceilometerclient from ceilometerclient import client as ceiloclient from ceilometerclient.common import utils from ceilometerclient import exc def _positive_non_zero_int(argument_value): if argument_value is None: return None try: value = int(argument_value) except ValueError: msg = "%s must be an integer" % argument_value raise argparse.ArgumentTypeError(msg) if value <= 0: msg = "%s must be greater than 0" % argument_value raise argparse.ArgumentTypeError(msg) return value class CeilometerShell(object): def __init__(self): self.auth_plugin = ceiloclient.AuthPlugin() def get_base_parser(self): parser = argparse.ArgumentParser( prog='ceilometer', description=__doc__.strip(), epilog='See "ceilometer help COMMAND" ' 'for help on a specific command.', add_help=False, formatter_class=HelpFormatter, ) # Global arguments parser.add_argument('-h', '--help', action='store_true', help=argparse.SUPPRESS, ) parser.add_argument('--version', action='version', version=ceilometerclient.__version__) parser.add_argument('-d', '--debug', default=bool(utils.env('CEILOMETERCLIENT_DEBUG') ), action='store_true', help='Defaults to env[CEILOMETERCLIENT_DEBUG].') parser.add_argument('-v', '--verbose', default=False, action="store_true", help="Print more verbose output.") parser.add_argument('--timeout', default=600, type=_positive_non_zero_int, help='Number of seconds to wait for a response.') parser.add_argument('--ceilometer-url', metavar='', dest='os_endpoint', default=utils.env('CEILOMETER_URL'), help=("DEPRECATED, use --os-endpoint instead. " "Defaults to env[CEILOMETER_URL].")) parser.add_argument('--ceilometer_url', dest='os_endpoint', help=argparse.SUPPRESS) parser.add_argument('--ceilometer-api-version', default=utils.env( 'CEILOMETER_API_VERSION', default='2'), help='Defaults to env[CEILOMETER_API_VERSION] ' 'or 2.') parser.add_argument('--ceilometer_api_version', help=argparse.SUPPRESS) self.auth_plugin.add_opts(parser) self.auth_plugin.add_common_opts(parser) return parser def get_subcommand_parser(self, version): parser = self.get_base_parser() self.subcommands = {} subparsers = parser.add_subparsers(metavar='') submodule = importutils.import_versioned_module('ceilometerclient', version, 'shell') self._find_actions(subparsers, submodule) self._find_actions(subparsers, self) return parser def _find_actions(self, subparsers, actions_module): for attr in (a for a in dir(actions_module) if a.startswith('do_')): # I prefer to be hypen-separated instead of underscores. command = attr[3:].replace('_', '-') callback = getattr(actions_module, attr) desc = callback.__doc__ or '' help = desc.strip().split('\n')[0] arguments = getattr(callback, 'arguments', []) subparser = subparsers.add_parser(command, help=help, description=desc, add_help=False, formatter_class=HelpFormatter) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS) self.subcommands[command] = subparser for (args, kwargs) in arguments: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) @staticmethod def _setup_logging(debug): format = '%(levelname)s (%(module)s) %(message)s' if debug: logging.basicConfig(format=format, level=logging.DEBUG) else: logging.basicConfig(format=format, level=logging.WARN) logging.getLogger('iso8601').setLevel(logging.WARNING) logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING) def parse_args(self, argv): # Parse args once to find version parser = self.get_base_parser() (options, args) = parser.parse_known_args(argv) self.auth_plugin.parse_opts(options) self._setup_logging(options.debug) # build available subcommands based on version api_version = options.ceilometer_api_version subcommand_parser = self.get_subcommand_parser(api_version) self.parser = subcommand_parser # Handle top-level --help/-h before attempting to parse # a command off the command line if options.help or not argv: self.do_help(options) return 0 # Return parsed args return api_version, subcommand_parser.parse_args(argv) def main(self, argv): warnings.warn( "ceilometerclient is now deprecated as the Ceilometer API has " "been deprecated. Please use either aodhclient, pankoclient or " "gnocchiclient.") parsed = self.parse_args(argv) if parsed == 0: return 0 api_version, args = parsed # Short-circuit and deal with help command right away. if args.func == self.do_help: self.do_help(args) return 0 elif args.func == self.do_bash_completion: self.do_bash_completion(args) return 0 if not ((self.auth_plugin.opts.get('token') or self.auth_plugin.opts.get('auth_token')) and self.auth_plugin.opts['endpoint']): if not self.auth_plugin.opts['username']: raise exc.CommandError("You must provide a username via " "either --os-username or via " "env[OS_USERNAME]") if not self.auth_plugin.opts['password']: raise exc.CommandError("You must provide a password via " "either --os-password or via " "env[OS_PASSWORD]") if not (args.os_project_id or args.os_project_name or args.os_tenant_id or args.os_tenant_name): # steer users towards Keystone V3 API raise exc.CommandError("You must provide a project_id " "(or name) via either --os-project-id " "or via env[OS_PROJECT_ID]") if not self.auth_plugin.opts['auth_url']: raise exc.CommandError("You must provide an auth url via " "either --os-auth-url or via " "env[OS_AUTH_URL]") client_kwargs = vars(args) client_kwargs.update(self.auth_plugin.opts) client_kwargs['auth_plugin'] = self.auth_plugin client = ceiloclient.get_client(api_version, **client_kwargs) # call whatever callback was selected try: args.func(client, args) except exc.HTTPUnauthorized: raise exc.CommandError("Invalid OpenStack Identity credentials.") def do_bash_completion(self, args): """Prints all of the commands and options to stdout. The ceilometer.bash_completion script doesn't have to hard code them. """ commands = set() options = set() for sc_str, sc in self.subcommands.items(): commands.add(sc_str) for option in list(sc._optionals._option_string_actions): options.add(option) commands.remove('bash-completion') print(' '.join(commands | options)) @utils.arg('command', metavar='', nargs='?', help='Display help for ') def do_help(self, args): """Display help about this program or one of its subcommands.""" if getattr(args, 'command', None): if args.command in self.subcommands: self.subcommands[args.command].print_help() else: raise exc.CommandError("'%s' is not a valid subcommand" % args.command) else: self.parser.print_help() class HelpFormatter(argparse.HelpFormatter): def __init__(self, prog, indent_increment=2, max_help_position=32, width=None): super(HelpFormatter, self).__init__(prog, indent_increment, max_help_position, width) def start_section(self, heading): # Title-case the headings heading = '%s%s' % (heading[0].upper(), heading[1:]) super(HelpFormatter, self).start_section(heading) def main(args=None): try: if args is None: args = sys.argv[1:] CeilometerShell().main(args) except Exception as e: if '--debug' in args or '-d' in args: raise else: print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr) sys.exit(1) except KeyboardInterrupt: print("Stopping Ceilometer Client", file=sys.stderr) sys.exit(130) if __name__ == "__main__": main() python-ceilometerclient-2.9.0/ceilometerclient/i18n.py0000664000175000017500000000153213117513017024176 0ustar jenkinsjenkins00000000000000# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See http://docs.openstack.org/developer/oslo.i18n/usage.html . """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='ceilometerclient') # The primary translation function using the well-known name "_" _ = _translators.primary python-ceilometerclient-2.9.0/ceilometerclient/__init__.py0000664000175000017500000000140713117513017025157 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. __all__ = ['__version__'] import pbr.version version_info = pbr.version.VersionInfo('python-ceilometerclient') try: __version__ = version_info.version_string() except AttributeError: __version__ = None python-ceilometerclient-2.9.0/ceilometerclient/exc.py0000664000175000017500000000732313117513017024202 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import sys class BaseException(Exception): """An error occurred.""" def __init__(self, message=None): self.message = message def __str__(self): return self.message or self.__class__.__doc__ class CommandError(BaseException): """Invalid usage of CLI.""" class InvalidEndpoint(BaseException): """The provided endpoint is invalid.""" class CommunicationError(BaseException): """Unable to communicate with server.""" class HTTPException(BaseException): """Base exception for all HTTP-derived exceptions.""" code = 'N/A' def __init__(self, details=None): self.details = details def __str__(self): message = "" if self.details: message = self.details try: data = json.loads(self.details) message = data.get("error_message", "") if isinstance(message, dict) and "faultstring" in message: message = "ERROR %s" % message["faultstring"] except (ValueError, TypeError, AttributeError): pass if message: message = " %s" % message return "%s (HTTP %s)%s" % (self.__class__.__name__, self.code, message) class HTTPMultipleChoices(HTTPException): code = 300 def __str__(self): self.details = ("Requested version of OpenStack Images API is not" "available.") return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code, self.details) class HTTPBadRequest(HTTPException): code = 400 class HTTPUnauthorized(HTTPException): code = 401 class HTTPForbidden(HTTPException): code = 403 class HTTPNotFound(HTTPException): code = 404 class HTTPMethodNotAllowed(HTTPException): code = 405 class HTTPConflict(HTTPException): code = 409 class HTTPOverLimit(HTTPException): code = 413 class HTTPInternalServerError(HTTPException): code = 500 class HTTPNotImplemented(HTTPException): code = 501 class HTTPBadGateway(HTTPException): code = 502 class HTTPServiceUnavailable(HTTPException): code = 503 # NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception # classes _code_map = {} for obj_name in dir(sys.modules[__name__]): if obj_name.startswith('HTTP'): obj = getattr(sys.modules[__name__], obj_name) _code_map[obj.code] = obj def from_response(response, details=None): """Return an instance of an HTTPException based on http response.""" if hasattr(response, "status"): # it is response from HTTPClient (httplib) code = response.status elif hasattr(response, "status_code"): # it is response from SessionClient (requests) code = response.status_code else: # it is something unexpected raise TypeError("Function 'from_response' expects only response object" " from httplib or requests libraries.") cls = _code_map.get(code) if cls is None: exc = HTTPException(details) exc.code = code return exc else: return cls(details) python-ceilometerclient-2.9.0/ceilometerclient/apiclient/0000775000175000017500000000000013117513272025017 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/apiclient/utils.py0000664000175000017500000000650313117513017026532 0ustar jenkinsjenkins00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. ######################################################################## # # THIS MODULE IS DEPRECATED # # Please refer to # https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for # the discussion leading to this deprecation. # # We recommend checking out the python-openstacksdk project # (https://launchpad.net/python-openstacksdk) instead. # ######################################################################## from oslo_utils import encodeutils from oslo_utils import uuidutils import six from ceilometerclient.apiclient import exceptions from ceilometerclient.i18n import _ def find_resource(manager, name_or_id, **find_args): """Look for resource in a given manager. Used as a helper for the _find_* methods. Example: .. code-block:: python def _find_hypervisor(cs, hypervisor): #Get a hypervisor by name or ID. return cliutils.find_resource(cs.hypervisors, hypervisor) """ # first try to get entity as integer id try: return manager.get(int(name_or_id)) except (TypeError, ValueError, exceptions.NotFound): pass # now try to get entity as uuid try: if six.PY2: tmp_id = encodeutils.safe_encode(name_or_id) else: tmp_id = encodeutils.safe_decode(name_or_id) if uuidutils.is_uuid_like(tmp_id): return manager.get(tmp_id) except (TypeError, ValueError, exceptions.NotFound): pass # for str id which is not uuid if getattr(manager, 'is_alphanum_id_allowed', False): try: return manager.get(name_or_id) except exceptions.NotFound: pass try: try: return manager.find(human_id=name_or_id, **find_args) except exceptions.NotFound: pass # finally try to find entity by name try: resource = getattr(manager, 'resource_class', None) name_attr = resource.NAME_ATTR if resource else 'name' kwargs = {name_attr: name_or_id} kwargs.update(find_args) return manager.find(**kwargs) except exceptions.NotFound: msg = _("No %(name)s with a name or " "ID of '%(name_or_id)s' exists.") % \ { "name": manager.resource_class.__name__.lower(), "name_or_id": name_or_id } raise exceptions.CommandError(msg) except exceptions.NoUniqueMatch: msg = _("Multiple %(name)s matches found for " "'%(name_or_id)s', use an ID to be more specific.") % \ { "name": manager.resource_class.__name__.lower(), "name_or_id": name_or_id } raise exceptions.CommandError(msg) python-ceilometerclient-2.9.0/ceilometerclient/apiclient/fake_client.py0000664000175000017500000001456113117513017027641 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ A fake server that "responds" to API methods with pre-canned responses. All of these responses come from the spec, so if for some reason the spec's wrong the tests might raise AssertionError. I've indicated in comments the places where actual behavior differs from the spec. """ ######################################################################## # # THIS MODULE IS DEPRECATED # # Please refer to # https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for # the discussion leading to this deprecation. # # We recommend checking out the python-openstacksdk project # (https://launchpad.net/python-openstacksdk) instead. # ######################################################################## # W0102: Dangerous default value %s as argument # pylint: disable=W0102 import json import requests import six from six.moves.urllib import parse from ceilometerclient.apiclient import client def assert_has_keys(dct, required=None, optional=None): required = required or [] optional = optional or [] for k in required: try: assert k in dct except AssertionError: extra_keys = set(dct.keys()).difference(set(required + optional)) raise AssertionError("found unexpected keys: %s" % list(extra_keys)) class TestResponse(requests.Response): """Wrap requests.Response and provide a convenient initialization.""" def __init__(self, data): super(TestResponse, self).__init__() self._content_consumed = True if isinstance(data, dict): self.status_code = data.get('status_code', 200) # Fake the text attribute to streamline Response creation text = data.get('text', "") if isinstance(text, (dict, list)): self._content = json.dumps(text) default_headers = { "Content-Type": "application/json", } else: self._content = text default_headers = {} if six.PY3 and isinstance(self._content, six.string_types): self._content = self._content.encode('utf-8', 'strict') self.headers = data.get('headers') or default_headers else: self.status_code = data def __eq__(self, other): return (self.status_code == other.status_code and self.headers == other.headers and self._content == other._content) def __ne__(self, other): return not self.__eq__(other) class FakeHTTPClient(client.HTTPClient): def __init__(self, *args, **kwargs): self.callstack = [] self.fixtures = kwargs.pop("fixtures", None) or {} if not args and "auth_plugin" not in kwargs: args = (None, ) super(FakeHTTPClient, self).__init__(*args, **kwargs) def assert_called(self, method, url, body=None, pos=-1): """Assert than an API method was just called.""" expected = (method, url) called = self.callstack[pos][0:2] assert self.callstack, \ "Expected %s %s but no calls were made." % expected assert expected == called, 'Expected %s %s; got %s %s' % \ (expected + called) if body is not None: if self.callstack[pos][3] != body: raise AssertionError('%r != %r' % (self.callstack[pos][3], body)) def assert_called_anytime(self, method, url, body=None): """Assert than an API method was called anytime in the test.""" expected = (method, url) assert self.callstack, \ "Expected %s %s but no calls were made." % expected found = False entry = None for entry in self.callstack: if expected == entry[0:2]: found = True break assert found, 'Expected %s %s; got %s' % \ (method, url, self.callstack) if body is not None: assert entry[3] == body, "%s != %s" % (entry[3], body) self.callstack = [] def clear_callstack(self): self.callstack = [] def authenticate(self): pass def client_request(self, client, method, url, **kwargs): # Check that certain things are called correctly if method in ["GET", "DELETE"]: assert "json" not in kwargs # Note the call self.callstack.append( (method, url, kwargs.get("headers") or {}, kwargs.get("json") or kwargs.get("data"))) try: fixture = self.fixtures[url][method] except KeyError: pass else: return TestResponse({"headers": fixture[0], "text": fixture[1]}) # Call the method args = parse.parse_qsl(parse.urlparse(url)[4]) kwargs.update(args) munged_url = url.rsplit('?', 1)[0] munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') munged_url = munged_url.replace('-', '_') callback = "%s_%s" % (method.lower(), munged_url) if not hasattr(self, callback): raise AssertionError('Called unknown API method: %s %s, ' 'expected fakes method name: %s' % (method, url, callback)) resp = getattr(self, callback)(**kwargs) if len(resp) == 3: status, headers, body = resp else: status, body = resp headers = {} self.last_request_id = headers.get('x-openstack-request-id', 'req-test') return TestResponse({ "status_code": status, "text": body, "headers": headers, }) python-ceilometerclient-2.9.0/ceilometerclient/apiclient/client.py0000664000175000017500000003302413117513017026646 0ustar jenkinsjenkins00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2011 Piston Cloud Computing, Inc. # Copyright 2013 Alessio Ababilov # Copyright 2013 Grid Dynamics # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ OpenStack Client interface. Handles the REST calls and responses. """ # E0202: An attribute inherited from %s hide this method # pylint: disable=E0202 import hashlib import logging import time try: import simplejson as json except ImportError: import json from oslo_utils import encodeutils from oslo_utils import importutils import requests from ceilometerclient.apiclient import exceptions from ceilometerclient.i18n import _ _logger = logging.getLogger(__name__) SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',) class HTTPClient(object): """This client handles sending HTTP requests to OpenStack servers. Features: - share authentication information between several clients to different services (e.g., for compute and image clients); - reissue authentication request for expired tokens; - encode/decode JSON bodies; - raise exceptions on HTTP errors; - pluggable authentication; - store authentication information in a keyring; - store time spent for requests; - register clients for particular services, so one can use `http_client.identity` or `http_client.compute`; - log requests and responses in a format that is easy to copy-and-paste into terminal and send the same request with curl. """ user_agent = "ceilometerclient.apiclient" def __init__(self, auth_plugin, region_name=None, endpoint_type="publicURL", original_ip=None, verify=True, cert=None, timeout=None, timings=False, keyring_saver=None, debug=False, user_agent=None, http=None): self.auth_plugin = auth_plugin self.endpoint_type = endpoint_type self.region_name = region_name self.original_ip = original_ip self.timeout = timeout self.verify = verify self.cert = cert self.keyring_saver = keyring_saver self.debug = debug self.user_agent = user_agent or self.user_agent self.times = [] # [("item", starttime, endtime), ...] self.timings = timings # requests within the same session can reuse TCP connections from pool self.http = http or requests.Session() self.cached_token = None self.last_request_id = None def _safe_header(self, name, value): if name in SENSITIVE_HEADERS: # because in python3 byte string handling is ... ug v = value.encode('utf-8') h = hashlib.sha1(v) d = h.hexdigest() return encodeutils.safe_decode(name), "{SHA1}%s" % d else: return (encodeutils.safe_decode(name), encodeutils.safe_decode(value)) def _http_log_req(self, method, url, kwargs): if not self.debug: return string_parts = [ "curl -g -i", "-X '%s'" % method, "'%s'" % url, ] for element in kwargs['headers']: header = ("-H '%s: %s'" % self._safe_header(element, kwargs['headers'][element])) string_parts.append(header) _logger.debug("REQ: %s", " ".join(string_parts)) if 'data' in kwargs: _logger.debug("REQ BODY: %s\n", kwargs['data']) def _http_log_resp(self, resp): if not self.debug: return _logger.debug( "RESP: [%s] %s\n", resp.status_code, resp.headers) if resp._content_consumed: _logger.debug( "RESP BODY: %s\n", resp.text) def serialize(self, kwargs): if kwargs.get('json') is not None: kwargs['headers']['Content-Type'] = 'application/json' kwargs['data'] = json.dumps(kwargs['json']) try: del kwargs['json'] except KeyError: pass def get_timings(self): return self.times def reset_timings(self): self.times = [] def request(self, method, url, **kwargs): """Send an http request with the specified characteristics. Wrapper around `requests.Session.request` to handle tasks such as setting headers, JSON encoding/decoding, and error handling. :param method: method of HTTP request :param url: URL of HTTP request :param kwargs: any other parameter that can be passed to requests.Session.request (such as `headers`) or `json` that will be encoded as JSON and used as `data` argument """ kwargs.setdefault("headers", {}) kwargs["headers"]["User-Agent"] = self.user_agent if self.original_ip: kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( self.original_ip, self.user_agent) if self.timeout is not None: kwargs.setdefault("timeout", self.timeout) kwargs.setdefault("verify", self.verify) if self.cert is not None: kwargs.setdefault("cert", self.cert) self.serialize(kwargs) self._http_log_req(method, url, kwargs) if self.timings: start_time = time.time() resp = self.http.request(method, url, **kwargs) if self.timings: self.times.append(("%s %s" % (method, url), start_time, time.time())) self._http_log_resp(resp) self.last_request_id = resp.headers.get('x-openstack-request-id') if resp.status_code >= 400: _logger.debug( "Request returned failure status: %s", resp.status_code) raise exceptions.from_response(resp, method, url) return resp @staticmethod def concat_url(endpoint, url): """Concatenate endpoint and final URL. E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to "http://keystone/v2.0/tokens". :param endpoint: the base URL :param url: the final URL """ return "%s/%s" % (endpoint.rstrip("/"), url.strip("/")) def client_request(self, client, method, url, **kwargs): """Send an http request using `client`'s endpoint and specified `url`. If request was rejected as unauthorized (possibly because the token is expired), issue one authorization attempt and send the request once again. :param client: instance of BaseClient descendant :param method: method of HTTP request :param url: URL of HTTP request :param kwargs: any other parameter that can be passed to `HTTPClient.request` """ filter_args = { "endpoint_type": client.endpoint_type or self.endpoint_type, "service_type": client.service_type, } token, endpoint = (self.cached_token, client.cached_endpoint) just_authenticated = False if not (token and endpoint): try: token, endpoint = self.auth_plugin.token_and_endpoint( **filter_args) except exceptions.EndpointException: pass if not (token and endpoint): self.authenticate() just_authenticated = True token, endpoint = self.auth_plugin.token_and_endpoint( **filter_args) if not (token and endpoint): raise exceptions.AuthorizationFailure( _("Cannot find endpoint or token for request")) old_token_endpoint = (token, endpoint) kwargs.setdefault("headers", {})["X-Auth-Token"] = token self.cached_token = token client.cached_endpoint = endpoint # Perform the request once. If we get Unauthorized, then it # might be because the auth token expired, so try to # re-authenticate and try again. If it still fails, bail. try: return self.request( method, self.concat_url(endpoint, url), **kwargs) except exceptions.Unauthorized as unauth_ex: if just_authenticated: raise self.cached_token = None client.cached_endpoint = None if self.auth_plugin.opts.get('token'): self.auth_plugin.opts['token'] = None if self.auth_plugin.opts.get('endpoint'): self.auth_plugin.opts['endpoint'] = None self.authenticate() try: token, endpoint = self.auth_plugin.token_and_endpoint( **filter_args) except exceptions.EndpointException: raise unauth_ex if (not (token and endpoint) or old_token_endpoint == (token, endpoint)): raise unauth_ex self.cached_token = token client.cached_endpoint = endpoint kwargs["headers"]["X-Auth-Token"] = token return self.request( method, self.concat_url(endpoint, url), **kwargs) def add_client(self, base_client_instance): """Add a new instance of :class:`BaseClient` descendant. `self` will store a reference to `base_client_instance`. Example: >>> def test_clients(): ... from keystoneclient.auth import keystone ... from openstack.common.apiclient import client ... auth = keystone.KeystoneAuthPlugin( ... username="user", password="pass", tenant_name="tenant", ... auth_url="http://auth:5000/v2.0") ... openstack_client = client.HTTPClient(auth) ... # create nova client ... from novaclient.v1_1 import client ... client.Client(openstack_client) ... # create keystone client ... from keystoneclient.v2_0 import client ... client.Client(openstack_client) ... # use them ... openstack_client.identity.tenants.list() ... openstack_client.compute.servers.list() """ service_type = base_client_instance.service_type if service_type and not hasattr(self, service_type): setattr(self, service_type, base_client_instance) def authenticate(self): self.auth_plugin.authenticate(self) # Store the authentication results in the keyring for later requests if self.keyring_saver: self.keyring_saver.save(self) class BaseClient(object): """Top-level object to access the OpenStack API. This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient` will handle a bunch of issues such as authentication. """ service_type = None endpoint_type = None # "publicURL" will be used cached_endpoint = None def __init__(self, http_client, extensions=None): self.http_client = http_client http_client.add_client(self) # Add in any extensions... if extensions: for extension in extensions: if extension.manager_class: setattr(self, extension.name, extension.manager_class(self)) def client_request(self, method, url, **kwargs): return self.http_client.client_request( self, method, url, **kwargs) @property def last_request_id(self): return self.http_client.last_request_id def head(self, url, **kwargs): return self.client_request("HEAD", url, **kwargs) def get(self, url, **kwargs): return self.client_request("GET", url, **kwargs) def post(self, url, **kwargs): return self.client_request("POST", url, **kwargs) def put(self, url, **kwargs): return self.client_request("PUT", url, **kwargs) def delete(self, url, **kwargs): return self.client_request("DELETE", url, **kwargs) def patch(self, url, **kwargs): return self.client_request("PATCH", url, **kwargs) @staticmethod def get_class(api_name, version, version_map): """Returns the client class for the requested API version :param api_name: the name of the API, e.g. 'compute', 'image', etc :param version: the requested API version :param version_map: a dict of client classes keyed by version :rtype: a client class for the requested API version """ try: client_path = version_map[str(version)] except (KeyError, ValueError): msg = _("Invalid %(api_name)s client version '%(version)s'. " "Must be one of: %(version_map)s") % { 'api_name': api_name, 'version': version, 'version_map': ', '.join(version_map.keys())} raise exceptions.UnsupportedVersion(msg) return importutils.import_class(client_path) python-ceilometerclient-2.9.0/ceilometerclient/apiclient/__init__.py0000664000175000017500000000000013117513017027113 0ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/apiclient/auth.py0000664000175000017500000001637613117513017026344 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 Spanish National Research Council. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # E0202: An attribute inherited from %s hide this method # pylint: disable=E0202 ######################################################################## # # THIS MODULE IS DEPRECATED # # Please refer to # https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for # the discussion leading to this deprecation. # # We recommend checking out the python-openstacksdk project # (https://launchpad.net/python-openstacksdk) instead. # ######################################################################## import abc import argparse import os import six from stevedore import extension from ceilometerclient.apiclient import exceptions _discovered_plugins = {} def discover_auth_systems(): """Discover the available auth-systems. This won't take into account the old style auth-systems. """ global _discovered_plugins _discovered_plugins = {} def add_plugin(ext): _discovered_plugins[ext.name] = ext.plugin ep_namespace = "ceilometerclient.apiclient.auth" mgr = extension.ExtensionManager(ep_namespace) mgr.map(add_plugin) def load_auth_system_opts(parser): """Load options needed by the available auth-systems into a parser. This function will try to populate the parser with options from the available plugins. """ group = parser.add_argument_group("Common auth options") BaseAuthPlugin.add_common_opts(group) for name, auth_plugin in six.iteritems(_discovered_plugins): group = parser.add_argument_group( "Auth-system '%s' options" % name, conflict_handler="resolve") auth_plugin.add_opts(group) def load_plugin(auth_system): try: plugin_class = _discovered_plugins[auth_system] except KeyError: raise exceptions.AuthSystemNotFound(auth_system) return plugin_class(auth_system=auth_system) def load_plugin_from_args(args): """Load required plugin and populate it with options. Try to guess auth system if it is not specified. Systems are tried in alphabetical order. :type args: argparse.Namespace :raises: AuthPluginOptionsMissing """ auth_system = args.os_auth_system if auth_system: plugin = load_plugin(auth_system) plugin.parse_opts(args) plugin.sufficient_options() return plugin for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)): plugin_class = _discovered_plugins[plugin_auth_system] plugin = plugin_class() plugin.parse_opts(args) try: plugin.sufficient_options() except exceptions.AuthPluginOptionsMissing: continue return plugin raise exceptions.AuthPluginOptionsMissing(["auth_system"]) @six.add_metaclass(abc.ABCMeta) class BaseAuthPlugin(object): """Base class for authentication plugins. An authentication plugin needs to override at least the authenticate method to be a valid plugin. """ auth_system = None opt_names = [] common_opt_names = [ "auth_system", "username", "password", "tenant_name", "token", "auth_url", ] def __init__(self, auth_system=None, **kwargs): self.auth_system = auth_system or self.auth_system self.opts = dict((name, kwargs.get(name)) for name in self.opt_names) @staticmethod def _parser_add_opt(parser, opt): """Add an option to parser in two variants. :param opt: option name (with underscores) """ dashed_opt = opt.replace("_", "-") env_var = "OS_%s" % opt.upper() arg_default = os.environ.get(env_var, "") arg_help = "Defaults to env[%s]." % env_var parser.add_argument( "--os-%s" % dashed_opt, metavar="<%s>" % dashed_opt, default=arg_default, help=arg_help) parser.add_argument( "--os_%s" % opt, metavar="<%s>" % dashed_opt, help=argparse.SUPPRESS) @classmethod def add_opts(cls, parser): """Populate the parser with the options for this plugin.""" for opt in cls.opt_names: # use `BaseAuthPlugin.common_opt_names` since it is never # changed in child classes if opt not in BaseAuthPlugin.common_opt_names: cls._parser_add_opt(parser, opt) @classmethod def add_common_opts(cls, parser): """Add options that are common for several plugins.""" for opt in cls.common_opt_names: cls._parser_add_opt(parser, opt) @staticmethod def get_opt(opt_name, args): """Return option name and value. :param opt_name: name of the option, e.g., "username" :param args: parsed arguments """ return (opt_name, getattr(args, "os_%s" % opt_name, None)) def parse_opts(self, args): """Parse the actual auth-system options if any. This method is expected to populate the attribute `self.opts` with a dict containing the options and values needed to make authentication. """ self.opts.update(dict(self.get_opt(opt_name, args) for opt_name in self.opt_names)) def authenticate(self, http_client): """Authenticate using plugin defined method. The method usually analyses `self.opts` and performs a request to authentication server. :param http_client: client object that needs authentication :type http_client: HTTPClient :raises: AuthorizationFailure """ self.sufficient_options() self._do_authenticate(http_client) @abc.abstractmethod def _do_authenticate(self, http_client): """Protected method for authentication.""" def sufficient_options(self): """Check if all required options are present. :raises: AuthPluginOptionsMissing """ missing = [opt for opt in self.opt_names if not self.opts.get(opt)] if missing: raise exceptions.AuthPluginOptionsMissing(missing) @abc.abstractmethod def token_and_endpoint(self, endpoint_type, service_type): """Return token and endpoint. :param service_type: Service type of the endpoint :type service_type: string :param endpoint_type: Type of endpoint. Possible values: public or publicURL, internal or internalURL, admin or adminURL :type endpoint_type: string :returns: tuple of token and endpoint strings :raises: EndpointException """ python-ceilometerclient-2.9.0/ceilometerclient/apiclient/base.py0000664000175000017500000004222513117513017026305 0ustar jenkinsjenkins00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2012 Grid Dynamics # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base utilities to build API operation managers and objects on top of. """ ######################################################################## # # THIS MODULE IS DEPRECATED # # Please refer to # https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for # the discussion leading to this deprecation. # # We recommend checking out the python-openstacksdk project # (https://launchpad.net/python-openstacksdk) instead. # ######################################################################## # E1102: %s is not callable # pylint: disable=E1102 import abc import copy from oslo_utils import reflection from oslo_utils import strutils import six from six.moves.urllib import parse from ceilometerclient.apiclient import exceptions from ceilometerclient.i18n import _ def getid(obj): """Return id if argument is a Resource. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ try: if obj.uuid: return obj.uuid except AttributeError: pass try: return obj.id except AttributeError: return obj # TODO(aababilov): call run_hooks() in HookableMixin's child classes class HookableMixin(object): """Mixin so classes can register and run hooks.""" _hooks_map = {} @classmethod def add_hook(cls, hook_type, hook_func): """Add a new hook of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param hook_func: hook function """ if hook_type not in cls._hooks_map: cls._hooks_map[hook_type] = [] cls._hooks_map[hook_type].append(hook_func) @classmethod def run_hooks(cls, hook_type, *args, **kwargs): """Run all hooks of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param args: args to be passed to every hook function :param kwargs: kwargs to be passed to every hook function """ hook_funcs = cls._hooks_map.get(hook_type) or [] for hook_func in hook_funcs: hook_func(*args, **kwargs) class BaseManager(HookableMixin): """Basic manager type providing common operations. Managers interact with a particular type of API (servers, flavors, images, etc.) and provide CRUD operations for them. """ resource_class = None def __init__(self, client): """Initializes BaseManager with `client`. :param client: instance of BaseClient descendant for HTTP requests """ super(BaseManager, self).__init__() self.client = client def _list(self, url, response_key=None, obj_class=None, json=None): """List the collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'servers'. If response_key is None - all response body will be used. :param obj_class: class for constructing the returned objects (self.resource_class will be used by default) :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) """ if json: body = self.client.post(url, json=json).json() else: body = self.client.get(url).json() if obj_class is None: obj_class = self.resource_class data = body[response_key] if response_key is not None else body # NOTE(ja): keystone returns values as list as {'values': [ ... ]} # unlike other services which just return the list... try: data = data['values'] except (KeyError, TypeError): pass return [obj_class(self, res, loaded=True) for res in data if res] def _get(self, url, response_key=None): """Get an object from collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'server'. If response_key is None - all response body will be used. """ body = self.client.get(url).json() data = body[response_key] if response_key is not None else body return self.resource_class(self, data, loaded=True) def _head(self, url): """Retrieve request headers for an object. :param url: a partial URL, e.g., '/servers' """ resp = self.client.head(url) return resp.status_code == 204 def _post(self, url, json, response_key=None, return_raw=False): """Create an object. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'server'. If response_key is None - all response body will be used. :param return_raw: flag to force returning raw JSON instead of Python object of self.resource_class """ body = self.client.post(url, json=json).json() data = body[response_key] if response_key is not None else body if return_raw: return data return self.resource_class(self, data) def _put(self, url, json=None, response_key=None): """Update an object with PUT method. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers'. If response_key is None - all response body will be used. """ resp = self.client.put(url, json=json) # PUT requests may not return a body if resp.content: body = resp.json() if response_key is not None: return self.resource_class(self, body[response_key]) else: return self.resource_class(self, body) def _patch(self, url, json=None, response_key=None): """Update an object with PATCH method. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers'. If response_key is None - all response body will be used. """ body = self.client.patch(url, json=json).json() if response_key is not None: return self.resource_class(self, body[response_key]) else: return self.resource_class(self, body) def _delete(self, url): """Delete an object. :param url: a partial URL, e.g., '/servers/my-server' """ return self.client.delete(url) @six.add_metaclass(abc.ABCMeta) class ManagerWithFind(BaseManager): """Manager with additional `find()`/`findall()` methods.""" @abc.abstractmethod def list(self): pass def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ matches = self.findall(**kwargs) num_matches = len(matches) if num_matches == 0: msg = _("No %(name)s matching %(args)s.") % { 'name': self.resource_class.__name__, 'args': kwargs } raise exceptions.NotFound(msg) elif num_matches > 1: raise exceptions.NoUniqueMatch() else: return matches[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ found = [] searches = kwargs.items() for obj in self.list(): try: if all(getattr(obj, attr) == value for (attr, value) in searches): found.append(obj) except AttributeError: continue return found class CrudManager(BaseManager): """Base manager class for manipulating entities. Children of this class are expected to define a `collection_key` and `key`. - `collection_key`: Usually a plural noun by convention (e.g. `entities`); used to refer collections in both URL's (e.g. `/v3/entities`) and JSON objects containing a list of member resources (e.g. `{'entities': [{}, {}, {}]}`). - `key`: Usually a singular noun by convention (e.g. `entity`); used to refer to an individual member of the collection. """ collection_key = None key = None def build_url(self, base_url=None, **kwargs): """Builds a resource URL for the given kwargs. Given an example collection where `collection_key = 'entities'` and `key = 'entity'`, the following URL's could be generated. By default, the URL will represent a collection of entities, e.g.:: /entities If kwargs contains an `entity_id`, then the URL will represent a specific member, e.g.:: /entities/{entity_id} :param base_url: if provided, the generated URL will be appended to it """ url = base_url if base_url is not None else '' url += '/%s' % self.collection_key # do we have a specific entity? entity_id = kwargs.get('%s_id' % self.key) if entity_id is not None: url += '/%s' % entity_id return url def _filter_kwargs(self, kwargs): """Drop null values and handle ids.""" for key, ref in six.iteritems(kwargs.copy()): if ref is None: kwargs.pop(key) else: if isinstance(ref, Resource): kwargs.pop(key) kwargs['%s_id' % key] = getid(ref) return kwargs def create(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._post( self.build_url(**kwargs), {self.key: kwargs}, self.key) def get(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._get( self.build_url(**kwargs), self.key) def head(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._head(self.build_url(**kwargs)) def list(self, base_url=None, **kwargs): """List the collection. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', }, self.collection_key) def put(self, base_url=None, **kwargs): """Update an element. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._put(self.build_url(base_url=base_url, **kwargs)) def update(self, **kwargs): kwargs = self._filter_kwargs(kwargs) params = kwargs.copy() params.pop('%s_id' % self.key) return self._patch( self.build_url(**kwargs), {self.key: params}, self.key) def delete(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._delete( self.build_url(**kwargs)) def find(self, base_url=None, **kwargs): """Find a single item with attributes matching ``**kwargs``. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) rl = self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', }, self.collection_key) num = len(rl) if num == 0: msg = _("No %(name)s matching %(args)s.") % { 'name': self.resource_class.__name__, 'args': kwargs } raise exceptions.NotFound(404, msg) elif num > 1: raise exceptions.NoUniqueMatch else: return rl[0] class Extension(HookableMixin): """Extension descriptor.""" SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') manager_class = None def __init__(self, name, module): super(Extension, self).__init__() self.name = name self.module = module self._parse_extension_module() def _parse_extension_module(self): self.manager_class = None for attr_name, attr_value in self.module.__dict__.items(): if attr_name in self.SUPPORTED_HOOKS: self.add_hook(attr_name, attr_value) else: try: if issubclass(attr_value, BaseManager): self.manager_class = attr_value except TypeError: pass def __repr__(self): return "" % self.name class Resource(object): """Base class for OpenStack resources (tenant, user, etc.). This is pretty much just a bag for attributes. """ HUMAN_ID = False NAME_ATTR = 'name' def __init__(self, manager, info, loaded=False): """Populate and bind to a manager. :param manager: BaseManager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) self_cls_name = reflection.get_class_name(self, fully_qualified=False) return "<%s %s>" % (self_cls_name, info) @property def human_id(self): """Human-readable ID which can be used for bash completion.""" if self.HUMAN_ID: name = getattr(self, self.NAME_ATTR, None) if name is not None: return strutils.to_slug(name) return None def _add_details(self, info): for (k, v) in six.iteritems(info): try: setattr(self, k, v) self._info[k] = v except AttributeError: # In this case we already defined the attribute on the class pass def __getattr__(self, k): if k not in self.__dict__: # NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded(): self.get() return self.__getattr__(k) raise AttributeError(k) else: return self.__dict__[k] def get(self): """Support for lazy loading details. Some clients, such as novaclient have the option to lazy load the details, details which can be loaded with this function. """ # set_loaded() first ... so if we have to bail, we know we tried. self.set_loaded(True) if not hasattr(self.manager, 'get'): return new = self.manager.get(self.id) if new: self._add_details(new._info) self._add_details( {'x_request_id': self.manager.client.last_request_id}) def __eq__(self, other): if not isinstance(other, Resource): return NotImplemented # two resources of different types are not equal if not isinstance(other, self.__class__): return False return self._info == other._info def __ne__(self, other): return not self.__eq__(other) def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val def to_dict(self): return copy.deepcopy(self._info) python-ceilometerclient-2.9.0/ceilometerclient/apiclient/exceptions.py0000664000175000017500000003126313117513017027554 0ustar jenkinsjenkins00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 Nebula, Inc. # Copyright 2013 Alessio Ababilov # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Exception definitions. """ ######################################################################## # # THIS MODULE IS DEPRECATED # # Please refer to # https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for # the discussion leading to this deprecation. # # We recommend checking out the python-openstacksdk project # (https://launchpad.net/python-openstacksdk) instead. # ######################################################################## import inspect import sys import six from ceilometerclient.i18n import _ class ClientException(Exception): """The base exception class for all exceptions this library raises.""" pass class ValidationError(ClientException): """Error in validation on API client side.""" pass class UnsupportedVersion(ClientException): """User is trying to use an unsupported version of the API.""" pass class CommandError(ClientException): """Error in CLI tool.""" pass class AuthorizationFailure(ClientException): """Cannot authorize API client.""" pass class ConnectionError(ClientException): """Cannot connect to API service.""" pass class ConnectionRefused(ConnectionError): """Connection refused while trying to connect to API service.""" pass class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): super(AuthPluginOptionsMissing, self).__init__( _("Authentication failed. Missing options: %s") % ", ".join(opt_names)) self.opt_names = opt_names class AuthSystemNotFound(AuthorizationFailure): """User has specified an AuthSystem that is not installed.""" def __init__(self, auth_system): super(AuthSystemNotFound, self).__init__( _("AuthSystemNotFound: %s") % repr(auth_system)) self.auth_system = auth_system class NoUniqueMatch(ClientException): """Multiple entities found instead of one.""" pass class EndpointException(ClientException): """Something is rotten in Service Catalog.""" pass class EndpointNotFound(EndpointException): """Could not find requested endpoint in Service Catalog.""" pass class AmbiguousEndpoints(EndpointException): """Found more than one matching endpoint in Service Catalog.""" def __init__(self, endpoints=None): super(AmbiguousEndpoints, self).__init__( _("AmbiguousEndpoints: %s") % repr(endpoints)) self.endpoints = endpoints class HttpError(ClientException): """The base exception class for all HTTP exceptions.""" http_status = 0 message = _("HTTP Error") def __init__(self, message=None, details=None, response=None, request_id=None, url=None, method=None, http_status=None): self.http_status = http_status or self.http_status self.message = message or self.message self.details = details self.request_id = request_id self.response = response self.url = url self.method = method formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) if request_id: formatted_string += " (Request-ID: %s)" % request_id super(HttpError, self).__init__(formatted_string) class HTTPRedirection(HttpError): """HTTP Redirection.""" message = _("HTTP Redirection") class HTTPClientError(HttpError): """Client-side HTTP error. Exception for cases in which the client seems to have erred. """ message = _("HTTP Client Error") class HttpServerError(HttpError): """Server-side HTTP error. Exception for cases in which the server is aware that it has erred or is incapable of performing the request. """ message = _("HTTP Server Error") class MultipleChoices(HTTPRedirection): """HTTP 300 - Multiple Choices. Indicates multiple options for the resource that the client may follow. """ http_status = 300 message = _("Multiple Choices") class BadRequest(HTTPClientError): """HTTP 400 - Bad Request. The request cannot be fulfilled due to bad syntax. """ http_status = 400 message = _("Bad Request") class Unauthorized(HTTPClientError): """HTTP 401 - Unauthorized. Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. """ http_status = 401 message = _("Unauthorized") class PaymentRequired(HTTPClientError): """HTTP 402 - Payment Required. Reserved for future use. """ http_status = 402 message = _("Payment Required") class Forbidden(HTTPClientError): """HTTP 403 - Forbidden. The request was a valid request, but the server is refusing to respond to it. """ http_status = 403 message = _("Forbidden") class NotFound(HTTPClientError): """HTTP 404 - Not Found. The requested resource could not be found but may be available again in the future. """ http_status = 404 message = _("Not Found") class MethodNotAllowed(HTTPClientError): """HTTP 405 - Method Not Allowed. A request was made of a resource using a request method not supported by that resource. """ http_status = 405 message = _("Method Not Allowed") class NotAcceptable(HTTPClientError): """HTTP 406 - Not Acceptable. The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. """ http_status = 406 message = _("Not Acceptable") class ProxyAuthenticationRequired(HTTPClientError): """HTTP 407 - Proxy Authentication Required. The client must first authenticate itself with the proxy. """ http_status = 407 message = _("Proxy Authentication Required") class RequestTimeout(HTTPClientError): """HTTP 408 - Request Timeout. The server timed out waiting for the request. """ http_status = 408 message = _("Request Timeout") class Conflict(HTTPClientError): """HTTP 409 - Conflict. Indicates that the request could not be processed because of conflict in the request, such as an edit conflict. """ http_status = 409 message = _("Conflict") class Gone(HTTPClientError): """HTTP 410 - Gone. Indicates that the resource requested is no longer available and will not be available again. """ http_status = 410 message = _("Gone") class LengthRequired(HTTPClientError): """HTTP 411 - Length Required. The request did not specify the length of its content, which is required by the requested resource. """ http_status = 411 message = _("Length Required") class PreconditionFailed(HTTPClientError): """HTTP 412 - Precondition Failed. The server does not meet one of the preconditions that the requester put on the request. """ http_status = 412 message = _("Precondition Failed") class RequestEntityTooLarge(HTTPClientError): """HTTP 413 - Request Entity Too Large. The request is larger than the server is willing or able to process. """ http_status = 413 message = _("Request Entity Too Large") def __init__(self, *args, **kwargs): try: self.retry_after = int(kwargs.pop('retry_after')) except (KeyError, ValueError): self.retry_after = 0 super(RequestEntityTooLarge, self).__init__(*args, **kwargs) class RequestUriTooLong(HTTPClientError): """HTTP 414 - Request-URI Too Long. The URI provided was too long for the server to process. """ http_status = 414 message = _("Request-URI Too Long") class UnsupportedMediaType(HTTPClientError): """HTTP 415 - Unsupported Media Type. The request entity has a media type which the server or resource does not support. """ http_status = 415 message = _("Unsupported Media Type") class RequestedRangeNotSatisfiable(HTTPClientError): """HTTP 416 - Requested Range Not Satisfiable. The client has asked for a portion of the file, but the server cannot supply that portion. """ http_status = 416 message = _("Requested Range Not Satisfiable") class ExpectationFailed(HTTPClientError): """HTTP 417 - Expectation Failed. The server cannot meet the requirements of the Expect request-header field. """ http_status = 417 message = _("Expectation Failed") class UnprocessableEntity(HTTPClientError): """HTTP 422 - Unprocessable Entity. The request was well-formed but was unable to be followed due to semantic errors. """ http_status = 422 message = _("Unprocessable Entity") class InternalServerError(HttpServerError): """HTTP 500 - Internal Server Error. A generic error message, given when no more specific message is suitable. """ http_status = 500 message = _("Internal Server Error") # NotImplemented is a python keyword. class HttpNotImplemented(HttpServerError): """HTTP 501 - Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request. """ http_status = 501 message = _("Not Implemented") class BadGateway(HttpServerError): """HTTP 502 - Bad Gateway. The server was acting as a gateway or proxy and received an invalid response from the upstream server. """ http_status = 502 message = _("Bad Gateway") class ServiceUnavailable(HttpServerError): """HTTP 503 - Service Unavailable. The server is currently unavailable. """ http_status = 503 message = _("Service Unavailable") class GatewayTimeout(HttpServerError): """HTTP 504 - Gateway Timeout. The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. """ http_status = 504 message = _("Gateway Timeout") class HttpVersionNotSupported(HttpServerError): """HTTP 505 - HttpVersion Not Supported. The server does not support the HTTP protocol version used in the request. """ http_status = 505 message = _("HTTP Version Not Supported") # _code_map contains all the classes that have http_status attribute. _code_map = dict( (getattr(obj, 'http_status', None), obj) for name, obj in six.iteritems(vars(sys.modules[__name__])) if inspect.isclass(obj) and getattr(obj, 'http_status', False) ) def from_response(response, method, url): """Returns an instance of :class:`HttpError` or subclass based on response. :param response: instance of `requests.Response` class :param method: HTTP method used for request :param url: URL used for request """ req_id = response.headers.get("x-openstack-request-id") # NOTE(hdd) true for older versions of nova and cinder if not req_id: req_id = response.headers.get("x-compute-request-id") kwargs = { "http_status": response.status_code, "response": response, "method": method, "url": url, "request_id": req_id, } if "retry-after" in response.headers: kwargs["retry_after"] = response.headers["retry-after"] content_type = response.headers.get("Content-Type", "") if content_type.startswith("application/json"): try: body = response.json() except ValueError: pass else: if isinstance(body, dict): error = body.get(list(body)[0]) if isinstance(error, dict): kwargs["message"] = (error.get("message") or error.get("faultstring")) kwargs["details"] = (error.get("details") or six.text_type(body)) elif content_type.startswith("text/"): kwargs["details"] = response.text try: cls = _code_map[response.status_code] except KeyError: if 500 <= response.status_code < 600: cls = HttpServerError elif 400 <= response.status_code < 500: cls = HTTPClientError else: cls = HttpError return cls(**kwargs) python-ceilometerclient-2.9.0/ceilometerclient/v2/0000775000175000017500000000000013117513272023376 5ustar jenkinsjenkins00000000000000python-ceilometerclient-2.9.0/ceilometerclient/v2/capabilities.py0000664000175000017500000000163113117513017026377 0ustar jenkinsjenkins00000000000000# # Copyright 2014 Huawei, Inc # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.common import base class Capabilities(base.Resource): def __repr__(self): return "" % self._info class CapabilitiesManager(base.Manager): resource_class = Capabilities def get(self): path = "/v2/capabilities" return Capabilities(self, self.api.get(path).json()) python-ceilometerclient-2.9.0/ceilometerclient/v2/client.py0000664000175000017500000001342413117513017025227 0ustar jenkinsjenkins00000000000000# Copyright Ericsson AB 2014. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import requests from ceilometerclient import client as ceiloclient from ceilometerclient.v2 import alarms from ceilometerclient.v2 import capabilities from ceilometerclient.v2 import event_types from ceilometerclient.v2 import events from ceilometerclient.v2 import meters from ceilometerclient.v2 import query from ceilometerclient.v2 import resources from ceilometerclient.v2 import samples from ceilometerclient.v2 import statistics from ceilometerclient.v2 import trait_descriptions from ceilometerclient.v2 import traits from keystoneauth1 import exceptions as ka_exc class Client(object): """Client for the Ceilometer v2 API. :param session: a keystoneauth session object :type session: keystoneauth1.session.Session :param str service_type: The default service_type for URL discovery :param str service_name: The default service_name for URL discovery :param str interface: The default interface for URL discovery (Default: public) :param str region_name: The default region_name for URL discovery :param str endpoint_override: Always use this endpoint URL for requests for this ceiloclient :param auth: An auth plugin to use instead of the session one :type auth: keystoneauth1.plugin.BaseAuthPlugin :param str user_agent: The User-Agent string to set (Default is python-ceilometer-client) :param int connect_retries: the maximum number of retries that should be attempted for connection errors :param logger: A logging object :type logger: logging.Logger """ def __init__(self, *args, **kwargs): """Initialize a new client for the Ceilometer v2 API.""" if not kwargs.get('auth_plugin') and not kwargs.get('session'): kwargs['auth_plugin'] = ceiloclient.get_auth_plugin(*args, **kwargs) self.auth_plugin = kwargs.get('auth_plugin') self.http_client = ceiloclient._construct_http_client(**kwargs) self.alarm_client = self._get_redirect_client( 'alarming', 'aodh', **kwargs) aodh_enabled = self.alarm_client is not None if not aodh_enabled: self.alarm_client = self.http_client self.event_client = self._get_redirect_client( 'event', 'panko', **kwargs) panko_enabled = self.event_client is not None if not panko_enabled: self.event_client = self.http_client self.meters = meters.MeterManager(self.http_client) self.samples = samples.OldSampleManager(self.http_client) self.new_samples = samples.SampleManager(self.http_client) self.statistics = statistics.StatisticsManager(self.http_client) self.resources = resources.ResourceManager(self.http_client) self.alarms = alarms.AlarmManager(self.alarm_client) self.events = events.EventManager(self.event_client) self.event_types = event_types.EventTypeManager(self.event_client) self.traits = traits.TraitManager(self.event_client) self.trait_descriptions = trait_descriptions.\ TraitDescriptionManager(self.event_client) self.query_samples = query.QuerySamplesManager( self.http_client) self.query_alarms = query.QueryAlarmsManager(self.alarm_client) self.query_alarm_history = query.QueryAlarmHistoryManager( self.alarm_client) self.capabilities = capabilities.CapabilitiesManager(self.http_client) @staticmethod def _get_redirect_client(new_service_type, new_service, **ceilo_kwargs): """Get client for new service manager to redirect to.""" # NOTE(sileht): the auth_plugin/keystone session cannot be copied # because they rely on threading module. auth_plugin = ceilo_kwargs.pop('auth_plugin', None) session = ceilo_kwargs.pop('session', None) kwargs = copy.deepcopy(ceilo_kwargs) kwargs["service_type"] = new_service_type endpoint = ceilo_kwargs.get('%s_endpoint' % new_service) if session: # keystone session can be shared between client ceilo_kwargs['session'] = kwargs['session'] = session if endpoint: kwargs['endpoint_override'] = endpoint elif auth_plugin and kwargs.get('auth_url'): ceilo_kwargs['auth_plugin'] = auth_plugin kwargs.pop('endpoint', None) kwargs['auth_plugin'] = ceiloclient.get_auth_plugin( endpoint, **kwargs) else: # Users may just provide ceilometer endpoint and token, and no # auth_url, in this case, we need 'aodh_endpoint' also to be # provided, otherwise we cannot get aodh endpoint from # keystone, and assume aodh is unavailable. Same applies to panko. return None try: # NOTE(sileht): try to use redirect c = ceiloclient._construct_http_client(**kwargs) c.get("/") return c except ka_exc.EndpointNotFound: return None except requests.exceptions.ConnectionError: return None python-ceilometerclient-2.9.0/ceilometerclient/v2/query.py0000664000175000017500000000311013117513017025105 0ustar jenkinsjenkins00000000000000# Copyright Ericsson AB 2014. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.common import base from ceilometerclient.v2 import alarms from ceilometerclient.v2 import samples class QueryManager(base.Manager): path_suffix = None def query(self, filter=None, orderby=None, limit=None): query = {} if filter: query["filter"] = filter if orderby: query["orderby"] = orderby if limit: query["limit"] = limit url = '/v2/query%s' % self.path_suffix body = self.api.post(url, json=query).json() if body: return [self.resource_class(self, b) for b in body] else: return [] class QuerySamplesManager(QueryManager): resource_class = samples.Sample path_suffix = '/samples' class QueryAlarmsManager(QueryManager): resource_class = alarms.Alarm path_suffix = '/alarms' class QueryAlarmHistoryManager(QueryManager): resource_class = alarms.AlarmChange path_suffix = '/alarms/history' python-ceilometerclient-2.9.0/ceilometerclient/v2/traits.py0000664000175000017500000000172713117513017025262 0ustar jenkinsjenkins00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ceilometerclient.common import base class Trait(base.Resource): def __repr__(self): return "" % self._info class TraitManager(base.Manager): resource_class = Trait def list(self, event_type, trait_name): path = '/v2/event_types/%s/traits/%s' % (event_type, trait_name) return self._list(path) python-ceilometerclient-2.9.0/ceilometerclient/v2/shell.py0000664000175000017500000016203413117513017025062 0ustar jenkinsjenkins00000000000000# # Copyright 2013-2016 Red Hat, Inc # Copyright Ericsson AB 2014. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import functools import json import warnings from oslo_serialization import jsonutils from oslo_utils import strutils import six from six import moves from ceilometerclient.common import utils from ceilometerclient import exc from ceilometerclient.v2 import options ALARM_STATES = ['ok', 'alarm', 'insufficient data'] ALARM_SEVERITY = ['low', 'moderate', 'critical'] ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt'] ALARM_COMBINATION_OPERATORS = ['and', 'or'] STATISTICS = ['max', 'min', 'avg', 'sum', 'count'] GNOCCHI_AGGREGATION = ['last', 'min', 'median', 'sum', 'std', 'first', 'mean', 'count', 'moving-average', 'max'] GNOCCHI_AGGREGATION.extend(['%spct' % num for num in moves.xrange(1, 100)]) AGGREGATES = {'avg': 'Avg', 'count': 'Count', 'max': 'Max', 'min': 'Min', 'sum': 'Sum', 'stddev': 'Standard deviation', 'cardinality': 'Cardinality'} OPERATORS_STRING = dict(gt='>', ge='>=', lt='<', le="<=", eq='==', ne='!=') ORDER_DIRECTIONS = ['asc', 'desc'] COMPLEX_OPERATORS = ['and', 'or'] SIMPLE_OPERATORS = ["=", "!=", "<", "<=", '>', '>='] DEFAULT_API_LIMIT = ('API server limits result to ' 'rows if no limit provided. Option is configured in ' 'ceilometer.conf [api] group') class NotEmptyAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): values = values or getattr(namespace, self.dest) if not values or values.isspace(): raise exc.CommandError('%s should not be empty' % self.dest) setattr(namespace, self.dest, values) def obsoleted_by(new_dest): class ObsoletedByAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): old_dest = option_string or self.dest print('%s is obsolete! See help for more details.' % old_dest) setattr(namespace, new_dest, values) return ObsoletedByAction @utils.arg('-q', '--query', metavar='', help='key[op]data_type::value; list. data_type is optional, ' 'but if supplied must be string, integer, float, or boolean.') @utils.arg('-m', '--meter', metavar='', required=True, action=NotEmptyAction, help='Name of meter to list statistics for.') @utils.arg('-p', '--period', metavar='', help='Period in seconds over which to group samples.') @utils.arg('-g', '--groupby', metavar='', action='append', help='Field for group by.') @utils.arg('-a', '--aggregate', metavar='[<-]', action='append', default=[], help=('Function for data aggregation. ' 'Available aggregates are: ' '%s.' % ", ".join(AGGREGATES.keys()))) def do_statistics(cc, args): """List the statistics for a meter.""" aggregates = [] for a in args.aggregate: aggregates.append(dict(zip(('func', 'param'), a.split("<-")))) api_args = {'meter_name': args.meter, 'q': options.cli_to_array(args.query), 'period': args.period, 'groupby': args.groupby, 'aggregates': aggregates} try: statistics = cc.statistics.list(**api_args) except exc.HTTPNotFound: raise exc.CommandError('Samples not found: %s' % args.meter) else: fields_display = {'duration': 'Duration', 'duration_end': 'Duration End', 'duration_start': 'Duration Start', 'period': 'Period', 'period_end': 'Period End', 'period_start': 'Period Start', 'groupby': 'Group By'} fields_display.update(AGGREGATES) fields = ['period', 'period_start', 'period_end'] if args.groupby: fields.append('groupby') if args.aggregate: for a in aggregates: if 'param' in a: fields.append("%(func)s/%(param)s" % a) else: fields.append(a['func']) for stat in statistics: stat.__dict__.update(stat.aggregate) else: fields.extend(['max', 'min', 'avg', 'sum', 'count']) fields.extend(['duration', 'duration_start', 'duration_end']) cols = [fields_display.get(f, f) for f in fields] utils.print_list(statistics, fields, cols) @utils.arg('-q', '--query', metavar='', help='key[op]data_type::value; list. data_type is optional, ' 'but if supplied must be string, integer, float, or boolean.') @utils.arg('-m', '--meter', metavar='', action=NotEmptyAction, help='Name of meter to show samples for.') @utils.arg('-l', '--limit', metavar='', help='Maximum number of samples to return. %s' % DEFAULT_API_LIMIT) def do_sample_list(cc, args): """List the samples (return OldSample objects if -m/--meter is set).""" if not args.meter: return _do_sample_list(cc, args) else: return _do_old_sample_list(cc, args) def _do_old_sample_list(cc, args): fields = {'meter_name': args.meter, 'q': options.cli_to_array(args.query), 'limit': args.limit} try: samples = cc.samples.list(**fields) except exc.HTTPNotFound: raise exc.CommandError('Samples not found: %s' % args.meter) else: field_labels = ['Resource ID', 'Name', 'Type', 'Volume', 'Unit', 'Timestamp'] fields = ['resource_id', 'counter_name', 'counter_type', 'counter_volume', 'counter_unit', 'timestamp'] utils.print_list(samples, fields, field_labels, sortby=None) def _do_sample_list(cc, args): fields = { 'q': options.cli_to_array(args.query), 'limit': args.limit } samples = cc.new_samples.list(**fields) field_labels = ['ID', 'Resource ID', 'Name', 'Type', 'Volume', 'Unit', 'Timestamp'] fields = ['id', 'resource_id', 'meter', 'type', 'volume', 'unit', 'timestamp'] utils.print_list(samples, fields, field_labels, sortby=None) @utils.arg('sample_id', metavar='', action=NotEmptyAction, help='ID (aka message ID) of the sample to show.') def do_sample_show(cc, args): '''Show a sample.''' try: sample = cc.new_samples.get(args.sample_id) except exc.HTTPNotFound: raise exc.CommandError('Sample not found: %s' % args.sample_id) fields = ['id', 'meter', 'volume', 'type', 'unit', 'source', 'resource_id', 'user_id', 'project_id', 'timestamp', 'recorded_at', 'metadata'] data = dict((f, getattr(sample, f, '')) for f in fields) utils.print_dict(data, wrap=72) def _restore_shadowed_arg(shadowed, observed): def wrapper(func): @functools.wraps(func) def wrapped(cc, args): v = getattr(args, observed, None) setattr(args, shadowed, v) return func(cc, args) return wrapped return wrapper @utils.arg('--project-id', metavar='', dest='sample_project_id', help='Tenant to associate with sample ' '(configurable by admin users only).') @utils.arg('--user-id', metavar='', dest='sample_user_id', help='User to associate with sample ' '(configurable by admin users only).') @utils.arg('-r', '--resource-id', metavar='', required=True, help='ID of the resource.') @utils.arg('-m', '--meter-name', metavar='', required=True, action=NotEmptyAction, help='The meter name.') @utils.arg('--meter-type', metavar='', required=True, help='The meter type.') @utils.arg('--meter-unit', metavar='', required=True, help='The meter unit.') @utils.arg('--sample-volume', metavar='', required=True, help='The sample volume.') @utils.arg('--resource-metadata', metavar='', help='Resource metadata. Provided value should be a set of ' 'key-value pairs e.g. {"key":"value"}.') @utils.arg('--timestamp', metavar='', help='The sample timestamp.') @utils.arg('--direct', metavar='', default=False, help='Post sample to storage directly.') @_restore_shadowed_arg('project_id', 'sample_project_id') @_restore_shadowed_arg('user_id', 'sample_user_id') def do_sample_create(cc, args={}): """Create a sample.""" arg_to_field_mapping = { 'meter_name': 'counter_name', 'meter_unit': 'counter_unit', 'meter_type': 'counter_type', 'sample_volume': 'counter_volume', } fields = {} for var in vars(args).items(): k, v = var[0], var[1] if v is not None: if k == 'resource_metadata': try: fields[k] = json.loads(v) except ValueError: msg = ('Invalid resource metadata, it should be a json' ' string, like: \'{"foo":"bar"}\'') raise exc.CommandError(msg) else: fields[arg_to_field_mapping.get(k, k)] = v sample = cc.samples.create(**fields) fields = ['counter_name', 'user_id', 'resource_id', 'timestamp', 'message_id', 'source', 'counter_unit', 'counter_volume', 'project_id', 'resource_metadata', 'counter_type'] data = dict([(f.replace('counter_', ''), getattr(sample[0], f, '')) for f in fields]) utils.print_dict(data, wrap=72) @utils.arg('-q', '--query', metavar='', help='key[op]data_type::value; list. data_type is optional, ' 'but if supplied must be string, integer, float, or boolean.') @utils.arg('-l', '--limit', metavar='', help='Maximum number of meters to return. %s' % DEFAULT_API_LIMIT) @utils.arg('--unique', dest='unique', metavar='{True|False}', type=lambda v: strutils.bool_from_string(v, True), help='Retrieves unique list of meters.') def do_meter_list(cc, args={}): """List the user's meters.""" meters = cc.meters.list(q=options.cli_to_array(args.query), limit=args.limit, unique=args.unique) field_labels = ['Name', 'Type', 'Unit', 'Resource ID', 'User ID', 'Project ID'] fields = ['name', 'type', 'unit', 'resource_id', 'user_id', 'project_id'] utils.print_list(meters, fields, field_labels, sortby=0) @utils.arg('samples_list', metavar='', action=NotEmptyAction, help='Json array with samples to create.') @utils.arg('--direct', metavar='', default=False, help='Post samples to storage directly.') def do_sample_create_list(cc, args={}): """Create a sample list.""" sample_list_array = json.loads(args.samples_list) samples = cc.samples.create_list(sample_list_array, direct=args.direct) field_labels = ['Resource ID', 'Name', 'Type', 'Volume', 'Unit', 'Timestamp'] fields = ['resource_id', 'counter_name', 'counter_type', 'counter_volume', 'counter_unit', 'timestamp'] utils.print_list(samples, fields, field_labels, sortby=None) def _display_alarm_list(alarms, sortby=None): # omit action initially to keep output width sane # (can switch over to vertical formatting when available from CLIFF) field_labels = ['Alarm ID', 'Name', 'State', 'Severity', 'Enabled', 'Continuous', 'Alarm condition', 'Time constraints'] fields = ['alarm_id', 'name', 'state', 'severity', 'enabled', 'repeat_actions', 'rule', 'time_constraints'] utils.print_list( alarms, fields, field_labels, formatters={'rule': alarm_rule_formatter, 'time_constraints': time_constraints_formatter_brief}, sortby=sortby) def _display_rule(type, rule): if type == 'threshold': return ('%(statistic)s(%(meter_name)s) %(comparison_operator)s ' '%(threshold)s during %(evaluation_periods)s x %(period)ss' % { 'meter_name': rule['meter_name'], 'threshold': rule['threshold'], 'statistic': rule['statistic'], 'evaluation_periods': rule['evaluation_periods'], 'period': rule['period'], 'comparison_operator': OPERATORS_STRING.get( rule['comparison_operator']) }) elif type == 'combination': return ('combinated states (%(operator)s) of %(alarms)s' % { 'operator': rule['operator'].upper(), 'alarms': ", ".join(rule['alarm_ids'])}) else: # just dump all return "\n".join(["%s: %s" % (f, v) for f, v in six.iteritems(rule)]) def alarm_rule_formatter(alarm): return _display_rule(alarm.type, alarm.rule) def _display_time_constraints_brief(time_constraints): if time_constraints: return ', '.join('%(name)s at %(start)s %(timezone)s for %(duration)ss' % { 'name': tc['name'], 'start': tc['start'], 'duration': tc['duration'], 'timezone': tc.get('timezone', '') } for tc in time_constraints) else: return 'None' def time_constraints_formatter_brief(alarm): return _display_time_constraints_brief(getattr(alarm, 'time_constraints', None)) def _infer_type(detail): if 'type' in detail: return detail['type'] elif 'meter_name' in detail['rule']: return 'threshold' elif 'alarms' in detail['rule']: return 'combination' else: return 'unknown' def alarm_change_detail_formatter(change): detail = json.loads(change.detail) fields = [] if change.type == 'state transition': fields.append('state: %s' % detail['state']) elif change.type == 'creation' or change.type == 'deletion': for k in ['name', 'description', 'type', 'rule', 'severity']: if k == 'rule': fields.append('rule: %s' % _display_rule(detail['type'], detail[k])) else: fields.append('%s: %s' % (k, detail[k])) if 'time_constraints' in detail: fields.append('time_constraints: %s' % _display_time_constraints_brief( detail['time_constraints'])) elif change.type == 'rule change': for k, v in six.iteritems(detail): if k == 'rule': fields.append('rule: %s' % _display_rule(_infer_type(detail), v)) else: fields.append('%s: %s' % (k, v)) return '\n'.join(fields) @utils.arg('-q', '--query', metavar='', help='key[op]data_type::value; list. data_type is optional, ' 'but if supplied must be string, integer, float, or boolean.') def do_alarm_list(cc, args={}): """List the user's alarms.""" warnings.warn("Alarm commands are deprecated, please use aodhclient") alarms = cc.alarms.list(q=options.cli_to_array(args.query)) _display_alarm_list(alarms, sortby=0) def alarm_query_formater(alarm): qs = [] for q in alarm.rule['query']: qs.append('%s %s %s' % ( q['field'], OPERATORS_STRING.get(q['op']), q['value'])) return r' AND\n'.join(qs) def time_constraints_formatter_full(alarm): time_constraints = [] for tc in alarm.time_constraints: lines = [] for k in ['name', 'description', 'start', 'duration', 'timezone']: if k in tc and tc[k]: lines.append(r'%s: %s' % (k, tc[k])) time_constraints.append('{' + r',\n '.join(lines) + '}') return '[' + r',\n '.join(time_constraints) + ']' def _display_alarm(alarm): fields = ['name', 'description', 'type', 'state', 'severity', 'enabled', 'alarm_id', 'user_id', 'project_id', 'alarm_actions', 'ok_actions', 'insufficient_data_actions', 'repeat_actions'] data = dict([(f, getattr(alarm, f, '')) for f in fields]) data.update(alarm.rule) if alarm.type == 'threshold': data['query'] = alarm_query_formater(alarm) if alarm.time_constraints: data['time_constraints'] = time_constraints_formatter_full(alarm) utils.print_dict(data, wrap=72) @utils.arg('-a', '--alarm_id', metavar='', action=obsoleted_by('alarm_id'), help=argparse.SUPPRESS, dest='alarm_id_deprecated') @utils.arg('alarm_id', metavar='', nargs='?', action=NotEmptyAction, help='ID of the alarm to show.') def do_alarm_show(cc, args={}): """Show an alarm.""" warnings.warn("Alarm commands are deprecated, please use aodhclient") alarm = cc.alarms.get(args.alarm_id) # alarm.get actually catches the HTTPNotFound exception and turns the # result into None if the alarm wasn't found. if alarm is None: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) else: _display_alarm(alarm) def common_alarm_arguments(create=False): def _wrapper(func): @utils.arg('--name', metavar='', required=create, help='Name of the alarm (must be unique per tenant).') @utils.arg('--project-id', metavar='', dest='alarm_project_id', help='Tenant to associate with alarm ' '(configurable by admin users only).') @utils.arg('--user-id', metavar='', dest='alarm_user_id', help='User to associate with alarm ' '(configurable by admin users only).') @utils.arg('--description', metavar='', help='Free text description of the alarm.') @utils.arg('--state', metavar='', help='State of the alarm, one of: ' + str(ALARM_STATES)) @utils.arg('--severity', metavar='', help='Severity of the alarm, one of: ' + str(ALARM_SEVERITY)) @utils.arg('--enabled', type=lambda v: strutils.bool_from_string(v, True), metavar='{True|False}', help='True if alarm evaluation/actioning is enabled.') @utils.arg('--alarm-action', dest='alarm_actions', metavar='', action='append', default=None, help=('URL to invoke when state transitions to alarm. ' 'May be used multiple times.')) @utils.arg('--ok-action', dest='ok_actions', metavar='', action='append', default=None, help=('URL to invoke when state transitions to OK. ' 'May be used multiple times.')) @utils.arg('--insufficient-data-action', dest='insufficient_data_actions', metavar='', action='append', default=None, help=('URL to invoke when state transitions to ' 'insufficient data. May be used multiple times.')) @utils.arg('--time-constraint', dest='time_constraints', metavar='