././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6814146 virtualbmc-3.1.0/0000775000175000017500000000000000000000000013706 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/.coveragerc0000664000175000017500000000014100000000000016023 0ustar00zuulzuul00000000000000[run] branch = True source = virtualbmc omit = virtualbmc/tests/* [report] ignore_errors = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/.stestr.conf0000664000175000017500000000010600000000000016154 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${TESTS_DIR:-./virtualbmc/tests/unit/} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/AUTHORS0000664000175000017500000000346600000000000014767 0ustar00zuulzuul0000000000000098k <18552437190@163.com> Akira Yoshiyama Amy Marrich (spotz) Andreas Jaeger Arundhati Surpur Charles Short Derek Higgins Dmitry Tantsur Dmitry Tantsur Doug Hellmann Flavio Percoco Ghanshyam Mann Hervé Beraud Ilya Etingof Iury Gregory Melo Ferreira Iury Gregory Melo Ferreira Jacek Tomasiak James E. Blair Jay Faulkner Jim Rollenhagen John L. Villalovos John L. Villalovos Julia Kreger Kaifeng Wang Lucas Alvares Gomes Maciej Kucia Mark Goddard Matthew Thode Miles Gould Nam Nguyen Hoai Nguyen Hai Truong Nisha Agarwal Pete Zaitcev Pierre Riteau Riccardo Pittau Ruby Loo Sean McGinnis Vu Cong Tuan William Caban Zhijiang Hu caoyuan chenxing huang.zhiping luke.li pengyuesheng wangjiaqi07 wangqi xiexs yangyawei ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/CONTRIBUTING.rst0000664000175000017500000000121600000000000016347 0ustar00zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed in Launchpad, not GitHub: https://bugs.launchpad.net/virtualbmc ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/ChangeLog0000664000175000017500000001702300000000000015463 0ustar00zuulzuul00000000000000CHANGES ======= 3.1.0 ----- * Remove duplicate space in log message * Bugs are now in Launchpad, update docs to reflect * Update pep8 dep hacking to latest * Use generic openstack python3 tests 3.0.1 ----- * Fixes for tox 4.0 * remove python-dev from bindep * Document VirtualBMC is not for production use 3.0.0 ----- * preserve secure information on xml changes * remove unicode from code * Remove support for Python 3.6 and 3.7 2.2.2 ----- * Use zed jobs * Re-add python 3.6/3.7 in classifier * Updating python testing as per Yoga testing runtime * Update pep8 test requirements 2.2.1 ----- * Update min version of tox to use allowlist * Use xena jobs * Use TOX\_CONSTRAINTS\_FILE * setup.cfg: Replace dashes with underscores * Remove "iscsi" from job names * Update minversion of tox * Add doc/requirements * Remove lower-constraints job * Fix lower-constraints with the new pip resolver * Use wallaby jobs * Set safe version of hacking * Drop redundant milliseconds from logging 2.2.0 ----- * Fix lower-constraints for ubuntu focal * Correct vmbc add command * Set min version of tox to 3.2.1 * drop mock from lower-constraints * Remove unneded Babel config * Update lower-constraints.txt * Switch to newer openstackdocstheme and reno versions * Fix doc build failure * Do not import print function from \_\_future\_\_ 2.1.0 ----- * Set flake8-import-order version to 0.17.1 * Restore default netboot boot option * Restore pep8 checking on import order * Use unittest.mock instead of third party mock * Stop configuring install\_command in tox * Convert virtualbmc tempest to dib * Cleanup py27 support * Bump hacking to 3.0.0 * Explicitly set ramdisk type * Fix stop command by using default SIGTERM handler in children * [trivial] change vbmc error message 2.0.0 ----- * Remove \`vbmc\` feature of starting up \`vbmcd\` * Fix hanging on shell-pipe * Add \`error\` instance status to \`vbmc\` reporing * Fix logging in vbmcd * Fix error triggered by reading intergers as string from virtualbmc.conf * Enforce running tox with correct python version based on env * Remove version check for encoding * Stop using six library * Drop python 2.7 support and testing * Drop py2 jobs * Switch to Ussuri job * Switch jobs to python3 * Update the constraints url * Build pdf doc * Blacklist sphinx 2.1.0 (autodoc bug) 1.6.0 ----- * Tiny fix for documentation RST convention * CI: clean up required projects * Add a bindep file * Bump the openstackdocstheme extension to 1.20 * Update Python 3 test runtimes for Train * Update sphinx requirements * Reword config file search logic in the docs * Respect VIRTUAL\_BMC for non-default config path * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch 1.5.0 ----- * Ignore "start" command if instance appears to be running * Bump lower libvirt-python to 3.7 * Move to zuulv3 * Update README * Update home-page * Change openstack-dev to openstack-discuss * Update min tox version to 2.0 * Don't quote {posargs} in tox.ini * Tolerate missing openstackdocstheme in docs * Remove duplicate lines in README * Change CI job to use python3 * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Change py35 to py3 so Python 3.6 can be picked up * Update name for virtualbmc CI job * Enable debug logging in tests * Fix release note theme for virtualbmc 1.4.0 ----- * Fix ZMQError class usage * Fix crash caused by log message interpolation bug * Fix log message interpolation bugs * Specifiy encoding for xml string * Add CI job to publish docs * pycrypto is not used by virtualbmc * Add reno noting recent changes * Add reno for release notes management * Switch to using stestr * Improve PID file creation * Domain \`start\` command accepts multiple domain names * Make server spawn and response times configurable * Minor code style improvements * Improve logging * Improve the documentation * fix tox python3 overrides * multiprocess server, ZMQ-based management cli tool * Fix power status command error reporting * Unrevert usage example inconsistency fix in docs * Revert "Add Serial-over-LAN (SOL) support" * Do not run functional (API) tests in the CI * Report libvirt failures as IPMI-retryable * Update bug tracker url for storyboard * add lower-constraints job * Switch the CI to hardware types and clean up the playbook * Add Serial-over-LAN (SOL) support * Updated from global requirements * Migrate CLI to cliff * Updated from global requirements * Update links in README * Updated from global requirements * Remove devices/\*/boot when setting bootdev 1.3.0 ----- * Zuul: Remove project name * Updated from global requirements * Change numeric constants to VARIABLE\_NAME * Updated from global requirements * Support power reset command * Updated from global requirements * Updated from global requirements * Use the tempest plugin from openstack/ironic-tempest-plugin * Avoid tox\_install.sh for constraints support * Clean up zuul files * Updated from global requirements * Add Zuulv3 job in tree in virtualbmc * Updated from global requirements * Updated from global requirements * Updated from global requirements 1.2.0 ----- * Updated from global requirements * Update the documentation link for doc migration * Fixes cli parser when run under Python 3.3+ * Removes unnecessary utf-8 encoding * Drop MANIFEST.in - it's not needed by pbr * Updated from global requirements * rearrange existing documentation to follow the new layout standard * Updated from global requirements * Replace assertRaisesRegexp with assertRaisesRegex * Updated from global requirements * Updated from global requirements * Updated from global requirements * Properly handle upper-constraints 1.1.0 ----- * Fix TypeError when writing config file in Python 3 * Update hacking requirements to fix the pep8 CI job 1.0.0 ----- * Fix documentation title and README * Updated from global requirements * Document some usage with examples * Fix typo in help message * Delete support py33 and py34 * Fix help string about adding a VBMC using IPv4 * Adds power diag support to vbmc * Adds power\_soft support to vbmc * Return proper errors on BMC action failures * Add support for a "global" configuration file * Fix Python3 test errors * Show real error when it fails to create a new VBMC * Show team and repo badges on README * Updated from global requirements * Updated from global requirements * Bump pyghmi version to 1.0.9 0.1.0 ----- * Use upper-constraints for all tox targets * Remove unused releasenote setup * Set IPMI session timeout for the virtual BMCs * Use constraints for all the things * Bump pyghmi version to 1.0.3 * Add unittests for the config.py module * Add unittests for the vbmc.py module * Raise an exception for domains already registered * Add "cover" to .gitignore * Add unittests for the manager.py module * "vbmc list" to sort the result by domain name * Add unittests for the cmd/vbmc.py module * Add unittests for the utils.py module * Restructure the repository according to OpenStack * Mask passwords before displaying them * Bump the version of the project to 0.0.4 * Add support for SASL authentication with libvirt * Add support for parsing multiple values * Add --version parameter * Clarify the 'address' parameter * Bump the version of the project to 0.0.3 * Fix "show" command * Bump the version of the project to 0.0.2 * Add config.py module * Create utils.py module * Split VirtualBMCManager to its own file * Add better logs * Make the XML handling more resilient to failures * Check VM state prior to power it on/off * Add the "vbmc" utility * Add a SIGINT handler * Initial Commit with code * Initial commit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/HACKING.rst0000664000175000017500000000024000000000000015500 0ustar00zuulzuul00000000000000virtualbmc Style Commandments =============================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/LICENSE0000664000175000017500000002363700000000000014726 0ustar00zuulzuul00000000000000 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. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6814146 virtualbmc-3.1.0/PKG-INFO0000664000175000017500000000545600000000000015015 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: virtualbmc Version: 3.1.0 Summary: Create virtual BMCs for controlling virtual instances via IPMI Home-page: https://docs.openstack.org/virtualbmc/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ========== VirtualBMC ========== Team and repository tags ------------------------ .. image:: https://governance.openstack.org/tc/badges/virtualbmc.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Overview -------- A virtual BMC for controlling virtual machines using IPMI commands. This software is intended for CI and development use only. Please do not run VirtualBMC in a production environment for any reason. Installation ~~~~~~~~~~~~ .. code-block:: bash pip install virtualbmc Supported IPMI commands ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash # Power the virtual machine on, off, graceful off, NMI and reset ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power on|off|soft|diag|reset # Check the power status ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power status # Set the boot device to network, hd or cdrom ipmitool -I lanplus -U admin -P password -H 127.0.0.1 chassis bootdev pxe|disk|cdrom # Get the current boot device ipmitool -I lanplus -U admin -P password -H 127.0.0.1 chassis bootparam get 5 Project resources ~~~~~~~~~~~~~~~~~ * Documentation: https://docs.openstack.org/virtualbmc/latest * Source: https://opendev.org/openstack/virtualbmc * Bugs: https://bugs.launchpad.net/virtualbmc * Release Notes: https://docs.openstack.org/releasenotes/virtualbmc/ For information on how to contribute to VirtualBMC, see https://docs.openstack.org/virtualbmc/latest/contributor Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/README.rst0000664000175000017500000000270500000000000015401 0ustar00zuulzuul00000000000000========== VirtualBMC ========== Team and repository tags ------------------------ .. image:: https://governance.openstack.org/tc/badges/virtualbmc.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Overview -------- A virtual BMC for controlling virtual machines using IPMI commands. This software is intended for CI and development use only. Please do not run VirtualBMC in a production environment for any reason. Installation ~~~~~~~~~~~~ .. code-block:: bash pip install virtualbmc Supported IPMI commands ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash # Power the virtual machine on, off, graceful off, NMI and reset ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power on|off|soft|diag|reset # Check the power status ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power status # Set the boot device to network, hd or cdrom ipmitool -I lanplus -U admin -P password -H 127.0.0.1 chassis bootdev pxe|disk|cdrom # Get the current boot device ipmitool -I lanplus -U admin -P password -H 127.0.0.1 chassis bootparam get 5 Project resources ~~~~~~~~~~~~~~~~~ * Documentation: https://docs.openstack.org/virtualbmc/latest * Source: https://opendev.org/openstack/virtualbmc * Bugs: https://bugs.launchpad.net/virtualbmc * Release Notes: https://docs.openstack.org/releasenotes/virtualbmc/ For information on how to contribute to VirtualBMC, see https://docs.openstack.org/virtualbmc/latest/contributor ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/bindep.txt0000664000175000017500000000062600000000000015714 0ustar00zuulzuul00000000000000# these are needed to compile Python dependencies from sources python3-all-dev [platform:dpkg !platform:ubuntu-precise test compile] python3-devel [platform:rpm test compile] build-essential [platform:dpkg test compile] libssl-dev [platform:dpkg test compile] libvirt-dev [platform:dpkg test compile] libvirt-devel [platform:rpm test compile] libzmq5 [platform:dpkg test compile] pkg-config [compile test] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6774147 virtualbmc-3.1.0/doc/0000775000175000017500000000000000000000000014453 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/doc/requirements.txt0000664000175000017500000000021000000000000017730 0ustar00zuulzuul00000000000000reno>=3.1.0 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6774147 virtualbmc-3.1.0/doc/source/0000775000175000017500000000000000000000000015753 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/doc/source/conf.py0000775000175000017500000000525600000000000017265 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', ] try: import openstackdocstheme extensions.append('openstackdocstheme') except ImportError: openstackdocstheme = None # openstackdocstheme options openstackdocs_repo_name = 'openstack/virtualbmc' openstackdocs_pdf_link = True openstackdocs_use_storyboard = False # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. copyright = '2016, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # -- 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'] if openstackdocstheme is not None: html_theme = 'openstackdocs' else: html_theme = 'default' # Output file base name for HTML help builder. htmlhelp_basename = 'virtualbmcdoc' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'doc-virtualbmc.tex', 'VirtualBMC Documentation', 'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6774147 virtualbmc-3.1.0/doc/source/contributor/0000775000175000017500000000000000000000000020325 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/doc/source/contributor/index.rst0000664000175000017500000000011600000000000022164 0ustar00zuulzuul00000000000000============ Contributing ============ .. include:: ../../../CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/doc/source/index.rst0000664000175000017500000000153400000000000017617 0ustar00zuulzuul00000000000000.. virtualbmc documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to VirtualBMC's documentation! ====================================== The VirtualBMC tool simulates a `Baseboard Management Controller `_ (BMC) by exposing `IPMI `_ responder to the network and talking to `libvirt `_ at the host vBMC is running at to manipulate virtual machines which pretend to be bare metal servers. Contents: .. toctree:: :maxdepth: 1 install/index user/index contributor/index ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6774147 virtualbmc-3.1.0/doc/source/install/0000775000175000017500000000000000000000000017421 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/doc/source/install/index.rst0000664000175000017500000000030700000000000021262 0ustar00zuulzuul00000000000000============ Installation ============ At the command line:: $ pip install virtualbmc Or, if you have virtualenvwrapper installed:: $ mkvirtualenv virtualbmc $ pip install virtualbmc ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6774147 virtualbmc-3.1.0/doc/source/user/0000775000175000017500000000000000000000000016731 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/doc/source/user/index.rst0000664000175000017500000001142000000000000020570 0ustar00zuulzuul00000000000000 How to use VirtualBMC ===================== For the VirtualBMC tool to operate you first need to create libvirt domain(s) for example, via ``virsh``. The VirtualBMC tool is a client-server system where ``vbmcd`` server does all the heavy-lifting (speaks IPMI, calls libvirt) while ``vbmc`` client is merely a command-line tool sending commands to the server and rendering responses to the user. Both tools can make use of an optional configuration file, which is looked for in the following locations (in this order): * ``VIRTUALBMC_CONFIG`` environment variable pointing to a file * ``$HOME/.vbmc/virtualbmc.conf`` file * ``/etc/virtualbmc/virtualbmc.conf`` file If no configuration file has been found, the internal defaults apply. You should set up your systemd to launch the ``vbmcd`` server on system start up or you can just run ``vbmcd`` from command line if you do not need the tool running persistently on the system. Once the server is up and running, you can use the ``vbmc`` tool to configure your libvirt domains as if they were physical hardware servers. The ``vbmc`` client can only communicate with ``vbmcd`` server if both are running on the same host. However ``vbmcd`` can manage libvirt domains remotely. By this moment you should be able to have the ``ipmitool`` managing VirtualBMC instances over the network. Configuring virtual servers --------------------------- Use the ``vbmc`` command-line tool to create, delete, list, start and stop virtual BMCs for the virtual machines being managed over IPMI. * In order to see all command options supported by the ``vbmc`` tool do:: $ vbmc --help It's also possible to list the options from a specific command. For example, in order to know what can be provided as part of the ``add`` command do:: $ vbmc add --help * Adding a new virtual BMC to control libvirt domain called ``node-0``:: $ vbmc add node-0 * Adding a new virtual BMC to control libvirt domain called ``node-1`` that will listen for IPMI commands on port ``6230``:: $ vbmc add node-1 --port 6230 Alternatively, libvirt can be configured to ssh into a remote machine and manage libvirt domain through ssh connection:: $ vbmc add node-1 --port 6230 \ --libvirt-uri qemu+ssh://username@192.168.122.1/system .. note:: Binding a network port number below 1025 is restricted and only users with privilege will be able to start a virtual BMC on those ports. * Starting the virtual BMC to control libvirt domain ``node-0``:: $ vbmc start node-0 * Stopping the virtual BMC that controls libvirt domain ``node-0``:: $ vbmc stop node-0 * Getting the list of virtual BMCs including their libvirt domains and IPMI network endpoints they are reachable at:: $ vbmc list +-------------+---------+---------+------+ | Domain name | Status | Address | Port | +-------------+---------+---------+------+ | node-0 | running | :: | 623 | | node-1 | running | :: | 6230 | +-------------+---------+---------+------+ * To view configuration information for a specific virtual BMC:: $ vbmc show node-0 +-----------------------+----------------+ | Property | Value | +-----------------------+----------------+ | address | :: | | domain_name | node-0 | | libvirt_sasl_password | *** | | libvirt_sasl_username | None | | libvirt_uri | qemu:///system | | password | *** | | port | 623 | | status | running | | username | admin | +-----------------------+----------------+ Server simulation ----------------- Once the virtual BMC for a specific domain has been created and started you can then issue IPMI commands against the address and port of that virtual BMC to control the libvirt domain. For example: * To power on the virtual machine:: $ ipmitool -I lanplus -U admin -P password -H 127.0.0.1 -p 6230 power on * To check its power status:: $ ipmitool -I lanplus -U admin -P password -H 127.0.0.1 -p 6230 power status * To set the boot device to disk:: $ ipmitool -I lanplus -U admin -P password -H 127.0.0.1 -p 6230 chassis bootdev disk * To get the current boot device:: $ ipmitool -I lanplus -U admin -P password -H 127.0.0.1 -p 6230 chassis bootparam get 5 Backward compatible behaviour ----------------------------- In the past the ``vbmc`` tool was the only part of the vBMC system. To help users keeping their existing server-less workflows, the ``vbmc`` tool attempts to spawn the ``vbmcd`` piece whenever it figures server is not running. .. warning:: The backward compabible behaviour will be removed in two-cycle time past Queens. ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1695307724.673415 virtualbmc-3.1.0/releasenotes/0000775000175000017500000000000000000000000016377 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6774147 virtualbmc-3.1.0/releasenotes/notes/0000775000175000017500000000000000000000000017527 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000022000 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/add-client-server-overhaul-c5b6f8c01126b4a3.yaml0000664000175000017500000000163100000000000027751 0ustar00zuulzuul00000000000000--- features: - | Changes the design of the VirtualBMC tool. Instead of forking the ``vbmc`` command-line tool to become a daemon and serve a single libvirt domain, the ``vbmcd`` master process and ``vbmc`` command-line client have been introduced. These client-server tools communicate over the ZeroMQ queue. The ``vbmcd`` process is responsible for herding its children, each child still serves a single libvirt domain. - | The ``vbmc start`` command now supports multiple domains. upgrade: - | It is advised to invoke ``vbmcd`` master process on system boot, perhaps by a systemd unit file. deprecations: - | Deprecates automatically starting up the ``vbmcd`` daemon process if it is not running. This backward-compatibility feature will be removed in the OpenStack Stein release. security: - | Hardens PID file creation to prevent the symlink attack. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/add-config-env-8287bea486821653.yaml0000664000175000017500000000034700000000000025211 0ustar00zuulzuul00000000000000features: - | Adds the ability to override default configuration file location by exporting the ``$VIRTUALBMC_CONFIG`` variable, pointing to the desired config file, into ``vbmcd`` and ``vbmc`` processes environment. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/drop-py-2-7-afe69612bfabaeee.yaml0000664000175000017500000000031600000000000025104 0ustar00zuulzuul00000000000000upgrade: - | Python 2.7 support has been dropped. Last release of virtualbmc to support Python 2.7 is OpenStack Train. The minimum version of Python now supported by virtualbmc is Python 3.6. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/fix-hanging-on-pipe-7c4b5f9c81623b524.yaml0000664000175000017500000000020700000000000026403 0ustar00zuulzuul00000000000000--- fixes: - | Properly closes standard I/O streams to prevent shell-piped processes from hanging infinitely on a dead pipe. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/ignore-start-if-running-c9a8f6c0514624a1.yaml0000664000175000017500000000047200000000000027237 0ustar00zuulzuul00000000000000--- fixes: - | Ignores instance "start" command if instance appears to be running. This helps preserving backward-compatible behaviour, as previous implementation has required the user to explicitly "start" enabled instances. With current virtualbmc, only the master process needs to be started. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/preserve-libvirt-domain-info-955410f570060241.yaml0000664000175000017500000000050000000000000027734 0ustar00zuulzuul00000000000000--- security: - | Secure information in the Libvirt domain XML document is now preserved. For more information, please see `story 2010382 `_. fixes: - | Fixes an issue where secure fields were accidently lost in the Libvirt domain XML document. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/py36-37-bye-bye-f3268421bf6c5bb4.yaml0000664000175000017500000000016600000000000025213 0ustar00zuulzuul00000000000000--- upgrade: - | Virtualbmc does not support Python 3.6 and 3.7 anymore, please use version 3.8 or higher. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/remove-vbmcd-autostart-d1f567803526a4c1.yaml0000664000175000017500000000035400000000000027102 0ustar00zuulzuul00000000000000--- upgrade: - | Removes the backward compatibility feature of ``vbmc`` to automatically start up ``vbmcd`` daemon process if it is not running. From now on, ``vbmcd`` should be started by systemd or some other mechanism. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/notes/show-error-status-13456782b3a5a6e2.yaml0000664000175000017500000000032200000000000026112 0ustar00zuulzuul00000000000000features: - | Added ``error`` status to ``vbmc list`` and ``vbmc start`` commands output. If the instance is failing to start, such instance will be shown as ``error`` rather than being ``down``. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6774147 virtualbmc-3.1.0/releasenotes/source/0000775000175000017500000000000000000000000017677 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6774147 virtualbmc-3.1.0/releasenotes/source/_static/0000775000175000017500000000000000000000000021325 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000023576 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6774147 virtualbmc-3.1.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000022034 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000024305 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/source/conf.py0000664000175000017500000001332500000000000021202 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # VirtualBMC Release Notes documentation build configuration file, created by # sphinx-quickstart on Mon Jun 25 13:25:41 2018. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'reno.sphinxext' ] try: import openstackdocstheme extensions.append('openstackdocstheme') except ImportError: openstackdocstheme = None # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. copyright = '2018, Ironic Developers' author = 'Ironic Developers' # openstackdocstheme options openstackdocs_repo_name = 'openstack/virtualbmc' openstackdocs_use_storyboard = False # 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. version = '' # The full version, including alpha/beta/rc tags. release = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # if openstackdocstheme is not None: html_theme = 'openstackdocs' else: html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { '**': [ 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'VirtualBMCReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'VirtualBMCReleaseNotes.tex', 'VirtualBMC Release Notes Documentation', 'Ironic Developers', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'virtualbmcreleasenotes', 'VirtualBMC Release Notes Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'VirtualBMCReleaseNotes', 'VirtualBMC Release Notes Documentation', author, 'VirtualBMCReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/source/index.rst0000664000175000017500000000017000000000000021536 0ustar00zuulzuul00000000000000======================== VirtualBMC Release Notes ======================== .. toctree:: :maxdepth: 1 unreleased ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/releasenotes/source/unreleased.rst0000664000175000017500000000016000000000000022555 0ustar00zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/requirements.txt0000664000175000017500000000055200000000000017174 0ustar00zuulzuul00000000000000# 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!=2.1.0,>=2.0.0 # Apache-2.0 libvirt-python>=6.0.0 # LGPLv2+ pyghmi>=1.2.0 # Apache-2.0 cliff!=2.9.0,>=2.8.0 # Apache-2.0 pyzmq>=19.0.0 # LGPL+BSD ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6854146 virtualbmc-3.1.0/setup.cfg0000664000175000017500000000231700000000000015532 0ustar00zuulzuul00000000000000[metadata] name = virtualbmc summary = Create virtual BMCs for controlling virtual instances via IPMI description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/virtualbmc/latest/ python_requires = >=3.8 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 :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 [files] packages = virtualbmc [entry_points] console_scripts = vbmc = virtualbmc.cmd.vbmc:main vbmcd = virtualbmc.cmd.vbmcd:main virtualbmc = add = virtualbmc.cmd.vbmc:AddCommand delete = virtualbmc.cmd.vbmc:DeleteCommand start = virtualbmc.cmd.vbmc:StartCommand stop = virtualbmc.cmd.vbmc:StopCommand list = virtualbmc.cmd.vbmc:ListCommand show = virtualbmc.cmd.vbmc:ShowCommand [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/setup.py0000664000175000017500000000127100000000000015421 0ustar00zuulzuul00000000000000# 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. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/test-requirements.txt0000664000175000017500000000071700000000000020154 0ustar00zuulzuul00000000000000# 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!=4.4,>=4.0 # Apache-2.0 doc8>=0.6.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD oslotest>=3.2.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT stestr>=1.0.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/tox.ini0000664000175000017500000000401600000000000015222 0ustar00zuulzuul00000000000000[tox] minversion = 3.18.0 envlist = py3,pep8 ignore_basepython_conflict=true [testenv] basepython = python3 usedevelop = True setenv = VIRTUAL_ENV={envdir} PYTHONDONTWRITEBYTECODE = 1 LANGUAGE=en_US LC_ALL=en_US.UTF-8 TESTS_DIR=./virtualbmc/tests/unit/ deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run {posargs} stestr slowest passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY [testenv:pep8] deps= hacking~=6.0.0 # Apache-2.0 flake8-import-order>=0.17.1 # LGPLv3 pycodestyle>=2.0.0,<3.0.0 # MIT doc8>=0.8.1 # Apache-2.0 commands = flake8 {posargs} doc8 README.rst CONTRIBUTING.rst HACKING.rst doc/source [testenv:venv] commands = {posargs} [testenv:cover] setenv = {[testenv]setenv} PYTHON=coverage run --source virtualbmc --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:pdf-docs] allowlist_externals = make deps = {[testenv:docs]deps} commands = sphinx-build -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:releasenotes] deps = {[testenv:docs]deps} commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:debug] commands = oslo_debug_helper {posargs} [flake8] # [E129] Visually indented line with same indent as next logical line. # [W503] Line break occurred before a binary operator. Conflicts with W504. ignore = E129,W503 filename = *.py import-order-style = pep8 application-import-names = virtualbmc exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build max-complexity=17 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6814146 virtualbmc-3.1.0/virtualbmc/0000775000175000017500000000000000000000000016056 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/__init__.py0000664000175000017500000000122600000000000020170 0ustar00zuulzuul00000000000000# 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__ = pbr.version.VersionInfo('virtualbmc').version_string() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6814146 virtualbmc-3.1.0/virtualbmc/cmd/0000775000175000017500000000000000000000000016621 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/cmd/__init__.py0000664000175000017500000000000000000000000020720 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/cmd/vbmc.py0000664000175000017500000002304500000000000020126 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import logging import sys from cliff.app import App from cliff.command import Command from cliff.commandmanager import CommandManager from cliff.lister import Lister import zmq import virtualbmc from virtualbmc import config as vbmc_config from virtualbmc.exception import VirtualBMCError from virtualbmc import log CONF = vbmc_config.get_config() LOG = log.get_logger() class ZmqClient(object): """Client part of the VirtualBMC system. The command-line client tool communicates with the server part of the VirtualBMC system by exchanging JSON-encoded messages. Client builds requests out of its command-line options which include the command (e.g. `start`, `list` etc) and command-specific options. Server response is a JSON document which contains at least the `rc` and `msg` attributes, used to indicate the outcome of the command, and optionally 2-D table conveyed through the `header` and `rows` attributes pointing to lists of cell values. """ SERVER_TIMEOUT = CONF['default']['server_response_timeout'] @staticmethod def to_dict(obj): return {attr: getattr(obj, attr) for attr in dir(obj) if not attr.startswith('_')} def communicate(self, command, args, no_daemon=False): data_out = self.to_dict(args) data_out.update(command=command) data_out = json.dumps(data_out) server_port = CONF['default']['server_port'] context = socket = None try: context = zmq.Context() socket = context.socket(zmq.REQ) socket.setsockopt(zmq.LINGER, 5) socket.connect("tcp://127.0.0.1:%s" % server_port) poller = zmq.Poller() poller.register(socket, zmq.POLLIN) try: socket.send(data_out.encode('utf-8')) socks = dict(poller.poll(timeout=self.SERVER_TIMEOUT)) if socket in socks and socks[socket] == zmq.POLLIN: data_in = socket.recv() else: raise zmq.ZMQError( zmq.RCVTIMEO, msg='Server response timed out') except zmq.ZMQError as ex: msg = ('Failed to connect to the vbmcd server on port ' '%(port)s, error: %(error)s' % {'port': server_port, 'error': ex}) LOG.error(msg) raise VirtualBMCError(msg) finally: if socket: socket.close() context.destroy() try: data_in = json.loads(data_in.decode('utf-8')) except ValueError as ex: msg = 'Server response parsing error %(error)s' % {'error': ex} LOG.error(msg) raise VirtualBMCError(msg) rc = data_in.pop('rc', None) if rc: msg = '(%(rc)s): %(msg)s' % { 'rc': rc, 'msg': '\n'.join(data_in.get('msg', ())) } LOG.error(msg) raise VirtualBMCError(msg) return data_in class AddCommand(Command): """Create a new BMC for a virtual machine instance""" def get_parser(self, prog_name): parser = super(AddCommand, self).get_parser(prog_name) parser.add_argument('domain_name', help='The name of the virtual machine') parser.add_argument('--username', dest='username', default='admin', help='The BMC username; defaults to "admin"') parser.add_argument('--password', dest='password', default='password', help='The BMC password; defaults to "password"') parser.add_argument('--port', dest='port', type=int, default=623, help='Port to listen on; defaults to 623') parser.add_argument('--address', dest='address', default='::', help=('The address to bind to (IPv4 and IPv6 ' 'are supported); defaults to ::')) parser.add_argument('--libvirt-uri', dest='libvirt_uri', default="qemu:///system", help=('The libvirt URI; defaults to ' '"qemu:///system"')) parser.add_argument('--libvirt-sasl-username', dest='libvirt_sasl_username', default=None, help=('The libvirt SASL username; defaults to ' 'None')) parser.add_argument('--libvirt-sasl-password', dest='libvirt_sasl_password', default=None, help=('The libvirt SASL password; defaults to ' 'None')) return parser def take_action(self, args): log = logging.getLogger(__name__) # Check if the username and password were given for SASL sasl_user = args.libvirt_sasl_username sasl_pass = args.libvirt_sasl_password if any((sasl_user, sasl_pass)): if not all((sasl_user, sasl_pass)): msg = ("A password and username are required to use " "Libvirt's SASL authentication") log.error(msg) raise VirtualBMCError(msg) self.app.zmq.communicate( 'add', args, no_daemon=self.app.options.no_daemon ) class DeleteCommand(Command): """Delete a virtual BMC for a virtual machine instance""" def get_parser(self, prog_name): parser = super(DeleteCommand, self).get_parser(prog_name) parser.add_argument('domain_names', nargs='+', help='A list of virtual machine names') return parser def take_action(self, args): self.app.zmq.communicate('delete', args, self.app.options.no_daemon) class StartCommand(Command): """Start a virtual BMC for a virtual machine instance""" def get_parser(self, prog_name): parser = super(StartCommand, self).get_parser(prog_name) parser.add_argument('domain_names', nargs='+', help='A list of virtual machine names') return parser def take_action(self, args): self.app.zmq.communicate( 'start', args, no_daemon=self.app.options.no_daemon ) class StopCommand(Command): """Stop a virtual BMC for a virtual machine instance""" def get_parser(self, prog_name): parser = super(StopCommand, self).get_parser(prog_name) parser.add_argument('domain_names', nargs='+', help='A list of virtual machine names') return parser def take_action(self, args): self.app.zmq.communicate( 'stop', args, no_daemon=self.app.options.no_daemon ) class ListCommand(Lister): """List all virtual BMC instances""" def take_action(self, args): rsp = self.app.zmq.communicate( 'list', args, no_daemon=self.app.options.no_daemon ) return rsp['header'], sorted(rsp['rows']) class ShowCommand(Lister): """Show virtual BMC properties""" def get_parser(self, prog_name): parser = super(ShowCommand, self).get_parser(prog_name) parser.add_argument('domain_name', help='The name of the virtual machine') return parser def take_action(self, args): rsp = self.app.zmq.communicate( 'show', args, no_daemon=self.app.options.no_daemon ) return rsp['header'], sorted(rsp['rows']) class VirtualBMCApp(App): def __init__(self): super(VirtualBMCApp, self).__init__( description='Virtual Baseboard Management Controller (BMC) backed ' 'by virtual machines', version=virtualbmc.__version__, command_manager=CommandManager('virtualbmc'), deferred_help=True, ) def build_option_parser(self, description, version, argparse_kwargs=None): parser = super(VirtualBMCApp, self).build_option_parser( description, version, argparse_kwargs ) parser.add_argument('--no-daemon', action='store_true', help='Do not start vbmcd automatically') return parser def initialize_app(self, argv): self.zmq = ZmqClient() def clean_up(self, cmd, result, err): self.LOG.debug('clean_up %(name)s', {'name': cmd.__class__.__name__}) if err: self.LOG.debug('got an error: %(error)s', {'error': err}) def main(argv=sys.argv[1:]): vbmc_app = VirtualBMCApp() return vbmc_app.run(argv) if __name__ == '__main__': sys.exit(main()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/cmd/vbmcd.py0000664000175000017500000000501700000000000020271 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import os import sys import tempfile import virtualbmc from virtualbmc import config as vbmc_config from virtualbmc import control from virtualbmc import log from virtualbmc import utils LOG = log.get_logger() CONF = vbmc_config.get_config() def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser( prog='VirtualBMC server', description='A virtual BMC server for controlling virtual instances', ) parser.add_argument('--version', action='version', version=virtualbmc.__version__) parser.add_argument('--foreground', action='store_true', default=False, help='Do not daemonize') args = parser.parse_args(argv) pid_file = CONF['default']['pid_file'] try: with open(pid_file) as f: pid = int(f.read()) os.kill(pid, 0) except Exception: pass else: LOG.error('server PID #%(pid)d still running', {'pid': pid}) return 1 def wrap_with_pidfile(func, pid): dir_name = os.path.dirname(pid_file) if not os.path.exists(dir_name): os.makedirs(dir_name, mode=0o700) try: with tempfile.NamedTemporaryFile(mode='w+t', dir=dir_name, delete=False) as f: f.write(str(pid)) os.rename(f.name, pid_file) func() except Exception as e: LOG.error('%(error)s', {'error': e}) return 1 finally: try: os.unlink(pid_file) except Exception: pass if args.foreground: return wrap_with_pidfile(control.application, os.getpid()) else: with utils.detach_process() as pid: if pid > 0: return 0 return wrap_with_pidfile(control.application, pid) if __name__ == '__main__': sys.exit(main()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/config.py0000664000175000017500000000604300000000000017700 0ustar00zuulzuul00000000000000# 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 configparser import os from virtualbmc import utils __all__ = ['get_config'] _CONFIG_FILE_PATHS = ( os.environ.get('VIRTUALBMC_CONFIG', ''), os.path.join(os.path.expanduser('~'), '.vbmc', 'virtualbmc.conf'), '/etc/virtualbmc/virtualbmc.conf') CONFIG_FILE = next((x for x in _CONFIG_FILE_PATHS if os.path.exists(x)), '') CONFIG = None class VirtualBMCConfig(object): DEFAULTS = { 'default': { 'show_passwords': 'false', 'config_dir': os.path.join( os.path.expanduser('~'), '.vbmc' ), 'pid_file': os.path.join( os.path.expanduser('~'), '.vbmc', 'master.pid' ), 'server_port': 50891, 'server_response_timeout': 5000, # milliseconds 'server_spawn_wait': 3000, # milliseconds }, 'log': { 'logfile': None, 'debug': 'false' }, 'ipmi': { # Maximum time (in seconds) to wait for the data to come across 'session_timeout': 1 }, } def initialize(self): config = configparser.ConfigParser() config.read(CONFIG_FILE) self._conf_dict = self._as_dict(config) self._validate() def _as_dict(self, config): conf_dict = self.DEFAULTS for section in config.sections(): if section not in conf_dict: conf_dict[section] = {} for key, val in config.items(section): conf_dict[section][key] = val return conf_dict def _validate(self): self._conf_dict['log']['debug'] = utils.str2bool( self._conf_dict['log']['debug']) self._conf_dict['default']['show_passwords'] = utils.str2bool( self._conf_dict['default']['show_passwords']) self._conf_dict['default']['server_port'] = int( self._conf_dict['default']['server_port']) self._conf_dict['default']['server_spawn_wait'] = int( self._conf_dict['default']['server_spawn_wait']) self._conf_dict['default']['server_response_timeout'] = int( self._conf_dict['default']['server_response_timeout']) self._conf_dict['ipmi']['session_timeout'] = int( self._conf_dict['ipmi']['session_timeout']) def __getitem__(self, key): return self._conf_dict[key] def get_config(): global CONFIG if CONFIG is None: CONFIG = VirtualBMCConfig() CONFIG.initialize() return CONFIG ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/control.py0000664000175000017500000001516700000000000020122 0ustar00zuulzuul00000000000000# Copyright 2017 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 json import signal import sys import zmq from virtualbmc import config as vbmc_config from virtualbmc import exception from virtualbmc import log from virtualbmc.manager import VirtualBMCManager CONF = vbmc_config.get_config() LOG = log.get_logger() TIMER_PERIOD = 3000 # milliseconds def main_loop(vbmc_manager, handle_command): """Server part of the CLI control interface Receives JSON messages from ZMQ socket, calls the command handler and sends JSON response back to the client. Client builds requests out of its command-line options which include the command (e.g. `start`, `list` etc) and command-specific options. Server handles the commands and responds with a JSON document which contains at least the `rc` and `msg` attributes, used to indicate the outcome of the command, and optionally 2-D table conveyed through the `header` and `rows` attributes pointing to lists of cell values. """ server_port = CONF['default']['server_port'] context = socket = None try: context = zmq.Context() socket = context.socket(zmq.REP) socket.setsockopt(zmq.LINGER, 5) socket.bind("tcp://127.0.0.1:%s" % server_port) poller = zmq.Poller() poller.register(socket, zmq.POLLIN) LOG.info('Started vBMC server on port %s', server_port) while True: socks = dict(poller.poll(timeout=TIMER_PERIOD)) if socket in socks and socks[socket] == zmq.POLLIN: message = socket.recv() else: vbmc_manager.periodic() continue try: data_in = json.loads(message.decode('utf-8')) except ValueError as ex: LOG.warning( 'Control server request deserialization error: ' '%(error)s', {'error': ex} ) continue LOG.debug('Command request data: %(request)s', {'request': data_in}) try: data_out = handle_command(vbmc_manager, data_in) except exception.VirtualBMCError as ex: msg = 'Command failed: %(error)s' % {'error': ex} LOG.error(msg) data_out = { 'rc': 1, 'msg': [msg] } LOG.debug('Command response data: %(response)s', {'response': data_out}) try: message = json.dumps(data_out) except ValueError as ex: LOG.warning( 'Control server response serialization error: ' '%(error)s', {'error': ex} ) continue socket.send(message.encode('utf-8')) finally: if socket: socket.close() if context: context.destroy() def command_dispatcher(vbmc_manager, data_in): """Control CLI command dispatcher Calls vBMC manager to execute commands, implements uniform dictionary-based interface to the caller. """ command = data_in.pop('command') LOG.debug('Running "%(cmd)s" command handler', {'cmd': command}) if command == 'add': # Check if the username and password were given for SASL sasl_user = data_in['libvirt_sasl_username'] sasl_pass = data_in['libvirt_sasl_password'] if any((sasl_user, sasl_pass)): if not all((sasl_user, sasl_pass)): error = ("A password and username are required to use " "Libvirt's SASL authentication") return {'msg': [error], 'rc': 1} rc, msg = vbmc_manager.add(**data_in) return { 'rc': rc, 'msg': [msg] if msg else [] } elif command == 'delete': data_out = [vbmc_manager.delete(domain_name) for domain_name in set(data_in['domain_names'])] return { 'rc': max(rc for rc, msg in data_out), 'msg': [msg for rc, msg in data_out if msg], } elif command == 'start': data_out = [vbmc_manager.start(domain_name) for domain_name in set(data_in['domain_names'])] return { 'rc': max(rc for rc, msg in data_out), 'msg': [msg for rc, msg in data_out if msg], } elif command == 'stop': data_out = [vbmc_manager.stop(domain_name) for domain_name in set(data_in['domain_names'])] return { 'rc': max(rc for rc, msg in data_out), 'msg': [msg for rc, msg in data_out if msg], } elif command == 'list': rc, tables = vbmc_manager.list() header = ('Domain name', 'Status', 'Address', 'Port') keys = ('domain_name', 'status', 'address', 'port') return { 'rc': rc, 'header': header, 'rows': [ [table.get(key, '?') for key in keys] for table in tables ] } elif command == 'show': rc, table = vbmc_manager.show(data_in['domain_name']) return { 'rc': rc, 'header': ('Property', 'Value'), 'rows': table, } else: return { 'rc': 1, 'msg': ['Unknown command'], } def application(): """vbmcd application entry point Initializes, serves and cleans up everything. """ vbmc_manager = VirtualBMCManager() vbmc_manager.periodic() def kill_children(*args): vbmc_manager.periodic(shutdown=True) sys.exit(0) # SIGTERM does not seem to propagate to multiprocessing signal.signal(signal.SIGTERM, kill_children) try: main_loop(vbmc_manager, command_dispatcher) except KeyboardInterrupt: LOG.info('Got keyboard interrupt, exiting') vbmc_manager.periodic(shutdown=True) except Exception as ex: LOG.error( 'Control server error: %(error)s', {'error': ex} ) vbmc_manager.periodic(shutdown=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/exception.py0000664000175000017500000000261300000000000020430 0ustar00zuulzuul00000000000000# 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. class VirtualBMCError(Exception): message = None def __init__(self, message=None, **kwargs): if self.message and kwargs: self.message = self.message % kwargs else: self.message = message super(VirtualBMCError, self).__init__(self.message) class DomainAlreadyExists(VirtualBMCError): message = 'Domain %(domain)s already exists' class DomainNotFound(VirtualBMCError): message = 'No domain with matching name %(domain)s was found' class LibvirtConnectionOpenError(VirtualBMCError): message = ('Fail to establish a connection with libvirt URI "%(uri)s". ' 'Error: %(error)s') class DetachProcessError(VirtualBMCError): message = ('Error when forking (detaching) the VirtualBMC process ' 'from its parent and session. Error: %(error)s') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/log.py0000664000175000017500000000331100000000000017207 0ustar00zuulzuul00000000000000# 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 errno import logging from virtualbmc import config __all__ = ['get_logger'] DEFAULT_LOG_FORMAT = ('%(asctime)s %(process)d %(levelname)s ' '%(name)s [-] %(message)s') LOGGER = None class VirtualBMCLogger(logging.Logger): def __init__(self, debug=False, logfile=None): logging.Logger.__init__(self, 'VirtualBMC') try: if logfile is not None: self.handler = logging.FileHandler(logfile) else: self.handler = logging.StreamHandler() formatter = logging.Formatter(DEFAULT_LOG_FORMAT) self.handler.setFormatter(formatter) self.addHandler(self.handler) if debug: self.setLevel(logging.DEBUG) else: self.setLevel(logging.INFO) except IOError as e: if e.errno == errno.EACCES: pass def get_logger(): global LOGGER if LOGGER is None: log_conf = config.get_config()['log'] LOGGER = VirtualBMCLogger(debug=log_conf['debug'], logfile=log_conf['logfile']) return LOGGER ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/manager.py0000664000175000017500000002516600000000000020054 0ustar00zuulzuul00000000000000# 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 configparser import errno import multiprocessing import os import shutil import signal from virtualbmc import config as vbmc_config from virtualbmc import exception from virtualbmc import log from virtualbmc import utils from virtualbmc.vbmc import VirtualBMC LOG = log.get_logger() # BMC status RUNNING = 'running' DOWN = 'down' ERROR = 'error' DEFAULT_SECTION = 'VirtualBMC' CONF = vbmc_config.get_config() class VirtualBMCManager(object): VBMC_OPTIONS = ['username', 'password', 'address', 'port', 'domain_name', 'libvirt_uri', 'libvirt_sasl_username', 'libvirt_sasl_password', 'active'] def __init__(self): super(VirtualBMCManager, self).__init__() self.config_dir = CONF['default']['config_dir'] self._running_domains = {} def _parse_config(self, domain_name): config_path = os.path.join(self.config_dir, domain_name, 'config') if not os.path.exists(config_path): raise exception.DomainNotFound(domain=domain_name) try: config = configparser.ConfigParser() config.read(config_path) bmc = {} for item in self.VBMC_OPTIONS: try: value = config.get(DEFAULT_SECTION, item) except configparser.NoOptionError: value = None bmc[item] = value # Port needs to be int bmc['port'] = config.getint(DEFAULT_SECTION, 'port') return bmc except OSError: raise exception.DomainNotFound(domain=domain_name) def _store_config(self, **options): config = configparser.ConfigParser() config.add_section(DEFAULT_SECTION) for option, value in options.items(): if value is not None: config.set(DEFAULT_SECTION, option, str(value)) config_path = os.path.join( self.config_dir, options['domain_name'], 'config' ) with open(config_path, 'w') as f: config.write(f) def _vbmc_enabled(self, domain_name, lets_enable=None, config=None): if not config: config = self._parse_config(domain_name) try: currently_enabled = utils.str2bool(config['active']) except Exception: currently_enabled = False if (lets_enable is not None and lets_enable != currently_enabled): config.update(active=lets_enable) self._store_config(**config) currently_enabled = lets_enable return currently_enabled def _sync_vbmc_states(self, shutdown=False): """Starts/stops vBMC instances Walks over vBMC instances configuration, starts enabled but dead instances, kills non-configured but alive ones. """ def vbmc_runner(bmc_config): # The manager process installs a signal handler for SIGTERM to # propagate it to children. Return to the default handler. signal.signal(signal.SIGTERM, signal.SIG_DFL) show_passwords = CONF['default']['show_passwords'] if show_passwords: show_options = bmc_config else: show_options = utils.mask_dict_password(bmc_config) try: vbmc = VirtualBMC(**bmc_config) except Exception as ex: LOG.exception( 'Error running vBMC with configuration ' '%(opts)s: %(error)s', {'opts': show_options, 'error': ex} ) return try: vbmc.listen(timeout=CONF['ipmi']['session_timeout']) except Exception as ex: LOG.exception( 'Shutdown vBMC for domain %(domain)s, cause ' '%(error)s', {'domain': show_options['domain_name'], 'error': ex} ) return for domain_name in os.listdir(self.config_dir): if not os.path.isdir( os.path.join(self.config_dir, domain_name) ): continue try: bmc_config = self._parse_config(domain_name) except exception.DomainNotFound: continue if shutdown: lets_enable = False else: lets_enable = self._vbmc_enabled( domain_name, config=bmc_config ) instance = self._running_domains.get(domain_name) if lets_enable: if not instance or not instance.is_alive(): instance = multiprocessing.Process( name='vbmcd-managing-domain-%s' % domain_name, target=vbmc_runner, args=(bmc_config,) ) instance.daemon = True instance.start() self._running_domains[domain_name] = instance LOG.info( 'Started vBMC instance for domain ' '%(domain)s', {'domain': domain_name} ) if not instance.is_alive(): LOG.debug( 'Found dead vBMC instance for domain %(domain)s ' '(rc %(rc)s)', {'domain': domain_name, 'rc': instance.exitcode} ) else: if instance: if instance.is_alive(): instance.terminate() LOG.info( 'Terminated vBMC instance for domain ' '%(domain)s', {'domain': domain_name} ) self._running_domains.pop(domain_name, None) def _show(self, domain_name): bmc_config = self._parse_config(domain_name) show_passwords = CONF['default']['show_passwords'] if show_passwords: show_options = bmc_config else: show_options = utils.mask_dict_password(bmc_config) instance = self._running_domains.get(domain_name) if instance and instance.is_alive(): show_options['status'] = RUNNING elif instance and not instance.is_alive(): show_options['status'] = ERROR else: show_options['status'] = DOWN return show_options def periodic(self, shutdown=False): self._sync_vbmc_states(shutdown) def add(self, username, password, port, address, domain_name, libvirt_uri, libvirt_sasl_username, libvirt_sasl_password, **kwargs): # check libvirt's connection and if domain exist prior to adding it utils.check_libvirt_connection_and_domain( libvirt_uri, domain_name, sasl_username=libvirt_sasl_username, sasl_password=libvirt_sasl_password) domain_path = os.path.join(self.config_dir, domain_name) try: os.makedirs(domain_path) except OSError as ex: if ex.errno == errno.EEXIST: return 1, str(ex) msg = ('Failed to create domain %(domain)s. ' 'Error: %(error)s' % {'domain': domain_name, 'error': ex}) LOG.error(msg) return 1, msg try: self._store_config(domain_name=domain_name, username=username, password=password, port=str(port), address=address, libvirt_uri=libvirt_uri, libvirt_sasl_username=libvirt_sasl_username, libvirt_sasl_password=libvirt_sasl_password, active=False) except Exception as ex: self.delete(domain_name) return 1, str(ex) return 0, '' def delete(self, domain_name): domain_path = os.path.join(self.config_dir, domain_name) if not os.path.exists(domain_path): raise exception.DomainNotFound(domain=domain_name) try: self.stop(domain_name) except exception.VirtualBMCError: pass shutil.rmtree(domain_path) return 0, '' def start(self, domain_name): try: bmc_config = self._parse_config(domain_name) except Exception as ex: return 1, str(ex) if domain_name in self._running_domains: self._sync_vbmc_states() if domain_name in self._running_domains: LOG.warning( 'BMC instance %(domain)s already running, ignoring ' '"start" command' % {'domain': domain_name}) return 0, '' try: self._vbmc_enabled(domain_name, config=bmc_config, lets_enable=True) except Exception as e: LOG.exception('Failed to start domain %s', domain_name) return 1, ('Failed to start domain %(domain)s. Error: ' '%(error)s' % {'domain': domain_name, 'error': e}) self._sync_vbmc_states() return 0, '' def stop(self, domain_name): try: self._vbmc_enabled(domain_name, lets_enable=False) except Exception as ex: LOG.exception('Failed to stop domain %s', domain_name) return 1, str(ex) self._sync_vbmc_states() return 0, '' def list(self): rc = 0 tables = [] try: for domain in os.listdir(self.config_dir): if os.path.isdir(os.path.join(self.config_dir, domain)): tables.append(self._show(domain)) except OSError as e: if e.errno == errno.EEXIST: rc = 1 return rc, tables def show(self, domain_name): return 0, list(self._show(domain_name).items()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6814146 virtualbmc-3.1.0/virtualbmc/tests/0000775000175000017500000000000000000000000017220 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/__init__.py0000664000175000017500000000000000000000000021317 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6814146 virtualbmc-3.1.0/virtualbmc/tests/unit/0000775000175000017500000000000000000000000020177 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/__init__.py0000664000175000017500000000000000000000000022276 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/base.py0000664000175000017500000000216300000000000021465 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Copyright 2010-2011 OpenStack Foundation # 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. import logging from oslotest import base from virtualbmc import log as vbmc_log class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" def setUp(self): super(TestCase, self).setUp() self._level = vbmc_log.get_logger().getEffectiveLevel() vbmc_log.get_logger().setLevel(logging.DEBUG) self.addCleanup(lambda level: vbmc_log.get_logger().setLevel(level), self._level) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6814146 virtualbmc-3.1.0/virtualbmc/tests/unit/cmd/0000775000175000017500000000000000000000000020742 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/cmd/__init__.py0000664000175000017500000000000000000000000023041 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/cmd/test_vbmc.py0000664000175000017500000002235300000000000023307 0ustar00zuulzuul00000000000000# Copyright 2016 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 io import json import sys from unittest import mock import zmq from virtualbmc.cmd import vbmc from virtualbmc.tests.unit import base from virtualbmc.tests.unit import utils as test_utils @mock.patch.object(sys, 'exit', lambda _: None) class VBMCTestCase(base.TestCase): def setUp(self): super(VBMCTestCase, self).setUp() self.domain = test_utils.get_domain() @mock.patch.object(zmq, 'Context') @mock.patch.object(zmq, 'Poller') def test_server_timeout(self, mock_zmq_poller, mock_zmq_context): expected_rc = 1 expected_output = ( 'Failed to connect to the vbmcd server on port 50891, error: ' 'Server response timed out\n') mock_zmq_poller = mock_zmq_poller.return_value mock_zmq_poller.poll.return_value = {} with mock.patch.object(sys, 'stderr', io.StringIO()) as output: rc = vbmc.main(['--no-daemon', 'add', '--username', 'ironic', 'bar']) self.assertEqual(expected_rc, rc) self.assertEqual(expected_output, output.getvalue()) @mock.patch.object(zmq, 'Context') @mock.patch.object(zmq, 'Poller') def test_main_add(self, mock_zmq_poller, mock_zmq_context): expected_rc = 0 expected_output = '' srv_rsp = { 'rc': expected_rc, 'msg': ['OK'] } mock_zmq_context = mock_zmq_context.return_value mock_zmq_socket = mock_zmq_context.socket.return_value mock_zmq_socket.recv.return_value = json.dumps(srv_rsp).encode() mock_zmq_poller = mock_zmq_poller.return_value mock_zmq_poller.poll.return_value = { mock_zmq_socket: zmq.POLLIN } with mock.patch.object(sys, 'stdout', io.StringIO()) as output: rc = vbmc.main(['add', '--username', 'ironic', 'bar']) query = json.loads(mock_zmq_socket.send.call_args[0][0].decode()) expected_query = { 'command': 'add', 'address': '::', 'port': 623, 'libvirt_uri': 'qemu:///system', 'libvirt_sasl_username': None, 'libvirt_sasl_password': None, 'username': 'ironic', 'password': 'password', 'domain_name': 'bar', } self.assertEqual(expected_query, query) self.assertEqual(expected_rc, rc) self.assertEqual(expected_output, output.getvalue()) @mock.patch.object(zmq, 'Context') @mock.patch.object(zmq, 'Poller') def test_main_delete(self, mock_zmq_poller, mock_zmq_context): expected_rc = 0 expected_output = '' srv_rsp = { 'rc': expected_rc, 'msg': ['OK'] } mock_zmq_context = mock_zmq_context.return_value mock_zmq_socket = mock_zmq_context.socket.return_value mock_zmq_socket.recv.return_value = json.dumps(srv_rsp).encode() mock_zmq_poller = mock_zmq_poller.return_value mock_zmq_poller.poll.return_value = { mock_zmq_socket: zmq.POLLIN } with mock.patch.object(sys, 'stdout', io.StringIO()) as output: rc = vbmc.main(['delete', 'foo', 'bar']) query = json.loads(mock_zmq_socket.send.call_args[0][0].decode()) expected_query = { "domain_names": ["foo", "bar"], "command": "delete", } self.assertEqual(expected_query, query) self.assertEqual(expected_rc, rc) self.assertEqual(expected_output, output.getvalue()) @mock.patch.object(zmq, 'Context') @mock.patch.object(zmq, 'Poller') def test_main_start(self, mock_zmq_poller, mock_zmq_context): expected_rc = 0 expected_output = '' srv_rsp = { 'rc': expected_rc, 'msg': ['OK'] } mock_zmq_context = mock_zmq_context.return_value mock_zmq_socket = mock_zmq_context.socket.return_value mock_zmq_socket.recv.return_value = json.dumps(srv_rsp).encode() mock_zmq_poller = mock_zmq_poller.return_value mock_zmq_poller.poll.return_value = { mock_zmq_socket: zmq.POLLIN } with mock.patch.object(sys, 'stdout', io.StringIO()) as output: rc = vbmc.main(['start', 'foo', 'bar']) query = json.loads(mock_zmq_socket.send.call_args[0][0].decode()) expected_query = { 'command': 'start', 'domain_names': ['foo', 'bar'] } self.assertEqual(expected_query, query) self.assertEqual(expected_rc, rc) self.assertEqual(expected_output, output.getvalue()) @mock.patch.object(zmq, 'Context') @mock.patch.object(zmq, 'Poller') def test_main_stop(self, mock_zmq_poller, mock_zmq_context): expected_rc = 0 expected_output = '' srv_rsp = { 'rc': expected_rc, 'msg': ['OK'] } mock_zmq_context = mock_zmq_context.return_value mock_zmq_socket = mock_zmq_context.socket.return_value mock_zmq_socket.recv.return_value = json.dumps(srv_rsp).encode() mock_zmq_poller = mock_zmq_poller.return_value mock_zmq_poller.poll.return_value = { mock_zmq_socket: zmq.POLLIN } with mock.patch.object(sys, 'stdout', io.StringIO()) as output: rc = vbmc.main(['stop', 'foo', 'bar']) query = json.loads(mock_zmq_socket.send.call_args[0][0].decode()) expected_query = { 'command': 'stop', 'domain_names': ['foo', 'bar'] } self.assertEqual(expected_query, query) self.assertEqual(expected_rc, rc) self.assertEqual(expected_output, output.getvalue()) @mock.patch.object(zmq, 'Context') @mock.patch.object(zmq, 'Poller') def test_main_list(self, mock_zmq_poller, mock_zmq_context): expected_rc = 0 expected_output = """+-------+-------+ | col1 | col2 | +-------+-------+ | cell1 | cell2 | | cell3 | cell4 | +-------+-------+ """ srv_rsp = { 'rc': expected_rc, 'header': ['col1', 'col2'], 'rows': [['cell1', 'cell2'], ['cell3', 'cell4']], } mock_zmq_context = mock_zmq_context.return_value mock_zmq_socket = mock_zmq_context.socket.return_value mock_zmq_socket.recv.return_value = json.dumps(srv_rsp).encode() mock_zmq_poller = mock_zmq_poller.return_value mock_zmq_poller.poll.return_value = { mock_zmq_socket: zmq.POLLIN } with mock.patch.object(sys, 'stdout', io.StringIO()) as output: rc = vbmc.main(['list']) query = json.loads(mock_zmq_socket.send.call_args[0][0].decode()) expected_query = { "command": "list", } # Cliff adds some extra args to the query query = {key: query[key] for key in query if key in expected_query} self.assertEqual(expected_query, query) self.assertEqual(expected_rc, rc) self.assertEqual(expected_output, output.getvalue()) @mock.patch.object(zmq, 'Context') @mock.patch.object(zmq, 'Poller') def test_main_show(self, mock_zmq_poller, mock_zmq_context): expected_rc = 0 expected_output = """+-------+-------+ | col1 | col2 | +-------+-------+ | cell1 | cell2 | | cell3 | cell4 | +-------+-------+ """ srv_rsp = { 'rc': expected_rc, 'header': ['col1', 'col2'], 'rows': [['cell1', 'cell2'], ['cell3', 'cell4']] } mock_zmq_context = mock_zmq_context.return_value mock_zmq_socket = mock_zmq_context.socket.return_value mock_zmq_socket.recv.return_value = json.dumps(srv_rsp).encode() mock_zmq_poller = mock_zmq_poller.return_value mock_zmq_poller.poll.return_value = { mock_zmq_socket: zmq.POLLIN } with mock.patch.object(sys, 'stdout', io.StringIO()) as output: rc = vbmc.main(['show', 'domain0']) query = json.loads(mock_zmq_socket.send.call_args[0][0].decode()) expected_query = { "domain_name": "domain0", "command": "show", } # Cliff adds some extra args to the query query = {key: query[key] for key in query if key in expected_query} self.assertEqual(expected_query, query) self.assertEqual(expected_rc, rc) self.assertEqual(expected_output, output.getvalue()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/cmd/test_vbmcd.py0000664000175000017500000000367000000000000023454 0ustar00zuulzuul00000000000000# Copyright 2017 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 builtins import os from unittest import mock from virtualbmc.cmd import vbmcd from virtualbmc import control from virtualbmc.tests.unit import base from virtualbmc import utils class VBMCDTestCase(base.TestCase): @mock.patch.object(builtins, 'open') @mock.patch.object(os, 'kill') @mock.patch.object(os, 'unlink') def test_main_foreground(self, mock_unlink, mock_kill, mock_open): with mock.patch.object(control, 'application') as mock_ml: mock_kill.side_effect = OSError() vbmcd.main(['--foreground']) mock_kill.assert_called_once() mock_ml.assert_called_once() mock_unlink.assert_called_once() @mock.patch.object(builtins, 'open') @mock.patch.object(os, 'kill') @mock.patch.object(os, 'unlink') def test_main_background(self, mock_unlink, mock_kill, mock_open): with mock.patch.object(utils, 'detach_process') as mock_dp: with mock.patch.object(control, 'application') as mock_ml: mock_kill.side_effect = OSError() mock_dp.return_value.__enter__.return_value = 0 vbmcd.main([]) mock_kill.assert_called_once() mock_dp.assert_called_once() mock_ml.assert_called_once() mock_unlink.assert_called_once() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/test_config.py0000664000175000017500000000654100000000000023063 0ustar00zuulzuul00000000000000# Copyright 2016 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 configparser import os from unittest import mock from virtualbmc import config from virtualbmc.tests.unit import base _CONFIG_FILE = '/foo/.vbmc/virtualbmc.conf' @mock.patch('virtualbmc.config.CONFIG_FILE', _CONFIG_FILE) class VirtualBMCConfigTestCase(base.TestCase): def setUp(self): super(VirtualBMCConfigTestCase, self).setUp() self.vbmc_config = config.VirtualBMCConfig() self.config_dict = {'default': {'show_passwords': 'true', 'config_dir': '/foo/bar/1', 'pid_file': '/foo/bar/2', 'server_port': '12345', 'server_spawn_wait': 3000, 'server_response_timeout': 5000}, 'log': {'debug': 'true', 'logfile': '/foo/bar/4'}, 'ipmi': {'session_timeout': '30'}} @mock.patch.object(config.VirtualBMCConfig, '_validate') @mock.patch.object(config.VirtualBMCConfig, '_as_dict') @mock.patch.object(configparser, 'ConfigParser') def test_initialize(self, mock_configparser, mock__as_dict, mock__validate): config = mock_configparser.return_value self.vbmc_config.initialize() config.read.assert_called_once_with(_CONFIG_FILE) mock__as_dict.assert_called_once_with(config) mock__validate.assert_called_once_with() @mock.patch.object(os.path, 'exists') def test__as_dict(self, mock_exists): mock_exists.side_effect = (False, True) config = mock.Mock() config.sections.side_effect = ['default', 'log', 'ipmi'], config.items.side_effect = [[('show_passwords', 'true'), ('config_dir', '/foo/bar/1'), ('pid_file', '/foo/bar/2'), ('server_port', '12345')], [('logfile', '/foo/bar/4'), ('debug', 'true')], [('session_timeout', '30')]] ret = self.vbmc_config._as_dict(config) self.assertEqual(self.config_dict, ret) def test_validate(self): self.vbmc_config._conf_dict = self.config_dict self.vbmc_config._validate() expected = self.config_dict.copy() expected['default']['show_passwords'] = True expected['default']['server_response_timeout'] = 5000 expected['default']['server_spawn_wait'] = 3000 expected['default']['server_port'] = 12345 expected['log']['debug'] = True expected['ipmi']['session_timeout'] = 30 self.assertEqual(expected, self.vbmc_config._conf_dict) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/test_control.py0000664000175000017500000000423500000000000023274 0ustar00zuulzuul00000000000000# Copyright 2017 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 json import os from unittest import mock import zmq from virtualbmc import control from virtualbmc.tests.unit import base class VBMCControlServerTestCase(base.TestCase): @mock.patch.object(zmq, 'Context') @mock.patch.object(zmq, 'Poller') @mock.patch.object(os, 'path') @mock.patch.object(os, 'remove') def test_control_loop(self, mock_rm, mock_path, mock_zmq_poller, mock_zmq_context): mock_path.exists.return_value = False mock_vbmc_manager = mock.MagicMock() mock_handle_command = mock.MagicMock() req = { 'command': 'list', } mock_zmq_context = mock_zmq_context.return_value mock_zmq_socket = mock_zmq_context.socket.return_value mock_zmq_socket.recv.return_value = json.dumps(req).encode() mock_zmq_poller = mock_zmq_poller.return_value mock_zmq_poller.poll.return_value = { mock_zmq_socket: zmq.POLLIN } rsp = { 'rc': 0, 'msg': ['OK'] } class QuitNow(Exception): pass mock_handle_command.return_value = rsp mock_zmq_socket.send.side_effect = QuitNow() self.assertRaises(QuitNow, control.main_loop, mock_vbmc_manager, mock_handle_command) mock_zmq_socket.bind.assert_called_once() mock_handle_command.assert_called_once() response = json.loads(mock_zmq_socket.send.call_args[0][0].decode()) self.assertEqual(rsp, response) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/test_manager.py0000664000175000017500000003073400000000000023231 0ustar00zuulzuul00000000000000# Copyright 2016 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 builtins import configparser import copy import errno import multiprocessing import os import shutil from unittest import mock from virtualbmc import exception from virtualbmc import manager from virtualbmc.tests.unit import base from virtualbmc.tests.unit import utils as test_utils from virtualbmc import utils _CONFIG_PATH = '/foo' class VirtualBMCManagerTestCase(base.TestCase): def setUp(self): super(VirtualBMCManagerTestCase, self).setUp() self.manager = manager.VirtualBMCManager() self.manager.config_dir = _CONFIG_PATH self.domain0 = test_utils.get_domain() self.domain1 = test_utils.get_domain(domain_name='Patrick', port=321) self.domain_name0 = self.domain0['domain_name'] self.domain_name1 = self.domain1['domain_name'] self.domain_path0 = os.path.join(_CONFIG_PATH, self.domain_name0) self.domain_path1 = os.path.join(_CONFIG_PATH, self.domain_name1) self.add_params = {'username': 'admin', 'password': 'pass', 'port': '777', 'address': '::', 'domain_name': 'Squidward Tentacles', 'libvirt_uri': 'foo://bar', 'libvirt_sasl_username': 'sasl_admin', 'libvirt_sasl_password': 'sasl_pass', 'active': 'False'} def _get_config(self, section, item): return self.domain0.get(item) @mock.patch.object(os.path, 'exists') @mock.patch.object(configparser, 'ConfigParser') def test__parse_config(self, mock_configparser, mock_exists): mock_exists.return_value = True config = mock_configparser.return_value config.get.side_effect = self._get_config config.getint.side_effect = self._get_config ret = self.manager._parse_config(self.domain_name0) self.assertEqual(self.domain0, ret) config.getint.assert_called_once_with('VirtualBMC', 'port') mock_configparser.assert_called_once_with() expected_get_calls = [mock.call('VirtualBMC', i) for i in ('username', 'password', 'address', 'port', 'domain_name', 'libvirt_uri', 'libvirt_sasl_username', 'libvirt_sasl_password', 'active')] self.assertEqual(expected_get_calls, config.get.call_args_list) @mock.patch.object(os.path, 'exists') def test__parse_config_domain_not_found(self, mock_exists): mock_exists.return_value = False self.assertRaises(exception.DomainNotFound, self.manager._parse_config, self.domain_name0) mock_exists.assert_called_once_with(self.domain_path0 + '/config') @mock.patch.object(builtins, 'open') @mock.patch.object(manager.VirtualBMCManager, '_parse_config') def _test__show(self, mock__parse, mock_open, expected=None): mock__parse.return_value = self.domain0 f = mock.MagicMock() f.read.return_value = self.domain0['port'] mock_open.return_value.__enter__.return_value = f if expected is None: expected = self.domain0.copy() expected['status'] = manager.DOWN ret = self.manager._show(self.domain_name0) self.assertEqual(expected, ret) def test__show(self): conf = {'default': {'show_passwords': True}} with mock.patch('virtualbmc.manager.CONF', conf): self._test__show() def test__show_mask_passwords(self): conf = {'default': {'show_passwords': False}} with mock.patch('virtualbmc.manager.CONF', conf): expected = self.domain0.copy() expected['password'] = '***' expected['libvirt_sasl_password'] = '***' expected['status'] = manager.DOWN self._test__show(expected=expected) @mock.patch.object(builtins, 'open') @mock.patch.object(configparser, 'ConfigParser') @mock.patch.object(os, 'makedirs') @mock.patch.object(utils, 'check_libvirt_connection_and_domain') def test_add(self, mock_check_conn, mock_makedirs, mock_configparser, mock_open): config = mock_configparser.return_value params = copy.copy(self.add_params) self.manager.add(**params) expected_calls = [mock.call('VirtualBMC', i, self.add_params[i]) for i in self.add_params] self.assertEqual(sorted(expected_calls), sorted(config.set.call_args_list)) config.add_section.assert_called_once_with('VirtualBMC') config.write.assert_called_once_with(mock.ANY) mock_check_conn.assert_called_once_with( self.add_params['libvirt_uri'], self.add_params['domain_name'], sasl_username=self.add_params['libvirt_sasl_username'], sasl_password=self.add_params['libvirt_sasl_password']) mock_makedirs.assert_called_once_with( os.path.join(_CONFIG_PATH, self.add_params['domain_name'])) mock_configparser.assert_called_once_with() @mock.patch.object(builtins, 'open') @mock.patch.object(configparser, 'ConfigParser') @mock.patch.object(os, 'makedirs') @mock.patch.object(utils, 'check_libvirt_connection_and_domain') def test_add_with_port_as_int(self, mock_check_conn, mock_makedirs, mock_configparser, mock_open): config = mock_configparser.return_value params = copy.copy(self.add_params) params['port'] = int(params['port']) self.manager.add(**params) expected_calls = [mock.call('VirtualBMC', i, self.add_params[i]) for i in self.add_params] self.assertEqual(sorted(expected_calls), sorted(config.set.call_args_list)) config.add_section.assert_called_once_with('VirtualBMC') config.write.assert_called_once_with(mock.ANY) mock_check_conn.assert_called_once_with( self.add_params['libvirt_uri'], self.add_params['domain_name'], sasl_username=self.add_params['libvirt_sasl_username'], sasl_password=self.add_params['libvirt_sasl_password']) mock_makedirs.assert_called_once_with( os.path.join(_CONFIG_PATH, self.add_params['domain_name'])) mock_configparser.assert_called_once_with() @mock.patch.object(os, 'makedirs') @mock.patch.object(utils, 'check_libvirt_connection_and_domain') def test_add_domain_already_exist(self, mock_check_conn, mock_makedirs): os_error = OSError() os_error.errno = errno.EEXIST mock_makedirs.side_effect = os_error ret, _ = self.manager.add(**self.add_params) expected_ret = 1 self.assertEqual(ret, expected_ret) mock_check_conn.assert_called_once_with( self.add_params['libvirt_uri'], self.add_params['domain_name'], sasl_username=self.add_params['libvirt_sasl_username'], sasl_password=self.add_params['libvirt_sasl_password']) @mock.patch.object(os, 'makedirs') @mock.patch.object(utils, 'check_libvirt_connection_and_domain') def test_add_oserror(self, mock_check_conn, mock_makedirs): mock_makedirs.side_effect = OSError ret, _ = self.manager.add(**self.add_params) expected_ret = 1 self.assertEqual(ret, expected_ret) mock_check_conn.assert_called_once_with( self.add_params['libvirt_uri'], self.add_params['domain_name'], sasl_username=self.add_params['libvirt_sasl_username'], sasl_password=self.add_params['libvirt_sasl_password']) @mock.patch.object(shutil, 'rmtree') @mock.patch.object(os.path, 'exists') @mock.patch.object(manager.VirtualBMCManager, 'stop') def test_delete(self, mock_stop, mock_exists, mock_rmtree): mock_exists.return_value = True self.manager.delete(self.domain_name0) mock_exists.assert_called_once_with(self.domain_path0) mock_stop.assert_called_once_with(self.domain_name0) mock_rmtree.assert_called_once_with(self.domain_path0) @mock.patch.object(os.path, 'exists') def test_delete_domain_not_found(self, mock_exists): mock_exists.return_value = False self.assertRaises(exception.DomainNotFound, self.manager.delete, self.domain_name0) mock_exists.assert_called_once_with(self.domain_path0) @mock.patch.object(builtins, 'open') @mock.patch.object(manager.VirtualBMCManager, '_parse_config') @mock.patch.object(os.path, 'exists') @mock.patch.object(os.path, 'isdir') @mock.patch.object(os, 'listdir') @mock.patch.object(multiprocessing, 'Process') def test_start(self, mock_process, mock_listdir, mock_isdir, mock_exists, mock__parse, mock_open): conf = {'ipmi': {'session_timeout': 10}, 'default': {'show_passwords': False}} with mock.patch('virtualbmc.manager.CONF', conf): mock_listdir.return_value = [self.domain_name0] mock_isdir.return_value = True mock_exists.return_value = True domain0_conf = self.domain0.copy() domain0_conf.update(active='False') mock__parse.return_value = domain0_conf file_handler = mock_open.return_value.__enter__.return_value self.manager.start(self.domain_name0) mock__parse.assert_called_with(self.domain_name0) self.assertEqual(file_handler.write.call_count, 9) @mock.patch.object(builtins, 'open') @mock.patch.object(manager.VirtualBMCManager, '_parse_config') @mock.patch.object(os.path, 'isdir') @mock.patch.object(os, 'listdir') def test_stop(self, mock_listdir, mock_isdir, mock__parse, mock_open): conf = {'ipmi': {'session_timeout': 10}, 'default': {'show_passwords': False}} with mock.patch('virtualbmc.manager.CONF', conf): mock_listdir.return_value = [self.domain_name0] mock_isdir.return_value = True domain0_conf = self.domain0.copy() domain0_conf.update(active='True') mock__parse.return_value = domain0_conf file_handler = mock_open.return_value.__enter__.return_value self.manager.stop(self.domain_name0) mock_isdir.assert_called_once_with(self.domain_path0) mock__parse.assert_called_with(self.domain_name0) self.assertEqual(file_handler.write.call_count, 9) @mock.patch.object(os.path, 'exists') def test_stop_domain_not_found(self, mock_exists): mock_exists.return_value = False ret = self.manager.stop(self.domain_name0) expected_ret = 1, 'No domain with matching name SpongeBob was found' self.assertEqual(ret, expected_ret) mock_exists.assert_called_once_with( os.path.join(self.domain_path0, 'config') ) @mock.patch.object(os.path, 'isdir') @mock.patch.object(os, 'listdir') @mock.patch.object(manager.VirtualBMCManager, '_show') def test_list(self, mock__show, mock_listdir, mock_isdir): mock_isdir.return_value = True mock_listdir.return_value = (self.domain_name0, self.domain_name1) ret, _ = self.manager.list() expected_ret = 0 self.assertEqual(ret, expected_ret) mock_listdir.assert_called_once_with(_CONFIG_PATH) expected_calls = [mock.call(self.domain_path0), mock.call(self.domain_path1)] self.assertEqual(expected_calls, mock_isdir.call_args_list) expected_calls = [mock.call(self.domain_name0), mock.call(self.domain_name1)] self.assertEqual(expected_calls, mock__show.call_args_list) @mock.patch.object(manager.VirtualBMCManager, '_show') def test_show(self, mock__show): self.manager.show(self.domain0) mock__show.assert_called_once_with(self.domain0) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/test_utils.py0000664000175000017500000001622700000000000022760 0ustar00zuulzuul00000000000000# Copyright 2016 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 os from unittest import mock import libvirt from virtualbmc import exception from virtualbmc.tests.unit import base from virtualbmc import utils class MiscUtilsTestCase(base.TestCase): @mock.patch.object(os, 'kill') def test_is_pid_running(self, mock_kill): self.assertTrue(utils.is_pid_running(123)) mock_kill.assert_called_once_with(123, 0) @mock.patch.object(os, 'kill') def test_is_pid_running_not_running(self, mock_kill): mock_kill.side_effect = OSError('boom') self.assertFalse(utils.is_pid_running(123)) mock_kill.assert_called_once_with(123, 0) def test_str2bool(self): for b in ('TRUE', 'true', 'True'): self.assertTrue(utils.str2bool(b)) for b in ('FALSE', 'false', 'False'): self.assertFalse(utils.str2bool(b)) self.assertRaises(ValueError, utils.str2bool, 'bogus value') def test_mask_dict_password(self): input_dict = {'foo': 'bar', 'password': 'SpongeBob SquarePants'} output_dict = utils.mask_dict_password(input_dict) expected = {'foo': 'bar', 'password': '***'} self.assertEqual(expected, output_dict) class LibvirtUtilsTestCase(base.TestCase): def setUp(self): super(LibvirtUtilsTestCase, self).setUp() self.fake_connection = mock.Mock() self.uri = 'fake:///patrick' def test_get_libvirt_domain(self): self.fake_connection.lookupByName.return_value = 'fake connection' ret = utils.get_libvirt_domain(self.fake_connection, 'SpongeBob') self.fake_connection.lookupByName.assert_called_once_with('SpongeBob') self.assertEqual('fake connection', ret) def test_get_libvirt_domain_not_found(self): self.fake_connection.lookupByName.side_effect = libvirt.libvirtError( 'boom') self.assertRaises(exception.DomainNotFound, utils.get_libvirt_domain, self.fake_connection, 'Fred') self.fake_connection.lookupByName.assert_called_once_with('Fred') def _test_libvirt_open(self, mock_open, **kwargs): mock_open.return_value = self.fake_connection with utils.libvirt_open(self.uri, **kwargs) as conn: self.assertEqual(self.fake_connection, conn) self.fake_connection.close.assert_called_once_with() @mock.patch.object(libvirt, 'open') def test_libvirt_open(self, mock_open): self._test_libvirt_open(mock_open) mock_open.assert_called_once_with(self.uri) @mock.patch.object(libvirt, 'open') def test_libvirt_open_error(self, mock_open): mock_open.side_effect = libvirt.libvirtError('boom') self.assertRaises(exception.LibvirtConnectionOpenError, self._test_libvirt_open, mock_open) mock_open.assert_called_once_with(self.uri) @mock.patch.object(libvirt, 'openReadOnly') def test_libvirt_open_readonly(self, mock_open): self._test_libvirt_open(mock_open, readonly=True) mock_open.assert_called_once_with(self.uri) @mock.patch.object(libvirt, 'openAuth') def _test_libvirt_open_sasl(self, mock_open, readonly=False): username = 'Eugene H. Krabs' password = ('hamburger, fresh lettuce, crisp onions, tomatoes, ' 'undersea cheese, pickles, mustard and ketchup') self._test_libvirt_open(mock_open, sasl_username=username, sasl_password=password, readonly=readonly) ro = 1 if readonly else 0 mock_open.assert_called_once_with(self.uri, mock.ANY, ro) def test_libvirt_open_sasl(self): self._test_libvirt_open_sasl() def test_libvirt_open_sasl_readonly(self): self._test_libvirt_open_sasl(readonly=True) @mock.patch.object(utils, 'os') class DetachProcessUtilsTestCase(base.TestCase): def test_detach_process(self, mock_os): # 2nd value > 0 so _exit get called and we can assert that we've # killed the parent's process mock_os.fork.side_effect = (0, 999) mock_os.devnull = os.devnull with utils.detach_process() as pid: self.assertEqual(0, pid) # assert fork() has been called twice expected_fork_calls = [mock.call()] * 2 self.assertEqual(expected_fork_calls, mock_os.fork.call_args_list) mock_os.setsid.assert_called_once_with() mock_os.chdir.assert_called_once_with('/') mock_os.umask.assert_called_once_with(0) mock_os._exit.assert_called_once_with(0) def test_detach_process_fork_fail(self, mock_os): error_msg = 'Kare-a-tay!' mock_os.fork.side_effect = OSError(error_msg) with self.assertRaisesRegex(exception.DetachProcessError, error_msg): with utils.detach_process(): pass mock_os.fork.assert_called_once_with() self.assertFalse(mock_os.setsid.called) self.assertFalse(mock_os.chdir.called) self.assertFalse(mock_os.umask.called) self.assertFalse(mock_os._exit.called) def test_detach_process_chdir_fail(self, mock_os): # 2nd value > 0 so _exit get called and we can assert that we've # killed the parent's process mock_os.fork.side_effect = (0, 999) error_msg = 'Fish paste!' mock_os.chdir.side_effect = Exception(error_msg) with self.assertRaisesRegex(exception.DetachProcessError, error_msg): with utils.detach_process(): pass # assert fork() has been called twice expected_fork_calls = [mock.call()] * 2 self.assertEqual(expected_fork_calls, mock_os.fork.call_args_list) mock_os.setsid.assert_called_once_with() mock_os.chdir.assert_called_once_with('/') mock_os._exit.assert_called_once_with(0) self.assertFalse(mock_os.umask.called) def test_detach_process_umask_fail(self, mock_os): # 2nd value > 0 so _exit get called and we can assert that we've # killed the parent's process mock_os.fork.side_effect = (0, 999) error_msg = 'Barnacles!' mock_os.umask.side_effect = Exception(error_msg) with self.assertRaisesRegex(exception.DetachProcessError, error_msg): with utils.detach_process(): pass # assert fork() has been called twice expected_fork_calls = [mock.call()] * 2 self.assertEqual(expected_fork_calls, mock_os.fork.call_args_list) mock_os.setsid.assert_called_once_with() mock_os.chdir.assert_called_once_with('/') mock_os._exit.assert_called_once_with(0) mock_os.umask.assert_called_once_with(0) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/tests/unit/test_vbmc.py0000664000175000017500000002621200000000000022542 0ustar00zuulzuul00000000000000# Copyright 2016 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. from unittest import mock import libvirt from virtualbmc import exception from virtualbmc.tests.unit import base from virtualbmc.tests.unit import utils as test_utils from virtualbmc import utils from virtualbmc import vbmc DOMAIN_XML_TEMPLATE = """\ hvm """ @mock.patch.object(utils, 'libvirt_open') @mock.patch.object(utils, 'get_libvirt_domain') class VirtualBMCTestCase(base.TestCase): def setUp(self): super(VirtualBMCTestCase, self).setUp() self.domain = test_utils.get_domain() # NOTE(lucasagomes): pyghmi's Bmc does create a socket in the # constructor so we need to mock it here mock.patch('pyghmi.ipmi.bmc.Bmc.__init__', lambda *args, **kwargs: None).start() self.vbmc = vbmc.VirtualBMC(**self.domain) def _assert_libvirt_calls(self, mock_libvirt_domain, mock_libvirt_open, readonly=False): """Helper method to assert that the LibVirt calls were invoked.""" mock_libvirt_domain.assert_called_once_with( mock.ANY, self.domain['domain_name']) params = {'sasl_password': self.domain['libvirt_sasl_password'], 'sasl_username': self.domain['libvirt_sasl_username'], 'uri': self.domain['libvirt_uri']} if readonly: params['readonly'] = True mock_libvirt_open.assert_called_once_with(**params) def test_get_boot_device(self, mock_libvirt_domain, mock_libvirt_open): for boot_device in vbmc.GET_BOOT_DEVICES_MAP: domain_xml = DOMAIN_XML_TEMPLATE % boot_device mock_libvirt_domain.return_value.XMLDesc.return_value = domain_xml ret = self.vbmc.get_boot_device() self.assertEqual(vbmc.GET_BOOT_DEVICES_MAP[boot_device], ret) self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open, readonly=True) # reset mocks for the next iteraction mock_libvirt_domain.reset_mock() mock_libvirt_open.reset_mock() def test_set_boot_device(self, mock_libvirt_domain, mock_libvirt_open): for boot_device in vbmc.SET_BOOT_DEVICES_MAP: domain_xml = DOMAIN_XML_TEMPLATE % 'foo' mock_libvirt_domain.return_value.XMLDesc.return_value = domain_xml conn = mock_libvirt_open.return_value.__enter__.return_value self.vbmc.set_boot_device(boot_device) expected = ('' % vbmc.SET_BOOT_DEVICES_MAP[boot_device]) self.assertIn(expected, str(conn.defineXML.call_args)) self.assertEqual(1, str(conn.defineXML.call_args).count(' 0 and parent_exits: os._exit(0) return pid except OSError as e: raise exception.DetachProcessError(error=e) def _change_root_directory(self): """Change to root directory. Ensure that our process doesn't keep any directory in use. Failure to do this could make it so that an administrator couldn't unmount a filesystem, because it was our current directory. """ try: os.chdir('/') except Exception as e: error = ('Failed to change root directory. Error: %s' % e) raise exception.DetachProcessError(error=error) def _change_file_creation_mask(self): """Set the umask for new files. Set the umask for new files the process creates so that it does have complete control over the permissions of them. We don't know what umask we may have inherited. """ try: os.umask(0) except Exception as e: error = ('Failed to change file creation mask. Error: %s' % e) raise exception.DetachProcessError(error=error) def __enter__(self): pid = self._fork(parent_exits=False) if pid > 0: return pid os.setsid() self._fork(parent_exits=True) self._change_root_directory() self._change_file_creation_mask() sys.stdout.flush() sys.stderr.flush() si = open(os.devnull, 'r') so = open(os.devnull, 'a+') se = open(os.devnull, 'a+') os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) return pid def __exit__(self, type, value, traceback): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/virtualbmc/vbmc.py0000664000175000017500000002147500000000000017370 0ustar00zuulzuul00000000000000# 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 xml.etree.ElementTree as ET import libvirt import pyghmi.ipmi.bmc as bmc from virtualbmc import exception from virtualbmc import log from virtualbmc import utils LOG = log.get_logger() # Power states POWEROFF = 0 POWERON = 1 # From the IPMI - Intelligent Platform Management Interface Specification # Second Generation v2.0 Document Revision 1.1 October 1, 2013 # https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf # # Command failed and can be retried IPMI_COMMAND_NODE_BUSY = 0xC0 # Invalid data field in request IPMI_INVALID_DATA = 0xcc # Boot device maps GET_BOOT_DEVICES_MAP = { 'network': 4, 'hd': 8, 'cdrom': 0x14, } SET_BOOT_DEVICES_MAP = { 'network': 'network', 'hd': 'hd', 'optical': 'cdrom', } class VirtualBMC(bmc.Bmc): def __init__(self, username, password, port, address, domain_name, libvirt_uri, libvirt_sasl_username=None, libvirt_sasl_password=None, **kwargs): super(VirtualBMC, self).__init__({username: password}, port=port, address=address) self.domain_name = domain_name self._conn_args = {'uri': libvirt_uri, 'sasl_username': libvirt_sasl_username, 'sasl_password': libvirt_sasl_password} # Copied from nova/virt/libvirt/guest.py def get_xml_desc(self, domain, dump_sensitive=False): """Returns xml description of guest. :param domain: The libvirt domain to call :param dump_sensitive: Dump security sensitive information :returns string: XML description of the guest """ flags = dump_sensitive and libvirt.VIR_DOMAIN_XML_SECURE or 0 return domain.XMLDesc(flags=flags) def get_boot_device(self): LOG.debug('Get boot device called for %(domain)s', {'domain': self.domain_name}) with utils.libvirt_open(readonly=True, **self._conn_args) as conn: domain = utils.get_libvirt_domain(conn, self.domain_name) boot_element = ET.fromstring(domain.XMLDesc()).find('.//os/boot') boot_dev = None if boot_element is not None: boot_dev = boot_element.attrib.get('dev') return GET_BOOT_DEVICES_MAP.get(boot_dev, 0) def _remove_boot_elements(self, parent_element): for boot_element in parent_element.findall('boot'): parent_element.remove(boot_element) def set_boot_device(self, bootdevice): LOG.debug('Set boot device called for %(domain)s with boot ' 'device "%(bootdev)s"', {'domain': self.domain_name, 'bootdev': bootdevice}) device = SET_BOOT_DEVICES_MAP.get(bootdevice) if device is None: # Invalid data field in request return IPMI_INVALID_DATA try: with utils.libvirt_open(**self._conn_args) as conn: domain = utils.get_libvirt_domain(conn, self.domain_name) tree = ET.fromstring( self.get_xml_desc(domain, dump_sensitive=True)) # Remove all "boot" element under "devices" # They are mutually exclusive with "os/boot" for device_element in tree.findall('devices/*'): self._remove_boot_elements(device_element) for os_element in tree.findall('os'): # Remove all "boot" elements under "os" self._remove_boot_elements(os_element) # Add a new boot element with the request boot device boot_element = ET.SubElement(os_element, 'boot') boot_element.set('dev', device) conn.defineXML(ET.tostring(tree, encoding="unicode")) except libvirt.libvirtError: LOG.error('Failed setting the boot device %(bootdev)s for ' 'domain %(domain)s', {'bootdev': device, 'domain': self.domain_name}) # Command failed, but let client to retry return IPMI_COMMAND_NODE_BUSY def get_power_state(self): LOG.debug('Get power state called for domain %(domain)s', {'domain': self.domain_name}) try: with utils.libvirt_open(readonly=True, **self._conn_args) as conn: domain = utils.get_libvirt_domain(conn, self.domain_name) if domain.isActive(): return POWERON except libvirt.libvirtError as e: msg = ('Error getting the power state of domain %(domain)s. ' 'Error: %(error)s' % {'domain': self.domain_name, 'error': e}) LOG.error(msg) raise exception.VirtualBMCError(message=msg) return POWEROFF def pulse_diag(self): LOG.debug('Power diag called for domain %(domain)s', {'domain': self.domain_name}) try: with utils.libvirt_open(**self._conn_args) as conn: domain = utils.get_libvirt_domain(conn, self.domain_name) if domain.isActive(): domain.injectNMI() except libvirt.libvirtError as e: LOG.error('Error powering diag the domain %(domain)s. ' 'Error: %(error)s', {'domain': self.domain_name, 'error': e}) # Command failed, but let client to retry return IPMI_COMMAND_NODE_BUSY def power_off(self): LOG.debug('Power off called for domain %(domain)s', {'domain': self.domain_name}) try: with utils.libvirt_open(**self._conn_args) as conn: domain = utils.get_libvirt_domain(conn, self.domain_name) if domain.isActive(): domain.destroy() except libvirt.libvirtError as e: LOG.error('Error powering off the domain %(domain)s. ' 'Error: %(error)s', {'domain': self.domain_name, 'error': e}) # Command failed, but let client to retry return IPMI_COMMAND_NODE_BUSY def power_on(self): LOG.debug('Power on called for domain %(domain)s', {'domain': self.domain_name}) try: with utils.libvirt_open(**self._conn_args) as conn: domain = utils.get_libvirt_domain(conn, self.domain_name) if not domain.isActive(): domain.create() except libvirt.libvirtError as e: LOG.error('Error powering on the domain %(domain)s. ' 'Error: %(error)s', {'domain': self.domain_name, 'error': e}) # Command failed, but let client to retry return IPMI_COMMAND_NODE_BUSY def power_shutdown(self): LOG.debug('Soft power off called for domain %(domain)s', {'domain': self.domain_name}) try: with utils.libvirt_open(**self._conn_args) as conn: domain = utils.get_libvirt_domain(conn, self.domain_name) if domain.isActive(): domain.shutdown() except libvirt.libvirtError as e: LOG.error('Error soft powering off the domain %(domain)s. ' 'Error: %(error)s', {'domain': self.domain_name, 'error': e}) # Command failed, but let client to retry return IPMI_COMMAND_NODE_BUSY def power_reset(self): LOG.debug('Power reset called for domain %(domain)s', {'domain': self.domain_name}) try: with utils.libvirt_open(**self._conn_args) as conn: domain = utils.get_libvirt_domain(conn, self.domain_name) if domain.isActive(): domain.reset() except libvirt.libvirtError as e: LOG.error('Error reseting the domain %(domain)s. ' 'Error: %(error)s', {'domain': self.domain_name, 'error': e}) # Command not supported in present state return IPMI_COMMAND_NODE_BUSY ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6814146 virtualbmc-3.1.0/virtualbmc.egg-info/0000775000175000017500000000000000000000000017550 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/virtualbmc.egg-info/PKG-INFO0000664000175000017500000000545600000000000020657 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: virtualbmc Version: 3.1.0 Summary: Create virtual BMCs for controlling virtual instances via IPMI Home-page: https://docs.openstack.org/virtualbmc/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ========== VirtualBMC ========== Team and repository tags ------------------------ .. image:: https://governance.openstack.org/tc/badges/virtualbmc.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Overview -------- A virtual BMC for controlling virtual machines using IPMI commands. This software is intended for CI and development use only. Please do not run VirtualBMC in a production environment for any reason. Installation ~~~~~~~~~~~~ .. code-block:: bash pip install virtualbmc Supported IPMI commands ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash # Power the virtual machine on, off, graceful off, NMI and reset ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power on|off|soft|diag|reset # Check the power status ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power status # Set the boot device to network, hd or cdrom ipmitool -I lanplus -U admin -P password -H 127.0.0.1 chassis bootdev pxe|disk|cdrom # Get the current boot device ipmitool -I lanplus -U admin -P password -H 127.0.0.1 chassis bootparam get 5 Project resources ~~~~~~~~~~~~~~~~~ * Documentation: https://docs.openstack.org/virtualbmc/latest * Source: https://opendev.org/openstack/virtualbmc * Bugs: https://bugs.launchpad.net/virtualbmc * Release Notes: https://docs.openstack.org/releasenotes/virtualbmc/ For information on how to contribute to VirtualBMC, see https://docs.openstack.org/virtualbmc/latest/contributor Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/virtualbmc.egg-info/SOURCES.txt0000664000175000017500000000402000000000000021430 0ustar00zuulzuul00000000000000.coveragerc .stestr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst bindep.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/user/index.rst releasenotes/notes/.placeholder releasenotes/notes/add-client-server-overhaul-c5b6f8c01126b4a3.yaml releasenotes/notes/add-config-env-8287bea486821653.yaml releasenotes/notes/drop-py-2-7-afe69612bfabaeee.yaml releasenotes/notes/fix-hanging-on-pipe-7c4b5f9c81623b524.yaml releasenotes/notes/ignore-start-if-running-c9a8f6c0514624a1.yaml releasenotes/notes/preserve-libvirt-domain-info-955410f570060241.yaml releasenotes/notes/py36-37-bye-bye-f3268421bf6c5bb4.yaml releasenotes/notes/remove-vbmcd-autostart-d1f567803526a4c1.yaml releasenotes/notes/show-error-status-13456782b3a5a6e2.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder virtualbmc/__init__.py virtualbmc/config.py virtualbmc/control.py virtualbmc/exception.py virtualbmc/log.py virtualbmc/manager.py virtualbmc/utils.py virtualbmc/vbmc.py virtualbmc.egg-info/PKG-INFO virtualbmc.egg-info/SOURCES.txt virtualbmc.egg-info/dependency_links.txt virtualbmc.egg-info/entry_points.txt virtualbmc.egg-info/not-zip-safe virtualbmc.egg-info/pbr.json virtualbmc.egg-info/requires.txt virtualbmc.egg-info/top_level.txt virtualbmc/cmd/__init__.py virtualbmc/cmd/vbmc.py virtualbmc/cmd/vbmcd.py virtualbmc/tests/__init__.py virtualbmc/tests/unit/__init__.py virtualbmc/tests/unit/base.py virtualbmc/tests/unit/test_config.py virtualbmc/tests/unit/test_control.py virtualbmc/tests/unit/test_manager.py virtualbmc/tests/unit/test_utils.py virtualbmc/tests/unit/test_vbmc.py virtualbmc/tests/unit/utils.py virtualbmc/tests/unit/cmd/__init__.py virtualbmc/tests/unit/cmd/test_vbmc.py virtualbmc/tests/unit/cmd/test_vbmcd.py zuul.d/project.yaml zuul.d/virtualbmc-jobs.yaml././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/virtualbmc.egg-info/dependency_links.txt0000664000175000017500000000000100000000000023616 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/virtualbmc.egg-info/entry_points.txt0000664000175000017500000000052100000000000023044 0ustar00zuulzuul00000000000000[console_scripts] vbmc = virtualbmc.cmd.vbmc:main vbmcd = virtualbmc.cmd.vbmcd:main [virtualbmc] add = virtualbmc.cmd.vbmc:AddCommand delete = virtualbmc.cmd.vbmc:DeleteCommand list = virtualbmc.cmd.vbmc:ListCommand show = virtualbmc.cmd.vbmc:ShowCommand start = virtualbmc.cmd.vbmc:StartCommand stop = virtualbmc.cmd.vbmc:StopCommand ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/virtualbmc.egg-info/not-zip-safe0000664000175000017500000000000100000000000021776 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/virtualbmc.egg-info/pbr.json0000664000175000017500000000005600000000000021227 0ustar00zuulzuul00000000000000{"git_version": "c1b75e0", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/virtualbmc.egg-info/requires.txt0000664000175000017500000000013200000000000022144 0ustar00zuulzuul00000000000000cliff!=2.9.0,>=2.8.0 libvirt-python>=6.0.0 pbr!=2.1.0,>=2.0.0 pyghmi>=1.2.0 pyzmq>=19.0.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307724.0 virtualbmc-3.1.0/virtualbmc.egg-info/top_level.txt0000664000175000017500000000001300000000000022274 0ustar00zuulzuul00000000000000virtualbmc ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1695307724.6814146 virtualbmc-3.1.0/zuul.d/0000775000175000017500000000000000000000000015127 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/zuul.d/project.yaml0000664000175000017500000000045400000000000017464 0ustar00zuulzuul00000000000000- project: templates: - check-requirements - openstack-cover-jobs - openstack-python3-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - virtualbmc-tempest-ironic gate: jobs: - virtualbmc-tempest-ironic ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1695307654.0 virtualbmc-3.1.0/zuul.d/virtualbmc-jobs.yaml0000664000175000017500000000124500000000000021120 0ustar00zuulzuul00000000000000- job: name: virtualbmc-tempest-ironic parent: ironic-base irrelevant-files: - ^.*\.rst$ - ^doc/.*$ - ^virtualbmc/tests/.*$ - ^setup.cfg$ - ^test-requirements.txt$ - ^tools/.*$ - ^tox.ini$ timeout: 10800 required-projects: - openstack/virtualbmc vars: devstack_localrc: EBTABLES_RACE_FIX: True IRONIC_BOOT_MODE: bios IRONIC_DEFAULT_BOOT_OPTION: netboot IRONIC_DEFAULT_RESCUE_INTERFACE: "" devstack_services: dstat: false # Remove when no longer used on pyghmi - job: name: virtualbmc-tempest-ironic-ipmi-iscsi parent: virtualbmc-tempest-ironic