From 78e1ad153b76c6e7acb288cbeebf9ceb81ea6938 Mon Sep 17 00:00:00 2001 From: Tony Breeds Date: Tue, 12 Sep 2017 16:00:22 -0600 Subject: [PATCH] Retire Packaging Deb project repos This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: Ifbec28e1510fd2fa5a48b471458c16b5a1ef8ad3 --- .gitignore | 35 - .gitreview | 4 - .testr.conf | 10 - CONTRIBUTING.rst | 10 - LICENSE | 202 ---- MANIFEST.in | 6 - README | 14 + README.rst | 23 - TESTING.rst | 88 -- doc/source/conf.py | 79 -- doc/source/index.rst | 88 -- etc/rootwrap.d/ironic-lib.filters | 26 - ironic_lib/__init__.py | 22 - ironic_lib/common/__init__.py | 0 ironic_lib/common/i18n.py | 31 - ironic_lib/disk_partitioner.py | 183 --- ironic_lib/disk_utils.py | 765 ------------- ironic_lib/exception.py | 106 -- ironic_lib/metrics.py | 305 ----- ironic_lib/metrics_statsd.py | 108 -- ironic_lib/metrics_utils.py | 100 -- ironic_lib/tests/__init__.py | 0 ironic_lib/tests/test_disk_partitioner.py | 178 --- ironic_lib/tests/test_disk_utils.py | 1242 --------------------- ironic_lib/tests/test_metrics.py | 158 --- ironic_lib/tests/test_metrics_statsd.py | 96 -- ironic_lib/tests/test_metrics_utils.py | 108 -- ironic_lib/tests/test_utils.py | 543 --------- ironic_lib/utils.py | 407 ------- ironic_lib/version.py | 18 - requirements.txt | 13 - setup.cfg | 38 - setup.py | 29 - test-requirements.txt | 17 - tools/tox_install.sh | 55 - tox.ini | 41 - 36 files changed, 14 insertions(+), 5134 deletions(-) delete mode 100644 .gitignore delete mode 100644 .gitreview delete mode 100644 .testr.conf delete mode 100644 CONTRIBUTING.rst delete mode 100644 LICENSE delete mode 100644 MANIFEST.in create mode 100644 README delete mode 100644 README.rst delete mode 100644 TESTING.rst delete mode 100644 doc/source/conf.py delete mode 100644 doc/source/index.rst delete mode 100644 etc/rootwrap.d/ironic-lib.filters delete mode 100644 ironic_lib/__init__.py delete mode 100644 ironic_lib/common/__init__.py delete mode 100644 ironic_lib/common/i18n.py delete mode 100644 ironic_lib/disk_partitioner.py delete mode 100644 ironic_lib/disk_utils.py delete mode 100644 ironic_lib/exception.py delete mode 100644 ironic_lib/metrics.py delete mode 100644 ironic_lib/metrics_statsd.py delete mode 100644 ironic_lib/metrics_utils.py delete mode 100644 ironic_lib/tests/__init__.py delete mode 100644 ironic_lib/tests/test_disk_partitioner.py delete mode 100644 ironic_lib/tests/test_disk_utils.py delete mode 100644 ironic_lib/tests/test_metrics.py delete mode 100644 ironic_lib/tests/test_metrics_statsd.py delete mode 100644 ironic_lib/tests/test_metrics_utils.py delete mode 100644 ironic_lib/tests/test_utils.py delete mode 100644 ironic_lib/utils.py delete mode 100644 ironic_lib/version.py delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100755 tools/tox_install.sh delete mode 100644 tox.ini diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 3c2b429..0000000 --- a/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# Compiled files -*.py[co] -*.a -*.o -*.so - -# Sphinx -_build -doc/source/api/ - -# Packages/installer info -*.egg -*.egg-info -dist -build -eggs -parts -var -sdist -develop-eggs -.installed.cfg - -# Other -*.DS_Store -.testrepository -.tox -.venv -.*.swp -.coverage -cover -AUTHORS -ChangeLog -*.sqlite -*~ -.idea diff --git a/.gitreview b/.gitreview deleted file mode 100644 index 5f0700d..0000000 --- a/.gitreview +++ /dev/null @@ -1,4 +0,0 @@ -[gerrit] -host=review.openstack.org -port=29418 -project=openstack/ironic-lib.git diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 74f1139..0000000 --- a/.testr.conf +++ /dev/null @@ -1,10 +0,0 @@ -[DEFAULT] -test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ - OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ - OS_LOG_CAPTURE=${OS_LOG_CAPTURE:-1} \ - OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - OS_DEBUG=${OS_DEBUG:-0} \ - ${PYTHON:-python} -m subunit.run discover -t ./ $LISTOPT $IDOPTION - -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 8f185c1..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,10 +0,0 @@ -If you would like to contribute to the development of OpenStack, -you must follow the steps documented at: - - http://docs.openstack.org/infra/manual/developers.html#development-workflow - -Pull requests submitted through GitHub will be ignored. - -Bugs should be filed on Launchpad, not GitHub: - - https://bugs.launchpad.net/ironic-lib diff --git a/LICENSE b/LICENSE deleted file mode 100644 index e06d208..0000000 --- a/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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. - diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index c978a52..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include AUTHORS -include ChangeLog -exclude .gitignore -exclude .gitreview - -global-exclude *.pyc diff --git a/README b/README new file mode 100644 index 0000000..8fcd2b2 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +For ongoing work on maintaining OpenStack packages in the Debian +distribution, please see the Debian OpenStack packaging team at +https://wiki.debian.org/OpenStack/. + +For any further questions, please email +openstack-dev@lists.openstack.org or join #openstack-dev on +Freenode. diff --git a/README.rst b/README.rst deleted file mode 100644 index f2bef1b..0000000 --- a/README.rst +++ /dev/null @@ -1,23 +0,0 @@ ----------- -ironic_lib ----------- - -Overview --------- - -A common library to be used **exclusively** by projects under the `Ironic -governance `_. - -Running Tests -------------- - -To run tests in virtualenvs (preferred):: - - sudo pip install tox - tox - -To run tests in the current environment:: - - sudo pip install -r requirements.txt - nosetests - diff --git a/TESTING.rst b/TESTING.rst deleted file mode 100644 index 4191b1b..0000000 --- a/TESTING.rst +++ /dev/null @@ -1,88 +0,0 @@ -=========================== -Testing Your OpenStack Code -=========================== ------------- -A Quickstart ------------- - -This is designed to be enough information for you to run your first tests. -Detailed information on testing can be found here: https://wiki.openstack.org/wiki/Testing - -*Install pip*:: - - [apt-get | yum] install python-pip -More information on pip here: http://www.pip-installer.org/en/latest/ - -*Use pip to install tox*:: - - pip install tox - -Run The Tests -------------- - -*Navigate to the project's root directory and execute*:: - - tox -Note: completing this command may take a long time (depends on system resources) -also, you might not see any output until tox is complete. - -Information about tox can be found here: http://testrun.org/tox/latest/ - - -Run The Tests in One Environment --------------------------------- - -Tox will run your entire test suite in the environments specified in the project tox.ini:: - - [tox] - - envlist = - -To run the test suite in just one of the environments in envlist execute:: - - tox -e -so for example, *run the test suite in py26*:: - - tox -e py26 - -Run One Test ------------- - -To run individual tests with tox: - -if testr is in tox.ini, for example:: - - [testenv] - - includes "python setup.py testr --slowest --testr-args='{posargs}'" - -run individual tests with the following syntax:: - - tox -e -- path.to.module:Class.test -so for example, *run the cpu_limited test in Nova*:: - - tox -e py27 -- nova.tests.test_claims:ClaimTestCase.test_cpu_unlimited - -if nose is in tox.ini, for example:: - - [testenv] - - includes "nosetests {posargs}" - -run individual tests with the following syntax:: - - tox -e -- --tests path.to.module:Class.test -so for example, *run the list test in Glance*:: - - tox -e py27 -- --tests glance.tests.unit.test_auth.py:TestImageRepoProxy.test_list - -Need More Info? ---------------- - -More information about testr: https://wiki.openstack.org/wiki/Testr - -More information about nose: https://nose.readthedocs.org/en/latest/ - - -More information about testing OpenStack code can be found here: -https://wiki.openstack.org/wiki/Testing diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 1787335..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# - -# -- General configuration ---------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'oslosphinx', - ] - -wsme_protocols = ['restjson'] - -# autodoc generation is a bit aggressive and a nuisance when doing heavy -# text edit cycles. -# execute "export SPHINX_DEBUG=1" in your terminal to disable - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Ironic Lib' -copyright = u'OpenStack Foundation' - -# 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. -# -# The short X.Y version. -from ironic_lib import version as il_version -# The full version, including alpha/beta/rc tags. -release = il_version.version_info.release_string() -# The short X.Y version. -version = il_version.version_info.version_string() - -# A list of ignored prefixes for module index sorting. -modindex_common_prefix = ['ironic_lib'] - -# If true, '()' will be appended to :func: etc. cross-reference text. -add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = True - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# -- Options for HTML output -------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -#html_theme_path = ["."] -#html_theme = '_theme' -#html_static_path = ['_static'] - -# Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project - - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto/manual]). -latex_documents = [ - ( - 'index', - '%s.tex' % project, - u'%s Documentation' % project, - u'OpenStack Foundation', - 'manual' - ), -] diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index 973d158..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,88 +0,0 @@ -====================== -Welcome to Ironic-lib! -====================== - -Overview -======== - -Ironic-lib is a library for use by projects under Bare Metal governance only. -This documentation is intended for developer use only. If you are looking for -documentation for deployers, please see the -`ironic documentation `_. - -Metrics -======= - -Ironic-lib provides a pluggable metrics library as of the 2.0.0 release. -Current provided backends are the default, 'noop', which discards all data, -and 'statsd', which emits metrics to a statsd daemon over the network. The -metrics backend to be used is configured via ``CONF.metrics.backend``. How -this configuration is set in practice may vary by project. - -The typical usage of metrics is to initialize and cache a metrics logger, -using the `get_metrics_logger()` method in `ironic_lib.metrics_utils`, then -use that object to decorate functions or create context managers to gather -metrics. The general convention is to provide the name of the module as the -first argument to set it as the prefix, then set the actual metric name to the -method name. For example:: - - from ironic_lib import metrics_utils - - METRICS = metrics_utils.get_metrics_logger(__name__) - - @METRICS.timer('my_simple_method') - def my_simple_method(arg, matey): - pass - - def my_complex_method(arg, matey): - with METRICS.timer('complex_method_pt_1'): - do_some_work() - - with METRICS.timer('complex_method_pt_2'): - do_more_work() - -There are three different kinds of metrics: - - **Timers** measure how long the code in the decorated method or context - manager takes to execute, and emits the value as a timer metric. These - are useful for measuring performance of a given block of code. - - **Counters** increment a counter each time a decorated method or context - manager is executed. These are useful for counting the number of times a - method is called, or the number of times an event occurs. - - **Gauges** return the value of a decorated method as a metric. This is - useful when you want to monitor the value returned by a method over time. - -Additionally, metrics can be sent directly, rather than using a context -manager or decorator, when appropriate. When used in this way, ironic-lib will -simply emit the value provided as the requested metric type. For example:: - - from ironic_lib import metrics_utils - - METRICS = metrics_utils.get_metrics_logger(__name__) - - def my_node_failure_method(node): - if node.failed: - METRICS.send_counter(node.uuid, 1) - -The provided statsd backend natively supports all three metric types. For more -information about how statsd changes behavior based on the metric type, see -`statsd metric types `_ - - -Generated Developer Documentation -================================= - -.. toctree:: - :maxdepth: 1 - - api/autoindex - - -References -========== - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/etc/rootwrap.d/ironic-lib.filters b/etc/rootwrap.d/ironic-lib.filters deleted file mode 100644 index a9e26bb..0000000 --- a/etc/rootwrap.d/ironic-lib.filters +++ /dev/null @@ -1,26 +0,0 @@ -# An ironic-lib.filters to be used with rootwrap command. -# The following commands should be used in filters for disk manipulation. -# This file should be owned by (and only-writeable by) the root user. - -# NOTE: -# if you update this file, you will also need to adjust the -# ironic-lib.filters from the ironic module. - -[Filters] -# ironic_lib/disk_utils.py -blkid: CommandFilter, blkid, root -blockdev: CommandFilter, blockdev, root -hexdump: CommandFilter, hexdump, root -qemu-img: CommandFilter, qemu-img, root -wipefs: CommandFilter, wipefs, root -sgdisk: CommandFilter, sgdisk, root -partprobe: CommandFilter, partprobe, root - -# ironic_lib/utils.py -mkswap: CommandFilter, mkswap, root -mkfs: CommandFilter, mkfs, root -dd: CommandFilter, dd, root - -# ironic_lib/disk_partitioner.py -fuser: CommandFilter, fuser, root -parted: CommandFilter, parted, root diff --git a/ironic_lib/__init__.py b/ironic_lib/__init__.py deleted file mode 100644 index d441282..0000000 --- a/ironic_lib/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# This ensures the ironic_lib namespace is defined -try: - import pkg_resources - pkg_resources.declare_namespace(__name__) -except ImportError: - import pkgutil - __path__ = pkgutil.extend_path(__path__, __name__) diff --git a/ironic_lib/common/__init__.py b/ironic_lib/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ironic_lib/common/i18n.py b/ironic_lib/common/i18n.py deleted file mode 100644 index 0537acb..0000000 --- a/ironic_lib/common/i18n.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. -# 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 oslo_i18n as i18n - -_translators = i18n.TranslatorFactory(domain='ironic-lib') - -# The primary translation function using the well-known name "_" -_ = _translators.primary - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical diff --git a/ironic_lib/disk_partitioner.py b/ironic_lib/disk_partitioner.py deleted file mode 100644 index 644d05a..0000000 --- a/ironic_lib/disk_partitioner.py +++ /dev/null @@ -1,183 +0,0 @@ -# Copyright 2014 Red Hat, Inc. -# 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 re - -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_log import log as logging -from oslo_service import loopingcall - -from ironic_lib.common.i18n import _ -from ironic_lib.common.i18n import _LW -from ironic_lib import exception -from ironic_lib import utils - - -opts = [ - cfg.IntOpt('check_device_interval', - default=1, - help='After Ironic has completed creating the partition table, ' - 'it continues to check for activity on the attached iSCSI ' - 'device status at this interval prior to copying the image' - ' to the node, in seconds'), - cfg.IntOpt('check_device_max_retries', - default=20, - help='The maximum number of times to check that the device is ' - 'not accessed by another process. If the device is still ' - 'busy after that, the disk partitioning will be treated as' - ' having failed.') -] - -CONF = cfg.CONF -opt_group = cfg.OptGroup(name='disk_partitioner', - title='Options for the disk partitioner') -CONF.register_group(opt_group) -CONF.register_opts(opts, opt_group) - -LOG = logging.getLogger(__name__) - - -class DiskPartitioner(object): - - def __init__(self, device, disk_label='msdos', alignment='optimal'): - """A convenient wrapper around the parted tool. - - :param device: The device path. - :param disk_label: The type of the partition table. Valid types are: - "bsd", "dvh", "gpt", "loop", "mac", "msdos", - "pc98", or "sun". - :param alignment: Set alignment for newly created partitions. - Valid types are: none, cylinder, minimal and - optimal. - - """ - self._device = device - self._disk_label = disk_label - self._alignment = alignment - self._partitions = [] - self._fuser_pids_re = re.compile(r'((\d)+\s*)+') - - def _exec(self, *args): - # NOTE(lucasagomes): utils.execute() is already a wrapper on top - # of processutils.execute() which raises specific - # exceptions. It also logs any failure so we don't - # need to log it again here. - utils.execute('parted', '-a', self._alignment, '-s', self._device, - '--', 'unit', 'MiB', *args, check_exit_code=[0], - use_standard_locale=True, run_as_root=True) - - def add_partition(self, size, part_type='primary', fs_type='', - boot_flag=None): - """Add a partition. - - :param size: The size of the partition in MiB. - :param part_type: The type of the partition. Valid values are: - primary, logical, or extended. - :param fs_type: The filesystem type. Valid types are: ext2, fat32, - fat16, HFS, linux-swap, NTFS, reiserfs, ufs. - If blank (''), it will create a Linux native - partition (83). - :param boot_flag: Boot flag that needs to be configured on the - partition. Ignored if None. It can take values - 'bios_grub', 'boot'. - :returns: The partition number. - - """ - self._partitions.append({'size': size, - 'type': part_type, - 'fs_type': fs_type, - 'boot_flag': boot_flag}) - return len(self._partitions) - - def get_partitions(self): - """Get the partitioning layout. - - :returns: An iterator with the partition number and the - partition layout. - - """ - return enumerate(self._partitions, 1) - - def _wait_for_disk_to_become_available(self, retries, max_retries, pids, - stderr): - retries[0] += 1 - if retries[0] > max_retries: - raise loopingcall.LoopingCallDone() - - try: - # NOTE(ifarkas): fuser returns a non-zero return code if none of - # the specified files is accessed - out, err = utils.execute('fuser', self._device, - check_exit_code=[0, 1], run_as_root=True) - - if not out and not err: - raise loopingcall.LoopingCallDone() - else: - if err: - stderr[0] = err - if out: - pids_match = re.search(self._fuser_pids_re, out) - pids[0] = pids_match.group() - except processutils.ProcessExecutionError as exc: - LOG.warning(_LW('Failed to check the device %(device)s with fuser:' - ' %(err)s'), {'device': self._device, 'err': exc}) - - def commit(self): - """Write to the disk.""" - LOG.debug("Committing partitions to disk.") - cmd_args = ['mklabel', self._disk_label] - # NOTE(lucasagomes): Lead in with 1MiB to allow room for the - # partition table itself. - start = 1 - for num, part in self.get_partitions(): - end = start + part['size'] - cmd_args.extend(['mkpart', part['type'], part['fs_type'], - str(start), str(end)]) - if part['boot_flag']: - cmd_args.extend(['set', str(num), part['boot_flag'], 'on']) - start = end - - self._exec(*cmd_args) - - retries = [0] - pids = [''] - fuser_err = [''] - interval = CONF.disk_partitioner.check_device_interval - max_retries = CONF.disk_partitioner.check_device_max_retries - - timer = loopingcall.FixedIntervalLoopingCall( - self._wait_for_disk_to_become_available, - retries, max_retries, pids, fuser_err) - timer.start(interval=interval).wait() - - if retries[0] > max_retries: - if pids[0]: - raise exception.InstanceDeployFailure( - _('Disk partitioning failed on device %(device)s. ' - 'Processes with the following PIDs are holding it: ' - '%(pids)s. Time out waiting for completion.') - % {'device': self._device, 'pids': pids[0]}) - else: - raise exception.InstanceDeployFailure( - _('Disk partitioning failed on device %(device)s. Fuser ' - 'exited with "%(fuser_err)s". Time out waiting for ' - 'completion.') - % {'device': self._device, 'fuser_err': fuser_err[0]}) - - -def list_opts(): - """Entry point for oslo-config-generator.""" - return [('disk_partitioner', opts)] diff --git a/ironic_lib/disk_utils.py b/ironic_lib/disk_utils.py deleted file mode 100644 index 530afe0..0000000 --- a/ironic_lib/disk_utils.py +++ /dev/null @@ -1,765 +0,0 @@ -# Copyright 2014 Red Hat, Inc. -# 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 base64 -import gzip -import logging -import math -import os -import re -import requests -import shutil -import six -import stat -import tempfile -import time - -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_utils import excutils -from oslo_utils import imageutils -from oslo_utils import units - -from ironic_lib.common.i18n import _ -from ironic_lib.common.i18n import _LE -from ironic_lib.common.i18n import _LI -from ironic_lib.common.i18n import _LW -from ironic_lib import disk_partitioner -from ironic_lib import exception -from ironic_lib import utils - - -opts = [ - cfg.IntOpt('efi_system_partition_size', - default=200, - help='Size of EFI system partition in MiB when configuring ' - 'UEFI systems for local boot.'), - cfg.IntOpt('bios_boot_partition_size', - default=1, - help='Size of BIOS Boot partition in MiB when configuring ' - 'GPT partitioned systems for local boot in BIOS.'), - cfg.StrOpt('dd_block_size', - default='1M', - help='Block size to use when writing to the nodes disk.'), - cfg.IntOpt('iscsi_verify_attempts', - default=3, - help='Maximum attempts to verify an iSCSI connection is ' - 'active, sleeping 1 second between attempts.'), -] - -CONF = cfg.CONF -CONF.register_opts(opts, group='disk_utils') - -LOG = logging.getLogger(__name__) - -_PARTED_PRINT_RE = re.compile(r"^(\d+):([\d\.]+)MiB:" - "([\d\.]+)MiB:([\d\.]+)MiB:(\w*)::(\w*)") - -CONFIGDRIVE_LABEL = "config-2" -MAX_CONFIG_DRIVE_SIZE_MB = 64 - -# Maximum disk size supported by MBR is 2TB (2 * 1024 * 1024 MB) -MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR = 2097152 - - -def list_partitions(device): - """Get partitions information from given device. - - :param device: The device path. - :returns: list of dictionaries (one per partition) with keys: - number, start, end, size (in MiB), filesystem, flags - """ - output = utils.execute( - 'parted', '-s', '-m', device, 'unit', 'MiB', 'print', - use_standard_locale=True, run_as_root=True)[0] - if isinstance(output, bytes): - output = output.decode("utf-8") - lines = [line for line in output.split('\n') if line.strip()][2:] - # Example of line: 1:1.00MiB:501MiB:500MiB:ext4::boot - fields = ('number', 'start', 'end', 'size', 'filesystem', 'flags') - result = [] - for line in lines: - match = _PARTED_PRINT_RE.match(line) - if match is None: - LOG.warning(_LW("Partition information from parted for device " - "%(device)s does not match " - "expected format: %(line)s"), - dict(device=device, line=line)) - continue - # Cast int fields to ints (some are floats and we round them down) - groups = [int(float(x)) if i < 4 else x - for i, x in enumerate(match.groups())] - result.append(dict(zip(fields, groups))) - return result - - -def get_disk_identifier(dev): - """Get the disk identifier from the disk being exposed by the ramdisk. - - This disk identifier is appended to the pxe config which will then be - used by chain.c32 to detect the correct disk to chainload. This is helpful - in deployments to nodes with multiple disks. - - http://www.syslinux.org/wiki/index.php/Comboot/chain.c32#mbr: - - :param dev: Path for the already populated disk device. - :returns: The Disk Identifier. - """ - disk_identifier = utils.execute('hexdump', '-s', '440', '-n', '4', - '-e', '''\"0x%08x\"''', - dev, - run_as_root=True, - check_exit_code=[0], - attempts=5, - delay_on_retry=True) - return disk_identifier[0] - - -def is_iscsi_device(dev, node_uuid): - """check whether the device path belongs to an iscsi device. """ - - iscsi_id = "iqn.2008-10.org.openstack:%s" % node_uuid - return iscsi_id in dev - - -def make_partitions(dev, root_mb, swap_mb, ephemeral_mb, - configdrive_mb, node_uuid, commit=True, - boot_option="netboot", boot_mode="bios", - disk_label=None): - """Partition the disk device. - - Create partitions for root, swap, ephemeral and configdrive on a - disk device. - - :param root_mb: Size of the root partition in mebibytes (MiB). - :param swap_mb: Size of the swap partition in mebibytes (MiB). If 0, - no partition will be created. - :param ephemeral_mb: Size of the ephemeral partition in mebibytes (MiB). - If 0, no partition will be created. - :param configdrive_mb: Size of the configdrive partition in - mebibytes (MiB). If 0, no partition will be created. - :param commit: True/False. Default for this setting is True. If False - partitions will not be written to disk. - :param boot_option: Can be "local" or "netboot". "netboot" by default. - :param boot_mode: Can be "bios" or "uefi". "bios" by default. - :param node_uuid: Node's uuid. Used for logging. - :param disk_label: The disk label to be used when creating the - partition table. Valid values are: "msdos", "gpt" or None; If None - Ironic will figure it out according to the boot_mode parameter. - :returns: A dictionary containing the partition type as Key and partition - path as Value for the partitions created by this method. - - """ - LOG.debug("Starting to partition the disk device: %(dev)s " - "for node %(node)s", - {'dev': dev, 'node': node_uuid}) - # the actual device names in the baremetal are like /dev/sda, /dev/sdb etc. - # While for the iSCSI device, the naming convention has a format which has - # iqn also embedded in it. - # When this function is called by ironic-conductor, the iSCSI device name - # should be appended by "part%d". While on the baremetal, it should name - # the device partitions as /dev/sda1 and not /dev/sda-part1. - if is_iscsi_device(dev, node_uuid): - part_template = dev + '-part%d' - else: - part_template = dev + '%d' - part_dict = {} - - if disk_label is None: - disk_label = 'gpt' if boot_mode == 'uefi' else 'msdos' - - dp = disk_partitioner.DiskPartitioner(dev, disk_label=disk_label) - - # For uefi localboot, switch partition table to gpt and create the efi - # system partition as the first partition. - if boot_mode == "uefi" and boot_option == "local": - part_num = dp.add_partition(CONF.disk_utils.efi_system_partition_size, - fs_type='fat32', - boot_flag='boot') - part_dict['efi system partition'] = part_template % part_num - - if boot_mode == "bios" and boot_option == "local" and disk_label == "gpt": - part_num = dp.add_partition(CONF.disk_utils.bios_boot_partition_size, - boot_flag='bios_grub') - part_dict['BIOS Boot partition'] = part_template % part_num - - if ephemeral_mb: - LOG.debug("Add ephemeral partition (%(size)d MB) to device: %(dev)s " - "for node %(node)s", - {'dev': dev, 'size': ephemeral_mb, 'node': node_uuid}) - part_num = dp.add_partition(ephemeral_mb) - part_dict['ephemeral'] = part_template % part_num - if swap_mb: - LOG.debug("Add Swap partition (%(size)d MB) to device: %(dev)s " - "for node %(node)s", - {'dev': dev, 'size': swap_mb, 'node': node_uuid}) - part_num = dp.add_partition(swap_mb, fs_type='linux-swap') - part_dict['swap'] = part_template % part_num - if configdrive_mb: - LOG.debug("Add config drive partition (%(size)d MB) to device: " - "%(dev)s for node %(node)s", - {'dev': dev, 'size': configdrive_mb, 'node': node_uuid}) - part_num = dp.add_partition(configdrive_mb) - part_dict['configdrive'] = part_template % part_num - - # NOTE(lucasagomes): Make the root partition the last partition. This - # enables tools like cloud-init's growroot utility to expand the root - # partition until the end of the disk. - LOG.debug("Add root partition (%(size)d MB) to device: %(dev)s " - "for node %(node)s", - {'dev': dev, 'size': root_mb, 'node': node_uuid}) - - boot_val = None - if (boot_mode == "bios" and boot_option == "local" and - disk_label == "msdos"): - boot_val = 'boot' - - part_num = dp.add_partition(root_mb, boot_flag=boot_val) - - part_dict['root'] = part_template % part_num - - if commit: - # write to the disk - dp.commit() - return part_dict - - -def is_block_device(dev): - """Check whether a device is block or not.""" - attempts = CONF.disk_utils.iscsi_verify_attempts - for attempt in range(attempts): - try: - s = os.stat(dev) - except OSError as e: - LOG.debug("Unable to stat device %(dev)s. Attempt %(attempt)d " - "out of %(total)d. Error: %(err)s", - {"dev": dev, "attempt": attempt + 1, - "total": attempts, "err": e}) - time.sleep(1) - else: - return stat.S_ISBLK(s.st_mode) - msg = _("Unable to stat device %(dev)s after attempting to verify " - "%(attempts)d times.") % {'dev': dev, 'attempts': attempts} - LOG.error(msg) - raise exception.InstanceDeployFailure(msg) - - -def dd(src, dst): - """Execute dd from src to dst.""" - utils.dd(src, dst, 'bs=%s' % CONF.disk_utils.dd_block_size, 'oflag=direct') - - -def qemu_img_info(path): - """Return an object containing the parsed output from qemu-img info.""" - if not os.path.exists(path): - return imageutils.QemuImgInfo() - - out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', path) - return imageutils.QemuImgInfo(out) - - -def convert_image(source, dest, out_format, run_as_root=False): - """Convert image to other format.""" - cmd = ('qemu-img', 'convert', '-O', out_format, source, dest) - utils.execute(*cmd, run_as_root=run_as_root) - - -def populate_image(src, dst): - data = qemu_img_info(src) - if data.file_format == 'raw': - dd(src, dst) - else: - convert_image(src, dst, 'raw', True) - - -# TODO(rameshg87): Remove this one-line method and use utils.mkfs -# directly. -def mkfs(fs, dev, label=None): - """Execute mkfs on a device.""" - utils.mkfs(fs, dev, label) - - -def block_uuid(dev): - """Get UUID of a block device.""" - out, _err = utils.execute('blkid', '-s', 'UUID', '-o', 'value', dev, - run_as_root=True, - check_exit_code=[0]) - return out.strip() - - -def get_image_mb(image_path, virtual_size=True): - """Get size of an image in Megabyte.""" - mb = 1024 * 1024 - if not virtual_size: - image_byte = os.path.getsize(image_path) - else: - data = qemu_img_info(image_path) - image_byte = data.virtual_size - - # round up size to MB - image_mb = int((image_byte + mb - 1) / mb) - return image_mb - - -def get_dev_block_size(dev): - """Get the device size in 512 byte sectors.""" - block_sz, cmderr = utils.execute('blockdev', '--getsz', dev, - run_as_root=True, check_exit_code=[0]) - return int(block_sz) - - -def destroy_disk_metadata(dev, node_uuid): - """Destroy metadata structures on node's disk. - - Ensure that node's disk magic strings are wiped without zeroing the - entire drive. To do this we use the wipefs tool from util-linux. - - :param dev: Path for the device to work on. - :param node_uuid: Node's uuid. Used for logging. - """ - # NOTE(NobodyCam): This is needed to work around bug: - # https://bugs.launchpad.net/ironic/+bug/1317647 - LOG.debug("Start destroy disk metadata for node %(node)s.", - {'node': node_uuid}) - try: - utils.execute('wipefs', '--force', '--all', dev, - run_as_root=True, - use_standard_locale=True) - except processutils.ProcessExecutionError as e: - # NOTE(zhenguo): Check if --force option is supported for wipefs, - # if not, we should try without it. - if '--force' in str(e): - utils.execute('wipefs', '--all', dev, - run_as_root=True, - use_standard_locale=True) - else: - raise e - LOG.info(_LI("Disk metadata on %(dev)s successfully destroyed for node " - "%(node)s"), {'dev': dev, 'node': node_uuid}) - - -def _get_configdrive(configdrive, node_uuid, tempdir=None): - """Get the information about size and location of the configdrive. - - :param configdrive: Base64 encoded Gzipped configdrive content or - configdrive HTTP URL. - :param node_uuid: Node's uuid. Used for logging. - :param tempdir: temporary directory for the temporary configdrive file - :raises: InstanceDeployFailure if it can't download or decode the - config drive. - :returns: A tuple with the size in MiB and path to the uncompressed - configdrive file. - - """ - # Check if the configdrive option is a HTTP URL or the content directly - is_url = utils.is_http_url(configdrive) - if is_url: - try: - data = requests.get(configdrive).content - except requests.exceptions.RequestException as e: - raise exception.InstanceDeployFailure( - _("Can't download the configdrive content for node %(node)s " - "from '%(url)s'. Reason: %(reason)s") % - {'node': node_uuid, 'url': configdrive, 'reason': e}) - else: - data = configdrive - - try: - data = six.BytesIO(base64.b64decode(data)) - except TypeError: - error_msg = (_('Config drive for node %s is not base64 encoded ' - 'or the content is malformed.') % node_uuid) - if is_url: - error_msg += _(' Downloaded from "%s".') % configdrive - raise exception.InstanceDeployFailure(error_msg) - - configdrive_file = tempfile.NamedTemporaryFile(delete=False, - prefix='configdrive', - dir=tempdir) - configdrive_mb = 0 - with gzip.GzipFile('configdrive', 'rb', fileobj=data) as gunzipped: - try: - shutil.copyfileobj(gunzipped, configdrive_file) - except EnvironmentError as e: - # Delete the created file - utils.unlink_without_raise(configdrive_file.name) - raise exception.InstanceDeployFailure( - _('Encountered error while decompressing and writing ' - 'config drive for node %(node)s. Error: %(exc)s') % - {'node': node_uuid, 'exc': e}) - else: - # Get the file size and convert to MiB - configdrive_file.seek(0, os.SEEK_END) - bytes_ = configdrive_file.tell() - configdrive_mb = int(math.ceil(float(bytes_) / units.Mi)) - finally: - configdrive_file.close() - - return (configdrive_mb, configdrive_file.name) - - -def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, - image_path, node_uuid, preserve_ephemeral=False, - configdrive=None, boot_option="netboot", boot_mode="bios", - tempdir=None, disk_label=None): - """Create partitions and copy an image to the root partition. - - :param dev: Path for the device to work on. - :param root_mb: Size of the root partition in megabytes. - :param swap_mb: Size of the swap partition in megabytes. - :param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0, - no ephemeral partition will be created. - :param ephemeral_format: The type of file system to format the ephemeral - partition. - :param image_path: Path for the instance's disk image. - :param node_uuid: node's uuid. Used for logging. - :param preserve_ephemeral: If True, no filesystem is written to the - ephemeral block device, preserving whatever content it had (if the - partition table has not changed). - :param configdrive: Optional. Base64 encoded Gzipped configdrive content - or configdrive HTTP URL. - :param boot_option: Can be "local" or "netboot". "netboot" by default. - :param boot_mode: Can be "bios" or "uefi". "bios" by default. - :param tempdir: A temporary directory - :param disk_label: The disk label to be used when creating the - partition table. Valid values are: "msdos", "gpt" or None; If None - Ironic will figure it out according to the boot_mode parameter. - :returns: a dictionary containing the following keys: - 'root uuid': UUID of root partition - 'efi system partition uuid': UUID of the uefi system partition - (if boot mode is uefi). - NOTE: If key exists but value is None, it means partition doesn't - exist. - """ - # the only way for preserve_ephemeral to be set to true is if we are - # rebuilding an instance with --preserve_ephemeral. - commit = not preserve_ephemeral - # now if we are committing the changes to disk clean first. - if commit: - destroy_disk_metadata(dev, node_uuid) - - try: - # If requested, get the configdrive file and determine the size - # of the configdrive partition - configdrive_mb = 0 - configdrive_file = None - if configdrive: - configdrive_mb, configdrive_file = _get_configdrive( - configdrive, node_uuid, tempdir=tempdir) - - part_dict = make_partitions(dev, root_mb, swap_mb, ephemeral_mb, - configdrive_mb, node_uuid, - commit=commit, - boot_option=boot_option, - boot_mode=boot_mode, - disk_label=disk_label) - LOG.info(_LI("Successfully completed the disk device" - " %(dev)s partitioning for node %(node)s"), - {'dev': dev, "node": node_uuid}) - - ephemeral_part = part_dict.get('ephemeral') - swap_part = part_dict.get('swap') - configdrive_part = part_dict.get('configdrive') - root_part = part_dict.get('root') - - if not is_block_device(root_part): - raise exception.InstanceDeployFailure( - _("Root device '%s' not found") % root_part) - - for part in ('swap', 'ephemeral', 'configdrive', - 'efi system partition'): - part_device = part_dict.get(part) - LOG.debug("Checking for %(part)s device (%(dev)s) on node " - "%(node)s.", {'part': part, 'dev': part_device, - 'node': node_uuid}) - if part_device and not is_block_device(part_device): - raise exception.InstanceDeployFailure( - _("'%(partition)s' device '%(part_device)s' not found") % - {'partition': part, 'part_device': part_device}) - - # If it's a uefi localboot, then we have created the efi system - # partition. Create a fat filesystem on it. - if boot_mode == "uefi" and boot_option == "local": - efi_system_part = part_dict.get('efi system partition') - mkfs(dev=efi_system_part, fs='vfat', label='efi-part') - - if configdrive_part: - # Copy the configdrive content to the configdrive partition - dd(configdrive_file, configdrive_part) - LOG.info(_LI("Configdrive for node %(node)s successfully copied " - "onto partition %(partition)s"), - {'node': node_uuid, 'partition': configdrive_part}) - - finally: - # If the configdrive was requested make sure we delete the file - # after copying the content to the partition - if configdrive_file: - utils.unlink_without_raise(configdrive_file) - - populate_image(image_path, root_part) - LOG.info(_LI("Image for %(node)s successfully populated"), - {'node': node_uuid}) - - if swap_part: - mkfs(dev=swap_part, fs='swap', label='swap1') - LOG.info(_LI("Swap partition %(swap)s successfully formatted " - "for node %(node)s"), - {'swap': swap_part, 'node': node_uuid}) - - if ephemeral_part and not preserve_ephemeral: - mkfs(dev=ephemeral_part, fs=ephemeral_format, label="ephemeral0") - LOG.info(_LI("Ephemeral partition %(ephemeral)s successfully " - "formatted for node %(node)s"), - {'ephemeral': ephemeral_part, 'node': node_uuid}) - - uuids_to_return = { - 'root uuid': root_part, - 'efi system partition uuid': part_dict.get('efi system partition') - } - - try: - for part, part_dev in uuids_to_return.items(): - if part_dev: - uuids_to_return[part] = block_uuid(part_dev) - - except processutils.ProcessExecutionError: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Failed to detect %s"), part) - - return uuids_to_return - - -def list_opts(): - """Entry point for oslo-config-generator.""" - return [('disk_utils', opts)] - - -def _is_disk_larger_than_max_size(device, node_uuid): - """Check if total disk size exceeds 2TB msdos limit - - :param device: device path. - :param node_uuid: node's uuid. Used for logging. - :raises: InstanceDeployFailure, if any disk partitioning related - commands fail. - :returns: True if total disk size exceeds 2TB. Returns False otherwise. - """ - try: - disksize_bytes = utils.execute('blockdev', '--getsize64', device, - use_standard_locale=True, - run_as_root=True) - except (processutils.UnknownArgumentError, - processutils.ProcessExecutionError, OSError) as e: - msg = (_('Failed to get size of disk %(disk)s for node %(node)s. ' - 'Error: %(error)s') % - {'disk': device, 'node': node_uuid, 'error': e}) - LOG.error(msg) - raise exception.InstanceDeployFailure(msg) - - disksize_mb = int(disksize_bytes) // 1024 // 1024 - - return disksize_mb > MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR - - -def _get_labelled_partition(device, label, node_uuid): - """Check and return if partition with given label exists - - :param device: The device path. - :param label: Partition label - :param node_uuid: UUID of the Node. Used for logging. - :raises: InstanceDeployFailure, if any disk partitioning related - commands fail. - :returns: block device file for partition if it exists; otherwise it - returns None. - """ - try: - utils.execute('partprobe', device, run_as_root=True) - label_arg = 'LABEL=%s' % label - output, err = utils.execute('blkid', '-o', 'device', device, - '-t', label_arg, check_exit_code=[0, 2], - use_standard_locale=True, run_as_root=True) - except (processutils.UnknownArgumentError, - processutils.ProcessExecutionError, OSError) as e: - msg = (_('Failed to retrieve partition labels on disk %(disk)s ' - 'for node %(node)s. Error: %(error)s') % - {'disk': device, 'node': node_uuid, 'error': e}) - LOG.error(msg) - raise exception.InstanceDeployFailure(msg) - - if output: - if len(output.split()) > 1: - raise exception.InstanceDeployFailure( - _('More than one config drive exists on device %(device)s ' - 'for node %(node)s.') - % {'device': device, 'node': node_uuid}) - - return output.rstrip() - - -def _is_disk_gpt_partitioned(device, node_uuid): - """Checks if the disk is GPT partitioned - - :param device: The device path. - :param node_uuid: UUID of the Node. Used for logging. - :raises: InstanceDeployFailure, if any disk partitioning related - commands fail. - :param node_uuid: UUID of the Node - :returns: Boolean. Returns True if disk is GPT partitioned - """ - try: - output = utils.execute('blkid', '-p', '-o', 'value', '-s', 'PTTYPE', - device, use_standard_locale=True, - run_as_root=True) - except (processutils.UnknownArgumentError, - processutils.ProcessExecutionError, OSError) as e: - msg = (_('Failed to retrieve partition table type for disk %(disk)s ' - 'for node %(node)s. Error: %(error)s') % - {'disk': device, 'node': node_uuid, 'error': e}) - LOG.error(msg) - raise exception.InstanceDeployFailure(msg) - - return 'gpt' in output - - -def _fix_gpt_structs(device, node_uuid): - """Checks backup GPT data structures and moves them to end of the device - - :param device: The device path. - :param node_uuid: UUID of the Node. Used for logging. - :raises: InstanceDeployFailure, if any disk partitioning related - commands fail. - """ - try: - output, err = utils.execute('partprobe', device, - use_standard_locale=True, - run_as_root=True) - - search_str = "fix the GPT to use all of the space" - if search_str in err: - utils.execute('sgdisk', '-e', device, run_as_root=True) - except (processutils.UnknownArgumentError, - processutils.ProcessExecutionError, OSError) as e: - msg = (_('Failed to fix GPT data structures on disk %(disk)s ' - 'for node %(node)s. Error: %(error)s') % - {'disk': device, 'node': node_uuid, 'error': e}) - LOG.error(msg) - raise exception.InstanceDeployFailure(msg) - - -def create_config_drive_partition(node_uuid, device, configdrive): - """Create a partition for config drive - - Checks if the device is GPT or MBR partitioned and creates config drive - partition accordingly. - - :param node_uuid: UUID of the Node. - :param device: The device path. - :param configdrive: Base64 encoded Gzipped configdrive content or - configdrive HTTP URL. - :raises: InstanceDeployFailure if config drive size exceeds maximum limit - or if it fails to create config drive. - """ - confdrive_file = None - try: - config_drive_part = _get_labelled_partition(device, - CONFIGDRIVE_LABEL, - node_uuid) - - confdrive_mb, confdrive_file = _get_configdrive(configdrive, - node_uuid) - if confdrive_mb > MAX_CONFIG_DRIVE_SIZE_MB: - raise exception.InstanceDeployFailure( - _('Config drive size exceeds maximum limit of 64MiB. ' - 'Size of the given config drive is %(size)d MiB for ' - 'node %(node)s.') - % {'size': confdrive_mb, 'node': node_uuid}) - - LOG.debug("Adding config drive partition %(size)d MiB to " - "device: %(dev)s for node %(node)s", - {'dev': device, 'size': confdrive_mb, 'node': node_uuid}) - - if config_drive_part: - LOG.debug("Configdrive for node %(node)s exists at " - "%(part)s", - {'node': node_uuid, 'part': config_drive_part}) - else: - cur_parts = set(part['number'] for part in list_partitions(device)) - - if _is_disk_gpt_partitioned(device, node_uuid): - _fix_gpt_structs(device, node_uuid) - create_option = '0:-%dMB:0' % MAX_CONFIG_DRIVE_SIZE_MB - utils.execute('sgdisk', '-n', create_option, device, - run_as_root=True) - else: - # Check if the disk has 4 partitions. The MBR based disk - # cannot have more than 4 partitions. - # TODO(stendulker): One can use logical partitions to create - # a config drive if there are 4 primary partitions. - # https://bugs.launchpad.net/ironic/+bug/1561283 - num_parts = len(list_partitions(device)) - if num_parts > 3: - raise exception.InstanceDeployFailure( - _('Config drive cannot be created for node %(node)s. ' - 'Disk uses MBR partitioning and already has ' - '%(parts)d primary partitions.') - % {'node': node_uuid, 'parts': num_parts}) - - # Check if disk size exceeds 2TB msdos limit - startlimit = '-%dMiB' % MAX_CONFIG_DRIVE_SIZE_MB - endlimit = '-0' - if _is_disk_larger_than_max_size(device, node_uuid): - # Need to create a small partition at 2TB limit - LOG.warning(_LW("Disk size is larger than 2TB for " - "node %(node)s. Creating config drive " - "at the end of the disk %(disk)s."), - {'node': node_uuid, 'disk': device}) - startlimit = (MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR - - MAX_CONFIG_DRIVE_SIZE_MB - 1) - endlimit = MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR - 1 - - utils.execute('parted', '-a', 'optimal', '-s', '--', device, - 'mkpart', 'primary', 'ext2', startlimit, - endlimit, run_as_root=True) - - upd_parts = set(part['number'] for part in list_partitions(device)) - new_part = set(upd_parts) - set(cur_parts) - if len(new_part) != 1: - raise exception.InstanceDeployFailure( - _('Disk partitioning failed on device %(device)s. ' - 'Unable to retrive config drive partition information.') - % {'device': device}) - - if is_iscsi_device(device, node_uuid): - config_drive_part = '%s-part%s' % (device, new_part.pop()) - else: - config_drive_part = '%s%s' % (device, new_part.pop()) - - dd(confdrive_file, config_drive_part) - LOG.info(_LI("Configdrive for node %(node)s successfully " - "copied onto partition %(part)s"), - {'node': node_uuid, 'part': config_drive_part}) - - except (processutils.UnknownArgumentError, - processutils.ProcessExecutionError, OSError) as e: - msg = (_('Failed to create config drive on disk %(disk)s ' - 'for node %(node)s. Error: %(error)s') % - {'disk': device, 'node': node_uuid, 'error': e}) - LOG.error(msg) - raise exception.InstanceDeployFailure(msg) - finally: - # If the configdrive was requested make sure we delete the file - # after copying the content to the partition - if confdrive_file: - utils.unlink_without_raise(confdrive_file) diff --git a/ironic_lib/exception.py b/ironic_lib/exception.py deleted file mode 100644 index de4e041..0000000 --- a/ironic_lib/exception.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. - -"""Ironic base exception handling. - -Includes decorator for re-raising Ironic-type exceptions. - -SHOULD include dedicated exception logging. - -""" - -import logging -import six - -from oslo_config import cfg -from oslo_utils import excutils - -from ironic_lib.common.i18n import _ -from ironic_lib.common.i18n import _LE - - -LOG = logging.getLogger(__name__) - -exc_log_opts = [ - cfg.BoolOpt('fatal_exception_format_errors', - default=False, - help='Make exception message format errors fatal.', - deprecated_group='DEFAULT'), -] - -CONF = cfg.CONF -CONF.register_opts(exc_log_opts, group='ironic_lib') - - -class IronicException(Exception): - """Base Ironic Exception - - To correctly use this class, inherit from it and define - a 'message' property. That message will get printf'd - with the keyword arguments provided to the constructor. - - """ - message = _("An unknown exception occurred.") - code = 500 - headers = {} - safe = False - - def __init__(self, message=None, **kwargs): - self.kwargs = kwargs - - if 'code' not in self.kwargs: - try: - self.kwargs['code'] = self.code - except AttributeError: - pass - - if not message: - try: - message = self.message % kwargs - - except Exception: - with excutils.save_and_reraise_exception() as ctxt: - # kwargs doesn't match a variable in the message - # log the issue and the kwargs - prs = ', '.join('%s=%s' % pair for pair in kwargs.items()) - LOG.exception(_LE('Exception in string format operation ' - '(arguments %s)'), prs) - if not CONF.ironic_lib.fatal_exception_format_errors: - # at least get the core message out if something - # happened - message = self.message - ctxt.reraise = False - - super(IronicException, self).__init__(message) - - def format_message(self): - if self.__class__.__name__.endswith('_Remote'): - return self.args[0] - else: - return six.text_type(self) - - -class InstanceDeployFailure(IronicException): - message = _("Failed to deploy instance: %(reason)s") - - -class FileSystemNotSupported(IronicException): - message = _("Failed to create a file system. " - "File system %(fs)s is not supported.") - - -class InvalidMetricConfig(IronicException): - message = _("Invalid value for metrics config option: %(reason)s") diff --git a/ironic_lib/metrics.py b/ironic_lib/metrics.py deleted file mode 100644 index 889ca33..0000000 --- a/ironic_lib/metrics.py +++ /dev/null @@ -1,305 +0,0 @@ -# Copyright 2016 Rackspace Hosting -# 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 abc -import functools -import random -import time - -import six - -from ironic_lib.common.i18n import _ - - -class Timer(object): - """A timer decorator and context manager. - - This metric type times the decorated method or code running inside the - context manager, and emits the time as the metric value. It is bound to - this MetricLogger. For example:: - - from ironic_lib import metrics_utils - - METRICS = metrics_utils.get_metrics_logger() - - @METRICS.timer('foo') - def foo(bar, baz): - print bar, baz - - with METRICS.timer('foo'): - do_something() - """ - def __init__(self, metrics, name): - """Init the decorator / context manager. - - :param metrics: The metric logger - :param name: The metric name - """ - if not isinstance(name, six.string_types): - raise TypeError(_("The metric name is expected to be a string. " - "Value is %s") % name) - self.metrics = metrics - self.name = name - self._start = None - - def __call__(self, f): - @functools.wraps(f) - def wrapped(*args, **kwargs): - start = _time() - result = f(*args, **kwargs) - duration = _time() - start - - # Log the timing data (in ms) - self.metrics.send_timer(self.metrics.get_metric_name(self.name), - duration * 1000) - return result - return wrapped - - def __enter__(self): - self._start = _time() - - def __exit__(self, exc_type, exc_val, exc_tb): - duration = _time() - self._start - # Log the timing data (in ms) - self.metrics.send_timer(self.metrics.get_metric_name(self.name), - duration * 1000) - - -class Counter(object): - """A counter decorator and context manager. - - This metric type increments a counter every time the decorated method or - context manager is executed. It is bound to this MetricLogger. For - example:: - - from ironic_lib import metrics_utils - - METRICS = metrics_utils.get_metrics_logger() - - @METRICS.counter('foo') - def foo(bar, baz): - print bar, baz - - with METRICS.counter('foo'): - do_something() - """ - def __init__(self, metrics, name, sample_rate): - """Init the decorator / context manager. - - :param metrics: The metric logger - :param name: The metric name - :param sample_rate: Probabilistic rate at which the values will be sent - """ - if not isinstance(name, six.string_types): - raise TypeError(_("The metric name is expected to be a string. " - "Value is %s") % name) - - if (sample_rate is not None and - (sample_rate < 0.0 or sample_rate > 1.0)): - msg = _("sample_rate is set to %s. Value must be None " - "or in the interval [0.0, 1.0]") % sample_rate - raise ValueError(msg) - - self.metrics = metrics - self.name = name - self.sample_rate = sample_rate - - def __call__(self, f): - @functools.wraps(f) - def wrapped(*args, **kwargs): - self.metrics.send_counter( - self.metrics.get_metric_name(self.name), - 1, sample_rate=self.sample_rate) - - result = f(*args, **kwargs) - - return result - return wrapped - - def __enter__(self): - self.metrics.send_counter(self.metrics.get_metric_name(self.name), - 1, sample_rate=self.sample_rate) - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - - -class Gauge(object): - """A gauge decorator. - - This metric type returns the value of the decorated method as a metric - every time the method is executed. It is bound to this MetricLogger. For - example:: - - from ironic_lib import metrics_utils - - METRICS = metrics_utils.get_metrics_logger() - - @METRICS.gauge('foo') - def add_foo(bar, baz): - return (bar + baz) - """ - def __init__(self, metrics, name): - """Init the decorator / context manager. - - :param metrics: The metric logger - :param name: The metric name - """ - if not isinstance(name, six.string_types): - raise TypeError(_("The metric name is expected to be a string. " - "Value is %s") % name) - self.metrics = metrics - self.name = name - - def __call__(self, f): - @functools.wraps(f) - def wrapped(*args, **kwargs): - result = f(*args, **kwargs) - self.metrics.send_gauge(self.metrics.get_metric_name(self.name), - result) - - return result - return wrapped - - -def _time(): - """Wraps time.time() for simpler testing.""" - return time.time() - - -@six.add_metaclass(abc.ABCMeta) -class MetricLogger(object): - """Abstract class representing a metrics logger. - - A MetricLogger sends data to a backend (noop or statsd). - The data can be a gauge, a counter, or a timer. - - The data sent to the backend is composed of: - - a full metric name - - a numeric value - - The format of the full metric name is: - _prefixname - where: - - _prefix: [global_prefix][uuid][host_name]prefix - - name: the name of this metric - - : the delimiter. Default is '.' - """ - - def __init__(self, prefix='', delimiter='.'): - """Init a MetricLogger. - - :param prefix: Prefix for this metric logger. This string will prefix - all metric names. - :param delimiter: Delimiter used to generate the full metric name. - """ - self._prefix = prefix - self._delimiter = delimiter - - def get_metric_name(self, name): - """Get the full metric name. - - The format of the full metric name is: - _prefixname - where: - - _prefix: [global_prefix][uuid][host_name] - prefix - - name: the name of this metric - - : the delimiter. Default is '.' - - - :param name: The metric name. - :return: The full metric name, with logger prefix, as a string. - """ - if not self._prefix: - return name - return self._delimiter.join([self._prefix, name]) - - def send_gauge(self, name, value): - """Send gauge metric data. - - Gauges are simple values. - The backend will set the value of gauge 'name' to 'value'. - - :param name: Metric name - :param value: Metric numeric value that will be sent to the backend - """ - self._gauge(name, value) - - def send_counter(self, name, value, sample_rate=None): - """Send counter metric data. - - Counters are used to count how many times an event occurred. - The backend will increment the counter 'name' by the value 'value'. - - Optionally, specify sample_rate in the interval [0.0, 1.0] to - sample data probabilistically where:: - - P(send metric data) = sample_rate - - If sample_rate is None, then always send metric data, but do not - have the backend send sample rate information (if supported). - - :param name: Metric name - :param value: Metric numeric value that will be sent to the backend - :param sample_rate: Probabilistic rate at which the values will be - sent. Value must be None or in the interval [0.0, 1.0]. - """ - if (sample_rate is None or random.random() < sample_rate): - return self._counter(name, value, - sample_rate=sample_rate) - - def send_timer(self, name, value): - """Send timer data. - - Timers are used to measure how long it took to do something. - - :param m_name: Metric name - :param m_value: Metric numeric value that will be sent to the backend - """ - self._timer(name, value) - - def timer(self, name): - return Timer(self, name) - - def counter(self, name, sample_rate=None): - return Counter(self, name, sample_rate) - - def gauge(self, name): - return Gauge(self, name) - - @abc.abstractmethod - def _gauge(self, name, value): - """Abstract method for backends to implement gauge behavior.""" - - @abc.abstractmethod - def _counter(self, name, value, sample_rate=None): - """Abstract method for backends to implement counter behavior.""" - - @abc.abstractmethod - def _timer(self, name, value): - """Abstract method for backends to implement timer behavior.""" - - -class NoopMetricLogger(MetricLogger): - """Noop metric logger that throws away all metric data.""" - def _gauge(self, name, value): - pass - - def _counter(self, name, value, sample_rate=None): - pass - - def _timer(self, m_name, value): - pass diff --git a/ironic_lib/metrics_statsd.py b/ironic_lib/metrics_statsd.py deleted file mode 100644 index f863cea..0000000 --- a/ironic_lib/metrics_statsd.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2016 Rackspace Hosting -# 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 contextlib -import socket - -from oslo_config import cfg -from oslo_log import log - -from ironic_lib.common.i18n import _LW -from ironic_lib import metrics - -statsd_opts = [ - cfg.StrOpt('statsd_host', - default='localhost', - help='Host for use with the statsd backend.'), - cfg.PortOpt('statsd_port', - default=8125, - help='Port to use with the statsd backend.') -] - -CONF = cfg.CONF -CONF.register_opts(statsd_opts, group='metrics_statsd') - -LOG = log.getLogger(__name__) - - -class StatsdMetricLogger(metrics.MetricLogger): - """Metric logger that reports data via the statsd protocol.""" - - GAUGE_TYPE = 'g' - COUNTER_TYPE = 'c' - TIMER_TYPE = 'ms' - - def __init__(self, prefix, delimiter='.', host=None, port=None): - """Initialize a StatsdMetricLogger - - The logger uses the given prefix list, delimiter, host, and port. - - :param prefix: Prefix for this metric logger. - :param delimiter: Delimiter used to generate the full metric name. - :param host: The statsd host - :param port: The statsd port - """ - super(StatsdMetricLogger, self).__init__(prefix, - delimiter=delimiter) - - self._host = host or CONF.metrics_statsd.statsd_host - self._port = port or CONF.metrics_statsd.statsd_port - - self._target = (self._host, self._port) - - def _send(self, name, value, metric_type, sample_rate=None): - """Send metrics to the statsd backend - - :param name: Metric name - :param value: Metric value - :param metric_type: Metric type (GAUGE_TYPE, COUNTER_TYPE, - or TIMER_TYPE) - :param sample_rate: Probabilistic rate at which the values will be sent - """ - if sample_rate is None: - metric = '%s:%s|%s' % (name, value, metric_type) - else: - metric = '%s:%s|%s@%s' % (name, value, metric_type, sample_rate) - - # Ideally, we'd cache a sending socket in self, but that - # results in a socket getting shared by multiple green threads. - with contextlib.closing(self._open_socket()) as sock: - try: - sock.settimeout(0.0) - sock.sendto(metric, self._target) - except socket.error as e: - LOG.warning(_LW("Failed to send the metric value to " - "host %(host)s port %(port)s. " - "Error: %(error)s"), - {'host': self._host, 'port': self._port, - 'error': e}) - - def _open_socket(self): - return socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - def _gauge(self, name, value): - return self._send(name, value, self.GAUGE_TYPE) - - def _counter(self, name, value, sample_rate=None): - return self._send(name, value, self.COUNTER_TYPE, - sample_rate=sample_rate) - - def _timer(self, name, value): - return self._send(name, value, self.TIMER_TYPE) - - -def list_opts(): - """Entry point for oslo-config-generator.""" - return [('metrics_statsd', statsd_opts)] diff --git a/ironic_lib/metrics_utils.py b/ironic_lib/metrics_utils.py deleted file mode 100644 index 0b47944..0000000 --- a/ironic_lib/metrics_utils.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2016 Rackspace Hosting -# 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 oslo_config import cfg -import six - -from ironic_lib.common.i18n import _ -from ironic_lib import exception -from ironic_lib import metrics -from ironic_lib import metrics_statsd - -metrics_opts = [ - cfg.StrOpt('backend', - default='noop', - choices=['noop', 'statsd'], - help='Backend to use for the metrics system.'), - cfg.BoolOpt('prepend_host', - default=False, - help='Prepend the hostname to all metric names. ' - 'The format of metric names is ' - '[global_prefix.][host_name.]prefix.metric_name.'), - cfg.BoolOpt('prepend_host_reverse', - default=True, - help='Split the prepended host value by "." and reverse it ' - '(to better match the reverse hierarchical form of ' - 'domain names).'), - cfg.StrOpt('global_prefix', - help='Prefix all metric names with this value. ' - 'By default, there is no global prefix. ' - 'The format of metric names is ' - '[global_prefix.][host_name.]prefix.metric_name.') -] - -CONF = cfg.CONF -CONF.register_opts(metrics_opts, group='metrics') - - -def get_metrics_logger(prefix='', backend=None, host=None, delimiter='.'): - """Return a metric logger with the specified prefix. - - The format of the prefix is: - [global_prefix][host_name]prefix - where is the delimiter (default is '.') - - :param prefix: Prefix for this metric logger. - Value should be a string or None. - :param backend: Backend to use for the metrics system. - Possible values are 'noop' and 'statsd'. - :param host: Name of this node. - :param delimiter: Delimiter to use for the metrics name. - :return: The new MetricLogger. - """ - if not isinstance(prefix, six.string_types): - msg = (_("This metric prefix (%s) is of unsupported type. " - "Value should be a string or None") - % str(prefix)) - raise exception.InvalidMetricConfig(msg) - - if CONF.metrics.prepend_host and host: - if CONF.metrics.prepend_host_reverse: - host = '.'.join(reversed(host.split('.'))) - - if prefix: - prefix = delimiter.join([host, prefix]) - else: - prefix = host - - if CONF.metrics.global_prefix: - if prefix: - prefix = delimiter.join([CONF.metrics.global_prefix, prefix]) - else: - prefix = CONF.metrics.global_prefix - - backend = backend or CONF.metrics.backend - if backend == 'statsd': - return metrics_statsd.StatsdMetricLogger(prefix, delimiter=delimiter) - elif backend == 'noop': - return metrics.NoopMetricLogger(prefix, delimiter=delimiter) - else: - msg = (_("The backend is set to an unsupported type: " - "%s. Value should be 'noop' or 'statsd'.") - % backend) - raise exception.InvalidMetricConfig(msg) - - -def list_opts(): - """Entry point for oslo-config-generator.""" - return [('metrics', metrics_opts)] diff --git a/ironic_lib/tests/__init__.py b/ironic_lib/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ironic_lib/tests/test_disk_partitioner.py b/ironic_lib/tests/test_disk_partitioner.py deleted file mode 100644 index 45b740f..0000000 --- a/ironic_lib/tests/test_disk_partitioner.py +++ /dev/null @@ -1,178 +0,0 @@ -# Copyright 2014 Red Hat, Inc. -# 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 eventlet -import mock - -from oslotest import base as test_base -from testtools.matchers import HasLength - -from ironic_lib import disk_partitioner -from ironic_lib import exception -from ironic_lib import utils - - -class DiskPartitionerTestCase(test_base.BaseTestCase): - - def test_add_partition(self): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - dp.add_partition(1024) - dp.add_partition(512, fs_type='linux-swap') - dp.add_partition(2048, boot_flag='boot') - dp.add_partition(2048, boot_flag='bios_grub') - expected = [(1, {'boot_flag': None, - 'fs_type': '', - 'type': 'primary', - 'size': 1024}), - (2, {'boot_flag': None, - 'fs_type': 'linux-swap', - 'type': 'primary', - 'size': 512}), - (3, {'boot_flag': 'boot', - 'fs_type': '', - 'type': 'primary', - 'size': 2048}), - (4, {'boot_flag': 'bios_grub', - 'fs_type': '', - 'type': 'primary', - 'size': 2048})] - partitions = [(n, p) for n, p in dp.get_partitions()] - self.assertThat(partitions, HasLength(4)) - self.assertEqual(expected, partitions) - - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', - autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - def test_commit(self, mock_utils_exc, mock_disk_partitioner_exec): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - fake_parts = [(1, {'boot_flag': None, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1}), - (2, {'boot_flag': 'boot', - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1}), - (3, {'boot_flag': 'bios_grub', - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1})] - with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: - mock_gp.return_value = fake_parts - mock_utils_exc.return_value = (None, None) - dp.commit() - - mock_disk_partitioner_exec.assert_called_once_with( - mock.ANY, 'mklabel', 'msdos', - 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', - 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', - 'set', '2', 'boot', 'on', - 'mkpart', 'fake-type', 'fake-fs-type', '3', '4', - 'set', '3', 'bios_grub', 'on') - mock_utils_exc.assert_called_once_with( - 'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) - - @mock.patch.object(eventlet.greenthread, 'sleep', lambda seconds: None) - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', - autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - def test_commit_with_device_is_busy_once(self, mock_utils_exc, - mock_disk_partitioner_exec): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - fake_parts = [(1, {'boot_flag': None, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1}), - (2, {'boot_flag': 'boot', - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1})] - fuser_outputs = iter([("/dev/fake: 10000 10001", None), (None, None)]) - - with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: - mock_gp.return_value = fake_parts - mock_utils_exc.side_effect = fuser_outputs - dp.commit() - - mock_disk_partitioner_exec.assert_called_once_with( - mock.ANY, 'mklabel', 'msdos', - 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', - 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', - 'set', '2', 'boot', 'on') - mock_utils_exc.assert_called_with( - 'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) - self.assertEqual(2, mock_utils_exc.call_count) - - @mock.patch.object(eventlet.greenthread, 'sleep', lambda seconds: None) - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', - autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - def test_commit_with_device_is_always_busy(self, mock_utils_exc, - mock_disk_partitioner_exec): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - fake_parts = [(1, {'boot_flag': None, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1}), - (2, {'boot_flag': 'boot', - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1})] - - with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: - mock_gp.return_value = fake_parts - mock_utils_exc.return_value = ("/dev/fake: 10000 10001", None) - self.assertRaises(exception.InstanceDeployFailure, dp.commit) - - mock_disk_partitioner_exec.assert_called_once_with( - mock.ANY, 'mklabel', 'msdos', - 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', - 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', - 'set', '2', 'boot', 'on') - mock_utils_exc.assert_called_with( - 'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) - self.assertEqual(20, mock_utils_exc.call_count) - - # Mock the eventlet.greenthread.sleep for the looping_call - @mock.patch.object(eventlet.greenthread, 'sleep', lambda seconds: None) - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', - autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - def test_commit_with_device_disconnected(self, mock_utils_exc, - mock_disk_partitioner_exec): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - fake_parts = [(1, {'boot_flag': None, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1}), - (2, {'boot_flag': 'boot', - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1})] - - with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: - mock_gp.return_value = fake_parts - mock_utils_exc.return_value = (None, "Specified filename /dev/fake" - " does not exist.") - self.assertRaises(exception.InstanceDeployFailure, dp.commit) - - mock_disk_partitioner_exec.assert_called_once_with( - mock.ANY, 'mklabel', 'msdos', - 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', - 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', - 'set', '2', 'boot', 'on') - mock_utils_exc.assert_called_with( - 'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) - self.assertEqual(20, mock_utils_exc.call_count) diff --git a/ironic_lib/tests/test_disk_utils.py b/ironic_lib/tests/test_disk_utils.py deleted file mode 100644 index 0bef303..0000000 --- a/ironic_lib/tests/test_disk_utils.py +++ /dev/null @@ -1,1242 +0,0 @@ -# Copyright 2014 Red Hat, Inc. -# 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 base64 -import gzip -import mock -import os -import shutil -import stat -import tempfile - - -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_service import loopingcall -from oslo_utils import imageutils -from oslotest import base as test_base -import requests - -from ironic_lib import disk_partitioner -from ironic_lib import disk_utils -from ironic_lib import exception -from ironic_lib import utils - -CONF = cfg.CONF - - -@mock.patch.object(utils, 'execute', autospec=True) -class ListPartitionsTestCase(test_base.BaseTestCase): - - def test_correct(self, execute_mock): - output = """ -BYT; -/dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:; -1:1.00MiB:501MiB:500MiB:ext4::boot; -2:501MiB:476940MiB:476439MiB:::; -""" - expected = [ - {'number': 1, 'start': 1, 'end': 501, 'size': 500, - 'filesystem': 'ext4', 'flags': 'boot'}, - {'number': 2, 'start': 501, 'end': 476940, 'size': 476439, - 'filesystem': '', 'flags': ''}, - ] - execute_mock.return_value = (output, '') - result = disk_utils.list_partitions('/dev/fake') - self.assertEqual(expected, result) - execute_mock.assert_called_once_with( - 'parted', '-s', '-m', '/dev/fake', 'unit', 'MiB', 'print', - use_standard_locale=True, run_as_root=True) - - @mock.patch.object(disk_utils.LOG, 'warning', autospec=True) - def test_incorrect(self, log_mock, execute_mock): - output = """ -BYT; -/dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:; -1:XX1076MiB:---:524MiB:ext4::boot; -""" - execute_mock.return_value = (output, '') - self.assertEqual([], disk_utils.list_partitions('/dev/fake')) - self.assertEqual(1, log_mock.call_count) - - -@mock.patch.object(disk_partitioner.DiskPartitioner, 'commit', lambda _: None) -class WorkOnDiskTestCase(test_base.BaseTestCase): - - def setUp(self): - super(WorkOnDiskTestCase, self).setUp() - self.image_path = '/tmp/xyz/image' - self.root_mb = 128 - self.swap_mb = 64 - self.ephemeral_mb = 0 - self.ephemeral_format = None - self.configdrive_mb = 0 - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - self.dev = '/dev/fake' - self.swap_part = '/dev/fake-part1' - self.root_part = '/dev/fake-part2' - - self.mock_ibd_obj = mock.patch.object( - disk_utils, 'is_block_device', autospec=True) - self.mock_ibd = self.mock_ibd_obj.start() - self.addCleanup(self.mock_ibd_obj.stop) - self.mock_mp_obj = mock.patch.object( - disk_utils, 'make_partitions', autospec=True) - self.mock_mp = self.mock_mp_obj.start() - self.addCleanup(self.mock_mp_obj.stop) - self.mock_remlbl_obj = mock.patch.object( - disk_utils, 'destroy_disk_metadata', autospec=True) - self.mock_remlbl = self.mock_remlbl_obj.start() - self.addCleanup(self.mock_remlbl_obj.stop) - self.mock_mp.return_value = {'swap': self.swap_part, - 'root': self.root_part} - - def test_no_root_partition(self): - self.mock_ibd.return_value = False - self.assertRaises(exception.InstanceDeployFailure, - disk_utils.work_on_disk, self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.ephemeral_format, self.image_path, - self.node_uuid) - self.mock_ibd.assert_called_once_with(self.root_part) - self.mock_mp.assert_called_once_with(self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.configdrive_mb, - self.node_uuid, commit=True, - boot_option="netboot", - boot_mode="bios", - disk_label=None) - - def test_no_swap_partition(self): - self.mock_ibd.side_effect = iter([True, False]) - calls = [mock.call(self.root_part), - mock.call(self.swap_part)] - self.assertRaises(exception.InstanceDeployFailure, - disk_utils.work_on_disk, self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.ephemeral_format, self.image_path, - self.node_uuid) - self.assertEqual(self.mock_ibd.call_args_list, calls) - self.mock_mp.assert_called_once_with(self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.configdrive_mb, - self.node_uuid, commit=True, - boot_option="netboot", - boot_mode="bios", - disk_label=None) - - def test_no_ephemeral_partition(self): - ephemeral_part = '/dev/fake-part1' - swap_part = '/dev/fake-part2' - root_part = '/dev/fake-part3' - ephemeral_mb = 256 - ephemeral_format = 'exttest' - - self.mock_mp.return_value = {'ephemeral': ephemeral_part, - 'swap': swap_part, - 'root': root_part} - self.mock_ibd.side_effect = iter([True, True, False]) - calls = [mock.call(root_part), - mock.call(swap_part), - mock.call(ephemeral_part)] - self.assertRaises(exception.InstanceDeployFailure, - disk_utils.work_on_disk, self.dev, self.root_mb, - self.swap_mb, ephemeral_mb, ephemeral_format, - self.image_path, self.node_uuid) - self.assertEqual(self.mock_ibd.call_args_list, calls) - self.mock_mp.assert_called_once_with(self.dev, self.root_mb, - self.swap_mb, ephemeral_mb, - self.configdrive_mb, - self.node_uuid, commit=True, - boot_option="netboot", - boot_mode="bios", - disk_label=None) - - @mock.patch.object(utils, 'unlink_without_raise', autospec=True) - @mock.patch.object(disk_utils, '_get_configdrive', autospec=True) - def test_no_configdrive_partition(self, mock_configdrive, mock_unlink): - mock_configdrive.return_value = (10, 'fake-path') - swap_part = '/dev/fake-part1' - configdrive_part = '/dev/fake-part2' - root_part = '/dev/fake-part3' - configdrive_url = 'http://1.2.3.4/cd' - configdrive_mb = 10 - - self.mock_mp.return_value = {'swap': swap_part, - 'configdrive': configdrive_part, - 'root': root_part} - self.mock_ibd.side_effect = iter([True, True, False]) - calls = [mock.call(root_part), - mock.call(swap_part), - mock.call(configdrive_part)] - self.assertRaises(exception.InstanceDeployFailure, - disk_utils.work_on_disk, self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.ephemeral_format, self.image_path, - self.node_uuid, preserve_ephemeral=False, - configdrive=configdrive_url, - boot_option="netboot") - self.assertEqual(self.mock_ibd.call_args_list, calls) - self.mock_mp.assert_called_once_with(self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - configdrive_mb, self.node_uuid, - commit=True, - boot_option="netboot", - boot_mode="bios", - disk_label=None) - mock_unlink.assert_called_once_with('fake-path') - - @mock.patch.object(utils, 'mkfs', lambda *_: None) - @mock.patch.object(disk_utils, 'block_uuid', lambda p: 'uuid') - @mock.patch.object(disk_utils, 'populate_image', lambda *_: None) - def test_gpt_disk_label(self): - ephemeral_part = '/dev/fake-part1' - swap_part = '/dev/fake-part2' - root_part = '/dev/fake-part3' - ephemeral_mb = 256 - ephemeral_format = 'exttest' - - self.mock_mp.return_value = {'ephemeral': ephemeral_part, - 'swap': swap_part, - 'root': root_part} - self.mock_ibd.return_value = True - calls = [mock.call(root_part), - mock.call(swap_part), - mock.call(ephemeral_part)] - disk_utils.work_on_disk(self.dev, self.root_mb, - self.swap_mb, ephemeral_mb, ephemeral_format, - self.image_path, self.node_uuid, - disk_label='gpt') - self.assertEqual(self.mock_ibd.call_args_list, calls) - self.mock_mp.assert_called_once_with(self.dev, self.root_mb, - self.swap_mb, ephemeral_mb, - self.configdrive_mb, - self.node_uuid, commit=True, - boot_option="netboot", - boot_mode="bios", - disk_label='gpt') - - -@mock.patch.object(utils, 'execute', autospec=True) -class MakePartitionsTestCase(test_base.BaseTestCase): - - def setUp(self): - super(MakePartitionsTestCase, self).setUp() - self.dev = 'fake-dev' - self.root_mb = 1024 - self.swap_mb = 512 - self.ephemeral_mb = 0 - self.configdrive_mb = 0 - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - self.efi_size = CONF.disk_utils.efi_system_partition_size - self.bios_size = CONF.disk_utils.bios_boot_partition_size - - def _get_parted_cmd(self, dev, label=None): - if label is None: - label = 'msdos' - - return ['parted', '-a', 'optimal', '-s', dev, - '--', 'unit', 'MiB', 'mklabel', label] - - def _test_make_partitions(self, mock_exc, boot_option, boot_mode='bios', - disk_label=None): - mock_exc.return_value = (None, None) - disk_utils.make_partitions(self.dev, self.root_mb, self.swap_mb, - self.ephemeral_mb, self.configdrive_mb, - self.node_uuid, boot_option=boot_option, - boot_mode=boot_mode, disk_label=disk_label) - - _s = lambda x, sz: x + sz - - if boot_option == "local" and boot_mode == "uefi": - add_efi_sz = lambda x: str(_s(x, self.efi_size)) - expected_mkpart = ['mkpart', 'primary', 'fat32', '1', - add_efi_sz(1), - 'set', '1', 'boot', 'on', - 'mkpart', 'primary', 'linux-swap', - add_efi_sz(1), add_efi_sz(513), 'mkpart', - 'primary', '', add_efi_sz(513), - add_efi_sz(1537)] - else: - if boot_option == "local": - if disk_label == "gpt": - add_bios_sz = lambda x: str(_s(x, self.bios_size)) - expected_mkpart = ['mkpart', 'primary', '', '1', - add_bios_sz(1), - 'set', '1', 'bios_grub', 'on', - 'mkpart', 'primary', 'linux-swap', - add_bios_sz(1), add_bios_sz(513), - 'mkpart', 'primary', '', - add_bios_sz(513), add_bios_sz(1537)] - else: - expected_mkpart = ['mkpart', 'primary', 'linux-swap', '1', - '513', 'mkpart', 'primary', '', '513', - '1537', 'set', '2', 'boot', 'on'] - else: - expected_mkpart = ['mkpart', 'primary', 'linux-swap', '1', - '513', 'mkpart', 'primary', '', '513', - '1537'] - self.dev = 'fake-dev' - parted_cmd = (self._get_parted_cmd(self.dev, disk_label) + - expected_mkpart) - parted_call = mock.call(*parted_cmd, use_standard_locale=True, - run_as_root=True, check_exit_code=[0]) - fuser_cmd = ['fuser', 'fake-dev'] - fuser_call = mock.call(*fuser_cmd, run_as_root=True, - check_exit_code=[0, 1]) - mock_exc.assert_has_calls([parted_call, fuser_call]) - - def test_make_partitions(self, mock_exc): - self._test_make_partitions(mock_exc, boot_option="netboot") - - def test_make_partitions_local_boot(self, mock_exc): - self._test_make_partitions(mock_exc, boot_option="local") - - def test_make_partitions_local_boot_uefi(self, mock_exc): - self._test_make_partitions(mock_exc, boot_option="local", - boot_mode="uefi", disk_label="gpt") - - def test_make_partitions_local_boot_gpt_bios(self, mock_exc): - self._test_make_partitions(mock_exc, boot_option="local", - disk_label="gpt") - - def test_make_partitions_disk_label_gpt(self, mock_exc): - self._test_make_partitions(mock_exc, boot_option="netboot", - disk_label="gpt") - - def test_make_partitions_with_ephemeral(self, mock_exc): - self.ephemeral_mb = 2048 - expected_mkpart = ['mkpart', 'primary', '', '1', '2049', - 'mkpart', 'primary', 'linux-swap', '2049', '2561', - 'mkpart', 'primary', '', '2561', '3585'] - self.dev = 'fake-dev' - cmd = self._get_parted_cmd(self.dev) + expected_mkpart - mock_exc.return_value = (None, None) - disk_utils.make_partitions(self.dev, self.root_mb, self.swap_mb, - self.ephemeral_mb, self.configdrive_mb, - self.node_uuid) - - parted_call = mock.call(*cmd, use_standard_locale=True, - run_as_root=True, check_exit_code=[0]) - mock_exc.assert_has_calls([parted_call]) - - def test_make_partitions_with_iscsi_device(self, mock_exc): - self.ephemeral_mb = 2048 - expected_mkpart = ['mkpart', 'primary', '', '1', '2049', - 'mkpart', 'primary', 'linux-swap', '2049', '2561', - 'mkpart', 'primary', '', '2561', '3585'] - self.dev = '/dev/iqn.2008-10.org.openstack:%s.fake-9' % self.node_uuid - ep = '/dev/iqn.2008-10.org.openstack:%s.fake-9-part1' % self.node_uuid - swap = ('/dev/iqn.2008-10.org.openstack:%s.fake-9-part2' - % self.node_uuid) - root = ('/dev/iqn.2008-10.org.openstack:%s.fake-9-part3' - % self.node_uuid) - expected_result = {'ephemeral': ep, - 'swap': swap, - 'root': root} - cmd = self._get_parted_cmd(self.dev) + expected_mkpart - mock_exc.return_value = (None, None) - result = disk_utils.make_partitions( - self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, - self.configdrive_mb, self.node_uuid) - - parted_call = mock.call(*cmd, use_standard_locale=True, - run_as_root=True, check_exit_code=[0]) - mock_exc.assert_has_calls([parted_call]) - self.assertEqual(expected_result, result) - - def test_make_partitions_with_local_device(self, mock_exc): - self.ephemeral_mb = 2048 - expected_mkpart = ['mkpart', 'primary', '', '1', '2049', - 'mkpart', 'primary', 'linux-swap', '2049', '2561', - 'mkpart', 'primary', '', '2561', '3585'] - self.dev = 'fake-dev' - expected_result = {'ephemeral': 'fake-dev1', - 'swap': 'fake-dev2', - 'root': 'fake-dev3'} - cmd = self._get_parted_cmd(self.dev) + expected_mkpart - mock_exc.return_value = (None, None) - result = disk_utils.make_partitions( - self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, - self.configdrive_mb, self.node_uuid) - - parted_call = mock.call(*cmd, use_standard_locale=True, - run_as_root=True, check_exit_code=[0]) - mock_exc.assert_has_calls([parted_call]) - self.assertEqual(expected_result, result) - - -@mock.patch.object(utils, 'execute', autospec=True) -class DestroyMetaDataTestCase(test_base.BaseTestCase): - - def setUp(self): - super(DestroyMetaDataTestCase, self).setUp() - self.dev = 'fake-dev' - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - - def test_destroy_disk_metadata(self, mock_exec): - expected_calls = [mock.call('wipefs', '--force', '--all', 'fake-dev', - run_as_root=True, - use_standard_locale=True)] - disk_utils.destroy_disk_metadata(self.dev, self.node_uuid) - mock_exec.assert_has_calls(expected_calls) - - def test_destroy_disk_metadata_wipefs_fail(self, mock_exec): - mock_exec.side_effect = processutils.ProcessExecutionError - - expected_call = [mock.call('wipefs', '--force', '--all', 'fake-dev', - run_as_root=True, - use_standard_locale=True)] - self.assertRaises(processutils.ProcessExecutionError, - disk_utils.destroy_disk_metadata, - self.dev, - self.node_uuid) - mock_exec.assert_has_calls(expected_call) - - def test_destroy_disk_metadata_wipefs_not_support_force(self, mock_exec): - mock_exec.side_effect = iter( - [processutils.ProcessExecutionError(description='--force'), - (None, None)]) - - expected_call = [mock.call('wipefs', '--force', '--all', 'fake-dev', - run_as_root=True, - use_standard_locale=True), - mock.call('wipefs', '--all', 'fake-dev', - run_as_root=True, - use_standard_locale=True)] - disk_utils.destroy_disk_metadata(self.dev, self.node_uuid) - mock_exec.assert_has_calls(expected_call) - - -@mock.patch.object(utils, 'execute', autospec=True) -class GetDeviceBlockSizeTestCase(test_base.BaseTestCase): - - def setUp(self): - super(GetDeviceBlockSizeTestCase, self).setUp() - self.dev = 'fake-dev' - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - - def test_get_dev_block_size(self, mock_exec): - mock_exec.return_value = ("64", "") - expected_call = [mock.call('blockdev', '--getsz', self.dev, - run_as_root=True, check_exit_code=[0])] - disk_utils.get_dev_block_size(self.dev) - mock_exec.assert_has_calls(expected_call) - - -@mock.patch.object(disk_utils, 'dd', autospec=True) -@mock.patch.object(disk_utils, 'qemu_img_info', autospec=True) -@mock.patch.object(disk_utils, 'convert_image', autospec=True) -class PopulateImageTestCase(test_base.BaseTestCase): - - def setUp(self): - super(PopulateImageTestCase, self).setUp() - - def test_populate_raw_image(self, mock_cg, mock_qinfo, mock_dd): - type(mock_qinfo.return_value).file_format = mock.PropertyMock( - return_value='raw') - disk_utils.populate_image('src', 'dst') - mock_dd.assert_called_once_with('src', 'dst') - self.assertFalse(mock_cg.called) - - def test_populate_qcow2_image(self, mock_cg, mock_qinfo, mock_dd): - type(mock_qinfo.return_value).file_format = mock.PropertyMock( - return_value='qcow2') - disk_utils.populate_image('src', 'dst') - mock_cg.assert_called_once_with('src', 'dst', 'raw', True) - self.assertFalse(mock_dd.called) - - -def _looping_call_done(*args, **kwargs): - raise loopingcall.LoopingCallDone() - - -@mock.patch.object(disk_partitioner.DiskPartitioner, - '_wait_for_disk_to_become_available', - _looping_call_done) -@mock.patch.object(disk_utils, 'is_block_device', lambda d: True) -@mock.patch.object(disk_utils, 'block_uuid', lambda p: 'uuid') -@mock.patch.object(disk_utils, 'dd', lambda *_: None) -@mock.patch.object(disk_utils, 'convert_image', lambda *_: None) -@mock.patch.object(utils, 'mkfs', lambda *_: None) -# NOTE(dtantsur): destroy_disk_metadata resets file size, disabling it -@mock.patch.object(disk_utils, 'destroy_disk_metadata', lambda *_: None) -class RealFilePartitioningTestCase(test_base.BaseTestCase): - """This test applies some real-world partitioning scenario to a file. - - This test covers the whole partitioning, mocking everything not possible - on a file. That helps us assure, that we do all partitioning math properly - and also conducts integration testing of DiskPartitioner. - """ - - def setUp(self): - super(RealFilePartitioningTestCase, self).setUp() - # NOTE(dtantsur): no parted utility on gate-ironic-python26 - try: - utils.execute('parted', '--version') - except OSError as exc: - self.skipTest('parted utility was not found: %s' % exc) - self.file = tempfile.NamedTemporaryFile(delete=False) - # NOTE(ifarkas): the file needs to be closed, so fuser won't report - # any usage - self.file.close() - # NOTE(dtantsur): 20 MiB file with zeros - utils.execute('dd', 'if=/dev/zero', 'of=%s' % self.file.name, - 'bs=1', 'count=0', 'seek=20MiB') - - @staticmethod - def _run_without_root(func, *args, **kwargs): - """Make sure root is not required when using utils.execute.""" - real_execute = utils.execute - - def fake_execute(*cmd, **kwargs): - kwargs['run_as_root'] = False - return real_execute(*cmd, **kwargs) - - with mock.patch.object(utils, 'execute', fake_execute): - return func(*args, **kwargs) - - def test_different_sizes(self): - # NOTE(dtantsur): Keep this list in order with expected partitioning - fields = ['ephemeral_mb', 'swap_mb', 'root_mb'] - variants = ((0, 0, 12), (4, 2, 8), (0, 4, 10), (5, 0, 10)) - for variant in variants: - kwargs = dict(zip(fields, variant)) - self._run_without_root(disk_utils.work_on_disk, - self.file.name, ephemeral_format='ext4', - node_uuid='', image_path='path', **kwargs) - part_table = self._run_without_root( - disk_utils.list_partitions, self.file.name) - for part, expected_size in zip(part_table, filter(None, variant)): - self.assertEqual(expected_size, part['size'], - "comparison failed for %s" % list(variant)) - - def test_whole_disk(self): - # 6 MiB ephemeral + 3 MiB swap + 9 MiB root + 1 MiB for MBR - # + 1 MiB MAGIC == 20 MiB whole disk - # TODO(dtantsur): figure out why we need 'magic' 1 more MiB - # and why the is different on Ubuntu and Fedora (see below) - self._run_without_root(disk_utils.work_on_disk, self.file.name, - root_mb=9, ephemeral_mb=6, swap_mb=3, - ephemeral_format='ext4', node_uuid='', - image_path='path') - part_table = self._run_without_root( - disk_utils.list_partitions, self.file.name) - sizes = [part['size'] for part in part_table] - # NOTE(dtantsur): parted in Ubuntu 12.04 will occupy the last MiB, - # parted in Fedora 20 won't - thus two possible variants for last part - self.assertEqual([6, 3], sizes[:2], - "unexpected partitioning %s" % part_table) - self.assertIn(sizes[2], (9, 10)) - - -@mock.patch.object(shutil, 'copyfileobj', autospec=True) -@mock.patch.object(requests, 'get', autospec=True) -class GetConfigdriveTestCase(test_base.BaseTestCase): - - @mock.patch.object(gzip, 'GzipFile', autospec=True) - def test_get_configdrive(self, mock_gzip, mock_requests, mock_copy): - mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy') - tempdir = tempfile.mkdtemp() - (size, path) = disk_utils._get_configdrive('http://1.2.3.4/cd', - 'fake-node-uuid', - tempdir=tempdir) - self.assertTrue(path.startswith(tempdir)) - mock_requests.assert_called_once_with('http://1.2.3.4/cd') - mock_gzip.assert_called_once_with('configdrive', 'rb', - fileobj=mock.ANY) - mock_copy.assert_called_once_with(mock.ANY, mock.ANY) - - @mock.patch.object(gzip, 'GzipFile', autospec=True) - def test_get_configdrive_base64_string(self, mock_gzip, mock_requests, - mock_copy): - disk_utils._get_configdrive('Zm9vYmFy', 'fake-node-uuid') - self.assertFalse(mock_requests.called) - mock_gzip.assert_called_once_with('configdrive', 'rb', - fileobj=mock.ANY) - mock_copy.assert_called_once_with(mock.ANY, mock.ANY) - - def test_get_configdrive_bad_url(self, mock_requests, mock_copy): - mock_requests.side_effect = requests.exceptions.RequestException - self.assertRaises(exception.InstanceDeployFailure, - disk_utils._get_configdrive, - 'http://1.2.3.4/cd', 'fake-node-uuid') - self.assertFalse(mock_copy.called) - - @mock.patch.object(base64, 'b64decode', autospec=True) - def test_get_configdrive_base64_error(self, mock_b64, mock_requests, - mock_copy): - mock_b64.side_effect = TypeError - self.assertRaises(exception.InstanceDeployFailure, - disk_utils._get_configdrive, - 'malformed', 'fake-node-uuid') - mock_b64.assert_called_once_with('malformed') - self.assertFalse(mock_copy.called) - - @mock.patch.object(gzip, 'GzipFile', autospec=True) - def test_get_configdrive_gzip_error(self, mock_gzip, mock_requests, - mock_copy): - mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy') - mock_copy.side_effect = IOError - self.assertRaises(exception.InstanceDeployFailure, - disk_utils._get_configdrive, - 'http://1.2.3.4/cd', 'fake-node-uuid') - mock_requests.assert_called_once_with('http://1.2.3.4/cd') - mock_gzip.assert_called_once_with('configdrive', 'rb', - fileobj=mock.ANY) - mock_copy.assert_called_once_with(mock.ANY, mock.ANY) - - -@mock.patch('time.sleep', lambda sec: None) -class OtherFunctionTestCase(test_base.BaseTestCase): - - @mock.patch.object(os, 'stat', autospec=True) - @mock.patch.object(stat, 'S_ISBLK', autospec=True) - def test_is_block_device_works(self, mock_is_blk, mock_os): - device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9' - mock_is_blk.return_value = True - mock_os().st_mode = 10000 - self.assertTrue(disk_utils.is_block_device(device)) - mock_is_blk.assert_called_once_with(mock_os().st_mode) - - @mock.patch.object(os, 'stat', autospec=True) - def test_is_block_device_raises(self, mock_os): - device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9' - mock_os.side_effect = OSError - self.assertRaises(exception.InstanceDeployFailure, - disk_utils.is_block_device, device) - mock_os.assert_has_calls([mock.call(device)] * 3) - - @mock.patch.object(imageutils, 'QemuImgInfo', autospec=True) - @mock.patch.object(os.path, 'exists', return_value=False, autospec=True) - def test_qemu_img_info_path_doesnt_exist(self, path_exists_mock, - qemu_img_info_mock): - disk_utils.qemu_img_info('noimg') - path_exists_mock.assert_called_once_with('noimg') - qemu_img_info_mock.assert_called_once_with() - - @mock.patch.object(utils, 'execute', return_value=('out', 'err'), - autospec=True) - @mock.patch.object(imageutils, 'QemuImgInfo', autospec=True) - @mock.patch.object(os.path, 'exists', return_value=True, autospec=True) - def test_qemu_img_info_path_exists(self, path_exists_mock, - qemu_img_info_mock, execute_mock): - disk_utils.qemu_img_info('img') - path_exists_mock.assert_called_once_with('img') - execute_mock.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', 'img') - qemu_img_info_mock.assert_called_once_with('out') - - @mock.patch.object(utils, 'execute', autospec=True) - def test_convert_image(self, execute_mock): - disk_utils.convert_image('source', 'dest', 'out_format') - execute_mock.assert_called_once_with('qemu-img', 'convert', '-O', - 'out_format', 'source', 'dest', - run_as_root=False) - - @mock.patch.object(os.path, 'getsize', autospec=True) - @mock.patch.object(disk_utils, 'qemu_img_info', autospec=True) - def test_get_image_mb(self, mock_qinfo, mock_getsize): - mb = 1024 * 1024 - - mock_getsize.return_value = 0 - type(mock_qinfo.return_value).virtual_size = mock.PropertyMock( - return_value=0) - self.assertEqual(0, disk_utils.get_image_mb('x', False)) - self.assertEqual(0, disk_utils.get_image_mb('x', True)) - mock_getsize.return_value = 1 - type(mock_qinfo.return_value).virtual_size = mock.PropertyMock( - return_value=1) - self.assertEqual(1, disk_utils.get_image_mb('x', False)) - self.assertEqual(1, disk_utils.get_image_mb('x', True)) - mock_getsize.return_value = mb - type(mock_qinfo.return_value).virtual_size = mock.PropertyMock( - return_value=mb) - self.assertEqual(1, disk_utils.get_image_mb('x', False)) - self.assertEqual(1, disk_utils.get_image_mb('x', True)) - mock_getsize.return_value = mb + 1 - type(mock_qinfo.return_value).virtual_size = mock.PropertyMock( - return_value=mb + 1) - self.assertEqual(2, disk_utils.get_image_mb('x', False)) - self.assertEqual(2, disk_utils.get_image_mb('x', True)) - - -@mock.patch.object(utils, 'execute') -class WholeDiskPartitionTestCases(test_base.BaseTestCase): - - def setUp(self): - super(WholeDiskPartitionTestCases, self).setUp() - self.dev = "/dev/fake" - self.config_part_label = "config-2" - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - - def test_get_partition_present(self, mock_execute): - blkid_output = '/dev/fake12\n' - mock_execute.side_effect = [(None, ''), (blkid_output, '')] - result = disk_utils._get_labelled_partition(self.dev, - self.config_part_label, - self.node_uuid) - self.assertEqual(blkid_output.rstrip(), result) - execute_calls = [ - mock.call('partprobe', self.dev, run_as_root=True), - mock.call('blkid', '-o', 'device', self.dev, '-t', - 'LABEL=config-2', check_exit_code=[0, 2], - use_standard_locale=True, run_as_root=True) - ] - mock_execute.assert_has_calls(execute_calls) - - def test_get_partition_absent(self, mock_execute): - mock_execute.side_effect = [(None, ''), - (None, '')] - result = disk_utils._get_labelled_partition(self.dev, - self.config_part_label, - self.node_uuid) - self.assertIsNone(result) - execute_calls = [ - mock.call('partprobe', self.dev, run_as_root=True), - mock.call('blkid', '-o', 'device', self.dev, '-t', - 'LABEL=config-2', check_exit_code=[0, 2], - use_standard_locale=True, run_as_root=True) - ] - mock_execute.assert_has_calls(execute_calls) - - def test_get_partition_DeployFail_exc(self, mock_execute): - blkid_output = '/dev/fake12\n/dev/fake13\n' - mock_execute.side_effect = [(None, ''), (blkid_output, '')] - self.assertRaises(exception.InstanceDeployFailure, - disk_utils._get_labelled_partition, self.dev, - self.config_part_label, self.node_uuid) - execute_calls = [ - mock.call('partprobe', self.dev, run_as_root=True), - mock.call('blkid', '-o', 'device', self.dev, '-t', - 'LABEL=config-2', check_exit_code=[0, 2], - use_standard_locale=True, run_as_root=True) - ] - mock_execute.assert_has_calls(execute_calls) - - @mock.patch.object(disk_utils.LOG, 'error') - def test_get_partition_exc(self, mock_log, mock_execute): - mock_execute.side_effect = processutils.ProcessExecutionError - self.assertRaisesRegex(exception.InstanceDeployFailure, - 'Failed to retrieve partition labels', - disk_utils._get_labelled_partition, self.dev, - self.config_part_label, self.node_uuid) - mock_execute.assert_called_once_with('partprobe', self.dev, - run_as_root=True) - self.assertEqual(1, mock_log.call_count) - - def _test_is_disk_larger_than_max_size(self, mock_execute, blk_out): - mock_execute.return_value = blk_out - result = disk_utils._is_disk_larger_than_max_size(self.dev, - self.node_uuid) - mock_execute.assert_called_once_with('blockdev', '--getsize64', - '/dev/fake', run_as_root=True, - use_standard_locale=True) - return result - - def test_is_disk_larger_than_max_size_false(self, mock_execute): - blkid_out = "53687091200" - ret = self._test_is_disk_larger_than_max_size(mock_execute, - blk_out=blkid_out) - self.assertFalse(ret) - - def test_is_disk_larger_than_max_size_true(self, mock_execute): - blkid_out = "4398046511104" - ret = self._test_is_disk_larger_than_max_size(mock_execute, - blk_out=blkid_out) - self.assertTrue(ret) - - @mock.patch.object(disk_utils.LOG, 'error') - def test_is_disk_larger_than_max_size_exc(self, mock_log, mock_execute): - mock_execute.side_effect = processutils.ProcessExecutionError - self.assertRaisesRegex(exception.InstanceDeployFailure, - 'Failed to get size of disk', - disk_utils._is_disk_larger_than_max_size, - self.dev, self.node_uuid) - mock_execute.assert_called_once_with('blockdev', '--getsize64', - '/dev/fake', run_as_root=True, - use_standard_locale=True) - self.assertEqual(1, mock_log.call_count) - - def test__is_disk_gpt_partitioned_true(self, mock_execute): - blkid_output = 'gpt' - mock_execute.return_value = (blkid_output, '') - result = disk_utils._is_disk_gpt_partitioned('/dev/fake', - self.node_uuid) - self.assertTrue(result) - mock_execute.assert_called_once_with('blkid', '-p', '-o', 'value', - '-s', 'PTTYPE', '/dev/fake', - use_standard_locale=True, - run_as_root=True) - - def test_is_disk_gpt_partitioned_false(self, mock_execute): - blkid_output = 'dos' - mock_execute.return_value = (blkid_output, '') - result = disk_utils._is_disk_gpt_partitioned('/dev/fake', - self.node_uuid) - self.assertFalse(result) - mock_execute.assert_called_once_with('blkid', '-p', '-o', 'value', - '-s', 'PTTYPE', '/dev/fake', - use_standard_locale=True, - run_as_root=True) - - @mock.patch.object(disk_utils.LOG, 'error') - def test_is_disk_gpt_partitioned_exc(self, mock_log, mock_execute): - mock_execute.side_effect = processutils.ProcessExecutionError - self.assertRaisesRegex(exception.InstanceDeployFailure, - 'Failed to retrieve partition table type', - disk_utils._is_disk_gpt_partitioned, - self.dev, self.node_uuid) - mock_execute.assert_called_once_with('blkid', '-p', '-o', 'value', - '-s', 'PTTYPE', '/dev/fake', - use_standard_locale=True, - run_as_root=True) - self.assertEqual(1, mock_log.call_count) - - def test_fix_gpt_structs_fix_required(self, mock_execute): - partprobe_err = """ -Error: The backup GPT table is not at the end of the disk, as it should be. -This might mean that another operating system believes the disk is smaller. -Fix, by moving the backup to the end (and removing the old backup)? -Warning: Not all of the space available to /dev/sdb appears to be used, -you can fix the GPT to use all of the space (an extra 581456476 blocks) -or continue with the current setting? -""" - mock_execute.return_value = ('', partprobe_err) - execute_calls = [ - mock.call('partprobe', '/dev/fake', use_standard_locale=True, - run_as_root=True), - mock.call('sgdisk', '-e', '/dev/fake', run_as_root=True) - ] - disk_utils._fix_gpt_structs('/dev/fake', self.node_uuid) - mock_execute.assert_has_calls(execute_calls) - - def test_fix_gpt_structs_fix_not_required(self, mock_execute): - mock_execute.return_value = ('', '') - - disk_utils._fix_gpt_structs('/dev/fake', self.node_uuid) - mock_execute.assert_called_once_with('partprobe', '/dev/fake', - use_standard_locale=True, - run_as_root=True) - - @mock.patch.object(disk_utils.LOG, 'error') - def test_fix_gpt_structs_exc(self, mock_log, mock_execute): - mock_execute.side_effect = processutils.ProcessExecutionError - self.assertRaisesRegex(exception.InstanceDeployFailure, - 'Failed to fix GPT data structures on disk', - disk_utils._fix_gpt_structs, - self.dev, self.node_uuid) - mock_execute.assert_called_once_with('partprobe', '/dev/fake', - use_standard_locale=True, - run_as_root=True) - self.assertEqual(1, mock_log.call_count) - - -class WholeDiskConfigDriveTestCases(test_base.BaseTestCase): - - def setUp(self): - super(WholeDiskConfigDriveTestCases, self).setUp() - self.dev = "/dev/fake" - self.config_part_label = "config-2" - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(utils, 'unlink_without_raise', - autospec=True) - @mock.patch.object(disk_utils, 'dd', - autospec=True) - @mock.patch.object(disk_utils, '_fix_gpt_structs', - autospec=True) - @mock.patch.object(disk_utils, '_is_disk_gpt_partitioned', - autospec=True) - @mock.patch.object(disk_utils, 'list_partitions', - autospec=True) - @mock.patch.object(disk_utils, '_get_labelled_partition', - autospec=True) - @mock.patch.object(disk_utils, '_get_configdrive', - autospec=True) - def test_create_partition_exists(self, mock_get_configdrive, - mock_get_labelled_partition, - mock_list_partitions, - mock_is_disk_gpt, mock_fix_gpt, - mock_dd, mock_unlink, mock_execute): - config_url = 'http://1.2.3.4/cd' - configdrive_part = '/dev/fake-part1' - configdrive_file = '/tmp/xyz' - configdrive_mb = 10 - - mock_get_labelled_partition.return_value = configdrive_part - mock_get_configdrive.return_value = (configdrive_mb, configdrive_file) - disk_utils.create_config_drive_partition(self.node_uuid, self.dev, - config_url) - mock_get_configdrive.assert_called_with(config_url, self.node_uuid) - mock_get_labelled_partition.assert_called_with(self.dev, - self.config_part_label, - self.node_uuid) - self.assertFalse(mock_list_partitions.called) - self.assertFalse(mock_is_disk_gpt.called) - self.assertFalse(mock_execute.called) - mock_dd.assert_called_with(configdrive_file, configdrive_part) - mock_unlink.assert_called_with(configdrive_file) - - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(utils, 'unlink_without_raise', - autospec=True) - @mock.patch.object(disk_utils, 'dd', - autospec=True) - @mock.patch.object(disk_utils, '_fix_gpt_structs', - autospec=True) - @mock.patch.object(disk_utils, '_is_disk_gpt_partitioned', - autospec=True) - @mock.patch.object(disk_utils, 'list_partitions', - autospec=True) - @mock.patch.object(disk_utils, '_get_labelled_partition', - autospec=True) - @mock.patch.object(disk_utils, '_get_configdrive', - autospec=True) - def test_create_partition_gpt(self, mock_get_configdrive, - mock_get_labelled_partition, - mock_list_partitions, - mock_is_disk_gpt, mock_fix_gpt, - mock_dd, mock_unlink, mock_execute): - config_url = 'http://1.2.3.4/cd' - configdrive_file = '/tmp/xyz' - configdrive_mb = 10 - - initial_partitions = [{'end': 49152, 'number': 1, 'start': 1, - 'flags': 'boot', 'filesystem': 'ext4', - 'size': 49151}, - {'end': 51099, 'number': 3, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 5, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}] - updated_partitions = [{'end': 49152, 'number': 1, 'start': 1, - 'flags': 'boot', 'filesystem': 'ext4', - 'size': 49151}, - {'end': 51099, 'number': 3, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 4, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 5, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}] - - mock_get_configdrive.return_value = (configdrive_mb, configdrive_file) - mock_get_labelled_partition.return_value = None - - mock_is_disk_gpt.return_value = True - mock_list_partitions.side_effect = [initial_partitions, - updated_partitions] - expected_part = '/dev/fake4' - disk_utils.create_config_drive_partition(self.node_uuid, self.dev, - config_url) - mock_execute.assert_called_with('sgdisk', '-n', '0:-64MB:0', - self.dev, run_as_root=True) - self.assertEqual(2, mock_list_partitions.call_count) - mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid) - mock_fix_gpt.assert_called_with(self.dev, self.node_uuid) - mock_dd.assert_called_with(configdrive_file, expected_part) - mock_unlink.assert_called_with(configdrive_file) - - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(disk_utils.LOG, 'warning') - @mock.patch.object(utils, 'unlink_without_raise', - autospec=True) - @mock.patch.object(disk_utils, 'dd', - autospec=True) - @mock.patch.object(disk_utils, '_is_disk_larger_than_max_size', - autospec=True) - @mock.patch.object(disk_utils, '_fix_gpt_structs', - autospec=True) - @mock.patch.object(disk_utils, '_is_disk_gpt_partitioned', - autospec=True) - @mock.patch.object(disk_utils, 'list_partitions', - autospec=True) - @mock.patch.object(disk_utils, '_get_labelled_partition', - autospec=True) - @mock.patch.object(disk_utils, '_get_configdrive', - autospec=True) - def _test_create_partition_mbr(self, mock_get_configdrive, - mock_get_labelled_partition, - mock_list_partitions, - mock_is_disk_gpt, mock_fix_gpt, - mock_disk_exceeds, mock_dd, - mock_unlink, mock_log, mock_execute, - disk_size_exceeds_max=False, - is_iscsi_device=False): - config_url = 'http://1.2.3.4/cd' - configdrive_file = '/tmp/xyz' - configdrive_mb = 10 - mock_disk_exceeds.return_value = disk_size_exceeds_max - - initial_partitions = [{'end': 49152, 'number': 1, 'start': 1, - 'flags': 'boot', 'filesystem': 'ext4', - 'size': 49151}, - {'end': 51099, 'number': 3, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 5, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}] - updated_partitions = [{'end': 49152, 'number': 1, 'start': 1, - 'flags': 'boot', 'filesystem': 'ext4', - 'size': 49151}, - {'end': 51099, 'number': 3, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 4, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 5, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}] - mock_list_partitions.side_effect = [initial_partitions, - initial_partitions, - updated_partitions] - mock_get_configdrive.return_value = (configdrive_mb, configdrive_file) - mock_get_labelled_partition.return_value = None - mock_is_disk_gpt.return_value = False - - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - if is_iscsi_device: - self.dev = ('/dev/iqn.2008-10.org.openstack:%s.fake' % - self.node_uuid) - expected_part = '%s-part4' % self.dev - else: - expected_part = '/dev/fake4' - - disk_utils.create_config_drive_partition(self.node_uuid, self.dev, - config_url) - mock_get_configdrive.assert_called_with(config_url, self.node_uuid) - if disk_size_exceeds_max: - self.assertEqual(1, mock_log.call_count) - mock_execute.assert_called_with('parted', '-a', 'optimal', '-s', - '--', self.dev, 'mkpart', - 'primary', 'ext2', 2097087, - 2097151, run_as_root=True) - else: - self.assertEqual(0, mock_log.call_count) - mock_execute.assert_called_with('parted', '-a', 'optimal', '-s', - '--', self.dev, 'mkpart', - 'primary', 'ext2', '-64MiB', - '-0', run_as_root=True) - self.assertEqual(3, mock_list_partitions.call_count) - mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid) - mock_disk_exceeds.assert_called_with(self.dev, self.node_uuid) - mock_dd.assert_called_with(configdrive_file, expected_part) - mock_unlink.assert_called_with(configdrive_file) - self.assertFalse(mock_fix_gpt.called) - - def test__create_partition_mbr_disk_under_2TB(self): - self._test_create_partition_mbr(disk_size_exceeds_max=False, - is_iscsi_device=True) - - def test__create_partition_mbr_disk_exceeds_2TB(self): - self._test_create_partition_mbr(disk_size_exceeds_max=True, - is_iscsi_device=False) - - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(utils, 'unlink_without_raise', - autospec=True) - @mock.patch.object(disk_utils, 'dd', - autospec=True) - @mock.patch.object(disk_utils, '_is_disk_larger_than_max_size', - autospec=True) - @mock.patch.object(disk_utils, '_fix_gpt_structs', - autospec=True) - @mock.patch.object(disk_utils, '_is_disk_gpt_partitioned', - autospec=True) - @mock.patch.object(disk_utils, 'list_partitions', - autospec=True) - @mock.patch.object(disk_utils, '_get_labelled_partition', - autospec=True) - @mock.patch.object(disk_utils, '_get_configdrive', - autospec=True) - def test_create_partition_part_create_fail(self, mock_get_configdrive, - mock_get_labelled_partition, - mock_list_partitions, - mock_is_disk_gpt, mock_fix_gpt, - mock_disk_exceeds, mock_dd, - mock_unlink, mock_execute): - config_url = 'http://1.2.3.4/cd' - configdrive_file = '/tmp/xyz' - configdrive_mb = 10 - - initial_partitions = [{'end': 49152, 'number': 1, 'start': 1, - 'flags': 'boot', 'filesystem': 'ext4', - 'size': 49151}, - {'end': 51099, 'number': 3, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 5, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}] - updated_partitions = [{'end': 49152, 'number': 1, 'start': 1, - 'flags': 'boot', 'filesystem': 'ext4', - 'size': 49151}, - {'end': 51099, 'number': 3, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 5, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}] - mock_get_configdrive.return_value = (configdrive_mb, configdrive_file) - mock_get_labelled_partition.return_value = None - mock_is_disk_gpt.return_value = False - mock_disk_exceeds.return_value = False - mock_list_partitions.side_effect = [initial_partitions, - initial_partitions, - updated_partitions] - - self.assertRaisesRegex(exception.InstanceDeployFailure, - 'Disk partitioning failed on device', - disk_utils.create_config_drive_partition, - self.node_uuid, self.dev, config_url) - - mock_get_configdrive.assert_called_with(config_url, self.node_uuid) - mock_execute.assert_called_with('parted', '-a', 'optimal', '-s', '--', - self.dev, 'mkpart', 'primary', - 'ext2', '-64MiB', '-0', - run_as_root=True) - self.assertEqual(3, mock_list_partitions.call_count) - mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid) - mock_disk_exceeds.assert_called_with(self.dev, self.node_uuid) - self.assertFalse(mock_fix_gpt.called) - self.assertFalse(mock_dd.called) - mock_unlink.assert_called_with(configdrive_file) - - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(utils, 'unlink_without_raise', - autospec=True) - @mock.patch.object(disk_utils, 'dd', - autospec=True) - @mock.patch.object(disk_utils, '_is_disk_larger_than_max_size', - autospec=True) - @mock.patch.object(disk_utils, '_fix_gpt_structs', - autospec=True) - @mock.patch.object(disk_utils, '_is_disk_gpt_partitioned', - autospec=True) - @mock.patch.object(disk_utils, 'list_partitions', - autospec=True) - @mock.patch.object(disk_utils, '_get_labelled_partition', - autospec=True) - @mock.patch.object(disk_utils, '_get_configdrive', - autospec=True) - def test_create_partition_part_create_exc(self, mock_get_configdrive, - mock_get_labelled_partition, - mock_list_partitions, - mock_is_disk_gpt, mock_fix_gpt, - mock_disk_exceeds, mock_dd, - mock_unlink, mock_execute): - config_url = 'http://1.2.3.4/cd' - configdrive_file = '/tmp/xyz' - configdrive_mb = 10 - - initial_partitions = [{'end': 49152, 'number': 1, 'start': 1, - 'flags': 'boot', 'filesystem': 'ext4', - 'size': 49151}, - {'end': 51099, 'number': 3, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 5, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}] - mock_get_configdrive.return_value = (configdrive_mb, configdrive_file) - mock_get_labelled_partition.return_value = None - mock_is_disk_gpt.return_value = False - mock_disk_exceeds.return_value = False - mock_list_partitions.side_effect = [initial_partitions, - initial_partitions] - - mock_execute.side_effect = processutils.ProcessExecutionError - - self.assertRaisesRegex(exception.InstanceDeployFailure, - 'Failed to create config drive on disk', - disk_utils.create_config_drive_partition, - self.node_uuid, self.dev, config_url) - - mock_get_configdrive.assert_called_with(config_url, self.node_uuid) - mock_execute.assert_called_with('parted', '-a', 'optimal', '-s', '--', - self.dev, 'mkpart', 'primary', - 'ext2', '-64MiB', '-0', - run_as_root=True) - self.assertEqual(2, mock_list_partitions.call_count) - mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid) - mock_disk_exceeds.assert_called_with(self.dev, self.node_uuid) - self.assertFalse(mock_fix_gpt.called) - self.assertFalse(mock_dd.called) - mock_unlink.assert_called_with(configdrive_file) - - @mock.patch.object(utils, 'unlink_without_raise', - autospec=True) - @mock.patch.object(disk_utils, 'dd', - autospec=True) - @mock.patch.object(disk_utils, '_fix_gpt_structs', - autospec=True) - @mock.patch.object(disk_utils, '_is_disk_gpt_partitioned', - autospec=True) - @mock.patch.object(disk_utils, 'list_partitions', - autospec=True) - @mock.patch.object(disk_utils, '_get_labelled_partition', - autospec=True) - @mock.patch.object(disk_utils, '_get_configdrive', - autospec=True) - def test_create_partition_num_parts_exceed(self, mock_get_configdrive, - mock_get_labelled_partition, - mock_list_partitions, - mock_is_disk_gpt, mock_fix_gpt, - mock_dd, mock_unlink): - config_url = 'http://1.2.3.4/cd' - configdrive_file = '/tmp/xyz' - configdrive_mb = 10 - - partitions = [{'end': 49152, 'number': 1, 'start': 1, - 'flags': 'boot', 'filesystem': 'ext4', - 'size': 49151}, - {'end': 51099, 'number': 2, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 3, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}, - {'end': 51099, 'number': 4, 'start': 49153, - 'flags': '', 'filesystem': '', 'size': 2046}] - mock_get_configdrive.return_value = (configdrive_mb, configdrive_file) - mock_get_labelled_partition.return_value = None - mock_is_disk_gpt.return_value = False - mock_list_partitions.side_effect = [partitions, partitions] - - self.assertRaisesRegex(exception.InstanceDeployFailure, - 'Config drive cannot be created for node', - disk_utils.create_config_drive_partition, - self.node_uuid, self.dev, config_url) - - mock_get_configdrive.assert_called_with(config_url, self.node_uuid) - self.assertEqual(2, mock_list_partitions.call_count) - mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid) - self.assertFalse(mock_fix_gpt.called) - self.assertFalse(mock_dd.called) - mock_unlink.assert_called_with(configdrive_file) - - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(utils, 'unlink_without_raise', - autospec=True) - @mock.patch.object(disk_utils, '_get_labelled_partition', - autospec=True) - @mock.patch.object(disk_utils, '_get_configdrive', - autospec=True) - def test_create_partition_conf_drive_sz_exceed(self, mock_get_configdrive, - mock_get_labelled_partition, - mock_unlink, mock_execute): - config_url = 'http://1.2.3.4/cd' - configdrive_file = '/tmp/xyz' - configdrive_mb = 65 - - mock_get_configdrive.return_value = (configdrive_mb, configdrive_file) - mock_get_labelled_partition.return_value = None - - self.assertRaisesRegex(exception.InstanceDeployFailure, - 'Config drive size exceeds maximum limit', - disk_utils.create_config_drive_partition, - self.node_uuid, self.dev, config_url) - - mock_get_configdrive.assert_called_with(config_url, self.node_uuid) - mock_unlink.assert_called_with(configdrive_file) diff --git a/ironic_lib/tests/test_metrics.py b/ironic_lib/tests/test_metrics.py deleted file mode 100644 index aad9713..0000000 --- a/ironic_lib/tests/test_metrics.py +++ /dev/null @@ -1,158 +0,0 @@ -# Copyright 2016 Rackspace Hosting -# 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 types - -import mock -from oslotest import base as test_base - -from ironic_lib import metrics as metricslib - - -class MockedMetricLogger(metricslib.MetricLogger): - _gauge = mock.Mock(spec_set=types.FunctionType) - _counter = mock.Mock(spec_set=types.FunctionType) - _timer = mock.Mock(spec_set=types.FunctionType) - - -class TestMetricLogger(test_base.BaseTestCase): - def setUp(self): - super(TestMetricLogger, self).setUp() - self.ml = MockedMetricLogger('prefix', '.') - self.ml_no_prefix = MockedMetricLogger('', '.') - self.ml_other_delim = MockedMetricLogger('prefix', '*') - self.ml_default = MockedMetricLogger() - - def test_init(self): - self.assertEqual(self.ml._prefix, 'prefix') - self.assertEqual(self.ml._delimiter, '.') - - self.assertEqual(self.ml_no_prefix._prefix, '') - self.assertEqual(self.ml_other_delim._delimiter, '*') - self.assertEqual(self.ml_default._prefix, '') - - def test_get_metric_name(self): - self.assertEqual( - self.ml.get_metric_name('metric'), - 'prefix.metric') - - self.assertEqual( - self.ml_no_prefix.get_metric_name('metric'), - 'metric') - - self.assertEqual( - self.ml_other_delim.get_metric_name('metric'), - 'prefix*metric') - - def test_send_gauge(self): - self.ml.send_gauge('prefix.metric', 10) - self.ml._gauge.assert_called_once_with('prefix.metric', 10) - - def test_send_counter(self): - self.ml.send_counter('prefix.metric', 10) - self.ml._counter.assert_called_once_with( - 'prefix.metric', 10, - sample_rate=None) - self.ml._counter.reset_mock() - - self.ml.send_counter('prefix.metric', 10, sample_rate=1.0) - self.ml._counter.assert_called_once_with( - 'prefix.metric', 10, - sample_rate=1.0) - self.ml._counter.reset_mock() - - self.ml.send_counter('prefix.metric', 10, sample_rate=0.0) - self.assertFalse(self.ml._counter.called) - - def test_send_timer(self): - self.ml.send_timer('prefix.metric', 10) - self.ml._timer.assert_called_once_with('prefix.metric', 10) - - @mock.patch('ironic_lib.metrics._time', autospec=True) - @mock.patch('ironic_lib.metrics.MetricLogger.send_timer', autospec=True) - def test_decorator_timer(self, mock_timer, mock_time): - mock_time.side_effect = [1, 43] - - @self.ml.timer('foo.bar.baz') - def func(x): - return x * x - - func(10) - - mock_timer.assert_called_once_with(self.ml, 'prefix.foo.bar.baz', - 42 * 1000) - - @mock.patch('ironic_lib.metrics.MetricLogger.send_counter', autospec=True) - def test_decorator_counter(self, mock_counter): - - @self.ml.counter('foo.bar.baz') - def func(x): - return x * x - - func(10) - - mock_counter.assert_called_once_with(self.ml, 'prefix.foo.bar.baz', 1, - sample_rate=None) - - @mock.patch('ironic_lib.metrics.MetricLogger.send_counter', autospec=True) - def test_decorator_counter_sample_rate(self, mock_counter): - - @self.ml.counter('foo.bar.baz', sample_rate=0.5) - def func(x): - return x * x - - func(10) - - mock_counter.assert_called_once_with(self.ml, 'prefix.foo.bar.baz', 1, - sample_rate=0.5) - - @mock.patch('ironic_lib.metrics.MetricLogger.send_gauge', autospec=True) - def test_decorator_gauge(self, mock_gauge): - @self.ml.gauge('foo.bar.baz') - def func(x): - return x - - func(10) - - mock_gauge.assert_called_once_with(self.ml, 'prefix.foo.bar.baz', 10) - - @mock.patch('ironic_lib.metrics._time', autospec=True) - @mock.patch('ironic_lib.metrics.MetricLogger.send_timer', autospec=True) - def test_context_mgr_timer(self, mock_timer, mock_time): - mock_time.side_effect = [1, 43] - - with self.ml.timer('foo.bar.baz'): - pass - - mock_timer.assert_called_once_with(self.ml, 'prefix.foo.bar.baz', - 42 * 1000) - - @mock.patch('ironic_lib.metrics.MetricLogger.send_counter', autospec=True) - def test_context_mgr_counter(self, mock_counter): - - with self.ml.counter('foo.bar.baz'): - pass - - mock_counter.assert_called_once_with(self.ml, 'prefix.foo.bar.baz', 1, - sample_rate=None) - - @mock.patch('ironic_lib.metrics.MetricLogger.send_counter', autospec=True) - def test_context_mgr_counter_sample_rate(self, mock_counter): - - with self.ml.counter('foo.bar.baz', sample_rate=0.5): - pass - - mock_counter.assert_called_once_with(self.ml, 'prefix.foo.bar.baz', 1, - sample_rate=0.5) diff --git a/ironic_lib/tests/test_metrics_statsd.py b/ironic_lib/tests/test_metrics_statsd.py deleted file mode 100644 index d328cd6..0000000 --- a/ironic_lib/tests/test_metrics_statsd.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2016 Rackspace Hosting -# 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 socket - -import mock -from oslotest import base as test_base - -from ironic_lib import metrics_statsd - - -class TestStatsdMetricLogger(test_base.BaseTestCase): - def setUp(self): - super(TestStatsdMetricLogger, self).setUp() - self.ml = metrics_statsd.StatsdMetricLogger('prefix', '.', 'test-host', - 4321) - - def test_init(self): - self.assertEqual(self.ml._host, 'test-host') - self.assertEqual(self.ml._port, 4321) - self.assertEqual(self.ml._target, ('test-host', 4321)) - - @mock.patch('ironic_lib.metrics_statsd.StatsdMetricLogger._send', - autospec=True) - def test_gauge(self, mock_send): - self.ml._gauge('metric', 10) - mock_send.assert_called_once_with(self.ml, 'metric', 10, 'g') - - @mock.patch('ironic_lib.metrics_statsd.StatsdMetricLogger._send', - autospec=True) - def test_counter(self, mock_send): - self.ml._counter('metric', 10) - mock_send.assert_called_once_with(self.ml, 'metric', 10, 'c', - sample_rate=None) - mock_send.reset_mock() - - self.ml._counter('metric', 10, sample_rate=1.0) - mock_send.assert_called_once_with(self.ml, 'metric', 10, 'c', - sample_rate=1.0) - - @mock.patch('ironic_lib.metrics_statsd.StatsdMetricLogger._send', - autospec=True) - def test_timer(self, mock_send): - self.ml._timer('metric', 10) - mock_send.assert_called_once_with(self.ml, 'metric', 10, 'ms') - - @mock.patch('socket.socket') - def test_open_socket(self, mock_socket_constructor): - self.ml._open_socket() - mock_socket_constructor.assert_called_once_with( - socket.AF_INET, - socket.SOCK_DGRAM) - - @mock.patch('socket.socket') - def test_send(self, mock_socket_constructor): - mock_socket = mock.Mock() - mock_socket_constructor.return_value = mock_socket - - self.ml._send('part1.part2', 2, 'type') - mock_socket.sendto.assert_called_once_with( - 'part1.part2:2|type', - ('test-host', 4321)) - mock_socket.close.assert_called_once_with() - mock_socket.reset_mock() - - self.ml._send('part1.part2', 3.14159, 'type') - mock_socket.sendto.assert_called_once_with( - 'part1.part2:3.14159|type', - ('test-host', 4321)) - mock_socket.close.assert_called_once_with() - mock_socket.reset_mock() - - self.ml._send('part1.part2', 5, 'type') - mock_socket.sendto.assert_called_once_with( - 'part1.part2:5|type', - ('test-host', 4321)) - mock_socket.close.assert_called_once_with() - mock_socket.reset_mock() - - self.ml._send('part1.part2', 5, 'type', sample_rate=0.5) - mock_socket.sendto.assert_called_once_with( - 'part1.part2:5|type@0.5', - ('test-host', 4321)) - mock_socket.close.assert_called_once_with() diff --git a/ironic_lib/tests/test_metrics_utils.py b/ironic_lib/tests/test_metrics_utils.py deleted file mode 100644 index a479d19..0000000 --- a/ironic_lib/tests/test_metrics_utils.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2016 Rackspace Hosting -# 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 oslotest import base as test_base - -from oslo_config import cfg - -from ironic_lib import exception -from ironic_lib import metrics as metricslib -from ironic_lib import metrics_statsd -from ironic_lib import metrics_utils - -CONF = cfg.CONF - - -class TestGetLogger(test_base.BaseTestCase): - def setUp(self): - super(TestGetLogger, self).setUp() - - def test_default_backend(self): - metrics = metrics_utils.get_metrics_logger('foo') - self.assertIsInstance(metrics, metricslib.NoopMetricLogger) - - def test_statsd_backend(self): - CONF.set_override('backend', 'statsd', group='metrics') - - metrics = metrics_utils.get_metrics_logger('foo') - self.assertIsInstance(metrics, metrics_statsd.StatsdMetricLogger) - CONF.clear_override('backend', group='metrics') - - def test_nonexisting_backend(self): - CONF.set_override('backend', 'none', group='metrics') - - self.assertRaises(exception.InvalidMetricConfig, - metrics_utils.get_metrics_logger, 'foo') - CONF.clear_override('backend', group='metrics') - - def test_numeric_prefix(self): - self.assertRaises(exception.InvalidMetricConfig, - metrics_utils.get_metrics_logger, 1) - - def test_numeric_list_prefix(self): - self.assertRaises(exception.InvalidMetricConfig, - metrics_utils.get_metrics_logger, (1, 2)) - - def test_default_prefix(self): - metrics = metrics_utils.get_metrics_logger() - self.assertIsInstance(metrics, metricslib.NoopMetricLogger) - self.assertEqual(metrics.get_metric_name("bar"), "bar") - - def test_prepend_host_backend(self): - CONF.set_override('prepend_host', True, group='metrics') - CONF.set_override('prepend_host_reverse', False, group='metrics') - - metrics = metrics_utils.get_metrics_logger(prefix='foo', - host="host.example.com") - self.assertIsInstance(metrics, metricslib.NoopMetricLogger) - self.assertEqual(metrics.get_metric_name("bar"), - "host.example.com.foo.bar") - - CONF.clear_override('prepend_host', group='metrics') - CONF.clear_override('prepend_host_reverse', group='metrics') - - def test_prepend_global_prefix_host_backend(self): - CONF.set_override('prepend_host', True, group='metrics') - CONF.set_override('prepend_host_reverse', False, group='metrics') - CONF.set_override('global_prefix', 'global_pre', group='metrics') - - metrics = metrics_utils.get_metrics_logger(prefix='foo', - host="host.example.com") - self.assertIsInstance(metrics, metricslib.NoopMetricLogger) - self.assertEqual(metrics.get_metric_name("bar"), - "global_pre.host.example.com.foo.bar") - - CONF.clear_override('prepend_host', group='metrics') - CONF.clear_override('prepend_host_reverse', group='metrics') - CONF.clear_override('global_prefix', group='metrics') - - def test_prepend_other_delim(self): - metrics = metrics_utils.get_metrics_logger('foo', delimiter='*') - self.assertIsInstance(metrics, metricslib.NoopMetricLogger) - self.assertEqual(metrics.get_metric_name("bar"), - "foo*bar") - - def test_prepend_host_reverse_backend(self): - CONF.set_override('prepend_host', True, group='metrics') - CONF.set_override('prepend_host_reverse', True, group='metrics') - - metrics = metrics_utils.get_metrics_logger('foo', - host="host.example.com") - self.assertIsInstance(metrics, metricslib.NoopMetricLogger) - self.assertEqual(metrics.get_metric_name("bar"), - "com.example.host.foo.bar") - - CONF.clear_override('prepend_host', group='metrics') - CONF.clear_override('prepend_host_reverse', group='metrics') diff --git a/ironic_lib/tests/test_utils.py b/ironic_lib/tests/test_utils.py deleted file mode 100644 index 6a62519..0000000 --- a/ironic_lib/tests/test_utils.py +++ /dev/null @@ -1,543 +0,0 @@ -# Copyright 2011 Justin Santa Barbara -# Copyright 2012 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. - -import copy -import errno -import os -import os.path -import tempfile - -import mock -from oslo_concurrency import processutils -from oslo_config import cfg -from oslotest import base as test_base - -from ironic_lib import exception -from ironic_lib import utils - -CONF = cfg.CONF - - -class BareMetalUtilsTestCase(test_base.BaseTestCase): - - def test_unlink(self): - with mock.patch.object(os, "unlink", autospec=True) as unlink_mock: - unlink_mock.return_value = None - utils.unlink_without_raise("/fake/path") - unlink_mock.assert_called_once_with("/fake/path") - - def test_unlink_ENOENT(self): - with mock.patch.object(os, "unlink", autospec=True) as unlink_mock: - unlink_mock.side_effect = OSError(errno.ENOENT) - utils.unlink_without_raise("/fake/path") - unlink_mock.assert_called_once_with("/fake/path") - - -class ExecuteTestCase(test_base.BaseTestCase): - - def test_retry_on_failure(self): - fd, tmpfilename = tempfile.mkstemp() - _, tmpfilename2 = tempfile.mkstemp() - try: - fp = os.fdopen(fd, 'w+') - fp.write('''#!/bin/sh -# If stdin fails to get passed during one of the runs, make a note. -if ! grep -q foo -then - echo 'failure' > "$1" -fi -# If stdin has failed to get passed during this or a previous run, exit early. -if grep failure "$1" -then - exit 1 -fi -runs="$(cat $1)" -if [ -z "$runs" ] -then - runs=0 -fi -runs=$(($runs + 1)) -echo $runs > "$1" -exit 1 -''') - fp.close() - os.chmod(tmpfilename, 0o755) - try: - self.assertRaises(processutils.ProcessExecutionError, - utils.execute, - tmpfilename, tmpfilename2, attempts=10, - process_input=b'foo', - delay_on_retry=False) - except OSError as e: - if e.errno == errno.EACCES: - self.skipTest("Permissions error detected. " - "Are you running with a noexec /tmp?") - else: - raise - fp = open(tmpfilename2, 'r') - runs = fp.read() - fp.close() - self.assertNotEqual(runs.strip(), 'failure', 'stdin did not ' - 'always get passed ' - 'correctly') - runs = int(runs.strip()) - self.assertEqual(10, runs, - 'Ran %d times instead of 10.' % (runs,)) - finally: - os.unlink(tmpfilename) - os.unlink(tmpfilename2) - - def test_unknown_kwargs_raises_error(self): - self.assertRaises(processutils.UnknownArgumentError, - utils.execute, - '/usr/bin/env', 'true', - this_is_not_a_valid_kwarg=True) - - def test_check_exit_code_boolean(self): - utils.execute('/usr/bin/env', 'false', check_exit_code=False) - self.assertRaises(processutils.ProcessExecutionError, - utils.execute, - '/usr/bin/env', 'false', check_exit_code=True) - - def test_no_retry_on_success(self): - fd, tmpfilename = tempfile.mkstemp() - _, tmpfilename2 = tempfile.mkstemp() - try: - fp = os.fdopen(fd, 'w+') - fp.write('''#!/bin/sh -# If we've already run, bail out. -grep -q foo "$1" && exit 1 -# Mark that we've run before. -echo foo > "$1" -# Check that stdin gets passed correctly. -grep foo -''') - fp.close() - os.chmod(tmpfilename, 0o755) - try: - utils.execute(tmpfilename, - tmpfilename2, - process_input=b'foo', - attempts=2) - except OSError as e: - if e.errno == errno.EACCES: - self.skipTest("Permissions error detected. " - "Are you running with a noexec /tmp?") - else: - raise - finally: - os.unlink(tmpfilename) - os.unlink(tmpfilename2) - - @mock.patch.object(processutils, 'execute', autospec=True) - @mock.patch.object(os.environ, 'copy', return_value={}, autospec=True) - def test_execute_use_standard_locale_no_env_variables(self, env_mock, - execute_mock): - utils.execute('foo', use_standard_locale=True) - execute_mock.assert_called_once_with('foo', - env_variables={'LC_ALL': 'C'}) - - @mock.patch.object(processutils, 'execute', autospec=True) - def test_execute_use_standard_locale_with_env_variables(self, - execute_mock): - utils.execute('foo', use_standard_locale=True, - env_variables={'foo': 'bar'}) - execute_mock.assert_called_once_with('foo', - env_variables={'LC_ALL': 'C', - 'foo': 'bar'}) - - @mock.patch.object(processutils, 'execute', autospec=True) - def test_execute_not_use_standard_locale(self, execute_mock): - utils.execute('foo', use_standard_locale=False, - env_variables={'foo': 'bar'}) - execute_mock.assert_called_once_with('foo', - env_variables={'foo': 'bar'}) - - def test_execute_without_root_helper(self): - CONF.set_override('root_helper', None, group='ironic_lib') - with mock.patch.object( - processutils, 'execute', autospec=True) as execute_mock: - utils.execute('foo', run_as_root=False) - execute_mock.assert_called_once_with('foo', run_as_root=False) - - def test_execute_without_root_helper_run_as_root(self): - CONF.set_override('root_helper', None, group='ironic_lib') - with mock.patch.object( - processutils, 'execute', autospec=True) as execute_mock: - utils.execute('foo', run_as_root=True) - execute_mock.assert_called_once_with('foo', run_as_root=False) - - def test_execute_with_root_helper(self): - with mock.patch.object( - processutils, 'execute', autospec=True) as execute_mock: - utils.execute('foo', run_as_root=False) - execute_mock.assert_called_once_with('foo', run_as_root=False) - - def test_execute_with_root_helper_run_as_root(self): - with mock.patch.object( - processutils, 'execute', autospec=True) as execute_mock: - utils.execute('foo', run_as_root=True) - execute_mock.assert_called_once_with( - 'foo', run_as_root=True, - root_helper=CONF.ironic_lib.root_helper) - - @mock.patch.object(utils, 'LOG', autospec=True) - def _test_execute_with_log_stdout(self, log_mock, log_stdout=None): - with mock.patch.object(processutils, 'execute') as execute_mock: - execute_mock.return_value = ('stdout', 'stderr') - if log_stdout is not None: - utils.execute('foo', log_stdout=log_stdout) - else: - utils.execute('foo') - execute_mock.assert_called_once_with('foo') - name, args, kwargs = log_mock.debug.mock_calls[1] - if log_stdout is False: - self.assertEqual(2, log_mock.debug.call_count) - self.assertNotIn('stdout', args[0]) - else: - self.assertEqual(3, log_mock.debug.call_count) - self.assertIn('stdout', args[0]) - - def test_execute_with_log_stdout_default(self): - self._test_execute_with_log_stdout() - - def test_execute_with_log_stdout_true(self): - self._test_execute_with_log_stdout(log_stdout=True) - - def test_execute_with_log_stdout_false(self): - self._test_execute_with_log_stdout(log_stdout=False) - - -class MkfsTestCase(test_base.BaseTestCase): - - @mock.patch.object(utils, 'execute', autospec=True) - def test_mkfs(self, execute_mock): - utils.mkfs('ext4', '/my/block/dev') - utils.mkfs('msdos', '/my/msdos/block/dev') - utils.mkfs('swap', '/my/swap/block/dev') - - expected = [mock.call('mkfs', '-t', 'ext4', '-F', '/my/block/dev', - run_as_root=True, - use_standard_locale=True), - mock.call('mkfs', '-t', 'msdos', '/my/msdos/block/dev', - run_as_root=True, - use_standard_locale=True), - mock.call('mkswap', '/my/swap/block/dev', - run_as_root=True, - use_standard_locale=True)] - self.assertEqual(expected, execute_mock.call_args_list) - - @mock.patch.object(utils, 'execute', autospec=True) - def test_mkfs_with_label(self, execute_mock): - utils.mkfs('ext4', '/my/block/dev', 'ext4-vol') - utils.mkfs('msdos', '/my/msdos/block/dev', 'msdos-vol') - utils.mkfs('swap', '/my/swap/block/dev', 'swap-vol') - - expected = [mock.call('mkfs', '-t', 'ext4', '-F', '-L', 'ext4-vol', - '/my/block/dev', run_as_root=True, - use_standard_locale=True), - mock.call('mkfs', '-t', 'msdos', '-n', 'msdos-vol', - '/my/msdos/block/dev', run_as_root=True, - use_standard_locale=True), - mock.call('mkswap', '-L', 'swap-vol', - '/my/swap/block/dev', run_as_root=True, - use_standard_locale=True)] - self.assertEqual(expected, execute_mock.call_args_list) - - @mock.patch.object(utils, 'execute', autospec=True, - side_effect=processutils.ProcessExecutionError( - stderr=os.strerror(errno.ENOENT))) - def test_mkfs_with_unsupported_fs(self, execute_mock): - self.assertRaises(exception.FileSystemNotSupported, - utils.mkfs, 'foo', '/my/block/dev') - - @mock.patch.object(utils, 'execute', autospec=True, - side_effect=processutils.ProcessExecutionError( - stderr='fake')) - def test_mkfs_with_unexpected_error(self, execute_mock): - self.assertRaises(processutils.ProcessExecutionError, utils.mkfs, - 'ext4', '/my/block/dev', 'ext4-vol') - - -class IsHttpUrlTestCase(test_base.BaseTestCase): - - def test_is_http_url(self): - self.assertTrue(utils.is_http_url('http://127.0.0.1')) - self.assertTrue(utils.is_http_url('https://127.0.0.1')) - self.assertTrue(utils.is_http_url('HTTP://127.1.2.3')) - self.assertTrue(utils.is_http_url('HTTPS://127.3.2.1')) - self.assertFalse(utils.is_http_url('Zm9vYmFy')) - self.assertFalse(utils.is_http_url('11111111')) - - -class ParseRootDeviceTestCase(test_base.BaseTestCase): - - def test_parse_root_device_hints_without_operators(self): - root_device = { - 'wwn': '123456', 'model': 'FOO model', 'size': 12345, - 'serial': 'foo-serial', 'vendor': 'foo VENDOR with space', - 'name': '/dev/sda', 'wwn_with_extension': '123456111', - 'wwn_vendor_extension': '111', 'rotational': True} - result = utils.parse_root_device_hints(root_device) - expected = { - 'wwn': 's== 123456', 'model': 's== foo%20model', - 'size': '== 12345', 'serial': 's== foo-serial', - 'vendor': 's== foo%20vendor%20with%20space', - 'name': 's== /dev/sda', 'wwn_with_extension': 's== 123456111', - 'wwn_vendor_extension': 's== 111', 'rotational': True} - self.assertEqual(expected, result) - - def test_parse_root_device_hints_with_operators(self): - root_device = { - 'wwn': 's== 123456', 'model': 's== foo MODEL', 'size': '>= 12345', - 'serial': 's!= foo-serial', 'vendor': 's== foo VENDOR with space', - 'name': ' /dev/sda /dev/sdb', - 'wwn_with_extension': 's!= 123456111', - 'wwn_vendor_extension': 's== 111', 'rotational': True} - - # Validate strings being normalized - expected = copy.deepcopy(root_device) - expected['model'] = 's== foo%20model' - expected['vendor'] = 's== foo%20vendor%20with%20space' - - result = utils.parse_root_device_hints(root_device) - # The hints already contain the operators, make sure we keep it - self.assertEqual(expected, result) - - def test_parse_root_device_hints_no_hints(self): - result = utils.parse_root_device_hints({}) - self.assertIsNone(result) - - def test_parse_root_device_hints_convert_size(self): - for size in (12345, '12345'): - result = utils.parse_root_device_hints({'size': size}) - self.assertEqual({'size': '== 12345'}, result) - - def test_parse_root_device_hints_invalid_size(self): - for value in ('not-int', -123, 0): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'size': value}) - - def test_parse_root_device_hints_int_or(self): - expr = ' 123 456 789' - result = utils.parse_root_device_hints({'size': expr}) - self.assertEqual({'size': expr}, result) - - def test_parse_root_device_hints_int_or_invalid(self): - expr = ' 123 non-int 789' - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'size': expr}) - - def test_parse_root_device_hints_string_or_space(self): - expr = ' foo foo bar bar' - expected = ' foo foo%20bar bar' - result = utils.parse_root_device_hints({'model': expr}) - self.assertEqual({'model': expected}, result) - - def _parse_root_device_hints_convert_rotational(self, values, - expected_value): - for value in values: - result = utils.parse_root_device_hints({'rotational': value}) - self.assertEqual({'rotational': expected_value}, result) - - def test_parse_root_device_hints_convert_rotational(self): - self._parse_root_device_hints_convert_rotational( - (True, 'true', 'on', 'y', 'yes'), True) - - self._parse_root_device_hints_convert_rotational( - (False, 'false', 'off', 'n', 'no'), False) - - def test_parse_root_device_hints_invalid_rotational(self): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'rotational': 'not-bool'}) - - def test_parse_root_device_hints_invalid_wwn(self): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'wwn': 123}) - - def test_parse_root_device_hints_invalid_wwn_with_extension(self): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'wwn_with_extension': 123}) - - def test_parse_root_device_hints_invalid_wwn_vendor_extension(self): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'wwn_vendor_extension': 123}) - - def test_parse_root_device_hints_invalid_model(self): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'model': 123}) - - def test_parse_root_device_hints_invalid_serial(self): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'serial': 123}) - - def test_parse_root_device_hints_invalid_vendor(self): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'vendor': 123}) - - def test_parse_root_device_hints_invalid_name(self): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'name': 123}) - - def test_parse_root_device_hints_non_existent_hint(self): - self.assertRaises(ValueError, utils.parse_root_device_hints, - {'non-existent': 'foo'}) - - def test_extract_hint_operator_and_values_single_value(self): - expected = {'op': '>=', 'values': ['123']} - self.assertEqual( - expected, utils._extract_hint_operator_and_values( - '>= 123', 'size')) - - def test_extract_hint_operator_and_values_multiple_values(self): - expected = {'op': '', 'values': ['123', '456', '789']} - expr = ' 123 456 789' - self.assertEqual( - expected, utils._extract_hint_operator_and_values(expr, 'size')) - - def test_extract_hint_operator_and_values_multiple_values_space(self): - expected = {'op': '', 'values': ['foo', 'foo bar', 'bar']} - expr = ' foo foo bar bar' - self.assertEqual( - expected, utils._extract_hint_operator_and_values(expr, 'model')) - - def test_extract_hint_operator_and_values_no_operator(self): - expected = {'op': '', 'values': ['123']} - self.assertEqual( - expected, utils._extract_hint_operator_and_values('123', 'size')) - - def test_extract_hint_operator_and_values_empty_value(self): - self.assertRaises( - ValueError, utils._extract_hint_operator_and_values, '', 'size') - - def test_extract_hint_operator_and_values_integer(self): - expected = {'op': '', 'values': ['123']} - self.assertEqual( - expected, utils._extract_hint_operator_and_values(123, 'size')) - - def test__append_operator_to_hints(self): - root_device = {'serial': 'foo', 'size': 12345, - 'model': 'foo model', 'rotational': True} - expected = {'serial': 's== foo', 'size': '== 12345', - 'model': 's== foo model', 'rotational': True} - - result = utils._append_operator_to_hints(root_device) - self.assertEqual(expected, result) - - def test_normalize_hint_expression_or(self): - expr = ' foo foo bar bar' - expected = ' foo foo%20bar bar' - result = utils._normalize_hint_expression(expr, 'model') - self.assertEqual(expected, result) - - def test_normalize_hint_expression_in(self): - expr = ' foo foo bar bar' - expected = ' foo foo%20bar bar' - result = utils._normalize_hint_expression(expr, 'model') - self.assertEqual(expected, result) - - def test_normalize_hint_expression_op_space(self): - expr = 's== test string with space' - expected = 's== test%20string%20with%20space' - result = utils._normalize_hint_expression(expr, 'model') - self.assertEqual(expected, result) - - def test_normalize_hint_expression_op_no_space(self): - expr = 's!= SpongeBob' - expected = 's!= spongebob' - result = utils._normalize_hint_expression(expr, 'model') - self.assertEqual(expected, result) - - def test_normalize_hint_expression_no_op_space(self): - expr = 'no operators' - expected = 'no%20operators' - result = utils._normalize_hint_expression(expr, 'model') - self.assertEqual(expected, result) - - def test_normalize_hint_expression_no_op_no_space(self): - expr = 'NoSpace' - expected = 'nospace' - result = utils._normalize_hint_expression(expr, 'model') - self.assertEqual(expected, result) - - def test_normalize_hint_expression_empty_value(self): - self.assertRaises( - ValueError, utils._normalize_hint_expression, '', 'size') - - -class MatchRootDeviceTestCase(test_base.BaseTestCase): - - def setUp(self): - super(MatchRootDeviceTestCase, self).setUp() - self.devices = [ - {'name': '/dev/sda', 'size': 64424509440, 'model': 'ok model', - 'serial': 'fakeserial'}, - {'name': '/dev/sdb', 'size': 128849018880, 'model': 'big model', - 'serial': 'veryfakeserial', 'rotational': 'yes'}, - {'name': '/dev/sdc', 'size': 10737418240, 'model': 'small model', - 'serial': 'veryveryfakeserial', 'rotational': False}, - ] - - def test_match_root_device_hints_one_hint(self): - root_device_hints = {'size': '>= 70'} - dev = utils.match_root_device_hints(self.devices, root_device_hints) - self.assertEqual('/dev/sdb', dev['name']) - - def test_match_root_device_hints_rotational(self): - root_device_hints = {'rotational': False} - dev = utils.match_root_device_hints(self.devices, root_device_hints) - self.assertEqual('/dev/sdc', dev['name']) - - def test_match_root_device_hints_rotational_convert_devices_bool(self): - root_device_hints = {'size': '>=100', 'rotational': True} - dev = utils.match_root_device_hints(self.devices, root_device_hints) - self.assertEqual('/dev/sdb', dev['name']) - - def test_match_root_device_hints_multiple_hints(self): - root_device_hints = {'size': '>= 50', 'model': 's==big model', - 'serial': 's==veryfakeserial'} - dev = utils.match_root_device_hints(self.devices, root_device_hints) - self.assertEqual('/dev/sdb', dev['name']) - - def test_match_root_device_hints_multiple_hints2(self): - root_device_hints = { - 'size': '<= 20', - 'model': ' model 5 foomodel small model ', - 'serial': 's== veryveryfakeserial'} - dev = utils.match_root_device_hints(self.devices, root_device_hints) - self.assertEqual('/dev/sdc', dev['name']) - - def test_match_root_device_hints_multiple_hints3(self): - root_device_hints = {'rotational': False, 'model': ' small'} - dev = utils.match_root_device_hints(self.devices, root_device_hints) - self.assertEqual('/dev/sdc', dev['name']) - - def test_match_root_device_hints_no_operators(self): - root_device_hints = {'size': '120', 'model': 'big model', - 'serial': 'veryfakeserial'} - dev = utils.match_root_device_hints(self.devices, root_device_hints) - self.assertEqual('/dev/sdb', dev['name']) - - def test_match_root_device_hints_no_device_found(self): - root_device_hints = {'size': '>=50', 'model': 's==foo'} - dev = utils.match_root_device_hints(self.devices, root_device_hints) - self.assertIsNone(dev) - - @mock.patch.object(utils.LOG, 'warning', autospec=True) - def test_match_root_device_hints_empty_device_attribute(self, mock_warn): - empty_dev = [{'name': '/dev/sda', 'model': ' '}] - dev = utils.match_root_device_hints(empty_dev, {'model': 'foo'}) - self.assertIsNone(dev) - self.assertTrue(mock_warn.called) diff --git a/ironic_lib/utils.py b/ironic_lib/utils.py deleted file mode 100644 index 039176a..0000000 --- a/ironic_lib/utils.py +++ /dev/null @@ -1,407 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2011 Justin Santa Barbara -# Copyright (c) 2012 NTT DOCOMO, INC. -# 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. - -"""Utilities and helper functions.""" - -import copy -import errno -import logging -import os -import re - -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_utils import excutils -from oslo_utils import specs_matcher -from oslo_utils import strutils -from oslo_utils import units -import six -from six.moves.urllib import parse - -from ironic_lib.common.i18n import _ -from ironic_lib.common.i18n import _LE -from ironic_lib.common.i18n import _LW -from ironic_lib import exception - -utils_opts = [ - cfg.StrOpt('root_helper', - default='sudo ironic-rootwrap /etc/ironic/rootwrap.conf', - help='Command that is prefixed to commands that are run as ' - 'root. If not specified, no commands are run as root.'), -] - -CONF = cfg.CONF -CONF.register_opts(utils_opts, group='ironic_lib') - -LOG = logging.getLogger(__name__) - -# A dictionary in the form {hint name: hint type} -VALID_ROOT_DEVICE_HINTS = { - 'size': int, 'model': str, 'wwn': str, 'serial': str, 'vendor': str, - 'wwn_with_extension': str, 'wwn_vendor_extension': str, 'name': str, - 'rotational': bool, -} - - -ROOT_DEVICE_HINTS_GRAMMAR = specs_matcher.make_grammar() - - -def execute(*cmd, **kwargs): - """Convenience wrapper around oslo's execute() method. - - Executes and logs results from a system command. See docs for - oslo_concurrency.processutils.execute for usage. - - :param \*cmd: positional arguments to pass to processutils.execute() - :param use_standard_locale: keyword-only argument. True | False. - Defaults to False. If set to True, - execute command with standard locale - added to environment variables. - :param log_stdout: keyword-only argument. True | False. Defaults - to True. If set to True, logs the output. - :param \*\*kwargs: keyword arguments to pass to processutils.execute() - :returns: (stdout, stderr) from process execution - :raises: UnknownArgumentError on receiving unknown arguments - :raises: ProcessExecutionError - :raises: OSError - """ - - use_standard_locale = kwargs.pop('use_standard_locale', False) - if use_standard_locale: - env = kwargs.pop('env_variables', os.environ.copy()) - env['LC_ALL'] = 'C' - kwargs['env_variables'] = env - - log_stdout = kwargs.pop('log_stdout', True) - - # If root_helper config is not specified, no commands are run as root. - run_as_root = kwargs.get('run_as_root', False) - if run_as_root: - if not CONF.ironic_lib.root_helper: - kwargs['run_as_root'] = False - else: - kwargs['root_helper'] = CONF.ironic_lib.root_helper - - result = processutils.execute(*cmd, **kwargs) - LOG.debug('Execution completed, command line is "%s"', - ' '.join(map(str, cmd))) - if log_stdout: - LOG.debug('Command stdout is: "%s"' % result[0]) - LOG.debug('Command stderr is: "%s"' % result[1]) - return result - - -def mkfs(fs, path, label=None): - """Format a file or block device - - :param fs: Filesystem type (examples include 'swap', 'ext3', 'ext4' - 'btrfs', etc.) - :param path: Path to file or block device to format - :param label: Volume label to use - """ - if fs == 'swap': - args = ['mkswap'] - else: - args = ['mkfs', '-t', fs] - # add -F to force no interactive execute on non-block device. - if fs in ('ext3', 'ext4'): - args.extend(['-F']) - if label: - if fs in ('msdos', 'vfat'): - label_opt = '-n' - else: - label_opt = '-L' - args.extend([label_opt, label]) - args.append(path) - try: - execute(*args, run_as_root=True, use_standard_locale=True) - except processutils.ProcessExecutionError as e: - with excutils.save_and_reraise_exception() as ctx: - if os.strerror(errno.ENOENT) in e.stderr: - ctx.reraise = False - LOG.exception(_LE('Failed to make file system. ' - 'File system %s is not supported.'), fs) - raise exception.FileSystemNotSupported(fs=fs) - else: - LOG.exception(_LE('Failed to create a file system ' - 'in %(path)s. Error: %(error)s'), - {'path': path, 'error': e}) - - -def unlink_without_raise(path): - try: - os.unlink(path) - except OSError as e: - if e.errno == errno.ENOENT: - return - else: - LOG.warning(_LW("Failed to unlink %(path)s, error: %(e)s"), - {'path': path, 'e': e}) - - -def dd(src, dst, *args): - """Execute dd from src to dst. - - :param src: the input file for dd command. - :param dst: the output file for dd command. - :param args: a tuple containing the arguments to be - passed to dd command. - :raises: processutils.ProcessExecutionError if it failed - to run the process. - """ - LOG.debug("Starting dd process.") - execute('dd', 'if=%s' % src, 'of=%s' % dst, *args, - use_standard_locale=True, run_as_root=True, check_exit_code=[0]) - - -def is_http_url(url): - url = url.lower() - return url.startswith('http://') or url.startswith('https://') - - -def list_opts(): - """Entry point for oslo-config-generator.""" - return [('ironic_lib', utils_opts)] - - -def _extract_hint_operator_and_values(hint_expression, hint_name): - """Extract the operator and value(s) of a root device hint expression. - - A root device hint expression could contain one or more values - depending on the operator. This method extracts the operator and - value(s) and returns a dictionary containing both. - - :param hint_expression: The hint expression string containing value(s) - and operator (optionally). - :param hint_name: The name of the hint. Used for logging. - :raises: ValueError if the hint_expression is empty. - :returns: A dictionary containing: - - :op: The operator. An empty string in case of None. - :values: A list of values stripped and converted to lowercase. - """ - expression = six.text_type(hint_expression).strip().lower() - if not expression: - raise ValueError( - _('Root device hint "%s" expression is empty') % hint_name) - - # parseString() returns a list of tokens which the operator (if - # present) is always the first element. - ast = ROOT_DEVICE_HINTS_GRAMMAR.parseString(expression) - if len(ast) <= 1: - # hint_expression had no operator - return {'op': '', 'values': [expression]} - - op = ast[0] - return {'values': [v.strip() for v in re.split(op, expression) if v], - 'op': op} - - -def _normalize_hint_expression(hint_expression, hint_name): - """Normalize a string type hint expression. - - A string-type hint expression contains one or more operators and - one or more values: [] [ ]*. This normalizes - the values by url-encoding white spaces and special characters. The - operators are not normalized. For example: the hint value of " - foo bar bar" will become " foo%20bar bar". - - :param hint_expression: The hint expression string containing value(s) - and operator (optionally). - :param hint_name: The name of the hint. Used for logging. - :raises: ValueError if the hint_expression is empty. - :returns: A normalized string. - """ - hdict = _extract_hint_operator_and_values(hint_expression, hint_name) - result = hdict['op'].join([' %s ' % parse.quote(t) - for t in hdict['values']]) - return (hdict['op'] + result).strip() - - -def _append_operator_to_hints(root_device): - """Add an equal (s== or ==) operator to the hints. - - For backwards compatibility, for root device hints where no operator - means equal, this method adds the equal operator to the hint. This is - needed when using oslo.utils.specs_matcher methods. - - :param root_device: The root device hints dictionary. - """ - for name, expression in root_device.items(): - # NOTE(lucasagomes): The specs_matcher from oslo.utils does not - # support boolean, so we don't need to append any operator - # for it. - if VALID_ROOT_DEVICE_HINTS[name] is bool: - continue - - expression = six.text_type(expression) - ast = ROOT_DEVICE_HINTS_GRAMMAR.parseString(expression) - if len(ast) > 1: - continue - - op = 's== %s' if VALID_ROOT_DEVICE_HINTS[name] is str else '== %s' - root_device[name] = op % expression - - return root_device - - -def parse_root_device_hints(root_device): - """Parse the root_device property of a node. - - Parses and validates the root_device property of a node. These are - hints for how a node's root device is created. The 'size' hint - should be a positive integer. The 'rotational' hint should be a - Boolean value. - - :param root_device: the root_device dictionary from the node's property. - :returns: a dictionary with the root device hints parsed or - None if there are no hints. - :raises: ValueError, if some information is invalid. - - """ - if not root_device: - return - - root_device = copy.deepcopy(root_device) - - invalid_hints = set(root_device) - set(VALID_ROOT_DEVICE_HINTS) - if invalid_hints: - raise ValueError( - _('The hints "%(invalid_hints)s" are invalid. ' - 'Valid hints are: "%(valid_hints)s"') % - {'invalid_hints': ', '.join(invalid_hints), - 'valid_hints': ', '.join(VALID_ROOT_DEVICE_HINTS)}) - - for name, expression in root_device.items(): - hint_type = VALID_ROOT_DEVICE_HINTS[name] - if hint_type is str: - if not isinstance(expression, six.string_types): - raise ValueError( - _('Root device hint "%(name)s" is not a string value. ' - 'Hint expression: %(expression)s') % - {'name': name, 'expression': expression}) - root_device[name] = _normalize_hint_expression(expression, name) - - elif hint_type is int: - for v in _extract_hint_operator_and_values(expression, - name)['values']: - try: - integer = int(v) - except ValueError: - raise ValueError( - _('Root device hint "%(name)s" is not an integer ' - 'value. Current value: %(expression)s') % - {'name': name, 'expression': expression}) - - if integer <= 0: - raise ValueError( - _('Root device hint "%(name)s" should be a positive ' - 'integer. Current value: %(expression)s') % - {'name': name, 'expression': expression}) - - elif hint_type is bool: - try: - root_device[name] = strutils.bool_from_string( - expression, strict=True) - except ValueError: - raise ValueError( - _('Root device hint "%(name)s" is not a Boolean value. ' - 'Current value: %(expression)s') % - {'name': name, 'expression': expression}) - - return _append_operator_to_hints(root_device) - - -def match_root_device_hints(devices, root_device_hints): - """Try to find a device that matches the root device hints. - - Try to find a device that matches the root device hints. In order - for a device to be matched it needs to satisfy all the given hints. - - :param devices: A list of dictionaries representing the devices - containing one or more of the following keys: - - :name: (String) The device name, e.g /dev/sda - :size: (Integer) Size of the device in *bytes* - :model: (String) Device model - :vendor: (String) Device vendor name - :serial: (String) Device serial number - :wwn: (String) Unique storage identifier - :wwn_with_extension: (String): Unique storage identifier with - the vendor extension appended - :wwn_vendor_extension: (String): United vendor storage identifier - :rotational: (Boolean) Whether it's a rotational device or - not. Useful to distinguish HDDs (rotational) and SSDs - (not rotational). - - :param root_device_hints: A dictionary with the root device hints. - :raises: ValueError, if some information is invalid. - :returns: The first device to match all the hints or None. - """ - LOG.debug('Trying to find a device from "%(devs)s" that matches the ' - 'root device hints "%(hints)s"', {'devs': devices, - 'hints': root_device_hints}) - parsed_hints = parse_root_device_hints(root_device_hints) - for dev in devices: - for hint in parsed_hints: - hint_type = VALID_ROOT_DEVICE_HINTS[hint] - device_value = dev.get(hint) - hint_value = parsed_hints[hint] - - if hint_type is str: - try: - device_value = _normalize_hint_expression(device_value, - hint) - except ValueError: - LOG.warning( - _LW('The attribute "%(attr)s" of the device "%(dev)s" ' - 'has an empty value. Skipping device.'), - {'attr': hint, 'dev': dev}) - break - - # NOTE(lucasagomes): Boolean hints are not supported by - # specs_matcher.match(), so we need to do the comparison - # ourselves - if hint_type is bool: - try: - device_value = strutils.bool_from_string(device_value, - strict=True) - except ValueError: - LOG.warning(_LW('The attribute "%(attr)s" (with value ' - '"%(value)s") of device "%(dev)s" is not ' - 'a valid Boolean. Skipping device.'), - {'attr': hint, 'value': device_value, - 'dev': dev}) - break - if device_value == hint_value: - continue - break - - if hint == 'size': - # Since we don't support units yet we expect the size - # in GiB for now - device_value = device_value / units.Gi - - if not specs_matcher.match(device_value, hint_value): - break - else: - LOG.debug('Device found! The device "%s" matches the root ' - 'device hints' % dev) - return dev - - LOG.debug('No device found that matches the root device hints') diff --git a/ironic_lib/version.py b/ironic_lib/version.py deleted file mode 100644 index cd45253..0000000 --- a/ironic_lib/version.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2011 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import pbr.version - -version_info = pbr.version.VersionInfo('ironic_lib') diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 9b1b50b..0000000 --- a/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -pbr>=1.6 # Apache-2.0 -oslo.concurrency>=3.8.0 # Apache-2.0 -oslo.config>=3.14.0 # Apache-2.0 -oslo.i18n>=2.1.0 # Apache-2.0 -oslo.service>=1.10.0 # Apache-2.0 -oslo.utils>=3.16.0 # Apache-2.0 -requests>=2.10.0 # Apache-2.0 -six>=1.9.0 # MIT -oslo.log>=1.14.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 3f102a8..0000000 --- a/setup.cfg +++ /dev/null @@ -1,38 +0,0 @@ -[metadata] -name = ironic-lib -summary = Ironic common library -description-file = - README.rst -author = OpenStack Ironic -author-email = openstack-dev@lists.openstack.org -home-page = http://www.openstack.org/ -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 - -[files] -packages = - ironic_lib - -[entry_points] -oslo.config.opts = - ironic_lib.disk_partitioner = ironic_lib.disk_partitioner:list_opts - ironic_lib.disk_utils = ironic_lib.disk_utils:list_opts - ironic_lib.utils = ironic_lib.utils:list_opts - ironic_lib.metrics = ironic_lib.metrics_utils:list_opts - ironic_lib.metrics_statsd = ironic_lib.metrics_statsd:list_opts - -[pbr] -autodoc_index_modules = True -warnerrors = True - -[build_sphinx] -all_files = 1 -build-dir = doc/build -source-dir = doc/source diff --git a/setup.py b/setup.py deleted file mode 100644 index 782bb21..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT -import setuptools - -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - -setuptools.setup( - setup_requires=['pbr>=1.8'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index b6f0e36..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,17 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -coverage>=3.6 # Apache-2.0 -eventlet!=0.18.3,>=0.18.2 # MIT -hacking<0.11,>=0.10.0 -mock>=2.0 # BSD -os-testr>=0.7.0 # Apache-2.0 -oslotest>=1.10.0 # Apache-2.0 -testscenarios>=0.4 # Apache-2.0/BSD -testtools>=1.4.0 # MIT - -# Doc requirements -doc8 # Apache-2.0 -sphinx!=1.3b1,<1.3,>=1.2.1 # BSD -oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 diff --git a/tools/tox_install.sh b/tools/tox_install.sh deleted file mode 100755 index 00654e4..0000000 --- a/tools/tox_install.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -# Client constraint file contains this client version pin that is in conflict -# with installing the client from source. We should replace the version pin in -# the constraints file before applying it for from-source installation. - -ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner -BRANCH_NAME=master -CLIENT_NAME=ironic-lib -requirements_installed=$(echo "import openstack_requirements" | python 2>/dev/null ; echo $?) - -set -e - -CONSTRAINTS_FILE=$1 -shift - -install_cmd="pip install" -mydir=$(mktemp -dt "$CLIENT_NAME-tox_install-XXXXXXX") -trap "rm -rf $mydir" EXIT -localfile=$mydir/upper-constraints.txt -if [[ $CONSTRAINTS_FILE != http* ]]; then - CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE -fi -curl $CONSTRAINTS_FILE -k -o $localfile -install_cmd="$install_cmd -c$localfile" - -if [ $requirements_installed -eq 0 ]; then - echo "ALREADY INSTALLED" > /tmp/tox_install.txt - echo "Requirements already installed; using existing package" -elif [ -x "$ZUUL_CLONER" ]; then - echo "ZUUL CLONER" > /tmp/tox_install.txt - pushd $mydir - $ZUUL_CLONER --cache-dir \ - /opt/git \ - --branch $BRANCH_NAME \ - git://git.openstack.org \ - openstack/requirements - cd openstack/requirements - $install_cmd -e . - popd -else - echo "PIP HARDCODE" > /tmp/tox_install.txt - if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then - REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements" - fi - $install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION} -fi - -# This is the main purpose of the script: Allow local installation of -# the current repo. It is listed in constraints file and thus any -# install will be constrained and we need to unconstrain it. -edit-constraints $localfile -- $CLIENT_NAME "-e file://$PWD#egg=$CLIENT_NAME" - -$install_cmd -U $* -exit $? diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 8aaa327..0000000 --- a/tox.ini +++ /dev/null @@ -1,41 +0,0 @@ -[tox] -minversion = 1.8 -skipsdist = True -envlist = py34,py27,pep8 - -[testenv] -install_command = - {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} -usedevelop = True -setenv = VIRTUAL_ENV={envdir} - PYTHONDONTWRITEBYTECODE = 1 - LANGUAGE=en_US - TESTS_DIR=./ironic_lib/tests/ -deps = -r{toxinidir}/test-requirements.txt -commands = ostestr {posargs} - -[flake8] -show-source = True -ignore = E129 -exclude = .venv,.tox,dist,doc,*.egg,.update-venv - -[testenv:pep8] -commands = - flake8 {posargs} - doc8 README.rst doc/source --ignore D001 - -[testenv:cover] -setenv = VIRTUALENV={envdir} - LANGUAGE=en_US -commands = - python setup.py test --coverage --coverage-package-name=ironic_lib --omit=ironic_lib/openstack/common/*.py {posargs} - -[testenv:venv] -commands = {posargs} - -[testenv:docs] -setenv = PYTHONHASHSEED=0 -sitepackages = False -envdir = {toxworkdir}/venv -commands = - python setup.py build_sphinx