Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
isv:SUSEInfra:Tools
python-virtualbmc
_service:obs_scm:virtualbmc-3.0.1.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:virtualbmc-3.0.1.obscpio of Package python-virtualbmc
07070100000000000081A400000000000000000000000163AC502600000061000000000000000000000000000000000000001D00000000virtualbmc-3.0.1/.coveragerc[run] branch = True source = virtualbmc omit = virtualbmc/tests/* [report] ignore_errors = True 07070100000001000081A400000000000000000000000163AC50260000021B000000000000000000000000000000000000001C00000000virtualbmc-3.0.1/.gitignore*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build .eggs eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage cover .tox nosetests.xml .testrepository .stestr .venv # Translations *.mo # Files created by releasenotes build releasenotes/build # Mr Developer .mr.developer.cfg .project .pydevproject # Complexity output/*.html output/*/index.html # Sphinx doc/build # pbr generates these AUTHORS ChangeLog # Editors *~ .*.swp .*sw? 07070100000002000081A400000000000000000000000163AC50260000004D000000000000000000000000000000000000001C00000000virtualbmc-3.0.1/.gitreview[gerrit] host=review.opendev.org port=29418 project=openstack/virtualbmc.git 07070100000003000081A400000000000000000000000163AC502600000046000000000000000000000000000000000000001E00000000virtualbmc-3.0.1/.stestr.conf[DEFAULT] test_path=${TESTS_DIR:-./virtualbmc/tests/unit/} top_dir=./ 07070100000004000081A400000000000000000000000163AC502600000299000000000000000000000000000000000000002200000000virtualbmc-3.0.1/CONTRIBUTING.rstIf 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 StoryBoard, not GitHub: https://storyboard.openstack.org/#!/project/962 07070100000005000081A400000000000000000000000163AC5026000000A0000000000000000000000000000000000000001D00000000virtualbmc-3.0.1/HACKING.rstvirtualbmc Style Commandments =============================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ 07070100000006000081A400000000000000000000000163AC50260000279F000000000000000000000000000000000000001900000000virtualbmc-3.0.1/LICENSE 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. 07070100000007000081A400000000000000000000000163AC502600000680000000000000000000000000000000000000001C00000000virtualbmc-3.0.1/README.rst========== 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://storyboard.openstack.org/#!/project/openstack/virtualbmc * Release Notes: https://docs.openstack.org/releasenotes/virtualbmc/ Project status, bugs, and requests for feature enhancements (RFEs) are tracked in StoryBoard: https://storyboard.openstack.org/#!/project/openstack/virtualbmc For information on how to contribute to VirtualBMC, see https://docs.openstack.org/virtualbmc/latest/contributor 07070100000008000081A400000000000000000000000163AC502600000196000000000000000000000000000000000000001C00000000virtualbmc-3.0.1/bindep.txt# 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] 07070100000009000041ED00000000000000000000000363AC502600000000000000000000000000000000000000000000001500000000virtualbmc-3.0.1/doc0707010000000A000081A400000000000000000000000163AC502600000088000000000000000000000000000000000000002600000000virtualbmc-3.0.1/doc/requirements.txtreno>=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 0707010000000B000041ED00000000000000000000000563AC502600000000000000000000000000000000000000000000001C00000000virtualbmc-3.0.1/doc/source0707010000000C000081ED00000000000000000000000163AC502600000AAD000000000000000000000000000000000000002400000000virtualbmc-3.0.1/doc/source/conf.py# 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 = True # 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} 0707010000000D000041ED00000000000000000000000263AC502600000000000000000000000000000000000000000000002800000000virtualbmc-3.0.1/doc/source/contributor0707010000000E000081A400000000000000000000000163AC50260000004E000000000000000000000000000000000000003200000000virtualbmc-3.0.1/doc/source/contributor/index.rst============ Contributing ============ .. include:: ../../../CONTRIBUTING.rst 0707010000000F000081A400000000000000000000000163AC50260000035C000000000000000000000000000000000000002600000000virtualbmc-3.0.1/doc/source/index.rst.. 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 <https://en.wikipedia.org/wiki/Intelligent_Platform_Management_Interface#Baseboard_management_controller>`_ (BMC) by exposing `IPMI <https://en.wikipedia.org/wiki/Intelligent_Platform_Management_Interface>`_ responder to the network and talking to `libvirt <https://en.wikipedia.org/wiki/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 07070100000010000041ED00000000000000000000000263AC502600000000000000000000000000000000000000000000002400000000virtualbmc-3.0.1/doc/source/install07070100000011000081A400000000000000000000000163AC5026000000C7000000000000000000000000000000000000002E00000000virtualbmc-3.0.1/doc/source/install/index.rst============ Installation ============ At the command line:: $ pip install virtualbmc Or, if you have virtualenvwrapper installed:: $ mkvirtualenv virtualbmc $ pip install virtualbmc 07070100000012000041ED00000000000000000000000263AC502600000000000000000000000000000000000000000000002100000000virtualbmc-3.0.1/doc/source/user07070100000013000081A400000000000000000000000163AC502600001310000000000000000000000000000000000000002B00000000virtualbmc-3.0.1/doc/source/user/index.rst 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. 07070100000014000041ED00000000000000000000000463AC502600000000000000000000000000000000000000000000001E00000000virtualbmc-3.0.1/releasenotes07070100000015000041ED00000000000000000000000263AC502600000000000000000000000000000000000000000000002400000000virtualbmc-3.0.1/releasenotes/notes07070100000016000081A400000000000000000000000163AC502600000000000000000000000000000000000000000000003100000000virtualbmc-3.0.1/releasenotes/notes/.placeholder07070100000017000081A400000000000000000000000163AC502600000399000000000000000000000000000000000000005500000000virtualbmc-3.0.1/releasenotes/notes/add-client-server-overhaul-c5b6f8c01126b4a3.yaml--- 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. 07070100000018000081A400000000000000000000000163AC5026000000E7000000000000000000000000000000000000004900000000virtualbmc-3.0.1/releasenotes/notes/add-config-env-8287bea486821653.yamlfeatures: - | 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. 07070100000019000081A400000000000000000000000163AC5026000000CE000000000000000000000000000000000000004600000000virtualbmc-3.0.1/releasenotes/notes/drop-py-2-7-afe69612bfabaeee.yamlupgrade: - | 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. 0707010000001A000081A400000000000000000000000163AC502600000087000000000000000000000000000000000000004F00000000virtualbmc-3.0.1/releasenotes/notes/fix-hanging-on-pipe-7c4b5f9c81623b524.yaml--- fixes: - | Properly closes standard I/O streams to prevent shell-piped processes from hanging infinitely on a dead pipe. 0707010000001B000081A400000000000000000000000163AC50260000013A000000000000000000000000000000000000005200000000virtualbmc-3.0.1/releasenotes/notes/ignore-start-if-running-c9a8f6c0514624a1.yaml--- 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. 0707010000001C000081A400000000000000000000000163AC502600000140000000000000000000000000000000000000005700000000virtualbmc-3.0.1/releasenotes/notes/preserve-libvirt-domain-info-955410f570060241.yaml--- security: - | Secure information in the Libvirt domain XML document is now preserved. For more information, please see `story 2010382 <https://storyboard.openstack.org/#!/story/2010382>`_. fixes: - | Fixes an issue where secure fields were accidently lost in the Libvirt domain XML document. 0707010000001D000081A400000000000000000000000163AC502600000076000000000000000000000000000000000000004A00000000virtualbmc-3.0.1/releasenotes/notes/py36-37-bye-bye-f3268421bf6c5bb4.yaml--- upgrade: - | Virtualbmc does not support Python 3.6 and 3.7 anymore, please use version 3.8 or higher. 0707010000001E000081A400000000000000000000000163AC5026000000EC000000000000000000000000000000000000005100000000virtualbmc-3.0.1/releasenotes/notes/remove-vbmcd-autostart-d1f567803526a4c1.yaml--- 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. 0707010000001F000081A400000000000000000000000163AC5026000000D2000000000000000000000000000000000000004C00000000virtualbmc-3.0.1/releasenotes/notes/show-error-status-13456782b3a5a6e2.yamlfeatures: - | 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``. 07070100000020000041ED00000000000000000000000463AC502600000000000000000000000000000000000000000000002500000000virtualbmc-3.0.1/releasenotes/source07070100000021000041ED00000000000000000000000263AC502600000000000000000000000000000000000000000000002D00000000virtualbmc-3.0.1/releasenotes/source/_static07070100000022000081A400000000000000000000000163AC502600000000000000000000000000000000000000000000003A00000000virtualbmc-3.0.1/releasenotes/source/_static/.placeholder07070100000023000041ED00000000000000000000000263AC502600000000000000000000000000000000000000000000003000000000virtualbmc-3.0.1/releasenotes/source/_templates07070100000024000081A400000000000000000000000163AC502600000000000000000000000000000000000000000000003D00000000virtualbmc-3.0.1/releasenotes/source/_templates/.placeholder07070100000025000081A400000000000000000000000163AC5026000016D4000000000000000000000000000000000000002D00000000virtualbmc-3.0.1/releasenotes/source/conf.py# -*- 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 = True # 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/'] 07070100000026000081A400000000000000000000000163AC502600000078000000000000000000000000000000000000002F00000000virtualbmc-3.0.1/releasenotes/source/index.rst======================== VirtualBMC Release Notes ======================== .. toctree:: :maxdepth: 1 unreleased 07070100000027000081A400000000000000000000000163AC502600000070000000000000000000000000000000000000003400000000virtualbmc-3.0.1/releasenotes/source/unreleased.rst============================== Current Series Release Notes ============================== .. release-notes:: 07070100000028000081A400000000000000000000000163AC50260000016A000000000000000000000000000000000000002200000000virtualbmc-3.0.1/requirements.txt# 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 07070100000029000081A400000000000000000000000163AC5026000004BB000000000000000000000000000000000000001B00000000virtualbmc-3.0.1/setup.cfg[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 [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 0707010000002A000081A400000000000000000000000163AC5026000002B9000000000000000000000000000000000000001A00000000virtualbmc-3.0.1/setup.py# 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) 0707010000002B000081A400000000000000000000000163AC5026000001CF000000000000000000000000000000000000002700000000virtualbmc-3.0.1/test-requirements.txt# 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 0707010000002C000081A400000000000000000000000163AC502600000815000000000000000000000000000000000000001900000000virtualbmc-3.0.1/tox.ini[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>=4.1.0,<5.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 0707010000002D000041ED00000000000000000000000463AC502600000000000000000000000000000000000000000000001C00000000virtualbmc-3.0.1/virtualbmc0707010000002E000081A400000000000000000000000163AC502600000296000000000000000000000000000000000000002800000000virtualbmc-3.0.1/virtualbmc/__init__.py# 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() 0707010000002F000041ED00000000000000000000000263AC502600000000000000000000000000000000000000000000002000000000virtualbmc-3.0.1/virtualbmc/cmd07070100000030000081A400000000000000000000000163AC502600000000000000000000000000000000000000000000002C00000000virtualbmc-3.0.1/virtualbmc/cmd/__init__.py07070100000031000081A400000000000000000000000163AC502600002625000000000000000000000000000000000000002800000000virtualbmc-3.0.1/virtualbmc/cmd/vbmc.py# 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()) 07070100000032000081A400000000000000000000000163AC502600000A0F000000000000000000000000000000000000002900000000virtualbmc-3.0.1/virtualbmc/cmd/vbmcd.py# 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()) 07070100000033000081A400000000000000000000000163AC502600000C23000000000000000000000000000000000000002600000000virtualbmc-3.0.1/virtualbmc/config.py# 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 07070100000034000081A400000000000000000000000163AC502600001A77000000000000000000000000000000000000002700000000virtualbmc-3.0.1/virtualbmc/control.py# 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) 07070100000035000081A400000000000000000000000163AC50260000058B000000000000000000000000000000000000002900000000virtualbmc-3.0.1/virtualbmc/exception.py# 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') 07070100000036000081A400000000000000000000000163AC5026000006C9000000000000000000000000000000000000002300000000virtualbmc-3.0.1/virtualbmc/log.py# 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 07070100000037000081A400000000000000000000000163AC502600002A76000000000000000000000000000000000000002700000000virtualbmc-3.0.1/virtualbmc/manager.py# 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()) 07070100000038000041ED00000000000000000000000363AC502600000000000000000000000000000000000000000000002200000000virtualbmc-3.0.1/virtualbmc/tests07070100000039000081A400000000000000000000000163AC502600000000000000000000000000000000000000000000002E00000000virtualbmc-3.0.1/virtualbmc/tests/__init__.py0707010000003A000041ED00000000000000000000000363AC502600000000000000000000000000000000000000000000002700000000virtualbmc-3.0.1/virtualbmc/tests/unit0707010000003B000081A400000000000000000000000163AC502600000000000000000000000000000000000000000000003300000000virtualbmc-3.0.1/virtualbmc/tests/unit/__init__.py0707010000003C000081A400000000000000000000000163AC502600000473000000000000000000000000000000000000002F00000000virtualbmc-3.0.1/virtualbmc/tests/unit/base.py# -*- 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) 0707010000003D000041ED00000000000000000000000263AC502600000000000000000000000000000000000000000000002B00000000virtualbmc-3.0.1/virtualbmc/tests/unit/cmd0707010000003E000081A400000000000000000000000163AC502600000000000000000000000000000000000000000000003700000000virtualbmc-3.0.1/virtualbmc/tests/unit/cmd/__init__.py0707010000003F000081A400000000000000000000000163AC5026000024EB000000000000000000000000000000000000003800000000virtualbmc-3.0.1/virtualbmc/tests/unit/cmd/test_vbmc.py# 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()) 07070100000040000081A400000000000000000000000163AC5026000007B8000000000000000000000000000000000000003900000000virtualbmc-3.0.1/virtualbmc/tests/unit/cmd/test_vbmcd.py# 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() 07070100000041000081A400000000000000000000000163AC502600000D61000000000000000000000000000000000000003600000000virtualbmc-3.0.1/virtualbmc/tests/unit/test_config.py# 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) 07070100000042000081A400000000000000000000000163AC50260000089D000000000000000000000000000000000000003700000000virtualbmc-3.0.1/virtualbmc/tests/unit/test_control.py# 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) 07070100000043000081A400000000000000000000000163AC5026000031DC000000000000000000000000000000000000003700000000virtualbmc-3.0.1/virtualbmc/tests/unit/test_manager.py# 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) 07070100000044000081A400000000000000000000000163AC502600001C97000000000000000000000000000000000000003500000000virtualbmc-3.0.1/virtualbmc/tests/unit/test_utils.py# 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) 07070100000045000081A400000000000000000000000163AC502600002C8A000000000000000000000000000000000000003400000000virtualbmc-3.0.1/virtualbmc/tests/unit/test_vbmc.py# 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 = """\ <domain type='qemu'> <os> <type arch='x86_64' machine='pc-1.0'>hvm</type> <boot dev='%s'/> <bootmenu enable='no'/> <bios useserial='yes'/> </os> <devices> <disk type='block' device='disk'> <boot order='2'/> </disk> <interface type='network'> <boot order='1'/> </interface> </devices> </domain> """ @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 = ('<boot dev="%s" />' % vbmc.SET_BOOT_DEVICES_MAP[boot_device]) self.assertIn(expected, str(conn.defineXML.call_args)) self.assertEqual(1, str(conn.defineXML.call_args).count('<boot ')) self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) # reset mocks for the next iteraction mock_libvirt_domain.reset_mock() mock_libvirt_open.reset_mock() def test_set_boot_device_error(self, mock_libvirt_domain, mock_libvirt_open): mock_libvirt_domain.side_effect = libvirt.libvirtError('boom') ret = self.vbmc.set_boot_device('network') self.assertEqual(0xc0, ret) self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_set_boot_device_unkown_device_error(self, mock_libvirt_domain, mock_libvirt_open): ret = self.vbmc.set_boot_device('device-foo-bar') self.assertEqual(0xcc, ret) self.assertFalse(mock_libvirt_open.called) self.assertFalse(mock_libvirt_domain.called) def _test_get_power_state(self, mock_libvirt_domain, mock_libvirt_open, power_on=True): mock_libvirt_domain.return_value.isActive.return_value = power_on ret = self.vbmc.get_power_state() expected = vbmc.POWERON if power_on else vbmc.POWEROFF self.assertEqual(expected, ret) self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open, readonly=True) def test_get_power_state_on(self, mock_libvirt_domain, mock_libvirt_open): self._test_get_power_state(mock_libvirt_domain, mock_libvirt_open, power_on=True) def test_get_power_state_off(self, mock_libvirt_domain, mock_libvirt_open): self._test_get_power_state(mock_libvirt_domain, mock_libvirt_open, power_on=False) def test_get_power_state_error(self, mock_libvirt_domain, mock_libvirt_open): mock_libvirt_domain.side_effect = libvirt.libvirtError('boom') self.assertRaises(exception.VirtualBMCError, self.vbmc.get_power_state) self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open, readonly=True) def test_pulse_diag_is_on(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = True self.vbmc.pulse_diag() domain.injectNMI.assert_called_once_with() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_pulse_diag_is_off(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = False self.vbmc.pulse_diag() # power is already off, assert injectNMI() wasn't invoked domain.injectNMI.assert_not_called() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_pulse_diag_error(self, mock_libvirt_domain, mock_libvirt_open): mock_libvirt_domain.side_effect = libvirt.libvirtError('boom') ret = self.vbmc.pulse_diag() self.assertEqual(0xC0, ret) mock_libvirt_domain.return_value.injectNMI.assert_not_called() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_off_is_on(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = True self.vbmc.power_off() domain.destroy.assert_called_once_with() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_off_is_off(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = False self.vbmc.power_off() # power is already off, assert destroy() wasn't invoked domain.destroy.assert_not_called() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_off_error(self, mock_libvirt_domain, mock_libvirt_open): mock_libvirt_domain.side_effect = libvirt.libvirtError('boom') ret = self.vbmc.power_off() self.assertEqual(0xC0, ret) mock_libvirt_domain.return_value.destroy.assert_not_called() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_reset_is_on(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = True self.vbmc.power_reset() domain.reset.assert_called_once_with() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_reset_is_off(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = False self.vbmc.power_reset() # power is already off, assert reset() wasn't invoked domain.reset.assert_not_called() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_reset_error(self, mock_libvirt_domain, mock_libvirt_open): mock_libvirt_domain.side_effect = libvirt.libvirtError('boom') ret = self.vbmc.power_reset() self.assertEqual(0xC0, ret) mock_libvirt_domain.return_value.reset.assert_not_called() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_shutdown_is_on(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = True self.vbmc.power_shutdown() domain.shutdown.assert_called_once_with() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_shutdown_is_off(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = False self.vbmc.power_shutdown() # power is already off, assert shutdown() wasn't invoked domain.shutdown.assert_not_called() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_shutdown_error(self, mock_libvirt_domain, mock_libvirt_open): mock_libvirt_domain.side_effect = libvirt.libvirtError('boom') ret = self.vbmc.power_shutdown() self.assertEqual(0xC0, ret) mock_libvirt_domain.return_value.shutdown.assert_not_called() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_on_is_on(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = True self.vbmc.power_on() # power is already on, assert create() wasn't invoked domain.create.assert_not_called() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_on_is_off(self, mock_libvirt_domain, mock_libvirt_open): domain = mock_libvirt_domain.return_value domain.isActive.return_value = False self.vbmc.power_on() domain.create.assert_called_once_with() self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) def test_power_on_error(self, mock_libvirt_domain, mock_libvirt_open): mock_libvirt_domain.side_effect = libvirt.libvirtError('boom') ret = self.vbmc.power_on() self.assertEqual(0xC0, ret) self.assertFalse(mock_libvirt_domain.return_value.create.called) self._assert_libvirt_calls(mock_libvirt_domain, mock_libvirt_open) 07070100000046000081A400000000000000000000000163AC502600000534000000000000000000000000000000000000003000000000virtualbmc-3.0.1/virtualbmc/tests/unit/utils.py# 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. def get_domain(**kwargs): domain = {'domain_name': kwargs.get('domain_name', 'SpongeBob'), 'address': kwargs.get('address', '::'), 'port': kwargs.get('port', 123), 'username': kwargs.get('username', 'admin'), 'password': kwargs.get('password', 'pass'), 'libvirt_uri': kwargs.get('libvirt_uri', 'foo://bar'), 'libvirt_sasl_username': kwargs.get('libvirt_sasl_username'), 'libvirt_sasl_password': kwargs.get('libvirt_sasl_password'), 'active': kwargs.get('active', False)} status = kwargs.get('status') if status is not None: domain['status'] = status return domain 07070100000047000081A400000000000000000000000163AC5026000013FC000000000000000000000000000000000000002500000000virtualbmc-3.0.1/virtualbmc/utils.py# 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 import libvirt from virtualbmc import exception class libvirt_open(object): def __init__(self, uri, sasl_username=None, sasl_password=None, readonly=False): self.uri = uri self.sasl_username = sasl_username self.sasl_password = sasl_password self.readonly = readonly def __enter__(self): try: if self.sasl_username and self.sasl_password: def request_cred(credentials, user_data): for credential in credentials: if credential[0] == libvirt.VIR_CRED_AUTHNAME: credential[4] = self.sasl_username elif credential[0] == libvirt.VIR_CRED_PASSPHRASE: credential[4] = self.sasl_password return 0 auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_PASSPHRASE], request_cred, None] flags = libvirt.VIR_CONNECT_RO if self.readonly else 0 self.conn = libvirt.openAuth(self.uri, auth, flags) elif self.readonly: self.conn = libvirt.openReadOnly(self.uri) else: self.conn = libvirt.open(self.uri) return self.conn except libvirt.libvirtError as e: raise exception.LibvirtConnectionOpenError(uri=self.uri, error=e) def __exit__(self, type, value, traceback): self.conn.close() def get_libvirt_domain(conn, domain): try: return conn.lookupByName(domain) except libvirt.libvirtError: raise exception.DomainNotFound(domain=domain) def check_libvirt_connection_and_domain(uri, domain, sasl_username=None, sasl_password=None): with libvirt_open(uri, readonly=True, sasl_username=sasl_username, sasl_password=sasl_password) as conn: get_libvirt_domain(conn, domain) def is_pid_running(pid): try: os.kill(pid, 0) return True except OSError: return False def str2bool(string): lower = string.lower() if lower not in ('true', 'false'): raise ValueError('Value "%s" can not be interpreted as ' 'boolean' % string) return lower == 'true' def mask_dict_password(dictionary, secret='***'): """Replace passwords with a secret in a dictionary.""" d = dictionary.copy() for k in d: if 'password' in k: d[k] = secret return d class detach_process(object): """Detach the process from its parent and session.""" def _fork(self, parent_exits): try: pid = os.fork() if pid > 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 07070100000048000081A400000000000000000000000163AC50260000233E000000000000000000000000000000000000002400000000virtualbmc-3.0.1/virtualbmc/vbmc.py# 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 07070100000049000041ED00000000000000000000000263AC502600000000000000000000000000000000000000000000001800000000virtualbmc-3.0.1/zuul.d0707010000004A000081A400000000000000000000000163AC502600000130000000000000000000000000000000000000002500000000virtualbmc-3.0.1/zuul.d/project.yaml- project: templates: - check-requirements - openstack-cover-jobs - openstack-python3-zed-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - virtualbmc-tempest-ironic gate: jobs: - virtualbmc-tempest-ironic 0707010000004B000081A400000000000000000000000163AC5026000002A5000000000000000000000000000000000000002D00000000virtualbmc-3.0.1/zuul.d/virtualbmc-jobs.yaml- 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 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!296 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor