././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8531115 networking-baremetal-6.4.0/0000775000175000017500000000000000000000000015665 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/.stestr.conf0000664000175000017500000000010100000000000020126 0ustar00zuulzuul00000000000000[DEFAULT] test_path=./networking_baremetal/tests/unit top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/AUTHORS0000664000175000017500000000313200000000000016734 0ustar00zuulzuul0000000000000098k <18552437190@163.com> Andreas Jaeger Boden R Charles Short Dmitry Tantsur Dmitry Tantsur Dongcan Ye Doug Hellmann Ghanshyam Mann Harald Jensas Harald Jensås Hervé Beraud Ian Wienand Iury Gregory Melo Ferreira Iury Gregory Melo Ferreira Jay Faulkner Julia Kreger Kaifeng Wang Le Hou Mark Goddard OpenStack Release Bot Pavlo Shchelokovskyy Pierre Riteau Riccardo Pittau Sam Betts Sean McGinnis Sharpz7 Sven Kieske Takashi Kajinami Tuan Do Anh Vasyl Saienko Vieri <15050873171@163.com> Vladyslav Drok Vu Cong Tuan YuehuiLei cid huang.zhiping inspurericzhang leiyashuai likui melissaml pengyuesheng wangfaxin wangjiaqi07 zhulingjie ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/CONTRIBUTING.rst0000664000175000017500000000123000000000000020322 0ustar00zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed in Launchpad, not GitHub: https://bugs.launchpad.net/networking-baremetal ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/ChangeLog0000664000175000017500000001533700000000000017450 0ustar00zuulzuul00000000000000CHANGES ======= 6.4.0 ----- * Update to match latest development cycle * Fix codespell reported errors * Remove call to enable\_python3\_package * reno: Update master for unmaintained/zed * Update master for stable/2024.1 * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria 6.3.0 ----- * don't force amqp\_auto\_delete for quorum queues * [codespell] Adding CI target for Tox Codespell * [codespell] Adding Tox Target for Codespell * [codespell] Fixing Spelling Mistakes * Bump hacking to 6.1.0 * reno: Update master for unmaintained/yoga * Remove deprecated pbr options * Do not try to bind port when we can't * Update master for stable/2023.2 6.2.0 ----- * Bugs are now in launchpad, doc fixes * Update to hacking v6 * Update master for stable/2023.1 * [CI] Explicitly disable port security 6.1.0 ----- * Fix tox4 errors * Fixes for tox 4.0 * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed 6.0.0 ----- * remove unicode from code * Doc - network device configuration capabilities * Add support for pre-configured link aggregates * Add LACP support to Netconf OpenConfig driver * Add netconf-openconfig device driver * Device management driver iface * OpenConfig YANG Model, python-bindings releasenote * Add OpenConfig classes for LACP * Add OpenConfig classes for interface aggregate * Add OpenConfig classes for network-instance * Add OpenConfig classes for switch vlans * Add OpenConfig classes for iface vlan plugging * The Python 3.6 and Python 3.7 Support has been dropped since zed * Remove babel.cfg * Replace deprecated UPPER\_CONSTRAINTS\_FILE variable * Drop lower-constraints.txt and its testing * Register neutron common config options * Set agent\_type in tests * Add Python3 zed unit tests * Update master for stable/yoga 5.1.0 ----- * Re-add python 3.6/3.7 in classifier * Updating yoga tested python versions in classifier * Add Python3 yoga unit tests * Update master for stable/xena 5.0.0 ----- * Add lower-constraints job to current development branch * Increase version of hacking and pycodestyle * Update min version of tox to use allowlist * setup.cfg: Replace dashes with underscores * Add Python3 xena unit tests * Update master for stable/wallaby 4.0.0 ----- * Update minversion of tox * Add doc/requirements * Fix exception handling when querying ironic ports * Remove lower-constraints job * Fix lower-constraints with the new pip resolver * Set safe version of hacking * Add Python3 wallaby unit tests * Update master for stable/victoria 3.0.0 ----- * Fix lower-constraints for networking-baremetal * Add missing keystoneauth1 and oslo.service to requirements * Set min version of tox to 3.2.1 * drop mock from lower-constraints * Use openstacksdk for ironic connection * Remove the unused coding style modules * Switch to newer openstackdocstheme and reno versions * Convert networking-baremetal job to dib * Bump hacking version to 3.0.0 and fix pep8 test * Update lower-constraints.txt * Add unit tests for \_get\_notification\_transport\_url() * Add py38 package metadata * Add Python3 victoria unit tests * Update master for stable/ussuri * Upgrade flake8-import-order version to 0.17.1 2.0.0 ----- * Stop configuring install\_command in tox * Remove the unused oslo.i18n bits * BUILD\_TIMEOUT is not needed * Use mock from unittest * Cleanup py27 support * Explicitly set ramdisk type * Enforce running tox with correct python version based on env * Stop using six library * Fix region option name in documentation * Drop python 2.7 support and testing * Add genconfig env to tox * fixed review link * Drop py2 job * Switch jobs to python3 * Switch to Ussuri jobs * Update neutron requirement * Add versions to release notes series * Update the constraints url * Fix unit tests with ironicclient >=3.0.0 * Update master for stable/train 1.4.0 ----- * Build pdf doc * Blacklist sphinx 2.1.0 (autodoc bug) * Fix networking-baremetal CI * Fix unit tests for networking-baremetal * Bump the openstackdocstheme extension to 1.20 * Update api-ref location * Update networking-baremetal installation * Update Python 3 test runtimes for Train * Update sphinx requirements * Use opendev repository * OpenDev Migration Patch * Replace openstack.org git:// URLs with https:// * Update master for stable/stein 1.3.0 ----- * Supporting all py3 environments with tox * Zuulv3 - Use ironic-base job * Rename agent queue - fixes broken minor update * Clean up oslo.messaging listener properly * Ensure notifications are consumed from non-pool queue * Set amqp\_auto\_delete=true for notifications transport * Docs: Devstack quickstart guides - change drivers * Break out ironic client from neutron agent * Change networking-baremetal to zuulv3/python3 * Correcting a typo in plugin.sh * Change openstack-dev to openstack-discuss * Restrict bashate to devstack/lib instead of lib * Change openstack-dev to openstack-discuss * Don't quote {posargs} in tox.ini * add python 3.6 unit test job * import zuul job settings from project-config * Changing CI job templates for python3-first * Update reno for stable/rocky 1.2.0 ----- * Remove testrepository and .testr.conf * Update neutron-lib requirement for rocky * Add release notes link in README * Updating required neutron version * Switch to using stestr * fix tox python3 overrides * Remove the duplicated "the" 1.1.0 ----- * fix tox python3 overrides * add lower-constraints job * Do not run functional (API) tests in the CI * ML2 Agent: Handle SIGHUP mutable config options * Change Launchpad references to Storyboard * Updated from global requirements * Avoid tox\_install.sh * use common agent topics from neutron-lib * Update reno for stable/queens 1.0.0 ----- * Add unit tests for member manager * Make the agent distributed using hashring and notifications * Fix devstack example * Node state configuration - add log\_agent\_heartbeat * Fix nits in networking-baremetal docs * Add dsvm job * Update docs and generate config file example * Add support to bind type vlan networks * Devstack - Add ironic-neutron-agent * Use reporting\_interval option from neutron * Switch from MechanismDriver to SimpleAgentMechanismDriverBase * Updated from global requirements * start\_flag = True, only first time, or conf change * Add baremetal neutron agent * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use constants from neutron-lib * Update URLs in documents according to document migration * Fix to use "." to source script files * Update reno for stable/pike 0.1.0 ----- * Add initial release note * Add installation documentation * Add devstack plugin to install networking\_baremetal * Add baremetal ML2 driver * Add .gitignore * Initial commit from cookiecutter * Added .gitreview ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/HACKING.rst0000664000175000017500000000025200000000000017462 0ustar00zuulzuul00000000000000networking-baremetal Style Commandments =============================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/LICENSE0000664000175000017500000002363700000000000016705 0ustar00zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/MANIFEST.in0000664000175000017500000000013600000000000017423 0ustar00zuulzuul00000000000000include AUTHORS include ChangeLog exclude .gitignore exclude .gitreview global-exclude *.pyc ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8531115 networking-baremetal-6.4.0/PKG-INFO0000664000175000017500000000331300000000000016762 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: networking-baremetal Version: 6.4.0 Summary: Neutron plugin that provides deep Ironic/Neutron integration. Home-page: https://docs.openstack.org/networking-baremetal/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: networking-baremetal plugin --------------------------- This project's goal is to provide deep integration between the Networking service and the Bare Metal service and advanced networking features like notifications of port status changes and routed networks support in clouds with Bare Metal service. * Free software: Apache license * Documentation: http://docs.openstack.org/networking-baremetal/latest * Source: http://opendev.org/openstack/networking-baremetal * Bugs: https://bugs.launchpad.net/networking-baremetal * Release notes: https://docs.openstack.org/releasenotes/networking-baremetal/ Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/README.rst0000664000175000017500000000113300000000000017352 0ustar00zuulzuul00000000000000networking-baremetal plugin --------------------------- This project's goal is to provide deep integration between the Networking service and the Bare Metal service and advanced networking features like notifications of port status changes and routed networks support in clouds with Bare Metal service. * Free software: Apache license * Documentation: http://docs.openstack.org/networking-baremetal/latest * Source: http://opendev.org/openstack/networking-baremetal * Bugs: https://bugs.launchpad.net/networking-baremetal * Release notes: https://docs.openstack.org/releasenotes/networking-baremetal/ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8291132 networking-baremetal-6.4.0/devstack/0000775000175000017500000000000000000000000017471 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8291132 networking-baremetal-6.4.0/devstack/lib/0000775000175000017500000000000000000000000020237 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/devstack/lib/networking-baremetal0000664000175000017500000000332700000000000024310 0ustar00zuulzuul00000000000000#!/bin/bash # # lib/networking-baremetal # # Functions to control the configuration and operation of the **Networking Baremetal** # Dependencies: # (none) # Save trace setting _XTRACE_NETWORKING_BAREMETAL=$(set +o | grep xtrace) set +o xtrace # Defaults # -------- # networking-baremetal service NETWORKING_BAREMETAL_REPO=${NETWORKING_BAREMETAL_REPO:-${GIT_BASE}/openstack/networking-baremetal.git} NETWORKING_BAREMETAL_BRANCH=${NETWORKING_BAREMETAL_BRANCH:-master} NETWORKING_BAREMETAL_DIR=${NETWORKING_BAREMETAL_DIR:-$DEST/networking-baremetal} NETWORKING_BAREMETAL_DATA_DIR=""$DATA_DIR/networking-baremetal"" # Support entry points installation of console scripts NETWORKING_BAREMETAL_BIN_DIR=$(get_python_exec_prefix) # Functions # --------- function install_networking_baremetal { setup_develop $NETWORKING_BAREMETAL_DIR } function configure_networking_baremetal { if [[ -z "$Q_ML2_PLUGIN_MECHANISM_DRIVERS" ]]; then Q_ML2_PLUGIN_MECHANISM_DRIVERS='baremetal' else if [[ ! $Q_ML2_PLUGIN_MECHANISM_DRIVERS =~ $(echo '\') ]]; then Q_ML2_PLUGIN_MECHANISM_DRIVERS+=',baremetal' fi fi populate_ml2_config /$Q_PLUGIN_CONF_FILE ml2 mechanism_drivers=$Q_ML2_PLUGIN_MECHANISM_DRIVERS } function configure_networking_baremetal_neutron_agent { configure_keystone_authtoken_middleware $NEUTRON_CONF ironic ironic configure_placement_nova_compute $NEUTRON_CONF } function start_networking_baremetal_neutron_agent { run_process ir-neutronagt "$NETWORKING_BAREMETAL_BIN_DIR/ironic-neutron-agent" } function stop_networking_baremetal_neutron_agent { stop_process ir-neutronagt } function cleanup_networking_baremetal { rm -rf $NETWORKING_BAREMETAL_DATA_DIR } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/devstack/plugin.sh0000664000175000017500000000233000000000000021321 0ustar00zuulzuul00000000000000#!/usr/bin/env bash # plugin.sh - DevStack plugin.sh dispatch script template echo_summary "networking-baremetal devstack plugin.sh called: $1/$2" source $DEST/networking-baremetal/devstack/lib/networking-baremetal # check for service enabled if is_service_enabled networking_baremetal; then if [[ "$1" == "stack" && "$2" == "install" ]]; then # Perform installation of service source echo_summary "Installing Networking Baremetal ML2" install_networking_baremetal elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then # Configure after the other layer 1 and 2 services have been configured echo_summary "Configuring Networking Baremetal Ml2" configure_networking_baremetal echo_summary "Configuring Networking Baremetal Neutron Agent" configure_networking_baremetal_neutron_agent echo_summary "Starting Networking Baremetal Neutron Agent" start_networking_baremetal_neutron_agent fi if [[ "$1" == "unstack" ]]; then echo_summary "Cleaning Networking Baremetal Ml2" cleanup_networking_baremetal echo_summary "Cleaning Networking Baremtal Neutron Agent" stop_networking_baremetal_neutron_agent fi fi ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/devstack/settings0000664000175000017500000000033300000000000021253 0ustar00zuulzuul00000000000000# settings file for networking_baremetal define_plugin networking-baremetal plugin_requires networking-baremetal ironic plugin_requires networking-baremetal networking-generic-switch enable_service networking_baremetal ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8291132 networking-baremetal-6.4.0/doc/0000775000175000017500000000000000000000000016432 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/requirements.txt0000664000175000017500000000017700000000000021723 0ustar00zuulzuul00000000000000reno>=3.1.0 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8291132 networking-baremetal-6.4.0/doc/source/0000775000175000017500000000000000000000000017732 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/conf.py0000775000175000017500000000771300000000000021244 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import os import sys # NOTE(amotoki): In case of oslo_config.sphinxext is enabled, # when resolving automodule neutron.tests.functional.db.test_migrations, # sphinx accesses tests/functional/__init__.py is processed, # eventlet.monkey_patch() is called and monkey_patch() tries to access # pyroute2.common.__class__ attribute. It raises pyroute2 warning and # it causes sphinx build failure due to warning-is-error = 1. # To pass sphinx build, ignore pyroute2 warning explicitly. logging.getLogger('pyroute2').setLevel(logging.ERROR) 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 = [ 'sphinxcontrib.apidoc', 'oslo_config.sphinxext', 'oslo_config.sphinxconfiggen', 'openstackdocstheme', ] # 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 = 'OpenStack Foundation' config_generator_config_file = [ ('../../tools/config/networking-baremetal-ironic-neutron-agent.conf', '_static/ironic_neutron_agent.ini'), ('../../tools/config/networking-baremetal-common-device-driver-opts.conf', '_static/common_device_driver_opts'), ('../../tools/config/networking-baremetal-netconf-openconfig-driver-opts.conf', '_static/netconf_openconfig_device_driver') ] # sample_config_basename = '_static/ironic_neutron_agent.ini' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['networking_baremetal.'] # 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' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # -- 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 = 'openstackdocs' # openstackdocstheme options openstackdocs_repo_name = 'openstack/networking-baremetal' openstackdocs_pdf_link = True openstackdocs_use_storyboard = False # Output file base name for HTML help builder. htmlhelp_basename = 'networking-baremetaldoc' latex_use_xindy = False # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'doc-networking-baremetal.tex', 'Networking Baremetal Documentation', 'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} # -- sphinxcontrib.apidoc configuration -------------------------------------- apidoc_module_dir = '../../networking_baremetal' apidoc_output_dir = 'contributor/api' apidoc_excluded_paths = [ 'tests', ] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8291132 networking-baremetal-6.4.0/doc/source/configuration/0000775000175000017500000000000000000000000022601 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/configuration/index.rst0000664000175000017500000000025000000000000024437 0ustar00zuulzuul00000000000000===================== Configuration Options ===================== .. toctree:: :maxdepth: 3 Ironic Neutron agent ML2 ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1724902937.833113 networking-baremetal-6.4.0/doc/source/configuration/ironic-neutron-agent/0000775000175000017500000000000000000000000026650 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/configuration/ironic-neutron-agent/config.rst0000664000175000017500000000060500000000000030650 0ustar00zuulzuul00000000000000============================================ ironic-neutron-agent - Configuration Options ============================================ The following is an overview of all available configuration options in networking-baremetal. For a sample configuration file, refer to :doc:`sample-config`. .. show-options:: :config-file: tools/config/networking-baremetal-ironic-neutron-agent.conf ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/configuration/ironic-neutron-agent/index.rst0000664000175000017500000000047500000000000030517 0ustar00zuulzuul00000000000000======================= Configuration Reference ======================= The following pages describe configuration options that can be used to adjust the ``ironic-neutron-agent`` service to your particular situation. .. toctree:: :maxdepth: 1 Configuration Options Sample Config File ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/configuration/ironic-neutron-agent/sample-config.rst0000664000175000017500000000123300000000000032125 0ustar00zuulzuul00000000000000========================= Sample Configuration File ========================= The following is a sample ironic-neutron-agent configuration for adaptation and use. For a detailed overview of all available configuration options, refer to :doc:`config`. The sample configuration can also be viewed in :download:`file form `. .. important:: The sample configuration file is auto-generated from networking-baremetal when this documentation is built. You must ensure your version of networking-baremetal matches the version of this documentation. .. literalinclude:: /_static/ironic_neutron_agent.ini.conf.sample././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1724902937.833113 networking-baremetal-6.4.0/doc/source/configuration/ml2/0000775000175000017500000000000000000000000023273 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1724902937.833113 networking-baremetal-6.4.0/doc/source/configuration/ml2/device_drivers/0000775000175000017500000000000000000000000026270 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/configuration/ml2/device_drivers/common_config.rst0000664000175000017500000000217500000000000031644 0ustar00zuulzuul00000000000000=================================================== Common configuration options for all device drivers =================================================== This page describes configuration options that is common to all networking- baremetal device drivers. Individual drivers may have independent configuration requirements depending on the implementation, refer to the device driver specific documentation. Configuration options ^^^^^^^^^^^^^^^^^^^^^ .. show-options:: :config-file: tools/config/networking-baremetal-common-device-driver-opts.conf Sample Configuration File ^^^^^^^^^^^^^^^^^^^^^^^^^ The following is a sample configuration section that would be added to ``/etc/neutron/plugins/ml2/ml2_conf.ini``. The sample configuration can also be viewed in :download:`file form `. .. important:: The sample configuration file is auto-generated from networking-baremetal when this documentation is built. You must ensure your version of networking-baremetal matches the version of this documentation. .. literalinclude:: /_static/common_device_driver_opts.conf.sample ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/configuration/ml2/device_drivers/index.rst0000664000175000017500000000127700000000000030140 0ustar00zuulzuul00000000000000============== Device drivers ============== The baremetal mechanism ML2 plug-in provides a device driver plug-in interface, this interface can be used to add device (switch) configuration capabilities. The interface uses `stevedore `__ for dynamic loading. Individual drivers may have independent configuration requirements depending on the implementation. :ref:`Driver specific options ` are documented separately. .. toctree:: :maxdepth: 2 Common configuration options .. _device_drivers: Available device drivers ~~~~~~~~~~~~~~~~~~~~~~~~ .. toctree:: :maxdepth: 3 netconf-openconfig ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/configuration/ml2/device_drivers/netconf-openconfig.rst0000664000175000017500000000356100000000000032610 0ustar00zuulzuul00000000000000Device driver - netconf-openconfig ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``netconf-openconfig`` device driver uses the Network Configuration Protocol (`NETCONF `__) and open source vendor-neutral `OpenConfig `__ YANG models. This driver has been tested with the following switch vendor/operating systems: * Cisco NXOS * Arista vEOS **Example configuration for Cisco NXOS device**: .. code-block:: ini [networking_baremetal] enabled_devices = nexus.example.net [nexus.example.net] driver = netconf-openconfig device_params = name:nexus switch_info = nexus switch_id = 00:53:00:0a:0a:0a host = nexus.example.net username = user key_filename = /etc/neutron/ssh_keys/nexus_sshkey **Example configuration for Arista EOS device**: .. code-block:: ini [networking_baremetal] enabled_devices = arista.example.net [arista.example.net] driver = netconf-openconfig device_params = name:default switch_info = arista switch_id = 00:53:00:0b:0b:0b host = arista.example.net username = user key_filename = /etc/neutron/ssh_keys/arista_sshkey Configuration options ^^^^^^^^^^^^^^^^^^^^^ .. show-options:: :config-file: tools/config/networking-baremetal-netconf-openconfig-driver-opts.conf Sample Configuration File ^^^^^^^^^^^^^^^^^^^^^^^^^ The following is a sample configuration section that would be added to ``/etc/neutron/plugins/ml2/ml2_conf.ini``. The sample configuration can also be viewed in :download:`file form `. .. important:: The sample configuration file is auto-generated from networking-baremetal when this documentation is built. You must ensure your version of networking-baremetal matches the version of this documentation. .. literalinclude:: /_static/netconf_openconfig_device_driver.conf.sample././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/configuration/ml2/index.rst0000664000175000017500000000207200000000000025135 0ustar00zuulzuul00000000000000======================= Configuration Reference ======================= The following pages describe configuration options that can be used to adjust the neutron ML2 configuration and the baremetal ML2 plug-in and device drivers to your particular situation. To enable mechanism drivers in the ML2 plug-in, edit the ``/etc/neutron/plugins/ml2/ml2_conf.ini`` configuration file. For example, this enables the ``openvswitch`` and ``baremetal`` mechanism drivers: .. code-block:: ini [ml2] mechanism_drivers = openvswitch,baremetal To add a device to manage, edit the ``/etc/neutron/plugins/ml2/ml2_conf.ini`` configuration file. The example below enables devices: ``device_a.example.net`` and ``device_b.example.net``. For each device a separate section in the same configuration file defines the device and driver specific configuration. Please refer to :doc:`device_drivers/index` for details. .. code-block:: ini [networking_baremetal] enabled_device = device_a.example.net,device_b.example.net .. toctree:: :maxdepth: 4 Device Drivers ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1724902937.833113 networking-baremetal-6.4.0/doc/source/contributor/0000775000175000017500000000000000000000000022304 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/contributor/index.rst0000664000175000017500000000436600000000000024156 0ustar00zuulzuul00000000000000============ Contributing ============ This document provides some necessary points for developers to consider when writing and reviewing networking-baremetal code. Getting Started =============== If you're completely new to OpenStack and want to contribute to the networking-baremetal project, please start by familiarizing yourself with the `Infra Team's Developer Guide `_. This will help you get your accounts set up in Launchpad and Gerrit, familiarize you with the workflow for the OpenStack continuous integration and testing systems, and help you with your first commit. LaunchPad Project ----------------- Most of the tools used for OpenStack require a launchpad.net ID for authentication. .. seealso:: * https://launchpad.net * https://launchpad.net/ironic Related Projects ---------------- Networking Baremetal is tightly integrated with the ironic and neutron projects. Ironic and its related projects are developed by the same community. .. seealso:: * https://launchpad.net/ironic * https://launchpad.net/neutron Project Hosting Details ----------------------- Bug tracker https://bugs.launchpad.net/networking-baremetal Mailing list (prefix Subject line with ``[ironic][networking-baremetal]``) http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss Code Hosting https://opendev.org/openstack/networking-baremetal Code Review https://review.opendev.org/#/q/status:open+project:openstack/networking-baremetal,n,z Developer quick-starts ====================== These are quick walk throughs to get you started developing code for networking-baremetal. These assume you are already familiar with submitting code reviews to an OpenStack project. .. toctree:: :maxdepth: 2 Deploying networking-baremetal with DevStack Deploying networking-baremetal and multi-tenant networking with DevStack Virtual lab with virtual switch and netconf-openconfig Device Driver Full networking-baremetal python API reference ============================================== * :ref:`modindex` .. # api/modules is hidden since it's in the modindex link above. .. toctree:: :hidden: api/modules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/contributor/quickstart-multitenant.rst0000664000175000017500000001126000000000000027572 0ustar00zuulzuul00000000000000Deploying networking-baremetal and multi-tenant networking with DevStack ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DevStack may be configured to deploy networking-baremetal Networking service plugin together with networking-generic-switch for multi-tenant networking. It is highly recommended to deploy on an expendable virtual machine and not on your personal work station. Deploying networking-baremetal with DevStack requires a machine running Ubuntu 14.04 (or later) or Fedora 20 (or later). .. seealso:: http://docs.openstack.org/devstack/latest Create ``devstack/local.conf`` with minimal settings required to enable networking-baremetal with ironic and networking-generic-switch for multi-tenant networking. Here is an example of local.conf:: [[local|localrc]] # Credentials ADMIN_PASSWORD=password DATABASE_PASSWORD=password RABBIT_PASSWORD=password SERVICE_PASSWORD=password SERVICE_TOKEN=password SWIFT_HASH=password SWIFT_TEMPURL_KEY=password # Install networking-generic-switch Neutron ML2 driver that interacts with OVS enable_plugin networking-generic-switch https://opendev.org/openstack/networking-generic-switch # Enable networking-baremetal plugin enable_plugin networking-baremetal https://opendev.org/openstack/networking-baremetal.git enable_service networking_baremetal enable_service ir-neutronagt # Add link local info when registering Ironic node IRONIC_USE_LINK_LOCAL=True IRONIC_ENABLED_NETWORK_INTERFACES=flat,neutron IRONIC_NETWORK_INTERFACE=neutron #Networking configuration OVS_PHYSICAL_BRIDGE=brbm PHYSICAL_NETWORK=mynetwork IRONIC_PROVISION_NETWORK_NAME=ironic-provision IRONIC_PROVISION_PROVIDER_NETWORK_TYPE=vlan IRONIC_PROVISION_SUBNET_PREFIX=10.0.5.0/24 IRONIC_PROVISION_SUBNET_GATEWAY=10.0.5.1 Q_PLUGIN=ml2 ENABLE_TENANT_VLANS=True Q_ML2_TENANT_NETWORK_TYPE=vlan TENANT_VLAN_RANGE=100:150 Q_USE_PROVIDERNET_FOR_PUBLIC=False # Enable segments service_plugin for routed networks Q_SERVICE_PLUGIN_CLASSES=neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,segments IRONIC_USE_NEUTRON_SEGMENTS=True # Configure ironic from ironic devstack plugin. enable_plugin ironic https://opendev.org/openstack/ironic # Enable Ironic API and Ironic Conductor enable_service ironic enable_service ir-api enable_service ir-cond # Enable Neutron which is required by Ironic and disable nova-network. disable_service n-net disable_service n-novnc enable_service q-svc enable_service q-agt enable_service q-dhcp enable_service q-l3 enable_service q-meta enable_service neutron # Enable Swift for agent_* drivers enable_service s-proxy enable_service s-object enable_service s-container enable_service s-account # Disable Horizon disable_service horizon # Disable Heat disable_service heat h-api h-api-cfn h-api-cw h-eng # Disable Cinder disable_service cinder c-sch c-api c-vol # Swift temp URL's are required for agent_* drivers. SWIFT_ENABLE_TEMPURLS=True # Create 3 virtual machines to pose as Ironic's baremetal nodes. IRONIC_VM_COUNT=3 IRONIC_BAREMETAL_BASIC_OPS=True DEFAULT_INSTANCE_TYPE=baremetal # Enable additional hardware types, if needed. #IRONIC_ENABLED_HARDWARE_TYPES=ipmi,fake-hardware # Don't forget that many hardware types require enabling of additional # interfaces, most often power and management: #IRONIC_ENABLED_MANAGEMENT_INTERFACES=ipmitool,fake #IRONIC_ENABLED_POWER_INTERFACES=ipmitool,fake # The 'ipmi' hardware type's default deploy interface is 'iscsi'. # This would change the default to 'direct': #IRONIC_DEFAULT_DEPLOY_INTERFACE=direct # Change this to alter the default driver for nodes created by devstack. # This driver should be in the enabled list above. IRONIC_DEPLOY_DRIVER=ipmi # The parameters below represent the minimum possible values to create # functional nodes. IRONIC_VM_SPECS_RAM=1024 IRONIC_VM_SPECS_DISK=10 # Size of the ephemeral partition in GB. Use 0 for no ephemeral partition. IRONIC_VM_EPHEMERAL_DISK=0 # To build your own IPA ramdisk from source, set this to True IRONIC_BUILD_DEPLOY_RAMDISK=False VIRT_DRIVER=ironic # By default, DevStack creates a 10.0.0.0/24 network for instances. # If this overlaps with the hosts network, you may adjust with the # following. NETWORK_GATEWAY=10.1.0.1 FIXED_RANGE=10.1.0.0/24 FIXED_NETWORK_SIZE=256 # Log all output to files LOGFILE=$HOME/devstack.log LOGDIR=$HOME/logs IRONIC_VM_LOG_DIR=$HOME/ironic-bm-logs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/contributor/quickstart-netconf-openconfig.rst0000664000175000017500000000047600000000000031016 0ustar00zuulzuul00000000000000Virtual lab with virtual switch and netconf-openconfig Device Driver #################################################################### Ansible playbooks that can be used to set up a lab for developing networking- baremetal network device integration is hosted on `GitHub `_. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/contributor/quickstart.rst0000664000175000017500000000673000000000000025236 0ustar00zuulzuul00000000000000Deploying networking-baremetal with DevStack ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DevStack may be configured to deploy networking-baremetal Networking service plugin. It is highly recommended to deploy on an expendable virtual machine and not on your personal work station. Deploying networking-baremetal with DevStack requires a machine running Ubuntu 14.04 (or later) or Fedora 20 (or later). .. seealso:: http://docs.openstack.org/devstack/latest Create ``devstack/local.conf`` with minimal settings required to enable networking-baremetal with ironic. Here is an example of local.conf:: cd devstack cat >local.conf <`_ Indices and tables ================== * :ref:`genindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1724902937.833113 networking-baremetal-6.4.0/doc/source/install/0000775000175000017500000000000000000000000021400 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/doc/source/install/index.rst0000664000175000017500000001065300000000000023246 0ustar00zuulzuul00000000000000============ Installation ============ This section describes how to install and configure the ``networking-baremetal`` plugin and ``ironic-neutron-agent``. The ``ironic-neutron-agent`` is a neutron agent that populates the host to physical network mapping for baremetal nodes in neutron. Neutron uses this to calculate the segment to host mapping information. Install the networking-baremetal plugin and agent ------------------------------------------------- At the command line: .. code-block:: shell $ pip install networking-baremetal Or, if you have neutron installed in a virtualenv, install the ``networking-baremetal`` plugin to the same virtualenv: .. code-block:: shell $ . /bin/activate $ pip install networking-baremetal Or, use the package from your distribution. For RHEL7/CentOS7: .. code-block:: shell $ yum install python2-networking-baremetal python2-ironic-neutron-agent Enable baremetal mechanism driver in the Networking service ----------------------------------------------------------- To enable mechanism drivers in the ML2 plug-in, edit the ``/etc/neutron/plugins/ml2/ml2_conf.ini`` configuration file. For example, this enables the ``openvswitch`` and ``baremetal`` mechanism drivers: .. code-block:: ini [ml2] mechanism_drivers = openvswitch,baremetal Add devices (switches) to manage -------------------------------- The baremetal mechanism ML2 plug-in provides a device driver plug-in interface. If a device driver for the switch model exist the baremetal ML2 plug-in can be configured to manage switch configuration, adding tenant VLANs and setting switch port VLAN configuration etc. To add a device to manage, edit the ``/etc/neutron/plugins/ml2/ml2_conf.ini`` configuration file. The example below enables devices: ``device_a.example.net`` and ``device_b.example.net``. Both devices in the example is using the ``netconf-openconfig`` device driver. For each device a separate section in configuration defines the device and driver specific configuration. .. code-block:: ini [networking_baremetal] enabled_devices = device_a.example.net,device_b.example.net [device_a.example.net] driver = netconf-openconfig switch_info = device_a switch_id = 00:53:00:0a:0a:0a host = device_a.example.net username = user key_filename = /etc/neutron/ssh_keys/device_a_sshkey hostkey_verify = false [device_b.example.net] driver = netconf-openconfig switch_info = device_b switch_id = 00:53:00:0b:0b:0b host = device_a.example.net username = user key_filename = /etc/neutron/ssh_keys/device_a_sshkey hostkey_verify = false Configure ironic-neutron-agent ------------------------------ To configure the baremetal neutron agent, edit the neutron configuration ``/etc/neutron/plugins/ml2/ironic_neutron_agent.ini`` file. Add an ``[ironic]`` section. For example: .. code-block:: ini [ironic] project_domain_name = Default project_name = service user_domain_name = Default password = password username = ironic auth_url = http://identity-server.example.com/identity auth_type = password os_region = RegionOne Start ironic-neutron-agent service ---------------------------------- To start the agent either run it from the command line like in the example below or add it to the init system. .. code-block:: shell $ ironic-neutron-agent \ --config-dir /etc/neutron \ --config-file /etc/neutron/plugins/ml2/ironic_neutron_agent.ini \ --log-file /var/log/neutron/ironic_neutron_agent.log You can create a systemd service file ``/etc/systemd/system/ironic-neutron-agent.service`` for ``ironic-neutron-agent`` for systemd based distributions. For example: .. code-block:: ini [Unit] Description=OpenStack Ironic Neutron Agent After=syslog.target network.target [Service] Type=simple User=neutron PermissionsStartOnly=true TimeoutStartSec=0 Restart=on-failure ExecStart=/usr/bin/ironic-neutron-agent --config-dir /etc/neutron --config-file /etc/neutron/plugins/ml2/ironic_neutron_agent.ini --log-file /var/log/neutron/ironic-neutron-agent.log PrivateTmp=true KillMode=process [Install] WantedBy=multi-user.target .. Note:: systemd service file may be already available if you are installing from package released by linux distributions. Enable and start the ``ironic-neutron-agent`` service: .. code-block:: shell $ sudo systemctl enable ironic-neutron-agent.service $ sudo systemctl start ironic-neutron-agent.service ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1724902937.833113 networking-baremetal-6.4.0/networking_baremetal/0000775000175000017500000000000000000000000022070 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/__init__.py0000664000175000017500000000124400000000000024202 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version __version__ = pbr.version.VersionInfo( 'networking_baremetal').version_string() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/_i18n.py0000664000175000017500000000140700000000000023362 0ustar00zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import oslo_i18n DOMAIN = "networking_baremetal" _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) # The primary translation function using the well-known name "_" _ = _translators.primary ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8371127 networking-baremetal-6.4.0/networking_baremetal/agent/0000775000175000017500000000000000000000000023166 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/agent/__init__.py0000664000175000017500000000000000000000000025265 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/agent/ironic_neutron_agent.py0000664000175000017500000002620100000000000027754 0ustar00zuulzuul00000000000000# Copyright 2017 Cisco Systems, 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 socket import sys from urllib import parse as urlparse import eventlet # oslo_messaging/notify/listener.py documents that monkeypatching is required eventlet.monkey_patch() from neutron.agent import rpc as agent_rpc from neutron.common import config as common_config from neutron.conf.agent import common as agent_config from neutron_lib.agent import topics from neutron_lib import constants as n_const from neutron_lib import context from openstack import exceptions as sdk_exc from oslo_config import cfg from oslo_log import log as logging import oslo_messaging from oslo_service import loopingcall from oslo_service import service from oslo_utils import timeutils from oslo_utils import uuidutils from tooz import hashring from networking_baremetal import constants from networking_baremetal import ironic_client CONF = cfg.CONF LOG = logging.getLogger(__name__) CONF.import_group('AGENT', 'neutron.plugins.ml2.drivers.agent.config') def list_opts(): return [('agent', agent_config.AGENT_STATE_OPTS)] def _get_notification_transport_url(): url = urlparse.urlparse(CONF.transport_url) if (CONF.oslo_messaging_rabbit.amqp_auto_delete is False and not getattr(CONF.oslo_messaging_rabbit, 'rabbit_quorum_queue', None)): q = urlparse.parse_qs(url.query) q.update({'amqp_auto_delete': ['true']}) query = urlparse.urlencode({k: v[0] for k, v in q.items()}) url = url._replace(query=query) return urlparse.urlunparse(url) def _set_up_notifier(transport, uuid): return oslo_messaging.Notifier( transport, publisher_id='ironic-neutron-agent-' + uuid, driver='messagingv2', topics=['ironic-neutron-agent-member-manager']) def _set_up_listener(transport, agent_id): targets = [ oslo_messaging.Target(topic='ironic-neutron-agent-member-manager')] endpoints = [HashRingMemberManagerNotificationEndpoint()] return oslo_messaging.get_notification_listener( transport, targets, endpoints, executor='eventlet', pool=agent_id) class HashRingMemberManagerNotificationEndpoint(object): """Class variables members and hashring is shared by all instances""" filter_rule = oslo_messaging.NotificationFilter( publisher_id='^ironic-neutron-agent.*') members = [] hashring = hashring.HashRing([]) def info(self, ctxt, publisher_id, event_type, payload, metadata): timestamp = timeutils.utcnow_ts() # Add members or update timestamp for existing members if not payload['id'] in [x['id'] for x in self.members]: try: LOG.info('Adding member id %s on host %s to hashring.', payload['id'], payload['host']) self.hashring.add_node(payload['id']) self.members.append(payload) except Exception: LOG.exception('Failed to add member %s to hash ring!', payload['id']) else: for member in self.members: if payload['id'] == member['id']: member['timestamp'] = payload['timestamp'] # Remove members that have not checked in for a while for member in self.members: if (timestamp - member['timestamp']) > ( CONF.AGENT.report_interval * 3): try: LOG.info('Removing member %s on host %s from hashring.', member['id'], member['host']) self.hashring.remove_node(member['id']) self.members.remove(member) except Exception: LOG.exception('Failed to remove member %s from hash ring!', member['id']) return oslo_messaging.NotificationResult.HANDLED class BaremetalNeutronAgent(service.ServiceBase): def __init__(self): self.context = context.get_admin_context_without_session() self.agent_id = uuidutils.generate_uuid(dashed=True) self.agent_host = socket.gethostname() # Set up oslo_messaging notifier and listener to keep track of other # members # NOTE(hjensas): Override the control_exchange for the notification # transport to allow setting amqp_auto_delete = true. # TODO(hjensas): Remove this and override the exchange when setting up # the notifier once the fix for bug is available. # https://bugs.launchpad.net/oslo.messaging/+bug/1814797 CONF.set_override('control_exchange', 'ironic-neutron-agent') self.transport = oslo_messaging.get_notification_transport( CONF, url=_get_notification_transport_url()) self.notifier = _set_up_notifier(self.transport, self.agent_id) # Note(hjensas): We need to have listener consuming the non-pool queue. # See bug: https://bugs.launchpad.net/oslo.messaging/+bug/1814544 self.listener = _set_up_listener(self.transport, None) self.pool_listener = _set_up_listener(self.transport, '-'.join( ['ironic-neutron-agent-member-manager-pool', self.agent_id])) self.member_manager = HashRingMemberManagerNotificationEndpoint() self.state_rpc = agent_rpc.PluginReportStateAPI(topics.REPORTS) self.ironic_client = ironic_client.get_client() self.reported_nodes = {} LOG.info('Agent networking-baremetal initialized.') def start(self): LOG.info('Starting agent networking-baremetal.') self.pool_listener.start() self.listener.start() self.notify_agents = loopingcall.FixedIntervalLoopingCall( self._notify_peer_agents) self.notify_agents.start(interval=(CONF.AGENT.report_interval / 3)) self.heartbeat = loopingcall.FixedIntervalLoopingCall( self._report_state) self.heartbeat.start(interval=CONF.AGENT.report_interval, initial_delay=CONF.AGENT.report_interval) def stop(self): LOG.info('Stopping agent networking-baremetal.') self.heartbeat.stop() self.notify_agents.stop() self.listener.stop() self.pool_listener.stop() self.listener.wait() self.pool_listener.wait() def reset(self): LOG.info('Resetting agent networking-baremetal.') self.heartbeat.stop() self.notify_agents.stop() self.listener.stop() self.pool_listener.stop() self.listener.wait() self.pool_listener.wait() def wait(self): pass def _notify_peer_agents(self): try: self.notifier.info({ 'ironic-neutron-agent': 'heartbeat'}, 'ironic-neutron-agent-member-manager', {'id': self.agent_id, 'host': self.agent_host, 'timestamp': timeutils.utcnow_ts()}) except Exception: LOG.exception('Failed to send hash ring membership heartbeat!') def get_template_node_state(self, node_uuid): return { 'binary': constants.BAREMETAL_BINARY, 'host': node_uuid, 'topic': n_const.L2_AGENT_TOPIC, 'configurations': { 'bridge_mappings': {}, 'log_agent_heartbeats': CONF.AGENT.log_agent_heartbeats, }, 'start_flag': False, 'agent_type': constants.BAREMETAL_AGENT_TYPE} def _report_state(self): node_states = {} ironic_ports = self.ironic_client.ports(details=True) # NOTE: the above calls returns a generator, so we need to handle # exceptions that happen just before the first loop iteration, when # the actual request to ironic happens try: for port in ironic_ports: node = port.node_id if (self.agent_id not in self.member_manager.hashring[node.encode('utf-8')]): continue template_node_state = self.get_template_node_state(node) node_states.setdefault(node, template_node_state) mapping = node_states[ node]["configurations"]["bridge_mappings"] if port.physical_network is not None: mapping[port.physical_network] = "yes" except sdk_exc.OpenStackCloudException: LOG.exception("Failed to get ironic ports data! " "Not reporting state.") return for state in node_states.values(): # If the node was not previously reported with current # configuration set the start_flag True. if not state['configurations'] == self.reported_nodes.get( state['host']): state.update({'start_flag': True}) LOG.info('Reporting state for host agent %s with new ' 'configuration: %s', state['host'], state['configurations']) try: LOG.debug('Reporting state for host: %s with configuration: ' '%s', state['host'], state['configurations']) self.state_rpc.report_state(self.context, state) except AttributeError: # This means the server does not support report_state LOG.exception("Neutron server does not support state report. " "State report for this agent will be disabled.") self.heartbeat.stop() # Don't continue reporting the remaining agents in this case. return except Exception: LOG.exception("Failed reporting state!") # Don't continue reporting the remaining nodes if one failed. return self.reported_nodes.update( {state['host']: state['configurations']}) def _unregiser_deprecated_opts(): CONF.reset() CONF.unregister_opts( [CONF._groups[ironic_client.IRONIC_GROUP]._opts[opt]['opt'] for opt in ironic_client._deprecated_opts], group=ironic_client.IRONIC_GROUP) def main(): common_config.register_common_config_options() # TODO(hjensas): Imports from neutron in ironic_neutron_agent registers the # client options. We need to unregister the options we are deprecating # first to avoid DuplicateOptError. Remove this when dropping deprecations. _unregiser_deprecated_opts() common_config.init(sys.argv[1:]) common_config.setup_logging() agent = BaremetalNeutronAgent() launcher = service.launch(cfg.CONF, agent, restart_method='mutate') launcher.wait() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/common.py0000664000175000017500000000417400000000000023740 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from xml.etree import ElementTree from oslo_config import cfg from oslo_log import log as logging import stevedore from networking_baremetal import constants from networking_baremetal import exceptions DRIVER_NAMESPACE = 'networking_baremetal.drivers' CONF = cfg.CONF LOG = logging.getLogger(__name__) def txt_subelement(parent, tag, text, *args, **kwargs): element = ElementTree.SubElement(parent, tag, *args, **kwargs) element.text = text return element def config_to_xml(config): element = ElementTree.Element(constants.CFG_ELEMENT) for conf in config: element.append(conf.to_xml_element()) return ElementTree.tostring(element).decode("utf-8") def driver_mgr(device_id): driver = CONF[device_id].driver try: mgr = stevedore.driver.DriverManager( namespace=DRIVER_NAMESPACE, name=driver, invoke_on_load=True, invoke_args=(device_id,), on_load_failure_callback=_load_failure_hook ) except stevedore.exception.NoUniqueMatch as exc: raise exceptions.DriverEntrypointLoadError( entry_point=f'{DRIVER_NAMESPACE}.{driver}', err=exc) return mgr.driver def _load_failure_hook(manager, entrypoint, exception): LOG.error("Driver manager %(manager)s failed to load device plugin " "%(entrypoint)s: %(exp)s", {'manager': manager, 'entrypoint': entrypoint, 'exp': exception}) raise exceptions.DriverEntrypointLoadError(entry_point=entrypoint, err=exception) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/config.py0000664000175000017500000000633400000000000023715 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import cfg from oslo_log import log as logging CONF = cfg.CONF LOG = logging.getLogger(__name__) _opts = [ cfg.ListOpt('enabled_devices', default=[], sample_default=['common-example', 'netconf-openconfig-example'], help=('Enabled devices for which the plugin should manage' 'configuration. Driver specific configuration for each ' 'device must be added in separate sections.')), ] _device_opts = [ cfg.StrOpt('driver', help='The driver to use when configuring the device'), cfg.StrOpt('switch_id', help='The switch ID, MAC address of the device.'), cfg.StrOpt('switch_info', help=('Optional string field to be used to store any ' 'vendor-specific information.')), cfg.ListOpt('physical_networks', default=[], help='A list of physical networks mapped to this device.'), cfg.BoolOpt('manage_vlans', default=True, help=('Set this to False for the device if VLANs should not ' 'be create and deleted on the device.')), ] networking_baremetal_group = cfg.OptGroup( name='networking_baremetal', title='ML2 networking-baremetal options') CONF.register_group(networking_baremetal_group) CONF.register_opts(_opts, group=networking_baremetal_group) for device in CONF.networking_baremetal.enabled_devices: group = cfg.OptGroup( name=device, title=f'{device} Device options') CONF.register_group(group) CONF.register_opts(_device_opts, group=group) def list_opts(): return [('networking_baremetal', _opts)] def list_common_device_driver_opts(): return [('networking_baremetal', _opts), ('common-example', _device_opts)] def get_devices(): """Get enabled network devices from configuration This is called during driver initialization, during initialization additional driver specific configuration is loaded and the drivers validation method is called. """ devices = dict() for dev in CONF.networking_baremetal.enabled_devices: if not CONF[dev].driver: LOG.error('IGNORING invalid device %s, driver not specified.', dev) if not CONF[dev].switch_id and not CONF[dev].switch_info: LOG.error('IGNORING invalid device %s, switch_id and/or ' 'switch_info is required', dev) if CONF[dev].switch_id: devices[CONF[dev].switch_id] = dev if CONF[dev].switch_info: devices[CONF[dev].switch_info] = dev return devices ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/constants.py0000664000175000017500000001156100000000000024462 0ustar00zuulzuul00000000000000# Copyright 2017 Cisco Systems, 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 enum BAREMETAL_AGENT_TYPE = "Baremetal Node" BAREMETAL_BINARY = 'ironic-neutron-agent' LOCAL_LINK_INFO = 'local_link_information' LOCAL_GROUP_INFO = 'local_group_information' IFACE_TYPE_ETHERNET = 'ethernet' IFACE_TYPE_AGGREGATE = 'aggregate' IFACE_TYPE_BASE = 'base' LAG_TYPE_LACP = 'LACP' LAG_TYPE_SATIC = 'SATIC' LACP_TIMEOUT_LONG = 'LONG' LACP_TIMEOUT_SHORT = 'SHORT' LACP_PERIOD_FAST = 'FAST' LACP_PERIOD_SLOW = 'SLOW' LACP_ACTIVITY_ACTIVE = 'ACTIVE' LACP_ACTIVITY_PASSIVE = 'PASSIVE' LACP_MIN_LINKS = 'bond_min_links' LACP_INTERVAL = 'bond_lacp_rate' # These bond modes require switch configuration the plugin cannot create. PRE_CONF_ONLY_BOND_MODES = {'balance-rr', '0', 'balance-xor', '2', 'broadcast', '3'} LACP_BOND_MODES = {'802.3ad', '4'} NON_SWITCH_BOND_MODES = {'active-backup', '1', 'balance-tlb', '5', 'balance-alb', '6'} VLAN_ACTIVE = 'ACTIVE' VLAN_SUSPENDED = 'SUSPENDED' VLAN_MODE_TRUNK = 'TRUNK' VLAN_MODE_ACCESS = 'ACCESS' VLAN_RANGE = range(1, 4094) PORT_ID = 'port_id' SWITCH_ID = 'switch_id' SWITCH_INFO = 'switch_info' class NetconfEditConfigOperation(enum.Enum): """RFC 6241 - operation attribute The "operation" attribute has one of the following values: merge: The configuration data identified by the element containing this attribute is merged with the configuration at the corresponding level in the configuration datastore identified by the parameter. This is the default behavior. replace: The configuration data identified by the element containing this attribute replaces any related configuration in the configuration datastore identified by the parameter. If no such configuration data exists in the configuration datastore, it is created. Unlike a operation, which replaces the entire target configuration, only the configuration actually present in the parameter is affected. create: The configuration data identified by the element containing this attribute is added to the configuration if and only if the configuration data does not already exist in the configuration datastore. If the configuration data exists, an element is returned with an value of "data-exists". delete: The configuration data identified by the element containing this attribute is deleted from the configuration if and only if the configuration data currently exists in the configuration datastore. If the configuration data does not exist, an element is returned with an value of "data-missing". remove: The configuration data identified by the element containing this attribute is deleted from the configuration if the configuration data currently exists in the configuration datastore. If the configuration data does not exist, the "remove" operation is silently ignored by the server. """ MERGE = 'merge' REPLACE = 'replace' CREATE = 'create' DELETE = 'delete' REMOVE = 'remove' CFG_ELEMENT = 'config' IANA_NETCONF_CAPABILITIES = { # [RFC4741][RFC6241] ':base:1.0': 'urn:ietf:params:netconf:base:1.0', # [RFC4741] ':confirmed-commit': 'urn:ietf:params:netconf:capability:confirmed-commit:1.0', ':validate': 'urn:ietf:params:netconf:capability:validate:1.0', # [RFC6241] ':base:1.1': 'urn:ietf:params:netconf:base:1.1', ':writable-running': 'urn:ietf:params:netconf:capability:writable-running:1.0', ':candidate': 'urn:ietf:params:netconf:capability:candidate:1.0', ':confirmed-commit:1.1': 'urn:ietf:params:netconf:capability:confirmed-commit:1.1', ':rollback-on-error': 'urn:ietf:params:netconf:capability:rollback-on-error:1.0', ':validate:1.1': 'urn:ietf:params:netconf:capability:validate:1.1', ':startup': 'urn:ietf:params:netconf:capability:startup:1.0', } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8371127 networking-baremetal-6.4.0/networking_baremetal/drivers/0000775000175000017500000000000000000000000023546 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/drivers/__init__.py0000664000175000017500000000000000000000000025645 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/drivers/base.py0000664000175000017500000000677500000000000025051 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc class BaseDeviceClient(object, metaclass=abc.ABCMeta): def __init__(self, device): self.device = device def get_client_args(self): """Get client connection arguments from configuration""" def get(self, **kwargs): """Get current configuration/state from device""" def edit_config(self, config): """Edit configuration on the device :param config: The configuration to apply to the device """ class BaseDeviceDriver(object, metaclass=abc.ABCMeta): SUPPORTED_BOND_MODES = set() def __init__(self, device): self.client = BaseDeviceClient(device) self.device = device def load_config(self): """Register driver specific configuration All drivers should register driver specific options in the device specific config group. This method will be called during mechanism driver initialization. """ def validate(self): """Driver validation This method will be called during mechanism driver initialization. Raising any exception other than DriverValidationError will cause service initialization failure. :raises DriverValidationError: On validation failure. """ def create_network(self, context): """Create network on device :param context: NetworkContext instance describing the new network. """ def update_network(self, context): """Update network on device :param context: NetworkContext instance describing the new network. """ def delete_network(self, context): """Delete network on device :param context: NetworkContext instance describing the new network. """ def create_port(self, context, segment, links): """Create/Configure port on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param segment: segment dictionary describing segment to bind :param links: Local link information filtered for the device. """ def update_port(self, context, links): """Update port on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param links: Local link information filtered for the device. """ def delete_port(self, context, links, current=True): """Delete/Un-configure port on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param links: Local link information filtered for the device. :param current: Boolean, when true use context.current, when false use context.original """ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8371127 networking-baremetal-6.4.0/networking_baremetal/drivers/netconf/0000775000175000017500000000000000000000000025202 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/drivers/netconf/openconfig.py0000664000175000017500000012056000000000000027707 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import random import re from urllib.parse import parse_qs as urlparse_qs from urllib.parse import urlparse import uuid from xml.etree import ElementTree from ncclient import manager from ncclient.operations.rpc import RPCError from ncclient.transport.errors import AuthenticationError from ncclient.transport.errors import SessionCloseError from ncclient.transport.errors import SSHError from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import provider_net from neutron_lib import constants as n_const from neutron_lib import exceptions as n_exec from neutron_lib.plugins.ml2 import api from oslo_config import cfg from oslo_log import log as logging import tenacity from networking_baremetal import common from networking_baremetal import config from networking_baremetal import constants from networking_baremetal.constants import NetconfEditConfigOperation as nc_op from networking_baremetal.drivers import base from networking_baremetal import exceptions from networking_baremetal.openconfig.interfaces import interfaces from networking_baremetal.openconfig.lacp import lacp from networking_baremetal.openconfig.network_instance import network_instance from networking_baremetal.openconfig.vlan import vlan CONF = cfg.CONF LOG = logging.getLogger(__name__) LOCK_DENIED_TAG = 'lock-denied' # [RFC 4741] CANDIDATE = 'candidate' RUNNING = 'running' DEFERRED = 'deferred' # Options for the device, maps to the local_link_information in the # port binding profile. _DEVICE_OPTS = [ cfg.StrOpt('network_instance', default='default', advanced=True, help=('The L2, L3, or L2+L3 forwarding instance to use when ' 'defining VLANs on the device.')), cfg.DictOpt('port_id_re_sub', default={}, sample_default={'pattern': 'Ethernet', 'repl': 'eth'}, help=('Regular expression pattern and replacement string. ' 'Some devices do not use the port description from ' 'LLDP in Netconf configuration. If the regular ' 'expression pattern and replacement string is set the ' 'port_id will be modified before passing configuration ' 'to the device.')), cfg.ListOpt('disabled_properties', item_type=cfg.types.String( choices=['port_mtu']), default=[], help=('A list of properties that should not be used, ' 'currently only "port_mtu" is valid')), cfg.BoolOpt('manage_lacp_aggregates', default=True, help=('When set to true the driver will manage LACP ' 'aggregates if link_group_information is defined in ' 'the binding:profile. When this is false the driver ' 'expect the link aggregation to be pre-configured on ' 'the device, and only perform vlan plugging.')), cfg.StrOpt('link_aggregate_prefix', default='Port-Channel', help=('The device specific prefix used for link-aggregation ' 'ports. Common values: "po", "port-channel" or ' '"Port-Channel".')), cfg.StrOpt('link_aggregate_range', default='1000..2000', help=('Range of link aggregation interface IDs that the driver ' 'can use when managing link aggregates.')), ] # Configuration option for Netconf client connection _NCCLIENT_OPTS = [ cfg.StrOpt('host', help=('The hostname or IP address to use for connecting to the ' 'netconf device.'), sample_default='device.example.com'), cfg.StrOpt('username', help='The username to use for SSH authentication.', sample_default='netconf'), cfg.IntOpt('port', default=830, help=('The port to use for connection to the netconf ' 'device.')), cfg.StrOpt('password', help=('The password used if using password authentication, or ' 'the passphrase to use for unlocking keys that require ' 'it. (To disable attempting key authentication ' 'altogether, set options *allow_agent* and ' '*look_for_keys* to `False`.'), sample_default='secret'), cfg.StrOpt('key_filename', help='Private key filename', default='~/.ssh/id_rsa'), cfg.BoolOpt('hostkey_verify', default=True, help=('Enables hostkey verification from ' '~/.ssh/known_hosts')), cfg.DictOpt('device_params', default={'name': 'default'}, help=('ncclient device handler parameters, see ncclient ' 'documentation for supported device handlers.')), cfg.BoolOpt('allow_agent', default=True, help='Enables querying SSH agent (if found) for keys.'), cfg.BoolOpt('look_for_keys', default=True, help=('Enables looking in the usual locations for ssh keys ' '(e.g. :file:`~/.ssh/id_*`)')), ] def list_driver_opts(): return [('networking_baremetal', config._opts), ('netconf-openconfig-example', config._device_opts + _DEVICE_OPTS + _NCCLIENT_OPTS)] class NetconfLockDenied(n_exec.NeutronException): message = ('Access to the requested lock is denied because the' 'lock is currently held by another entity.') class NetconfOpenConfigClient(base.BaseDeviceClient): def __init__(self, device): super().__init__(device) self.device = device self.capabilities = set() # Reduce the log level for ncclient, it is very chatty by default netconf_logger = logging.getLogger('ncclient') netconf_logger.setLevel(logging.WARNING) @staticmethod def _get_lock_session_id(err_info): """Parse lock-denied error [RFC6241] error-tag: lock-denied error-type: protocol error-severity: error error-info: : session ID of session holding the requested lock, or zero to indicate a non-NETCONF entity holds the lock Description: Access to the requested lock is denied because the lock is currently held by another entity. """ root = ElementTree.fromstring(err_info) session_id = root.find( "./{urn:ietf:params:xml:ns:netconf:base:1.0}session-id").text return session_id @staticmethod def process_capabilities(server_capabilities): capabilities = set() for capability in server_capabilities: for k, v in constants.IANA_NETCONF_CAPABILITIES.items(): if v in capability: capabilities.add(k) if capability.startswith('http://openconfig.net/yang'): openconfig_module = urlparse_qs( urlparse(capability).query).get('module').pop() capabilities.add(openconfig_module) return capabilities def get_capabilities(self): # https://github.com/ncclient/ncclient/issues/525 _ignore_close_issue_525 = False args = self.get_client_args() try: with manager.connect(**args) as nc_client: server_capabilities = nc_client.server_capabilities _ignore_close_issue_525 = True except SessionCloseError as e: if not _ignore_close_issue_525: raise e except (SSHError, AuthenticationError) as e: raise exceptions.DeviceConnectionError(device=self.device, err=e) return self.process_capabilities(server_capabilities) def get_client_args(self): """Get client connection arguments from configuration :param device: Device identifier """ args = dict( host=CONF[self.device].host, port=CONF[self.device].port, username=CONF[self.device].username, hostkey_verify=CONF[self.device].hostkey_verify, device_params=CONF[self.device].device_params, keepalive=True, allow_agent=CONF[self.device].allow_agent, look_for_keys=CONF[self.device].look_for_keys, ) if CONF[self.device].key_filename: args['key_filename'] = CONF[self.device].key_filename if CONF[self.device].password: args['password'] = CONF[self.device].password return args def get(self, **kwargs): """Get current configuration/staate from device""" # https://github.com/ncclient/ncclient/issues/525 _ignore_close_issue_525 = False query = kwargs.get('query') q_filter = ElementTree.tostring(query.to_xml_element()).decode('utf-8') try: with manager.connect(**self.get_client_args()) as client: reply = client.get(filter=('subtree', q_filter)) _ignore_close_issue_525 = True except SessionCloseError as e: # https://github.com/ncclient/ncclient/issues/525 if not _ignore_close_issue_525: raise e except RPCError as e: LOG.error('Netconf XML: %s', q_filter) raise e return reply.data_xml @tenacity.retry( reraise=True, retry=tenacity.retry_if_exception_type(NetconfLockDenied), wait=tenacity.wait_random_exponential(multiplier=1, min=2, max=10), stop=tenacity.stop_after_attempt(5)) def get_lock_and_configure(self, client, source, config, deferred_allocations): try: with client.locked(source): # Aggregate ID deferred until we have config lock # Get free aggregate ID by querying the device and update conf if deferred_allocations: aggregate_id = self.get_free_aggregate_id(client) self.allocate_deferred(aggregate_id, config) xml_config = common.config_to_xml(config) LOG.info( 'Sending configuration to Netconf device %(dev)s: ' '%(conf)s', {'dev': self.device, 'conf': xml_config}) if source == CANDIDATE: # Revert the candidate configuration to the current # running configuration. Any uncommitted changes are # discarded. client.discard_changes() # Edit the candidate configuration client.edit_config(target=source, config=xml_config) # Validate the candidate configuration if (':validate' in self.capabilities or ':validate:1.1' in self.capabilities): client.validate(source='candidate') # Commit the candidate config, 30 seconds timeout if (':confirmed-commit' in self.capabilities or ':confirmed-commit:1.1' in self.capabilities): client.commit(confirmed=True, timeout=str(30)) # Confirm the commit, if this commit does not # succeed the device will revert the config after # 30 seconds. client.commit() elif source == RUNNING: client.edit_config(target=source, config=xml_config) # TODO(hjensas): persist config. except RPCError as err: if err.tag == LOCK_DENIED_TAG: # If the candidate config is modified, some vendors do not # permit a new session to take a lock. This is per the RFC, # in this case a lock-denied error where session-id == 0 is # returned, because no session is actually holding the # lock we can discard changes which will release the lock. if (source == CANDIDATE and self._get_lock_session_id(err.info) == '0'): client.discard_changes() raise NetconfLockDenied() else: LOG.error('Netconf XML: %s', common.config_to_xml(config)) raise err def edit_config(self, config, deferred_allocations=False): """Edit configuration on the device :param config: Configuration, or list of configurations :param deferred_allocations: Used for link aggregates, the aggregate id cannot be allocated before device config is locked. When this is true an available aggregate id is identified by querying the device, and the configuration objects are updated accordingly before configuration is sent to the device. """ # https://github.com/ncclient/ncclient/issues/525 _ignore_close_issue_525 = False if not isinstance(config, list): config = [config] try: with manager.connect(**self.get_client_args()) as client: self.capabilities = self.process_capabilities( client.server_capabilities) if ':candidate' in self.capabilities: self.get_lock_and_configure( client, CANDIDATE, config, deferred_allocations) _ignore_close_issue_525 = True elif ':writable-running' in self.capabilities: self.get_lock_and_configure( client, RUNNING, config, deferred_allocations) _ignore_close_issue_525 = True except SessionCloseError as e: if not _ignore_close_issue_525: raise e def get_aggregation_ids(self): """Get aggregation IDs and aggregation prefix from config""" prefix = CONF[self.device].link_aggregate_prefix aggregate_id_range = CONF[self.device].link_aggregate_range.split('..') aggregate_ids = {f'{prefix}{x}' for x in range(int(aggregate_id_range[0]), int(aggregate_id_range[1]) + 1)} return aggregate_ids @staticmethod def allocate_deferred(aggregate_id, config): """Set aggregation id where it was deferred :param aggregate_id: Aggregation ID for the link aggregate, for example 'po123' :param config: Configuration objects to update """ for conf in config: if isinstance(conf, interfaces.Interfaces): for iface in conf: if isinstance(iface, interfaces.InterfaceAggregate): if iface.name == DEFERRED: iface.name = aggregate_id if iface.config.name == DEFERRED: iface.config.name = aggregate_id elif isinstance(iface, interfaces.InterfaceEthernet): if iface.ethernet.config.aggregate_id == DEFERRED: iface.ethernet.config.aggregate_id = aggregate_id if isinstance(conf, lacp.LACP): for lacp_iface in conf.interfaces.interfaces: if lacp_iface.name == DEFERRED: lacp_iface.name = aggregate_id def get_free_aggregate_id(self, client_locked): """Get free aggregate id by querying device config :param client_locked: Netconf client with active configuration lock """ aggregate_prefix = CONF[self.device].link_aggregate_prefix aggregate_ids = self.get_aggregation_ids() # Create a interfaces query oc_ifaces = interfaces.Interfaces() # Use empty string for the name, so the 'get' return all interfaces oc_iface = oc_ifaces.add('', interface_type=constants.IFACE_TYPE_BASE) # Don't need the config group del oc_iface.config # Get interfaces from device element = oc_ifaces.to_xml_element() device_interfaces = client_locked.get(filter=( 'subtree', ElementTree.tostring(element).decode("utf-8"))) # Find all interface names and filter on aggregate_prefix root = ElementTree.fromstring(device_interfaces.data_xml) used_aggregate_ids = { x.text for x in root.findall(f'.//{{{oc_ifaces.NAMESPACE}}}name') if x.text.startswith(aggregate_prefix)} # Get the difference, and make a random choice available_aggregate_ids = aggregate_ids.difference(used_aggregate_ids) return random.choice(list(available_aggregate_ids)) class NetconfOpenConfigDriver(base.BaseDeviceDriver): SUPPORTED_BOND_MODES = set().union(constants.NON_SWITCH_BOND_MODES, constants.LACP_BOND_MODES, constants.PRE_CONF_ONLY_BOND_MODES) def __init__(self, device): super().__init__(device) self.client = NetconfOpenConfigClient(device) self.device = device def validate(self): try: LOG.info('Device %(device)s was loaded. Device capabilities: ' '%(caps)s', {'device': self.device, 'caps': self.client.get_capabilities()}) except exceptions.DeviceConnectionError as e: raise exceptions.DriverValidationError(device=self.device, err=e) def load_config(self): """Register driver specific configuration""" CONF.register_opts(_DEVICE_OPTS, group=self.device) CONF.register_opts(_NCCLIENT_OPTS, group=self.device) def create_network(self, context): """Create network on device :param context: NetworkContext instance describing the new network. """ network = context.current segmentation_id = network[provider_net.SEGMENTATION_ID] net_instances = network_instance.NetworkInstances() net_instance = net_instances.add(CONF[self.device].network_instance) _vlan = net_instance.vlans.add(segmentation_id) # Devices has limitations for vlan names, use the hex variant of the # network UUID which is shorter. _vlan.config.name = self._uuid_as_hex(network[api.ID]) _vlan.config.status = constants.VLAN_ACTIVE self.client.edit_config(net_instances) def update_network(self, context): """Update network on device :param context: NetworkContext instance describing the new network. """ network = context.current network_orig = context.original segmentation_id = network[provider_net.SEGMENTATION_ID] segmentation_id_orig = network_orig[provider_net.SEGMENTATION_ID] admin_state = network['admin_state_up'] admin_state_orig = network_orig['admin_state_up'] add_net_instances = network_instance.NetworkInstances() add_net_instance = add_net_instances.add( CONF[self.device].network_instance) del_net_instances = None need_update = False if segmentation_id: _vlan = add_net_instance.vlans.add(segmentation_id) # Devices has limitations for vlan names, use the hex variant of # the network UUID which is shorter. _vlan.config.name = self._uuid_as_hex(network[api.ID]) if network['admin_state_up']: _vlan.config.status = constants.VLAN_ACTIVE else: _vlan.config.status = constants.VLAN_SUSPENDED if admin_state != admin_state_orig: need_update = True if segmentation_id_orig and segmentation_id != segmentation_id_orig: need_update = True del_net_instances = network_instance.NetworkInstances() del_net_instance = del_net_instances.add( CONF[self.device].network_instance) vlan_orig = del_net_instance.vlans.remove(segmentation_id_orig) # Not all devices support removing a VLAN, in that case lets # make sure the VLAN is suspended and set a name to indicate the # network was deleted. vlan_orig.config.name = f'neutron-DELETED-{segmentation_id_orig}' vlan_orig.config.status = constants.VLAN_SUSPENDED if not need_update: return # If the segmentation ID changed, delete the old VLAN first to avoid # vlan name conflict. if del_net_instances is not None: self.client.edit_config(del_net_instances) self.client.edit_config(add_net_instances) def delete_network(self, context): """Delete network on device :param context: NetworkContext instance describing the new network. """ network = context.current segmentation_id = network[provider_net.SEGMENTATION_ID] net_instances = network_instance.NetworkInstances() net_instance = net_instances.add(CONF[self.device].network_instance) _vlan = net_instance.vlans.remove(segmentation_id) # Not all devices support removing a VLAN, in that case lets # make sure the VLAN is suspended and set a name to indicate the # network was deleted. _vlan.config.name = f'neutron-DELETED-{segmentation_id}' _vlan.config.status = constants.VLAN_SUSPENDED self.client.edit_config(net_instances) def create_port(self, context, segment, links): """Create/Configure port on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param segment: segment dictionary describing segment to bind :param links: Local link information filtered for the device. """ port = context.current binding_profile = port[portbindings.PROFILE] local_group_information = binding_profile.get( constants.LOCAL_GROUP_INFO, {}) bond_mode = local_group_information.get('bond_mode') if segment[api.NETWORK_TYPE] != n_const.TYPE_VLAN: switched_vlan = None else: switched_vlan = vlan.VlanSwitchedVlan() switched_vlan.config.operation = nc_op.REPLACE switched_vlan.config.interface_mode = constants.VLAN_MODE_ACCESS switched_vlan.config.access_vlan = segment[api.SEGMENTATION_ID] if not bond_mode or bond_mode in constants.NON_SWITCH_BOND_MODES: self.create_non_bond(context, switched_vlan, links) elif bond_mode in constants.LACP_BOND_MODES: if CONF[self.device].manage_lacp_aggregates: self.create_lacp_aggregate(context, switched_vlan, links) else: self.create_pre_conf_aggregate(context, switched_vlan, links) elif bond_mode in constants.PRE_CONF_ONLY_BOND_MODES: self.create_pre_conf_aggregate(context, switched_vlan, links) def create_non_bond(self, context, switched_vlan, links): """Create/Configure ports on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param switched_vlan: switched_vlan OpenConfig object :param links: Local link information filtered for the device. """ port = context.current network = context.network.current ifaces = interfaces.Interfaces() for link in links: link_port_id = link.get(constants.PORT_ID) link_port_id = self._port_id_resub(link_port_id) iface = ifaces.add(link_port_id) iface.config.enabled = port['admin_state_up'] if 'port_mtu' not in CONF[self.device].disabled_properties: iface.config.mtu = network[api.MTU] iface.config.description = f'neutron-{port[api.ID]}' if switched_vlan is not None: iface.ethernet.switched_vlan = switched_vlan else: del iface.ethernet self.client.edit_config(ifaces) def create_lacp_aggregate(self, context, switched_vlan, links): """Create/Configure LACP aggregate on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param switched_vlan: switched_vlan OpenConfig object :param links: Local link information filtered for the device. """ port = context.current network = context.network.current binding_profile = port[portbindings.PROFILE] local_group_information = binding_profile.get( constants.LOCAL_GROUP_INFO, {}) dev_type = CONF[self.device].device_params.get('name') bond_properties = local_group_information.get('bond_properties', {}) lacp_interval = bond_properties.get(constants.LACP_INTERVAL) min_links = bond_properties.get(constants.LACP_MIN_LINKS) ifaces = interfaces.Interfaces() _lacp = lacp.LACP() lacp_iface = _lacp.interfaces.add(DEFERRED) lacp_iface.operation = nc_op.REPLACE lacp_iface.config.interval = (constants.LACP_PERIOD_FAST if lacp_interval in {'fast', 1, '1'} else constants.LACP_PERIOD_SLOW) # NX-API only allows configuring LACP interval rate on a port-channel # member which is not in shutdown state. Support would require a two # commit approach. if dev_type in {'nexus'}: LOG.warning('IGNORING LACP interval (bond_lacp_rate). The driver ' 'does not support LACP interval for this device type. ' 'Device: %(device)s, Port: %(port)s', {'device': self.device, 'port': port[api.ID]}) del lacp_iface.config.interval for link in links: link_port_id = link.get(constants.PORT_ID) link_port_id = self._port_id_resub(link_port_id) iface = ifaces.add( link_port_id, interface_type=constants.IFACE_TYPE_ETHERNET) iface.config.operation = nc_op.MERGE iface.config.enabled = port['admin_state_up'] if 'port_mtu' not in CONF[self.device].disabled_properties: iface.config.mtu = network[api.MTU] iface.config.description = f'neutron-{port[api.ID]}' iface.ethernet.config.aggregate_id = DEFERRED iface = ifaces.add(DEFERRED, interface_type=constants.IFACE_TYPE_AGGREGATE) iface.config.operation = nc_op.MERGE iface.config.name = DEFERRED iface.config.enabled = port['admin_state_up'] iface.config.description = f'neutron-{port[api.ID]}' iface.aggregation.config.lag_type = constants.LAG_TYPE_LACP if min_links: iface.aggregation.config.min_links = int(min_links) if switched_vlan is not None: iface.aggregation.switched_vlan = switched_vlan else: del iface.aggregation.switched_vlan self.client.edit_config([ifaces, _lacp], deferred_allocations=True) def create_pre_conf_aggregate(self, context, switched_vlan, links): """Create/Configure pre-configured aggregate on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param switched_vlan: switched_vlan OpenConfig object :param links: Local link information filtered for the device. """ port = context.current aggregate_ids = self.get_aggregate_ids(links) if not aggregate_ids: raise exceptions.PreConfiguredAggrergateNotFound( links=links, device=self.device) ifaces = interfaces.Interfaces() for aggregate_id in aggregate_ids: iface = ifaces.add(aggregate_id, interface_type=constants.IFACE_TYPE_AGGREGATE) iface.operation = nc_op.MERGE iface.config.enabled = port['admin_state_up'] if switched_vlan is not None: iface.aggregation.switched_vlan = switched_vlan else: del iface.aggregation.switched_vlan self.client.edit_config(ifaces) def update_port(self, context, links): """Update port on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param links: Local link information filtered for the device. """ if (not self.admin_state_changed(context) and not self.network_mtu_changed(context)): return port = context.current binding_profile = port[portbindings.PROFILE] local_group_information = binding_profile.get( constants.LOCAL_GROUP_INFO, {}) bond_mode = local_group_information.get('bond_mode') if not bond_mode or bond_mode in constants.NON_SWITCH_BOND_MODES: self.update_non_bond(context, links) elif bond_mode in constants.LACP_BOND_MODES: if CONF[self.device].manage_lacp_aggregates: self.update_lacp_aggregate(context, links) else: self.update_pre_conf_aggregate(context, links) elif bond_mode in constants.PRE_CONF_ONLY_BOND_MODES: self.update_pre_conf_aggregate(context, links) def update_non_bond(self, context, links): """Update port on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param links: Local link information filtered for the device. """ network = context.network.current ifaces = interfaces.Interfaces() port = context.current for link in links: link_port_id = link.get(constants.PORT_ID) link_port_id = self._port_id_resub(link_port_id) iface = ifaces.add(link_port_id) iface.config.enabled = port['admin_state_up'] if 'port_mtu' not in CONF[self.device].disabled_properties: iface.config.mtu = network[api.MTU] del iface.ethernet self.client.edit_config(ifaces) def update_lacp_aggregate(self, context, links): """Update LACP aggregate on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param links: Local link information filtered for the device. """ port = context.current network = context.network.current aggregate_ids = self.get_aggregate_ids(links) ifaces = interfaces.Interfaces() for link in links: link_port_id = link.get(constants.PORT_ID) link_port_id = self._port_id_resub(link_port_id) iface = ifaces.add(link_port_id, interface_type=constants.IFACE_TYPE_ETHERNET) iface.config.enabled = port['admin_state_up'] if 'port_mtu' not in CONF[self.device].disabled_properties: iface.config.mtu = network[api.MTU] del iface.ethernet for aggregate_id in aggregate_ids: iface = ifaces.add(aggregate_id, interface_type=constants.IFACE_TYPE_AGGREGATE) iface.operation = nc_op.MERGE iface.config.enabled = port['admin_state_up'] del iface.aggregation self.client.edit_config(ifaces) def update_pre_conf_aggregate(self, context, links): """Update pre-configured aggregate on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param links: Local link information filtered for the device. """ port = context.current aggregate_ids = self.get_aggregate_ids(links) if not aggregate_ids: raise exceptions.PreConfiguredAggrergateNotFound( links=links, device=self.device) ifaces = interfaces.Interfaces() for aggregate_id in aggregate_ids: iface = ifaces.add(aggregate_id, interface_type=constants.IFACE_TYPE_AGGREGATE) iface.operation = nc_op.MERGE iface.config.enabled = port['admin_state_up'] self.client.edit_config(ifaces) def delete_port(self, context, links, current=True): """Delete/Un-configure port on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param links: Local link information filtered for the device. :param current: Boolean, when true use context.current, when false use context.original """ port = context.current if current else context.original binding_profile = port[portbindings.PROFILE] local_group_information = binding_profile.get( constants.LOCAL_GROUP_INFO, {}) bond_mode = local_group_information.get('bond_mode') if not bond_mode or bond_mode in constants.NON_SWITCH_BOND_MODES: self.delete_non_bond(context, links) elif bond_mode in constants.LACP_BOND_MODES: if CONF[self.device].manage_lacp_aggregates: self.delete_lacp_aggregate(context, links) else: self.delete_pre_conf_aggregate(links) elif bond_mode in constants.PRE_CONF_ONLY_BOND_MODES: self.delete_pre_conf_aggregate(links) def delete_non_bond(self, context, links): """Delete/Un-configure port on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param links: Local link information filtered for the device. """ network = context.network.current ifaces = interfaces.Interfaces() for link in links: link_port_id = link.get(constants.PORT_ID) link_port_id = self._port_id_resub(link_port_id) iface = ifaces.add(link_port_id) iface.config.operation = nc_op.REMOVE # Not possible mark entire config for removal due to name leaf-ref # Set dummy values for properties to remove iface.config.description = '' iface.config.enabled = False if 'port_mtu' not in CONF[self.device].disabled_properties: iface.config.mtu = 0 if network[provider_net.NETWORK_TYPE] == n_const.TYPE_VLAN: iface.ethernet.switched_vlan.config.operation = nc_op.REMOVE else: del iface.ethernet self.client.edit_config(ifaces) def delete_lacp_aggregate(self, context, links): """Delete/Un-configure LACP aggregate on device :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param links: Local link information filtered for the device. """ network = context.network.current aggregate_ids = self.get_aggregate_ids(links) ifaces = interfaces.Interfaces() for link in links: link_port_id = link.get(constants.PORT_ID) link_port_id = self._port_id_resub(link_port_id) # Set up interface links for config remove iface = ifaces.add(link_port_id, interface_type=constants.IFACE_TYPE_ETHERNET) iface.config.operation = nc_op.REMOVE iface.config.description = '' iface.config.enabled = False if 'port_mtu' not in CONF[self.device].disabled_properties: iface.config.mtu = 0 iface.ethernet.config.operation = nc_op.REMOVE if network[provider_net.NETWORK_TYPE] == n_const.TYPE_VLAN: iface.ethernet.switched_vlan.config.operation = nc_op.REMOVE else: del iface.ethernet.switched_vlan # Set up lacp and aggregate interface for removal _lacp = lacp.LACP() for aggregate_id in aggregate_ids: # Remove LACP interface lacp_iface = _lacp.interfaces.add(aggregate_id) lacp_iface.operation = nc_op.REMOVE del lacp_iface.config # Remove Aggregate interface iface = ifaces.add(aggregate_id, interface_type=constants.IFACE_TYPE_AGGREGATE) iface.operation = nc_op.REMOVE del iface.config del iface.aggregation self.client.edit_config([_lacp, ifaces]) def delete_pre_conf_aggregate(self, links): """Delete/Un-configure pre-configured aggregate on device :param links: Local link information filtered for the device. """ aggregate_ids = self.get_aggregate_ids(links) if not aggregate_ids: raise exceptions.PreConfiguredAggrergateNotFound( links=links, device=self.device) ifaces = interfaces.Interfaces() for aggregate_id in aggregate_ids: iface = ifaces.add(aggregate_id, interface_type=constants.IFACE_TYPE_AGGREGATE) iface.config.enabled = False iface.aggregation.switched_vlan.config.operation = nc_op.REMOVE self.client.edit_config(ifaces) @staticmethod def _uuid_as_hex(_uuid): return uuid.UUID(_uuid).hex def _port_id_resub(self, link_port_id): """Replace pattern Regular expression pattern and replacement string. Some devices don not use the port description from LLDP in Netconf configuration. If the regular expression pattern and replacement string is set the port_id will be modified before passing configuration to the device. Replacing the leftmost non-overlapping occurrences of pattern in string by the replacement repl. """ if CONF[self.device].port_id_re_sub: pattern = CONF[self.device].port_id_re_sub.get('pattern') repl = CONF[self.device].port_id_re_sub.get('repl') link_port_id = re.sub(pattern, repl, link_port_id) return link_port_id def get_aggregate_ids(self, links): query = interfaces.Interfaces() for link in links: link_port_id = link.get(constants.PORT_ID) link_port_id = self._port_id_resub(link_port_id) # Set up query q_iface = query.add(link_port_id, interface_type=constants.IFACE_TYPE_ETHERNET) # Remove config and ethernet for broad filter. del q_iface.config del q_iface.ethernet # Get aggregate ids by querying the link interfaces xml_result = self.client.get(query=query) root = ElementTree.fromstring(xml_result) xpath_query_result = root.findall( './/{http://openconfig.net/yang/interfaces/aggregate}' 'aggregate-id') aggregate_ids = {x.text for x in xpath_query_result} return aggregate_ids @staticmethod def admin_state_changed(context): port = context.current port_orig = context.original return (port and port_orig and port['admin_state_up'] != port_orig['admin_state_up']) @staticmethod def network_mtu_changed(context): network = context.network.current network_orig = context.network.original return (network and network_orig and network[api.MTU] != network_orig[api.MTU]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/exceptions.py0000664000175000017500000000230000000000000024616 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib import exceptions as n_exc class DriverEntrypointLoadError(n_exc.NeutronException): message = 'Failed to load entrypoint %(entry_point)s: %(err)s' class DriverValidationError(n_exc.NeutronException): message = 'Failed driver validation for device %(device)s: %(err)s' class DeviceConnectionError(n_exc.NeutronException): message = 'Driver failed connecting to device %(device)s: %(err)s' class PreConfiguredAggrergateNotFound(n_exc.NeutronException): message = ('Driver could not find the aggregate ID for the pre-configured ' 'link aggregate for links %(links)s on device %(device)s.') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/ironic_client.py0000664000175000017500000000642300000000000025270 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneauth1 import loading import openstack from oslo_config import cfg from oslo_log import log as logging import tenacity CONF = cfg.CONF LOG = logging.getLogger(__name__) _IRONIC_SESSION = None IRONIC_GROUP = 'ironic' _deprecated_opts = {} _deprecated_opts['endpoint_override'] = [ cfg.DeprecatedOpt('ironic_url', group=IRONIC_GROUP)] _deprecated_opts['region_name'] = [ cfg.DeprecatedOpt('os_region', group=IRONIC_GROUP)] _deprecated_opts['status_code_retries'] = [ cfg.DeprecatedOpt('max_retries', group=IRONIC_GROUP)] _deprecated_opts['status_code_retry_delay'] = [ cfg.DeprecatedOpt('retry_interval', group=IRONIC_GROUP)] IRONIC_OPTS = [ cfg.StrOpt('auth_strategy', default='keystone', deprecated_for_removal=True, deprecated_reason='This option is no longer used, please use ' 'the [ironic]/auth_type option instead.', choices=('keystone', 'noauth'), help='Method to use for authentication: noauth or keystone.'), ] def list_opts(): return [ (IRONIC_GROUP, IRONIC_OPTS + loading.get_adapter_conf_options(deprecated_opts=_deprecated_opts) + loading.get_session_conf_options(deprecated_opts=_deprecated_opts) + loading.get_auth_plugin_conf_options('v3password'))] def get_session(group): loading.register_adapter_conf_options(CONF, group, deprecated_opts=_deprecated_opts) loading.register_session_conf_options(CONF, group, deprecated_opts=_deprecated_opts) loading.register_auth_conf_options(CONF, group) CONF.register_opts(IRONIC_OPTS, group=group) auth = loading.load_auth_from_conf_options(CONF, group) session = loading.load_session_from_conf_options(CONF, group, auth=auth) return session def _get_ironic_session(): global _IRONIC_SESSION if not _IRONIC_SESSION: _IRONIC_SESSION = get_session(IRONIC_GROUP) return _IRONIC_SESSION @tenacity.retry( retry=tenacity.retry_if_exception_type(openstack.exceptions.NotSupported), wait=tenacity.wait_exponential(max=30)) def get_client(): """Get an ironic client connection.""" session = _get_ironic_session() try: return openstack.connection.Connection( session=session, oslo_conf=CONF).baremetal except openstack.exceptions.NotSupported as exc: LOG.error('Ironic API might not be running, failed to establish a ' 'connection with ironic, reason: %s. Retrying ...', exc) raise except Exception as exc: LOG.error('Failed to establish a connection with ironic, reason: %s', exc) raise ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8371127 networking-baremetal-6.4.0/networking_baremetal/openconfig/0000775000175000017500000000000000000000000024217 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/__init__.py0000664000175000017500000000000000000000000026316 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8371127 networking-baremetal-6.4.0/networking_baremetal/openconfig/interfaces/0000775000175000017500000000000000000000000026342 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/interfaces/__init__.py0000664000175000017500000000000000000000000030441 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/interfaces/aggregate.py0000664000175000017500000001101200000000000030635 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from xml.etree import ElementTree from networking_baremetal import common from networking_baremetal import constants from networking_baremetal.openconfig.interfaces import types from networking_baremetal.openconfig.vlan import vlan class InterfacesAggregationConfig: NAMESPACE = 'http://openconfig.net/yang/interfaces/aggregate' PARENT = 'aggregation' TAG = 'config' def __init__(self, operation: str = constants.NetconfEditConfigOperation.MERGE): self.operation = operation self._lag_type = None self._min_links = None @property def operation(self): """RFC 6241 - operation attribute""" return self._operation.value if self._operation else None @operation.setter def operation(self, value): """RFC 6241 - operation attribute""" if isinstance(value, constants.NetconfEditConfigOperation): self._operation = value elif isinstance(value, str): self._operation = constants.NetconfEditConfigOperation(value) else: raise TypeError('Invalid type {} for config operation attribute.' .format(type(value))) @operation.deleter def operation(self): self._operation = None @property def lag_type(self): return self._lag_type.value if self._lag_type else None @lag_type.setter def lag_type(self, value: str): """the type of LAG, i.e., how it is configured / maintained""" self._lag_type = types.AggregationType(value) @lag_type.deleter def lag_type(self): self._lag_type = None @property def min_links(self): return self._min_links @min_links.setter def min_links(self, value: int): self._min_links = value @min_links.deleter def min_links(self): self._min_links = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) if self.operation: elem.set('operation', self.operation) if self.lag_type is not None: common.txt_subelement(elem, 'lag-type', self.lag_type) if self.min_links is not None: common.txt_subelement(elem, 'min-links', str(self.min_links)) return elem class InterfacesAggregation: """Options for logical interfaces representing aggregates""" NAMESPACE = 'http://openconfig.net/yang/interfaces/aggregate' PARENT = 'interface' TAG = 'aggregation' def __init__(self): self._switched_vlan = vlan.VlanSwitchedVlan() self._config = InterfacesAggregationConfig() @property def switched_vlan(self): return self._switched_vlan @switched_vlan.setter def switched_vlan(self, value): if not isinstance(value, vlan.VlanSwitchedVlan): raise TypeError('switched_vlan must be ' 'OpenConfigVlanSwitchedVlan, got {}' .format(type(value))) self._switched_vlan = value @switched_vlan.deleter def switched_vlan(self): self._switched_vlan = None @property def config(self): return self._config @config.setter def config(self, value): if not isinstance(value, InterfacesAggregationConfig): raise TypeError('config must be InterfacesAggregationConfig, got ' '{}'.format(type(value))) self._config = value @config.deleter def config(self): self._config = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) elem.set('xmlns', self.NAMESPACE) if self.config: elem.append(self.config.to_xml_element()) if self.switched_vlan: elem.append(self.switched_vlan.to_xml_element()) return elem ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/interfaces/ethernet.py0000664000175000017500000001066500000000000030542 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from xml.etree import ElementTree from networking_baremetal import common from networking_baremetal import constants from networking_baremetal.openconfig.vlan import vlan class InterfacesEthernetConfig: """OpenConfig interface ethernet configuration""" NAMESPACE = 'http://openconfig.net/yang/interfaces' PARENT = 'interface' TAG = 'config' def __init__(self, operation=constants.NetconfEditConfigOperation.MERGE): self.operation = operation self._aggregate_id = None self._aggregate_id_namespace = ( 'http://openconfig.net/yang/interfaces/aggregate') @property def operation(self): """RFC 6241 - operation attribute""" return self._operation.value if self._operation else None @operation.setter def operation(self, value): """RFC 6241 - operation attribute""" if isinstance(value, constants.NetconfEditConfigOperation): self._operation = value elif isinstance(value, str): self._operation = constants.NetconfEditConfigOperation(value) else: raise TypeError('Invalid type {} for config operation attribute.' .format(type(value))) @operation.deleter def operation(self): self._operation = None @property def aggregate_id(self): """Logical aggregate interface for interface""" return self._aggregate_id @aggregate_id.setter def aggregate_id(self, value: str): """Set logical aggregate interface for interface""" if not isinstance(value, str): raise TypeError('aggregate_id must be string, got {}' .format(type(value))) self._aggregate_id = value @aggregate_id.deleter def aggregate_id(self): self._aggregate_id = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ element = ElementTree.Element(self.TAG) if self.operation: element.set('operation', self.operation) if self.aggregate_id is not None: common.txt_subelement(element, 'aggregate-id', self.aggregate_id, xmlns=self._aggregate_id_namespace) return element class InterfacesEthernet: """Ethernet configuration and state""" NAMESPACE = 'http://openconfig.net/yang/interfaces/ethernet' PARENT = 'interface' TAG = 'ethernet' def __init__(self): self._switched_vlan = vlan.VlanSwitchedVlan() self._config = InterfacesEthernetConfig() @property def switched_vlan(self): return self._switched_vlan @switched_vlan.setter def switched_vlan(self, value): if not isinstance(value, vlan.VlanSwitchedVlan): raise TypeError('switched_vlan must be VlanSwitchedVlan, got {}' .format(type(value))) self._switched_vlan = value @switched_vlan.deleter def switched_vlan(self): self._switched_vlan = None @property def config(self): """Configuration parameters for interface""" return self._config @config.setter def config(self, value): if not isinstance(value, InterfacesEthernetConfig): raise TypeError('config must be InterfacesEthernetConfig, got {}' .format(type(value))) self._config = value @config.deleter def config(self): self._config = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) elem.set('xmlns', self.NAMESPACE) if self.config: elem.append(self.config.to_xml_element()) if self.switched_vlan: elem.append(self.switched_vlan.to_xml_element()) return elem ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/interfaces/interfaces.py0000664000175000017500000002616500000000000031051 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from collections import abc from typing import Optional from xml.etree import ElementTree from networking_baremetal import common from networking_baremetal import constants from networking_baremetal.openconfig.interfaces import aggregate from networking_baremetal.openconfig.interfaces import ethernet class InterfaceConfig: """OpenConfig interface configuration""" NAMESPACE = 'http://openconfig.net/yang/interfaces' PARENT = 'interface' TAG = 'config' def __init__(self, operation=constants.NetconfEditConfigOperation.MERGE, name: Optional[str] = None, description: Optional[str] = None, enabled: Optional[bool] = None, mtu: Optional[int] = None): self.operation = operation self._name = name self._description = None self._enabled = None self._mtu = None if description: self.description = description if enabled is not None: self.enabled = enabled if mtu: self.mtu = mtu @property def operation(self): """RFC 6241 - operation attribute""" return self._operation.value if self._operation else None @operation.setter def operation(self, value): """RFC 6241 - operation attribute""" if isinstance(value, constants.NetconfEditConfigOperation): self._operation = value elif isinstance(value, str): self._operation = constants.NetconfEditConfigOperation(value) else: raise TypeError('Invalid type {} for config operation attribute.' .format(type(value))) @operation.deleter def operation(self): """RFC 6241 - operation attribute""" self._operation = None @property def name(self): """The name of the interface.""" return self._name @name.setter def name(self, value: str): """The name of the interface.""" if not isinstance(value, str): raise TypeError('name must be string, got {}'.format(type(value))) self._name = value @name.deleter def name(self): """The name of the interface.""" self._name = None @property def description(self): """A textual description of the interface""" return self._description @description.setter def description(self, value: str): """A textual description of the interface""" if not isinstance(value, str): raise TypeError('description must be string, got {}' .format(type(value))) self._description = value @description.deleter def description(self): self._description = None @property def enabled(self): """The configured, desired state of the interface""" return self._enabled @enabled.setter def enabled(self, value: bool): """The configured, desired state of the interface""" if not isinstance(value, bool): raise TypeError('enabled must be boolean, got {}' .format(type(value))) self._enabled = value @enabled.deleter def enabled(self): self._enabled = None @property def mtu(self): """The max transmission unit size in octets""" return self._mtu @mtu.setter def mtu(self, value: int): """Set the max transmission unit size in octets""" if not isinstance(value, int): raise TypeError(f'mtu must be integer, got {type(value)}') self._mtu = value @mtu.deleter def mtu(self): self._mtu = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) if self.name is not None: common.txt_subelement(elem, 'name', self.name, attrib={'operation': self.operation}) if self.description is not None: common.txt_subelement(elem, 'description', self.description, attrib={'operation': self.operation}) if self.enabled is not None: common.txt_subelement(elem, 'enabled', str(self.enabled).lower(), attrib={'operation': self.operation}) if self.mtu is not None: common.txt_subelement(elem, 'mtu', str(self.mtu), attrib={'operation': self.operation}) return elem class BaseInterface: """Base interface""" NAMESPACE = 'http://openconfig.net/yang/interfaces' PARENT = 'interfaces' TAG = 'interface' def __init__(self, name: str): self.name = name self._config = InterfaceConfig() @property def name(self): """The name of the interface.""" return self._name @name.setter def name(self, value: str): """The name of the interface.""" if not isinstance(value, str): raise TypeError('name must be string, got {}'.format(type(value))) self._name = value @name.deleter def name(self): self._name = None @property def config(self): """Configuration parameters for interface""" return self._config @config.setter def config(self, value): if not isinstance(value, InterfaceConfig): raise TypeError('config must be InterfaceConfig, got {}' .format(type(value))) self._config = value @config.deleter def config(self): self._config = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) common.txt_subelement(elem, 'name', self.name) if self._config: elem.append(self.config.to_xml_element()) return elem class InterfaceEthernet(BaseInterface): def __init__(self, name: str): super(InterfaceEthernet, self).__init__(name) self._ethernet = ethernet.InterfacesEthernet() @property def ethernet(self): """Ethernet configuration and state""" return self._ethernet @ethernet.setter def ethernet(self, value): if not isinstance(value, ethernet.InterfacesEthernet): raise TypeError('ethernet must be InterfacesEthernet, got {}' .format(type(value))) self._ethernet = value @ethernet.deleter def ethernet(self): self._ethernet = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) common.txt_subelement(elem, 'name', self.name) if self.config: elem.append(self.config.to_xml_element()) if self.ethernet: elem.append(self.ethernet.to_xml_element()) return elem class InterfaceAggregate(BaseInterface): def __init__(self, name: str, operation: str = constants.NetconfEditConfigOperation.MERGE): super(InterfaceAggregate, self).__init__(name) self.operation = operation self._aggregation = aggregate.InterfacesAggregation() @property def operation(self): """RFC 6241 - operation attribute""" return self._operation.value if self._operation else None @operation.setter def operation(self, value): """RFC 6241 - operation attribute""" if isinstance(value, constants.NetconfEditConfigOperation): self._operation = value elif isinstance(value, str): self._operation = constants.NetconfEditConfigOperation(value) else: raise TypeError('Invalid type {} for config operation attribute.' .format(type(value))) @operation.deleter def operation(self): self._operation = None @property def aggregation(self): """Ethernet configuration and state""" return self._aggregation @aggregation.setter def aggregation(self, value): if not isinstance(value, aggregate.InterfacesAggregation): raise TypeError('ethernet must be OpenConfigInterfacesAggregation,' 'got {}'.format(type(value))) self._aggregation = value @aggregation.deleter def aggregation(self): self._aggregation = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) if self.operation: elem.set('operation', self.operation) common.txt_subelement(elem, 'name', self.name) if self.config: elem.append(self.config.to_xml_element()) if self.aggregation: elem.append(self.aggregation.to_xml_element()) return elem class Interfaces(abc.Collection): """Group/List of interfaces""" NAMESPACE = 'http://openconfig.net/yang/interfaces' TAG = 'interfaces' def __init__(self): # List of interfaces of type Interface self._interfaces = list() def __iter__(self): return iter(self._interfaces) def __len__(self): return len(self._interfaces) def __contains__(self, item): return item in self._interfaces @property def interfaces(self): """List of interfaces""" return self._interfaces def add(self, name: str, interface_type: str = constants.IFACE_TYPE_ETHERNET): """Add interface :param name: Interface name :type: str :param interface_type: Interface type ('ethernet', 'aggregate', 'base') :type: str """ if interface_type == constants.IFACE_TYPE_ETHERNET: interface = InterfaceEthernet(name) elif interface_type == constants.IFACE_TYPE_AGGREGATE: interface = InterfaceAggregate(name) elif interface_type == constants.IFACE_TYPE_BASE: interface = BaseInterface(name) else: raise ValueError('Invalid interface type {}'.format(type(name))) self._interfaces.append(interface) return interface def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ eleme = ElementTree.Element(self.TAG) eleme.set('xmlns', self.NAMESPACE) for interface in self.interfaces: eleme.append(interface.to_xml_element()) return eleme ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/interfaces/types.py0000664000175000017500000000144500000000000030064 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import enum from networking_baremetal import constants class AggregationType(enum.Enum): # LAG managed by LACP LACP = constants.LAG_TYPE_LACP # Statically configured bundle / LAG STATIC = constants.LAG_TYPE_SATIC ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8371127 networking-baremetal-6.4.0/networking_baremetal/openconfig/lacp/0000775000175000017500000000000000000000000025136 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/lacp/__init__.py0000664000175000017500000000000000000000000027235 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/lacp/lacp.py0000664000175000017500000002122700000000000026433 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from collections import abc from xml.etree import ElementTree from networking_baremetal import common from networking_baremetal import constants from networking_baremetal.openconfig.lacp import types class LACP: """LACP Top level LACP configuration and state variable containers """ NAMESPACE = 'http://openconfig.net/yang/lacp' TAG = 'lacp' def __init__(self): self._config = None self._interfaces = LACPInterfaces() @property def interfaces(self): return self._interfaces @interfaces.setter def interfaces(self, value): if not isinstance(value, LACPInterfaces): raise TypeError('interfaces must be OpenConfigLACPInterfaces,' 'got {}'.format(type(value))) self._interfaces = value @interfaces.deleter def interfaces(self): self._interfaces = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) elem.set('xmlns', self.NAMESPACE) if self.interfaces: elem.append(self.interfaces.to_xml_element()) return elem class LACPInterfaces(abc.Collection): """Top-level grouping for LACP-enabled interfaces""" NAMESPACE = 'http://openconfig.net/yang/lacp' PARENT = 'lacp' TAG = 'interfaces' def __init__(self): # List of interfaces of type OpenconfigInterface self._interfaces = list() def __iter__(self): return iter(self._interfaces) def __len__(self): return len(self._interfaces) def __contains__(self, item): return item in self._interfaces @property def interfaces(self): """List of interfaces""" return self._interfaces def add(self, name: str): """Add interface :param name: Interface name :type: str """ interface = LACPInterface(name) self._interfaces.append(interface) return interface def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) for interface in self.interfaces: elem.append(interface.to_xml_element()) return elem class LACPInterface: """Base LACP aggregate interface""" NAMESPACE = 'http://openconfig.net/yang/lacp' PARENT = 'interfaces' TAG = 'interface' def __init__(self, name: str, operation=constants.NetconfEditConfigOperation.MERGE): self.operation = operation self._config = LACPInterfaceConfig(name) self.name = name @property def operation(self): """RFC 6241 - operation attribute""" return self._operation.value @operation.setter def operation(self, value): """RFC 6241 - operation attribute""" if isinstance(value, constants.NetconfEditConfigOperation): self._operation = value elif isinstance(value, str): self._operation = constants.NetconfEditConfigOperation(value) else: raise TypeError('Invalid type {} for config operation attribute.' .format(type(value))) @property def name(self): """The name of the LACP aggregate interface.""" return self._name @name.setter def name(self, value: str): """The name of the LACP aggregate interface.""" if not isinstance(value, str): raise TypeError('name must be string, got {}'.format(type(value))) self._name = value # 'name' in the configuration is leaf-ref, should match. if self.config is not None: self.config.name = self.name @name.deleter def name(self): self._name = None @property def config(self): """Configuration data for each LACP aggregate interface""" return self._config @config.setter def config(self, value): if not isinstance(value, LACPInterfaceConfig): raise TypeError('config must be LACPInterfaceConfig,' 'got {}'.format(type(value))) self._config = value @config.deleter def config(self): self._config = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) elem.set('operation', self.operation) if self.name: common.txt_subelement(elem, 'name', self.name) if self.config: elem.append(self.config.to_xml_element()) return elem class LACPInterfaceConfig: """OpenConfig LACP aggregate interface configuration""" NAMESPACE = 'http://openconfig.net/yang/lacp' PARENT = 'interface' TAG = 'config' def __init__(self, name: str, operation=constants.NetconfEditConfigOperation.MERGE, interval=types.LACPPeriod.SLOW, lacp_mode=types.LACPActivity.ACTIVE): self._name = name self.operation = operation self.interval = interval self.lacp_mode = lacp_mode @property def operation(self): """RFC 6241 - operation attribute""" return self._operation.value @operation.setter def operation(self, value): """RFC 6241 - operation attribute""" if isinstance(value, constants.NetconfEditConfigOperation): self._operation = value elif isinstance(value, str): self._operation = constants.NetconfEditConfigOperation(value) else: raise TypeError('Invalid type {} for config operation attribute.' .format(type(value))) @property def name(self): """The name of the interface.""" return self._name @name.setter def name(self, value: str): if not isinstance(value, str): raise TypeError('name must be string, got {}'.format(type(value))) self._name = value @name.deleter def name(self): self._name = None @property def interval(self): """The period between LACP messages""" return self._interval.value if self._interval else None @interval.setter def interval(self, value: str): """Set the period between LACP messages (SLOW or FAST)""" if isinstance(value, types.LACPPeriod): self._interval = value elif isinstance(value, str): self._interval = types.LACPPeriod(value) else: raise TypeError('Invalid type {} for LACP interface interval.' .format(type(value))) @interval.deleter def interval(self): self._interval = None @property def lacp_mode(self): """The LACP mode if the aggregate interface""" return self._lacp_mode.value @lacp_mode.setter def lacp_mode(self, value: str): """Set the LACP mode if the aggregate interface ACTIVE: is to initiate the transmission of LACP packets. PASSIVE: is to wait for peer to initiate the transmission of LACP packets """ if isinstance(value, types.LACPActivity): self._lacp_mode = value elif isinstance(value, str): self._lacp_mode = types.LACPActivity(value) else: raise TypeError('Invalid type {} for LACP interface mode.' .format(type(value))) @lacp_mode.deleter def lacp_mode(self): self._lacp_mode = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) elem.set('operation', self.operation) if self.name is not None: common.txt_subelement(elem, 'name', self.name) if self.interval is not None: common.txt_subelement(elem, 'interval', self.interval) if self.lacp_mode is not None: common.txt_subelement(elem, 'lacp-mode', self.lacp_mode) return elem ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/lacp/types.py0000664000175000017500000000254500000000000026662 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import enum from networking_baremetal import constants class LACPPeriod(enum.Enum): """Defines the time between sending LACP messages reference "IEEE 802.3ad" FAST: Send LACP packets every second SLOW: Send LACP packets every 30 seconds """ FAST = constants.LACP_PERIOD_FAST SLOW = constants.LACP_PERIOD_SLOW class LACPActivity(enum.Enum): """Describes the LACP membership type Active or passive, of the interface in the aggregate. reference "IEEE 802.1AX-2008" ACTIVE: Interface is an active member, i.e., will detect and maintain aggregates PASSIVE: Interface is a passive member, i.e., it participates with an active partner """ ACTIVE = constants.LACP_ACTIVITY_ACTIVE PASSIVE = constants.LACP_ACTIVITY_PASSIVE ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8371127 networking-baremetal-6.4.0/networking_baremetal/openconfig/network_instance/0000775000175000017500000000000000000000000027574 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/network_instance/__init__.py0000664000175000017500000000000000000000000031673 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/network_instance/network_instance.py0000664000175000017500000000713300000000000033527 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from collections import abc from xml.etree import ElementTree from networking_baremetal import common from networking_baremetal.openconfig.vlan import vlan class NetworkInstances(abc.Collection): """Top-level grouping containing a list of network instances.""" NAMESPACE = 'http://openconfig.net/yang/network-instance' TAG = 'network-instances' def __init__(self): self._network_instances = list() def __iter__(self): return iter(self._network_instances) def __len__(self): return len(self._network_instances) def __contains__(self, item): return item in self._network_instances @property def network_instances(self): return self._network_instances def add(self, name: str): """Add network instance :param name: A unique name identifying the network instance :type: str :Keyword arguments: Network instance arguments """ network_instance = NetworkInstance(name) self._network_instances.append(network_instance) return network_instance def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) elem.set('xmlns', self.NAMESPACE) for instance in self.network_instances: elem.append(instance.to_xml_element()) return elem class NetworkInstance: """An OpenConfig description of a network_instance. This may be a Layer 3 forwarding construct such as a virtual routing and forwarding (VRF) instance, or a Layer 2 instance such as a virtual switch instance (VSI). Mixed Layer 2 and Layer 3 instances are also supported. """ NAMESPACE = 'http://openconfig.net/yang/network-instance' TAG = 'network-instance' def __init__(self, name): self.name = name self._vlans = vlan.Vlans() @property def name(self): """A unique name identifying the network instance""" return self._name @name.setter def name(self, value: str): """A unique name identifying the network instance""" if not isinstance(value, str): raise TypeError('name must be string, got {}'.format(type(value))) self._name = value @name.deleter def name(self): self._name = None @property def vlans(self): """Group/List of VLANs - keyed by id""" return self._vlans @vlans.setter def vlans(self, value): if not isinstance(value, vlan.Vlans): raise TypeError('vlans must be Vlans, got {}'.format(type(value))) self._vlans = value @vlans.deleter def vlans(self): self._vlans = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) if self.name: common.txt_subelement(elem, 'name', self.name) if self.vlans: elem.append(self.vlans.to_xml_element()) return elem ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/openconfig/vlan/0000775000175000017500000000000000000000000025157 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/vlan/__init__.py0000664000175000017500000000000000000000000027256 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/vlan/types.py0000664000175000017500000000533400000000000026702 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import enum import re from networking_baremetal import constants class VlanStatus(enum.Enum): """VLAN Admin state ACTIVE: VLAN is active SUSPENDED: VLAN is inactive / suspended """ ACTIVE = constants.VLAN_ACTIVE SUSPENDED = constants.VLAN_SUSPENDED class VlanInterfaceMode(enum.Enum): """VLAN interface mode (trunk or access)""" TRUNK = constants.VLAN_MODE_TRUNK ACCESS = constants.VLAN_MODE_ACCESS class VlanId: """Type definition representing a single-tagged VLAN""" def __init__(self, vlan_id: int): if not isinstance(vlan_id, int): raise TypeError('vlan_id must be integer, got {}' .format(type(vlan_id))) if vlan_id not in constants.VLAN_RANGE: raise ValueError('Invalid vlan id: {vlan_id} not in {range}' .format(vlan_id=vlan_id, range=constants.VLAN_RANGE)) self._vlan_id = vlan_id @property def vlan_id(self): return self._vlan_id class VlanRange: """Type definition representing a range of single-tagged VLANs. A range is specified as x..y where x and y are valid VLAN IDs (1 <= vlan-id <= 4094). The range is assumed to be inclusive, such that any VLAN-ID matching x <= VLAN-ID <= y falls within the range." """ # range specified as [lower]..[upper] pattern = re.compile( ('^(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' '[1-9][0-9]{1,2}|[1-9])\\.\\.(409[0-4]|' '40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{1,2}|' '[1-9])$')) def __init__(self, vlan_range: str): if not isinstance(vlan_range, str): raise TypeError('vlan_range must be string, got {}' .format(type(vlan_range))) if not self.pattern.match(vlan_range): raise ValueError('Invalid VLAN range {}'.format(vlan_range)) lower, _, upper = vlan_range.partition('..') if not int(lower) <= int(upper): raise ValueError('Invalid VLAN range {}'.format(vlan_range)) self._vlan_range = vlan_range @property def vlan_range(self): return self._vlan_range ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/openconfig/vlan/vlan.py0000664000175000017500000003260100000000000026473 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from collections import abc from typing import Optional from xml.etree import ElementTree from networking_baremetal import common from networking_baremetal import constants from networking_baremetal.openconfig.vlan import types class TrunkVlans(abc.Collection): def __init__(self): self._trunk_vlans = [] def __iter__(self): return iter(self._trunk_vlans) def __len__(self): return len(self._trunk_vlans) def __contains__(self, item): return item in self._trunk_vlans def add(self, value): """Add vlan or range of vlans (range: 100..200)""" try: value = int(value) if value not in self._trunk_vlans: self._trunk_vlans.append(types.VlanId(value).vlan_id) except ValueError: if value not in self._trunk_vlans: self._trunk_vlans.append(types.VlanRange(value).vlan_range) class VlanSwitchedConfig: """Ethernet interface VLAN config VLAN related configuration that is part of the physical Ethernet interface. """ NAMESPACE = 'http://openconfig.net/yang/vlan' PARENT = 'switched-vlan' TAG = 'config' def __init__(self, operation: str = constants.NetconfEditConfigOperation.MERGE, interface_mode: Optional[str] = None, native_vlan: Optional[int] = None, access_vlan: Optional[int] = None): self.operation = operation self._interface_mode = None self._native_vlan = None self._access_vlan = None self._trunk_vlans = TrunkVlans() if interface_mode: self.interface_mode = interface_mode if native_vlan: self.native_vlan = native_vlan if access_vlan: self.access_vlan = access_vlan @property def operation(self): """RFC 6241 - operation attribute""" return self._operation.value if self._operation else None @operation.setter def operation(self, value): """RFC 6241 - operation attribute""" if isinstance(value, constants.NetconfEditConfigOperation): self._operation = value elif isinstance(value, str): self._operation = constants.NetconfEditConfigOperation(value) else: raise TypeError('Invalid type {} for config operation attribute.' .format(type(value))) @operation.deleter def operation(self): self._operation = None @property def interface_mode(self): """Get the interface to access or trunk mode for VLANs""" return self._interface_mode.value if self._interface_mode else None @interface_mode.setter def interface_mode(self, value): """Set the interface to access or trunk mode for VLANs""" self._interface_mode = types.VlanInterfaceMode(value) @interface_mode.deleter def interface_mode(self): """Delete the interface to access or trunk mode for VLANs""" self._interface_mode = None @property def native_vlan(self): """Native VLAN is valid for trunk mode interfaces """ return self._native_vlan.vlan_id if self._native_vlan else None # TODO(hjensas): Only allow if interface_mode == trunk @native_vlan.setter def native_vlan(self, value: int): """Set native VLAN is valid for trunk mode interfaces """ self._native_vlan = types.VlanId(value) @native_vlan.deleter def native_vlan(self): """Delete native VLAN""" self._native_vlan = None # TODO(hjensas): Only allow if interface_mode == access @property def access_vlan(self): """Access VLAN assigned to the interfaces""" return self._access_vlan.vlan_id if self._access_vlan else None @access_vlan.setter def access_vlan(self, value: int): """Set access VLAN assigned to the interfaces""" self._access_vlan = types.VlanId(value) @access_vlan.deleter def access_vlan(self): """Unset access VLAN assigned to the interfaces""" self._access_vlan = None @property def trunk_vlans(self): """Allowed VLANs may be specified for trunk mode interfaces""" return self._trunk_vlans # TODO(hjensas): Only allow if interface_mode == trunk @trunk_vlans.setter def trunk_vlans(self, value: str): """Set allowed VLANs may be specified for trunk mode interfaces""" self._trunk_vlans.add(value) @trunk_vlans.deleter def trunk_vlans(self): self._trunk_vlans = TrunkVlans() def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) if self.operation: elem.set('operation', self.operation) if self.interface_mode: common.txt_subelement(elem, 'interface-mode', self.interface_mode) if self.access_vlan is not None: common.txt_subelement(elem, 'access-vlan', str(self.access_vlan)) if self.native_vlan is not None: common.txt_subelement(elem, 'native-vlan', str(self.native_vlan)) if self.trunk_vlans is not None: for item in self.trunk_vlans: common.txt_subelement(elem, 'trunk-vlans', str(item)) return elem class VlanSwitchedVlan: """VLAN interface-specific data on Ethernet interfaces. Enclosing container for VLAN interface-specific data on Ethernet interfaces. These are for standard L2, switched-style VLANs. """ NAMESPACE = 'http://openconfig.net/yang/vlan' PARENT = 'ethernet' TAG = 'switched-vlan' def __init__(self): self._config = VlanSwitchedConfig() @property def config(self): """Configuration parameters for VLANs""" return self._config @config.setter def config(self, value): if not isinstance(value, VlanSwitchedConfig): raise TypeError('config must be VlanSwitchedConfig, got {}' .format(type(value))) self._config = value @config.deleter def config(self): self._config = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) elem.set('xmlns', self.NAMESPACE) if self.config: elem.append(self.config.to_xml_element()) return elem class VlanConfig: """OpenConfig VLAN configuration""" NAMESPACE = 'http://openconfig.net/yang/vlan' PARENT = 'vlan' TAG = 'config' def __init__(self, operation=constants.NetconfEditConfigOperation.MERGE, vlan_id: int = None, name: str = None, status: str = None): self.operation = operation self._vlan_id = None self._name = None self._status = None if vlan_id: self.vlan_id = vlan_id if name: self.name = name if status: self.status = status @property def operation(self): """RFC 6241 - operation attribute""" return self._operation.value @operation.setter def operation(self, value): """RFC 6241 - operation attribute""" if isinstance(value, constants.NetconfEditConfigOperation): self._operation = value elif isinstance(value, str): self._operation = constants.NetconfEditConfigOperation(value) else: raise TypeError('Invalid type {} for config operation attribute.' .format(type(value))) @property def vlan_id(self): """The id of the VLAN""" return self._vlan_id.vlan_id if self._vlan_id else None @vlan_id.setter def vlan_id(self, value: int): self._vlan_id = types.VlanId(value) @vlan_id.deleter def vlan_id(self): self._vlan_id = None @property def name(self): """Interface VLAN name.""" return self._name @name.setter def name(self, value: str): if not isinstance(value, str): raise TypeError('name must be string, got {}' .format(type(value))) self._name = value @name.deleter def name(self): self._name = None @property def status(self): """Admin state of the VLAN""" return self._status.value if self._status else None @status.setter def status(self, value: str): """Admin state of the VLAN""" self._status = types.VlanStatus(value) @status.deleter def status(self): self._status = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) elem.set('operation', self.operation) if self.vlan_id is not None: common.txt_subelement(elem, 'vlan-id', str(self.vlan_id)) if self.name is not None: common.txt_subelement(elem, 'name', self.name) if self.status is not None: common.txt_subelement(elem, 'status', self.status) return elem class Vlan: """Base vlan""" NAMESPACE = 'http://openconfig.net/yang/vlan' PARENT = 'vlans' TAG = 'vlan' def __init__(self, vlan_id: int, operation=constants.NetconfEditConfigOperation.MERGE): self.operation = operation self.vlan_id = vlan_id self._config = VlanConfig(vlan_id=self.vlan_id) @property def operation(self): """RFC 6241 - operation attribute""" return self._operation.value @operation.setter def operation(self, value): """RFC 6241 - operation attribute""" if isinstance(value, constants.NetconfEditConfigOperation): self._operation = value elif isinstance(value, str): self._operation = constants.NetconfEditConfigOperation(value) else: raise TypeError('Invalid type {} for config operation ' 'attribute.'.format(type(value))) @property def vlan_id(self): """The id of the VLAN""" return self._vlan_id.vlan_id @vlan_id.setter def vlan_id(self, value: int): if not isinstance(value, int): raise TypeError(f'vlan_id must be integer, got {type(value)}') self._vlan_id = types.VlanId(value) @vlan_id.deleter def vlan_id(self): self._vlan_id = None @property def config(self): """Configuration parameters for VLAN""" return self._config @config.setter def config(self, value): """Configuration parameters for VLAN""" if not isinstance(value, VlanConfig): raise TypeError('config must be VlanConfig, got {}' .format(type(value))) self._config = value if self.vlan_id: self.config.vlan_id = self.vlan_id @config.deleter def config(self): self._config = None def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) if self.vlan_id: common.txt_subelement(elem, 'vlan-id', str(self.vlan_id), attrib={'operation': self.operation}) if self.config: elem.append(self.config.to_xml_element()) return elem class Vlans(abc.Collection): """Group/List of VLANs""" NAMESPACE = 'http://openconfig.net/yang/vlan' TAG = 'vlans' def __init__(self): # List of vlans of type OpenconfigVlan self._vlans = list() def __iter__(self): return iter(self._vlans) def __len__(self): return len(self._vlans) def __contains__(self, item): return item in self._vlans @property def vlans(self): """List of VLANs""" return self._vlans def add(self, vlan_id: int): """Add VLAN :param vlan_id: VLAN ID :type: int :Keyword arguments: VLAN configuration """ vlan = Vlan(vlan_id) self._vlans.append(vlan) return vlan def remove(self, vlan_id: int): """Remove VLAN :param vlan_id: VLAN ID :type: int """ vlan = Vlan(vlan_id) vlan.operation = constants.NetconfEditConfigOperation.REMOVE self._vlans.append(vlan) return vlan def to_xml_element(self): """Create XML Element :return: ElementTree Element with SubElements """ elem = ElementTree.Element(self.TAG) elem.set('xmlns', self.NAMESPACE) for vlan in self.vlans: elem.append(vlan.to_xml_element()) return elem ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/plugins/0000775000175000017500000000000000000000000023551 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/plugins/__init__.py0000664000175000017500000000000000000000000025650 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/plugins/ml2/0000775000175000017500000000000000000000000024243 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/plugins/ml2/__init__.py0000664000175000017500000000000000000000000026342 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/plugins/ml2/baremetal_mech.py0000664000175000017500000006614300000000000027557 0ustar00zuulzuul00000000000000# Copyright 2015 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.db import provisioning_blocks from neutron.plugins.ml2.drivers import mech_agent from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import provider_net from neutron_lib.callbacks import resources from neutron_lib import constants as n_const from neutron_lib.plugins.ml2 import api from oslo_config import cfg from oslo_log import log as logging from networking_baremetal import common from networking_baremetal import config from networking_baremetal import constants from networking_baremetal import exceptions CONF = cfg.CONF LOG = logging.getLogger(__name__) BAREMETAL_DRV_ENTITY = 'BAREMETAL_DRV_ENTITIY' class BaremetalMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): def __init__(self): super(BaremetalMechanismDriver, self).__init__( agent_type=constants.BAREMETAL_AGENT_TYPE, vif_type=portbindings.VIF_TYPE_OTHER, vif_details={ portbindings.VIF_DETAILS_CONNECTIVITY: self.connectivity}, supported_vnic_types=[portbindings.VNIC_BAREMETAL]) self.devices = config.get_devices() # Use set to remove duplicates, # i.e device has both switch_id and switch_info for device_id in set(self.devices.values()): device_driver = common.driver_mgr(device_id) device_driver.load_config() try: device_driver.validate() except exceptions.DriverValidationError as e: LOG.exception(e) @property def connectivity(self): return portbindings.CONNECTIVITY_L2 def get_allowed_network_types(self, agent): """Return the agent's or driver's allowed network types. For example: return ('flat', ...). You can also refer to the configuration the given agent exposes. """ return [n_const.TYPE_FLAT, n_const.TYPE_VLAN] def get_mappings(self, agent): """Return the agent's bridge or interface mappings. For example: agent['configurations'].get('bridge_mappings', {}). """ return agent['configurations'].get('bridge_mappings', {}) def create_network_precommit(self, context): """Allocate resources for a new network. Create a new network, allocating resources as necessary in the database. Called inside transaction context on session. Call cannot block. Raising an exception will result in a rollback of the current transaction. :param context: NetworkContext instance describing the new network. """ pass def create_network_postcommit(self, context): """Create a network. Called after the transaction commits. Call can block, though will block the entire process so care should be taken to not drastically affect performance. Raising an exception will cause the deletion of the resource. :param context: NetworkContext instance describing the new network. """ network = context.current network_type = network[provider_net.NETWORK_TYPE] segmentation_id = network[provider_net.SEGMENTATION_ID] physical_network = network[provider_net.PHYSICAL_NETWORK] # If not VLAN network, or no segmentation_id - nothing to do. if network_type != n_const.TYPE_VLAN or not segmentation_id: return # TODO(hjensas): This should be parallelized for device in CONF.networking_baremetal.enabled_devices: # VLAN management is disabled for this device if not CONF[device].manage_vlans: continue # Skip device if not on physical network if not self._is_device_on_physnet(device, physical_network): continue driver = common.driver_mgr(device) driver.create_network(context) def update_network_precommit(self, context): """Update resources of a network. Update values of a network, updating the associated resources in the database. Called inside transaction context on session. Raising an exception will result in rollback of the transaction. update_network_precommit is called for all changes to the network state. It is up to the mechanism driver to ignore state or state changes that it does not know or care about. :param context: NetworkContext instance describing the new state of the network, as well as the original state prior to the update_network call. """ pass def update_network_postcommit(self, context): """Update a network. Called after the transaction commits. Call can block, though will block the entire process so care should be taken to not drastically affect performance. Raising an exception will cause the deletion of the resource. update_network_postcommit is called for all changes to the network state. It is up to the mechanism driver to ignore state or state changes that it does not know or care about. :param context: NetworkContext instance describing the new state of the network, as well as the original state prior to the update_network call. """ network = context.current network_orig = context.original network_type = network[provider_net.NETWORK_TYPE] segmentation_id = network[provider_net.SEGMENTATION_ID] network_type_orig = network_orig[provider_net.NETWORK_TYPE] physical_network = network[provider_net.PHYSICAL_NETWORK] if (network_type != n_const.TYPE_VLAN and network_type_orig != n_const.TYPE_VLAN): return if not segmentation_id and not network_type_orig: return # TODO(hjensas): This should be parallelized for device in CONF.networking_baremetal.enabled_devices: # VLAN management is disabled for this device if not CONF[device].manage_vlans: continue # Skip device if not on physical network if not self._is_device_on_physnet(device, physical_network): continue driver = common.driver_mgr(device) driver.update_network(context) def delete_network_precommit(self, context): """Delete resources for a network. Delete network resources previously allocated by this mechanism driver for a network. Called inside transaction context on session. Runtime errors are not expected, but raising an exception will result in rollback of the transaction. :param context: NetworkContext instance describing the current state of the network, prior to the call to delete it. """ pass def delete_network_postcommit(self, context): """Delete a network. Called after the transaction commits. Call can block, though will block the entire process so care should be taken to not drastically affect performance. Runtime errors are not expected, and will not prevent the resource from being deleted. :param context: NetworkContext instance describing the current state of the network, prior to the call to delete it. """ network = context.current network_type = network[provider_net.NETWORK_TYPE] segmentation_id = network[provider_net.SEGMENTATION_ID] physical_network = network[provider_net.PHYSICAL_NETWORK] # If not VLAN network, or no segmentation_id - nothing to do. if network_type != n_const.TYPE_VLAN or not segmentation_id: return # TODO(hjensas): This should be parallelized for device in CONF.networking_baremetal.enabled_devices: # VLAN management is disabled for this device if not CONF[device].manage_vlans: continue # Skip device if not on physical network if not self._is_device_on_physnet(device, physical_network): continue driver = common.driver_mgr(device) driver.delete_network(context) def create_subnet_precommit(self, context): """Allocate resources for a new subnet. Create a new subnet, allocating resources as necessary in the database. Called inside transaction context on session. Call cannot block. Raising an exception will result in a rollback of the current transaction. :param context: SubnetContext instance describing the new subnet. """ pass def create_subnet_postcommit(self, context): """Create a subnet. Called after the transaction commits. Call can block, though will block the entire process so care should be taken to not drastically affect performance. Raising an exception will cause the deletion of the resource. :param context: SubnetContext instance describing the new subnet. """ pass def update_subnet_precommit(self, context): """Update resources of a subnet. Update values of a subnet, updating the associated resources in the database. Called inside transaction context on session. Raising an exception will result in rollback of the transaction. update_subnet_precommit is called for all changes to the subnet state. It is up to the mechanism driver to ignore state or state changes that it does not know or care about. :param context: SubnetContext instance describing the new state of the subnet, as well as the original state prior to the update_subnet call. """ pass def update_subnet_postcommit(self, context): """Update a subnet. Called after the transaction commits. Call can block, though will block the entire process so care should be taken to not drastically affect performance. Raising an exception will cause the deletion of the resource. update_subnet_postcommit is called for all changes to the subnet state. It is up to the mechanism driver to ignore state or state changes that it does not know or care about. :param context: SubnetContext instance describing the new state of the subnet, as well as the original state prior to the update_subnet call. """ pass def delete_subnet_precommit(self, context): """Delete resources for a subnet. Delete subnet resources previously allocated by this mechanism driver for a subnet. Called inside transaction context on session. Runtime errors are not expected, but raising an exception will result in rollback of the transaction. :param context: SubnetContext instance describing the current state of the subnet, prior to the call to delete it. """ pass def delete_subnet_postcommit(self, context): """Delete a subnet.a Called after the transaction commits. Call can block, though will block the entire process so care should be taken to not drastically affect performance. Runtime errors are not expected, and will not prevent the resource from being deleted. :param context: SubnetContext instance describing the current state of the subnet, prior to the call to delete it. """ pass def create_port_precommit(self, context): """Allocate resources for a new port. Create a new port, allocating resources as necessary in the database. Called inside transaction context on session. Call cannot block. Raising an exception will result in a rollback of the current transaction. :param context: PortContext instance describing the port. """ pass def create_port_postcommit(self, context): """Create a port. Called after the transaction completes. Call can block, though will block the entire process so care should be taken to not drastically affect performance. Raising an exception will result in the deletion of the resource. :param context: PortContext instance describing the port. """ pass def update_port_precommit(self, context): """Update resources of a port. Called inside transaction context on session to complete a port update as defined by this mechanism driver. Raising an exception will result in rollback of the transaction. update_port_precommit is called for all changes to the port state. It is up to the mechanism driver to ignore state or state changes that it does not know or care about. :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. """ pass def update_port_postcommit(self, context): """Update a port. Called after the transaction completes. Call can block, though will block the entire process so care should be taken to not drastically affect performance. Raising an exception will result in the deletion of the resource. update_port_postcommit is called for all changes to the port state. It is up to the mechanism driver to ignore state or state changes that it does not know or care about. :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. """ port = context.current port_orig = context.original if self._is_bound(port): if port_orig: self._update_port(context) provisioning_blocks.provisioning_complete( context._plugin_context, port['id'], resources.PORT, BAREMETAL_DRV_ENTITY) elif self._is_bound(port_orig): # The port has been unbound. This will cause the local link # information to be lost, so remove the port from the network on # the switch now while we have the required information. self._unplug_port(context, current=False) def delete_port_precommit(self, context): """Delete resources of a port. Called inside transaction context on session. Runtime errors are not expected, but raising an exception will result in rollback of the transaction. :param context: PortContext instance describing the current state of the port, prior to the call to delete it. """ pass def delete_port_postcommit(self, context): """Delete a port. state of the port, prior to the call to delete it. Called after the transaction completes. Call can block, though will block the entire process so care should be taken to not drastically affect performance. Runtime errors are not expected, and will not prevent the resource from being deleted. :param context: PortContext instance describing the current state of the port, prior to the call to delete it. """ self._unplug_port(context) def try_to_bind_segment_for_agent(self, context, segment, agent): """Try to bind with segment for agent. :param context: PortContext instance describing the port :param segment: segment dictionary describing segment to bind :param agent: agents_db entry describing agent to bind :returns: True iff segment has been bound for agent Neutron segments api-ref: https://docs.openstack.org/api-ref/network/v2/#segments Example segment dictionary: {'segmentation_id': 'segmentation_id', 'network_type': 'network_type', 'id': 'segment_uuid'} Called outside any transaction during bind_port() so that derived MechanismDrivers can use agent_db data along with built-in knowledge of the corresponding agent's capabilities to attempt to bind to the specified network segment for the agent. If the segment can be bound for the agent, this function must call context.set_binding() with appropriate values and then return True. Otherwise, it must return False. """ if not self.check_segment_for_agent(segment, agent): return False port = context.current binding_profile = port[portbindings.PROFILE] or {} local_link_information = binding_profile.get( constants.LOCAL_LINK_INFO, []) local_group_information = binding_profile.get( constants.LOCAL_GROUP_INFO, {}) bond_mode = local_group_information.get('bond_mode') by_device = {} for link in local_link_information: device = self._get_device(link) # If there is no device for all links the port will be bound, this # keeps backward compatibility. if not device: continue # Device was found, but no port_id in link - fail port binding if not link.get(constants.PORT_ID): LOG.warning('Cannot bind port %(port)s. no port_id in link ' 'information: %(link)s', {'port': port[api.ID], 'link': link}) return False # Check device on physnet, if not fail port binding if not self._is_device_on_physnet(device, segment[api.PHYSICAL_NETWORK]): LOG.warning( 'Cannot bind port %(port)s, device %(device)s is ' 'not on physical network %(physnet)s', {'port': port[api.ID], 'device': device, 'physnet': segment[api.PHYSICAL_NETWORK]}) return False by_device.setdefault(device, {}) by_device[device].setdefault('links', []) if 'driver' not in by_device[device]: # Load the driver, fail port binding on load error try: driver = common.driver_mgr(device) by_device[device]['driver'] = driver except exceptions.DriverEntrypointLoadError as e: LOG.warning('Cannot bind port %(port)s, failed to load ' 'driver for device %(device)s', {'link': link, 'port': port[api.ID], 'device': device}) LOG.debug(e.message) return False by_device[device]['links'].append(link) if not by_device: # NOTE(vsaienko): we can call set_binding ONLY when we complete # binding for the port in the segment. We do not handle the port # and want to let other drivers to bind it. return False # Check driver(s) support the bond_mode - if not fail port binding if (bond_mode and by_device and not self._is_bond_mode_supported(bond_mode, by_device)): LOG.warning('Cannot bind port %(port)s, unsupported ' 'bond_mode %(bond_mode)s', {'port': port[api.ID], 'bond_mode': bond_mode}) return False # Call each drivers create_port method to plug the device links for device, args in by_device.items(): driver = args['driver'] driver.create_port(context, segment, args['links']) # Complete the port binding provisioning_blocks.add_provisioning_component( context._plugin_context, port[api.ID], resources.PORT, BAREMETAL_DRV_ENTITY) context.set_binding(segment[api.ID], self.get_vif_type(context, agent, segment), self.get_vif_details(context, agent, segment)) return True def _is_bound(self, context): """Check if port is currently bound by this driver :param context: Port context :returns: True/False """ return (context[portbindings.VNIC_TYPE] in self.supported_vnic_types and context[portbindings.VIF_TYPE] == self.vif_type) @staticmethod def _is_device_on_physnet(device, physical_network): """Check if Device is connected to physical network If the device is not configured to any physical networks, return True so that all networks are created on the switch. :param device: Netconf device in config :param physical_network: Physical network :returns: True or False """ if (CONF[device].physical_networks and physical_network not in CONF[device].physical_networks): return False return True def _get_device(self, link): """Get device identifier from link information :param link: Link information :returns: Device identifier (switch_id or switch_info) """ device = None switch_id = link.get(constants.SWITCH_ID) switch_info = link.get(constants.SWITCH_INFO) if switch_id and switch_id in self.devices: device = self.devices[switch_id] elif switch_info and switch_info in self.devices: device = self.devices[switch_info] return device def _update_port(self, context): """Update port :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. """ port = context.current binding_profile = port[portbindings.PROFILE] local_link_information = binding_profile.get( constants.LOCAL_LINK_INFO, []) local_group_information = binding_profile.get( constants.LOCAL_GROUP_INFO, {}) bond_mode = local_group_information.get('bond_mode') by_device = {} for link in local_link_information: device = self._get_device(link) # No device == noop if not device: continue by_device.setdefault(device, {}) by_device[device].setdefault('links', []) if 'driver' not in by_device[device]: # Load the driver, if this fails the link cannot be updated try: driver = common.driver_mgr(device) by_device[device]['driver'] = driver except exceptions.DriverEntrypointLoadError as e: LOG.warning('Cannot update link %(link)s on port ' '%(port)s, failed to load driver for device ' '%(device)s', {'link': link, 'port': port[api.ID], 'device': device}) LOG.debug(e.message) continue by_device[device]['links'].append(link) # Check driver(s) support the bond_mode if (bond_mode and by_device and not self._is_bond_mode_supported(bond_mode, by_device)): LOG.error('Cannot update port %(port)s on device, unsupported ' 'bond_mode %(bond_mode)s', {'port': port[api.ID], 'bond_mode': bond_mode}) return # Call each drivers update_port method for device, args in by_device.items(): driver = args['driver'] driver.update_port(context, args['links']) def _unplug_port(self, context, current=True): """Unplug/Unbind/Delete port :param context: PortContext instance describing the new state of the port, as well as the original state prior to the update_port call. :param current: Boolean, when true use context.current, when false use context.original """ if current: port = context.current else: port = context.original binding_profile = port[portbindings.PROFILE] or {} local_link_information = binding_profile.get( constants.LOCAL_LINK_INFO, []) local_group_information = binding_profile.get( constants.LOCAL_GROUP_INFO, {}) bond_mode = local_group_information.get('bond_mode') by_device = {} for link in local_link_information: device = self._get_device(link) # No device == noop if not device: continue by_device.setdefault(device, {}) by_device[device].setdefault('links', []) if 'driver' not in by_device[device]: # Load the driver, if this fails the link cannot be unbound try: driver = common.driver_mgr(device) by_device[device]['driver'] = driver except exceptions.DriverEntrypointLoadError as e: LOG.warning('Cannot delete link %(link)s for port ' '%(port)s, failed to load driver for device ' '%(device)s', {'link': link, 'port': port[api.ID], 'device': device}) LOG.debug(e.message) continue by_device[device]['links'].append(link) # Check driver(s) support the bond_mode if (bond_mode and by_device and not self._is_bond_mode_supported(bond_mode, by_device)): LOG.warning('Cannot delete port %(port)s on device, unsupported ' 'bond_mode %(bond_mode)s', {'port': port[api.ID], 'bond_mode': bond_mode}) return # Call each drivers delete_port method to unplug the device links for device, args in by_device.items(): driver = args['driver'] driver.delete_port(context, args['links'], current=current) @staticmethod def _is_bond_mode_supported(bond_mode, by_device): """Check if drivers support the bond mode :param bond_mode: The bond mode :param by_device: Dictionary of driver and links per-device. """ for device, args in by_device.items(): driver = args['driver'] if bond_mode and bond_mode not in driver.SUPPORTED_BOND_MODES: return False return True ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/tests/0000775000175000017500000000000000000000000023232 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/__init__.py0000664000175000017500000000000000000000000025331 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/base.py0000664000175000017500000000143200000000000024516 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslotest import base class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/tests/unit/0000775000175000017500000000000000000000000024211 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/__init__.py0000664000175000017500000000000000000000000026310 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/tests/unit/drivers/0000775000175000017500000000000000000000000025667 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/drivers/__init__.py0000664000175000017500000000000000000000000027766 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/tests/unit/drivers/netconf/0000775000175000017500000000000000000000000027323 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/drivers/netconf/__init__.py0000664000175000017500000000000000000000000031422 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/drivers/netconf/test_openconfig.py0000664000175000017500000015773700000000000033107 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from xml.etree import ElementTree from ncclient import manager from neutron.plugins.ml2 import driver_context from neutron_lib import constants as n_const from neutron_lib.plugins.ml2 import api from oslo_config import fixture as config_fixture from oslo_utils import uuidutils from networking_baremetal import config from networking_baremetal import constants from networking_baremetal.constants import NetconfEditConfigOperation as nc_op from networking_baremetal.drivers.netconf import openconfig from networking_baremetal.openconfig.interfaces import interfaces from networking_baremetal.openconfig.lacp import lacp from networking_baremetal.tests import base from networking_baremetal.tests.unit.plugins.ml2 import utils as ml2_utils OC_IF_NS = 'http://openconfig.net/yang/interfaces' OC_IF_ETH_NS = 'http://openconfig.net/yang/interfaces/ethernet' OC_IF_AGG_NS = 'http://openconfig.net/yang/interfaces/aggregate' XML_IFACES_AGGREDATE_ID = f''' foo1/1 Po10 foo1/2 Po10 ''' XML_AGGREGATE_IFACES = f''' Po5 Po7 Po9 foo1/1 ''' class TestNetconfOpenConfigClient(base.TestCase): def setUp(self): super(TestNetconfOpenConfigClient, self).setUp() self.device = 'foo' self.conf = self.useFixture(config_fixture.Config()) self.conf.register_opts(config._opts + config._device_opts, group='foo') self.conf.register_opts((openconfig._DEVICE_OPTS + openconfig._NCCLIENT_OPTS), group='foo') self.conf.config(enabled_devices=['foo'], group='networking_baremetal') self.conf.config(driver='test-driver', switch_id='aa:bb:cc:dd:ee:ff', switch_info='foo', physical_networks=['fake_physical_network'], device_params={'name': 'default'}, host='foo.example.com', key_filename='/test/test_key_file', username='foo_user', group='foo') self.client = openconfig.NetconfOpenConfigClient(self.device) def test_get_lock_session_id(self): err_info = ( '' '' '{}' '') self.assertEqual('0', self.client._get_lock_session_id( err_info.format(0))) self.assertEqual('abc-123', self.client._get_lock_session_id( err_info.format('abc-123'))) def test_get_client_args(self): self.assertEqual( {'device_params': {'name': 'default'}, 'host': 'foo.example.com', 'hostkey_verify': True, 'keepalive': True, 'key_filename': '/test/test_key_file', 'port': 830, 'username': 'foo_user', 'allow_agent': True, 'look_for_keys': True}, self.client.get_client_args()) @mock.patch.object(manager, 'connect', autospec=True) def test_get_capabilities(self, mock_manager): fake_caps = set(constants.IANA_NETCONF_CAPABILITIES.values()) fake_caps.add('http://openconfig.net/yang/' 'network-instance?' 'module=openconfig-network-instance&' 'revision=2021-07-22') fake_caps.add('http://openconfig.net/yang/' 'interfaces?' 'module=openconfig-interfaces&' 'revision=2021-04-06') mock_ncclient = mock.Mock() mock_ncclient.server_capabilities = fake_caps mock_manager.return_value.__enter__.return_value = mock_ncclient self.assertEqual({ ':base:1.0', ':base:1.1', ':candidate', ':confirmed-commit', ':confirmed-commit:1.1', ':rollback-on-error', ':startup', ':validate', ':validate:1.1', ':writable-running', 'openconfig-network-instance', 'openconfig-interfaces'}, self.client.get_capabilities()) @mock.patch.object(manager, 'connect', autospec=True) @mock.patch.object(openconfig.NetconfOpenConfigClient, 'get_lock_and_configure', autospec=True) def test_edit_config_writable_running(self, mock_lock_config, mock_manager): fake_config = mock.Mock() fake_config.to_xml_element.return_value = ElementTree.Element('fake') mock_ncclient = mock.Mock() fake_caps = {constants.IANA_NETCONF_CAPABILITIES[':writable-running']} mock_ncclient.server_capabilities = fake_caps mock_manager.return_value.__enter__.return_value = mock_ncclient self.client.edit_config(fake_config) mock_lock_config.assert_called_once_with(self.client, mock_ncclient, openconfig.RUNNING, [fake_config], False) @mock.patch.object(manager, 'connect', autospec=True) @mock.patch.object(openconfig.NetconfOpenConfigClient, 'get_lock_and_configure', autospec=True) def test_edit_config_candidate(self, mock_lock_config, mock_manager): fake_config = mock.Mock() fake_config.to_xml_element.return_value = ElementTree.Element('fake') mock_ncclient = mock.Mock() fake_caps = {constants.IANA_NETCONF_CAPABILITIES[':candidate']} mock_ncclient.server_capabilities = fake_caps mock_manager.return_value.__enter__.return_value = mock_ncclient self.client.edit_config(fake_config) mock_lock_config.assert_called_once_with(self.client, mock_ncclient, openconfig.CANDIDATE, [fake_config], False) def test_get_lock_and_configure_confirmed_commit(self): self.client.capabilities = {':candidate', ':writable-running', ':confirmed-commit'} fake_config = mock.Mock() fake_config.to_xml_element.return_value = ElementTree.Element('fake') mock_client = mock.MagicMock() self.client.get_lock_and_configure(mock_client, openconfig.CANDIDATE, [fake_config], False) mock_client.locked.assert_called_with(openconfig.CANDIDATE) mock_client.discard_changes.assert_called_once() mock_client.edit_config.assert_called_with( target=openconfig.CANDIDATE, config='') mock_client.validate.assert_not_called() mock_client.commit.assert_has_calls([ mock.call(confirmed=True, timeout=str(30)), mock.call()]) def test_get_lock_and_configure_validate(self): self.client.capabilities = {':candidate', ':writable-running', ':validate'} fake_config = mock.Mock() fake_config.to_xml_element.return_value = ElementTree.Element('fake') mock_client = mock.MagicMock() self.client.get_lock_and_configure(mock_client, openconfig.CANDIDATE, [fake_config], False) mock_client.locked.assert_called_with(openconfig.CANDIDATE) mock_client.discard_changes.assert_called_once() mock_client.edit_config.assert_called_with( target=openconfig.CANDIDATE, config='') mock_client.validate.assert_called_once_with( source=openconfig.CANDIDATE) mock_client.commit.assert_called_once_with() def test_get_lock_and_configure_writeable_running(self): self.client.capabilities = {':writable-running'} fake_config = mock.Mock() fake_config.to_xml_element.return_value = ElementTree.Element('fake') mock_client = mock.MagicMock() self.client.get_lock_and_configure(mock_client, openconfig.RUNNING, [fake_config], False) mock_client.locked.assert_called_with(openconfig.RUNNING) mock_client.discard_changes.assert_not_called() mock_client.validate.assert_not_called() mock_client.commit.assert_not_called() mock_client.edit_config.assert_called_with( target=openconfig.RUNNING, config='') @mock.patch.object(manager, 'connect', autospec=True) def test_get(self, mock_manager): fake_query = interfaces.Interfaces() fake_query.add('foo1/1') mock_ncclient = mock.Mock() mock_manager.return_value.__enter__.return_value = mock_ncclient self.client.get(query=fake_query) mock_ncclient.get.assert_called_with(filter=('subtree', mock.ANY)) def test_get_aggregation_ids(self): self.conf.config(link_aggregate_prefix='foo', link_aggregate_range='5..10', group=self.device) self.assertEqual({'foo5', 'foo6', 'foo7', 'foo8', 'foo9', 'foo10'}, self.client.get_aggregation_ids()) def test_allocate_deferred(self): aggregate_id = 'foo5' _config = [] ifaces = interfaces.Interfaces() iface_a = ifaces.add('foo1/1', interface_type=constants.IFACE_TYPE_ETHERNET) iface_a.ethernet.config.aggregate_id = openconfig.DEFERRED iface_b = ifaces.add('foo1/2', interface_type=constants.IFACE_TYPE_ETHERNET) iface_b.ethernet.config.aggregate_id = openconfig.DEFERRED iface_agg = ifaces.add(openconfig.DEFERRED, interface_type=constants.IFACE_TYPE_AGGREGATE) iface_agg.config.name = openconfig.DEFERRED _config.append(ifaces) _lacp = lacp.LACP() lacp_ifaces = lacp.LACPInterfaces() lacp_ifaces.add(openconfig.DEFERRED) _config.append(_lacp) self.client.allocate_deferred(aggregate_id, _config) for conf in _config: if isinstance(conf, interfaces.Interfaces): for iface in ifaces: if isinstance(iface, interfaces.InterfaceAggregate): self.assertEqual(iface.name, aggregate_id) self.assertEqual(iface.config.name, aggregate_id) elif isinstance(iface, interfaces.InterfaceEthernet): self.assertEqual(iface.ethernet.config.aggregate_id, aggregate_id) if isinstance(conf, lacp.LACP): for lacp_iface in _lacp.interfaces.interfaces: self.assertEqual(lacp_iface.name, aggregate_id) self.assertEqual(lacp_iface.config.name, aggregate_id) def test_get_free_aggregate_id(self): self.conf.config(link_aggregate_prefix='Po', link_aggregate_range='5..10', group=self.device) mock_get_result = mock.Mock() mock_get_result.data_xml = XML_AGGREGATE_IFACES mock_client_locked = mock.Mock() mock_client_locked.get.return_value = mock_get_result used_aggregate_ids = {'Po5', 'Po7', 'Po9'} all_aggregate_ids = self.client.get_aggregation_ids() result = self.client.get_free_aggregate_id(mock_client_locked) self.assertNotIn(result, used_aggregate_ids) self.assertIn(result, all_aggregate_ids) class TestNetconfOpenConfigDriver(base.TestCase): def setUp(self): super(TestNetconfOpenConfigDriver, self).setUp() self.device = 'foo' self.conf = self.useFixture(config_fixture.Config()) self.conf.register_opts(config._opts + config._device_opts, group='foo') self.conf.register_opts((openconfig._DEVICE_OPTS + openconfig._NCCLIENT_OPTS), group='foo') self.conf.config(enabled_devices=['foo'], group='networking_baremetal') self.conf.config(driver='test-driver', switch_id='aa:bb:cc:dd:ee:ff', switch_info='foo', physical_networks=['fake_physical_network'], device_params={'name': 'default'}, host='foo.example.com', key_filename='/test/test_key_file', username='foo_user', group='foo') mock_client = mock.patch.object(openconfig, 'NetconfOpenConfigClient', autospec=True) self.mock_client = mock_client.start() self.addCleanup(mock_client.stop) self.driver = openconfig.NetconfOpenConfigDriver(self.device) self.mock_client.assert_called_once_with('foo') self.mock_client.reset_mock() def test_validate(self): self.driver.validate() self.driver.client.get_capabilities.assert_called_once_with() @mock.patch.object(openconfig, 'CONF', autospec=True) def test_load_config(self, mock_conf): self.driver.load_config() mock_conf.register_opts.assert_has_calls( [mock.call(openconfig._DEVICE_OPTS, group=self.driver.device), mock.call(openconfig._NCCLIENT_OPTS, group=self.driver.device)]) def test_create_network(self): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network() self.driver.create_network(m_nc) net_instances = self.driver.client.edit_config.call_args[0][0] for net_instance in net_instances: self.assertEqual(net_instance.name, 'default') vlans = net_instance.vlans for vlan in vlans: self.assertEqual(vlan.config.operation, nc_op.MERGE.value) self.assertEqual(vlan.config.name, self.driver._uuid_as_hex(m_nc.current['id'])) self.assertEqual(vlan.config.status, constants.VLAN_ACTIVE) def test_update_network_no_changes(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN) m_nc.original = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN) self.assertEqual(m_nc.current, m_nc.original) self.driver.update_network(m_nc) self.driver.client.edit_config.assert_not_called() def test_update_network_change_vlan_id(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=10) m_nc.original = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=20) self.driver.update_network(m_nc) call_args_list = self.driver.client.edit_config.call_args_list del_net_instances = call_args_list[0][0][0] add_net_instances = call_args_list[1][0][0] self.driver.client.edit_config.assert_has_calls( [mock.call(del_net_instances), mock.call(add_net_instances)]) for net_instance in del_net_instances: self.assertEqual(net_instance.name, 'default') for vlan in net_instance.vlans: self.assertEqual(vlan.operation, nc_op.REMOVE.value) self.assertEqual(vlan.vlan_id, 20) self.assertEqual(vlan.config.status, constants.VLAN_SUSPENDED) self.assertEqual(vlan.config.name, 'neutron-DELETED-20') for net_instance in add_net_instances: self.assertEqual(net_instance.name, 'default') for vlan in net_instance.vlans: self.assertEqual(vlan.operation, nc_op.MERGE.value) self.assertEqual(vlan.config.name, self.driver._uuid_as_hex(network_id)) self.assertEqual(vlan.vlan_id, 10) def test_update_network_change_admin_state(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=10, admin_state_up=False) m_nc.original = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=10, admin_state_up=True) self.driver.update_network(m_nc) call_args_list = self.driver.client.edit_config.call_args_list add_net_instances = call_args_list[0][0][0] self.driver.client.edit_config.assert_called_once_with( add_net_instances) for net_instance in add_net_instances: self.assertEqual(net_instance.name, 'default') for vlan in net_instance.vlans: self.assertEqual(vlan.operation, nc_op.MERGE.value) self.assertEqual(vlan.config.status, constants.VLAN_SUSPENDED) self.assertEqual(vlan.config.name, self.driver._uuid_as_hex(network_id)) self.assertEqual(vlan.vlan_id, 10) def test_delete_network(self): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN, segmentation_id=15) self.driver.delete_network(m_nc) self.driver.client.edit_config.assert_called_once() call_args_list = self.driver.client.edit_config.call_args_list net_instances = call_args_list[0][0][0] for net_instance in net_instances: self.assertEqual(net_instance.name, 'default') for vlan in net_instance.vlans: self.assertEqual(vlan.operation, nc_op.REMOVE.value) self.assertEqual(vlan.vlan_id, 15) self.assertEqual(vlan.config.status, constants.VLAN_SUSPENDED) self.assertEqual(vlan.config.name, 'neutron-DELETED-15') def test_create_port_vlan(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id) m_pc.network = m_nc segment = { api.ID: uuidutils.generate_uuid(), api.PHYSICAL_NETWORK: m_nc.current['provider:physical_network'], api.NETWORK_TYPE: m_nc.current['provider:network_type'], api.SEGMENTATION_ID: m_nc.current['provider:segmentation_id']} links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.create_port(m_pc, segment, links) self.driver.client.edit_config.assert_called_once() call_args_list = self.driver.client.edit_config.call_args_list ifaces = call_args_list[0][0][0] for iface in ifaces: self.assertEqual(iface.name, links[0]['port_id']) self.assertEqual(iface.config.enabled, m_pc.current['admin_state_up']) self.assertEqual(iface.config.mtu, m_nc.current[api.MTU]) self.assertEqual(iface.config.description, f'neutron-{m_pc.current[api.ID]}') self.assertEqual(iface.ethernet.switched_vlan.config.operation, nc_op.REPLACE.value) self.assertEqual( iface.ethernet.switched_vlan.config.interface_mode, constants.VLAN_MODE_ACCESS) self.assertEqual( iface.ethernet.switched_vlan.config.access_vlan, segment[api.SEGMENTATION_ID]) def test_create_port_flat(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_FLAT) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id) m_pc.network = m_nc segment = { api.ID: uuidutils.generate_uuid(), api.PHYSICAL_NETWORK: m_nc.current['provider:physical_network'], api.NETWORK_TYPE: m_nc.current['provider:network_type']} links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.create_port(m_pc, segment, links) self.driver.client.edit_config.assert_called_once() call_args_list = self.driver.client.edit_config.call_args_list ifaces = call_args_list[0][0][0] for iface in ifaces: self.assertEqual(iface.name, links[0]['port_id']) self.assertEqual(iface.config.enabled, m_pc.current['admin_state_up']) self.assertEqual(iface.config.mtu, m_nc.current[api.MTU]) self.assertEqual(iface.config.description, f'neutron-{m_pc.current[api.ID]}') self.assertIsNone(iface.ethernet) def test_update_port(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15, mtu=9000) m_nc.original = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15, mtu=1500) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, admin_state_up=False) m_pc.original = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, admin_state_up=True) m_pc.network = m_nc links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.update_port(m_pc, links) self.driver.client.edit_config.assert_called_once() call_args_list = self.driver.client.edit_config.call_args_list ifaces = call_args_list[0][0][0] for iface in ifaces: self.assertEqual(iface.name, links[0]['port_id']) self.assertEqual(iface.config.enabled, m_pc.current['admin_state_up']) self.assertEqual(iface.config.mtu, m_nc.current[api.MTU]) self.assertIsNone(iface.ethernet) def test_update_port_no_supported_attrib_changed(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15) m_nc.original = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, name='current') m_pc.original = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, name='original') m_pc.network = m_nc links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.update_port(m_pc, links) self.driver.client.edit_config.assert_not_called() def test_delete_port_vlan(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id) m_pc.network = m_nc links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.delete_port(m_pc, links) self.driver.client.edit_config.assert_called_once() call_args_list = self.driver.client.edit_config.call_args_list ifaces = call_args_list[0][0][0] for iface in ifaces: self.assertEqual(iface.name, links[0]['port_id']) self.assertEqual(iface.config.operation, nc_op.REMOVE.value) self.assertEqual(iface.config.description, '') self.assertFalse(iface.config.enabled) self.assertEqual(iface.config.mtu, 0) self.assertEqual(iface.ethernet.switched_vlan.config.operation, nc_op.REMOVE.value) def test_delete_port_flat(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_FLAT) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id) m_pc.network = m_nc links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.delete_port(m_pc, links) self.driver.client.edit_config.assert_called_once() call_args_list = self.driver.client.edit_config.call_args_list ifaces = call_args_list[0][0][0] for iface in ifaces: self.assertEqual(iface.name, links[0]['port_id']) self.assertEqual(iface.config.operation, nc_op.REMOVE.value) self.assertEqual(iface.config.description, '') self.assertFalse(iface.config.enabled) self.assertEqual(iface.config.mtu, 0) self.assertIsNone(iface.ethernet) def test_create_lacp_port_flat(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) binding_profile = { constants.LOCAL_LINK_INFO: [ {'port_id': 'foo1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}, {'port_id': 'foo1/2', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}], constants.LOCAL_GROUP_INFO: { 'id': uuidutils.generate_uuid(), 'name': 'PortGroup1', 'bond_mode': '802.3ad', 'bond_properties': { constants.LACP_INTERVAL: 'fast', constants.LACP_MIN_LINKS: 2} } } m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_FLAT) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, binding_profile=binding_profile) m_pc.network = m_nc segment = { api.ID: uuidutils.generate_uuid(), api.PHYSICAL_NETWORK: m_nc.current['provider:physical_network'], api.NETWORK_TYPE: m_nc.current['provider:network_type']} links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.create_port(m_pc, segment, links) self.driver.client.edit_config.assert_called_once_with( [mock.ANY, mock.ANY], deferred_allocations=True) call_args_list = self.driver.client.edit_config.call_args_list ifaces = list(call_args_list[0][0][0][0]) _lacp = call_args_list[0][0][0][1] lacp_iface = list(_lacp.interfaces)[0] if_link_a = ifaces[0] if_link_b = ifaces[1] if_agg = ifaces[2] assert isinstance(if_link_a, interfaces.InterfaceEthernet) assert isinstance(if_link_b, interfaces.InterfaceEthernet) assert isinstance(if_agg, interfaces.InterfaceAggregate) assert isinstance(lacp_iface, lacp.LACPInterface) self.assertEqual(if_link_a.name, 'foo1/1') self.assertEqual(if_link_b.name, 'foo1/2') self.assertEqual(if_agg.name, openconfig.DEFERRED) for iface in (if_link_a, if_link_b): self.assertEqual(iface.config.operation, nc_op.MERGE.value) self.assertEqual(iface.config.enabled, m_pc.current['admin_state_up']) self.assertEqual(iface.config.mtu, m_nc.current['mtu']) self.assertEqual(iface.config.description, f'neutron-{m_pc.current[api.ID]}') self.assertEqual(iface.ethernet.config.aggregate_id, openconfig.DEFERRED) self.assertEqual(if_agg.name, openconfig.DEFERRED) self.assertEqual(if_agg.config.name, openconfig.DEFERRED) self.assertEqual(if_agg.config.operation, nc_op.MERGE.value) self.assertEqual(if_agg.config.description, f'neutron-{m_pc.current[api.ID]}') self.assertEqual(if_agg.aggregation.config.lag_type, constants.LAG_TYPE_LACP) self.assertEqual(if_agg.aggregation.config.min_links, binding_profile[ constants.LOCAL_GROUP_INFO]['bond_properties'][ constants.LACP_MIN_LINKS]) self.assertIsNone(if_agg.aggregation.switched_vlan) self.assertEqual(lacp_iface.name, openconfig.DEFERRED) self.assertEqual(lacp_iface.operation, nc_op.REPLACE.value) self.assertEqual(lacp_iface.config.name, openconfig.DEFERRED) self.assertEqual(lacp_iface.config.interval, constants.LACP_PERIOD_FAST) def test_create_lacp_port_vlan(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) binding_profile = { constants.LOCAL_LINK_INFO: [ {'port_id': 'foo1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}, {'port_id': 'foo1/2', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}], constants.LOCAL_GROUP_INFO: { 'id': uuidutils.generate_uuid(), 'name': 'PortGroup1', 'bond_mode': '802.3ad', 'bond_properties': { constants.LACP_INTERVAL: 'fast', constants.LACP_MIN_LINKS: 2} } } m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=40) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, binding_profile=binding_profile) m_pc.network = m_nc segment = { api.ID: uuidutils.generate_uuid(), api.PHYSICAL_NETWORK: m_nc.current['provider:physical_network'], api.NETWORK_TYPE: m_nc.current['provider:network_type'], api.SEGMENTATION_ID: m_nc.current['provider:segmentation_id']} links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.create_port(m_pc, segment, links) self.driver.client.edit_config.assert_called_once_with( [mock.ANY, mock.ANY], deferred_allocations=True) call_args_list = self.driver.client.edit_config.call_args_list ifaces = list(call_args_list[0][0][0][0]) _lacp = call_args_list[0][0][0][1] lacp_iface = list(_lacp.interfaces)[0] if_link_a = ifaces[0] if_link_b = ifaces[1] if_agg = ifaces[2] assert isinstance(if_link_a, interfaces.InterfaceEthernet) assert isinstance(if_link_b, interfaces.InterfaceEthernet) assert isinstance(if_agg, interfaces.InterfaceAggregate) assert isinstance(lacp_iface, lacp.LACPInterface) self.assertEqual(if_link_a.name, 'foo1/1') self.assertEqual(if_link_b.name, 'foo1/2') self.assertEqual(if_agg.name, openconfig.DEFERRED) for iface in (if_link_a, if_link_b): self.assertEqual(iface.config.operation, nc_op.MERGE.value) self.assertEqual(iface.config.enabled, m_pc.current['admin_state_up']) self.assertEqual(iface.config.mtu, m_nc.current['mtu']) self.assertEqual(iface.config.description, f'neutron-{m_pc.current[api.ID]}') self.assertEqual(iface.ethernet.config.aggregate_id, openconfig.DEFERRED) self.assertEqual(if_agg.name, openconfig.DEFERRED) self.assertEqual(if_agg.config.name, openconfig.DEFERRED) self.assertEqual(if_agg.config.operation, nc_op.MERGE.value) self.assertEqual(if_agg.config.description, f'neutron-{m_pc.current[api.ID]}') self.assertEqual(if_agg.aggregation.config.lag_type, constants.LAG_TYPE_LACP) self.assertEqual(if_agg.aggregation.config.min_links, binding_profile[ constants.LOCAL_GROUP_INFO]['bond_properties'][ constants.LACP_MIN_LINKS]) self.assertEqual(if_agg.aggregation.switched_vlan.config.operation, nc_op.REPLACE.value) self.assertEqual( if_agg.aggregation.switched_vlan.config.interface_mode, constants.VLAN_MODE_ACCESS) self.assertEqual(if_agg.aggregation.switched_vlan.config.access_vlan, segment[api.SEGMENTATION_ID]) self.assertEqual(lacp_iface.name, openconfig.DEFERRED) self.assertEqual(lacp_iface.operation, nc_op.REPLACE.value) self.assertEqual(lacp_iface.config.name, openconfig.DEFERRED) self.assertEqual(lacp_iface.config.interval, constants.LACP_PERIOD_FAST) def test_update_lacp_port(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) binding_profile = { constants.LOCAL_LINK_INFO: [ {'port_id': 'foo1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}, {'port_id': 'foo1/2', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}], constants.LOCAL_GROUP_INFO: { 'id': uuidutils.generate_uuid(), 'name': 'PortGroup1', 'bond_mode': '802.3ad', 'bond_properties': { constants.LACP_INTERVAL: 'fast', constants.LACP_MIN_LINKS: 2} } } m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15, mtu=9000) m_nc.original = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15, mtu=1500) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, admin_state_up=False, binding_profile=binding_profile) m_pc.original = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, admin_state_up=True, binding_profile=binding_profile) m_pc.network = m_nc links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.client.get.return_value = XML_IFACES_AGGREDATE_ID self.driver.update_port(m_pc, links) self.driver.client.get.assert_called_once() self.driver.client.edit_config.assert_called_once() # Validate the query to get aggregate id query_call_args = self.driver.client.get.call_args ifaces = query_call_args[1]['query'] iface_a = list(ifaces)[0] iface_b = list(ifaces)[1] self.assertEqual(iface_a.name, 'foo1/1') self.assertEqual(iface_b.name, 'foo1/2') self.assertIsNone(iface_a.config) self.assertIsNone(iface_a.ethernet) self.assertIsNone(iface_b.config) self.assertIsNone(iface_b.ethernet) # Validate the edit config call edit_call_args_list = self.driver.client.edit_config.call_args_list ifaces = list(edit_call_args_list[0][0][0]) if_link_a = ifaces[0] if_link_b = ifaces[1] if_agg = ifaces[2] assert isinstance(if_link_a, interfaces.InterfaceEthernet) assert isinstance(if_link_b, interfaces.InterfaceEthernet) assert isinstance(if_agg, interfaces.InterfaceAggregate) self.assertEqual(if_link_a.name, 'foo1/1') self.assertEqual(if_link_b.name, 'foo1/2') self.assertEqual(if_agg.name, 'Po10') for iface in (if_link_a, if_link_b): self.assertEqual(iface.config.operation, nc_op.MERGE.value) self.assertEqual(False, iface.config.enabled) self.assertEqual(9000, iface.config.mtu) self.assertEqual(if_agg.name, 'Po10') self.assertEqual(if_agg.operation, nc_op.MERGE.value) self.assertEqual(False, if_agg.config.enabled) def test_delete_lacp_port_flat(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) binding_profile = { constants.LOCAL_LINK_INFO: [ {'port_id': 'foo1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}, {'port_id': 'foo1/2', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}], constants.LOCAL_GROUP_INFO: { 'id': uuidutils.generate_uuid(), 'name': 'PortGroup1', 'bond_mode': '802.3ad', 'bond_properties': { constants.LACP_INTERVAL: 'fast', constants.LACP_MIN_LINKS: 2} } } m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_FLAT) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, binding_profile=binding_profile) m_pc.network = m_nc links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.client.get.return_value = XML_IFACES_AGGREDATE_ID self.driver.delete_port(m_pc, links) self.driver.client.get.assert_called_once_with(query=mock.ANY) self.driver.client.edit_config.assert_called_once_with([mock.ANY, mock.ANY]) # Validate the query to get aggregate id query_call_args = self.driver.client.get.call_args ifaces = query_call_args[1]['query'] iface_a = list(ifaces)[0] iface_b = list(ifaces)[1] self.assertEqual(iface_a.name, 'foo1/1') self.assertEqual(iface_b.name, 'foo1/2') self.assertIsNone(iface_a.config) self.assertIsNone(iface_a.ethernet) self.assertIsNone(iface_b.config) self.assertIsNone(iface_b.ethernet) # Validate the edit config call edit_call_args_list = self.driver.client.edit_config.call_args_list _lacp = edit_call_args_list[0][0][0][0] ifaces = list(edit_call_args_list[0][0][0][1]) lacp_iface = list(_lacp.interfaces)[0] if_link_a = ifaces[0] if_link_b = ifaces[1] if_agg = ifaces[2] assert isinstance(if_link_a, interfaces.InterfaceEthernet) assert isinstance(if_link_b, interfaces.InterfaceEthernet) assert isinstance(if_agg, interfaces.InterfaceAggregate) assert isinstance(lacp_iface, lacp.LACPInterface) self.assertEqual(if_link_a.name, 'foo1/1') self.assertEqual(if_link_b.name, 'foo1/2') self.assertEqual(if_agg.name, 'Po10') for iface in (if_link_a, if_link_b): self.assertEqual(iface.config.operation, nc_op.REMOVE.value) self.assertEqual(iface.config.enabled, False) self.assertEqual(iface.config.mtu, 0) self.assertEqual(iface.config.description, '') self.assertIsNone(iface.ethernet.switched_vlan) self.assertIsNone(iface.ethernet.config.aggregate_id) self.assertEqual(iface.ethernet.config.operation, nc_op.REMOVE.value) self.assertEqual(if_agg.name, 'Po10') self.assertEqual(if_agg.operation, nc_op.REMOVE.value) self.assertIsNone(if_agg.config) self.assertIsNone(if_agg.aggregation) self.assertEqual(lacp_iface.name, 'Po10') self.assertEqual(lacp_iface.operation, nc_op.REMOVE.value) self.assertIsNone(lacp_iface.config) def test_delete_lacp_port_vlan(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) binding_profile = { constants.LOCAL_LINK_INFO: [ {'port_id': 'foo1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}, {'port_id': 'foo1/2', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}], constants.LOCAL_GROUP_INFO: { 'id': uuidutils.generate_uuid(), 'name': 'PortGroup1', 'bond_mode': '802.3ad', 'bond_properties': { constants.LACP_INTERVAL: 'fast', constants.LACP_MIN_LINKS: 2} } } m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=40) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, binding_profile=binding_profile) m_pc.network = m_nc links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.client.get.return_value = XML_IFACES_AGGREDATE_ID self.driver.delete_port(m_pc, links) self.driver.client.get.assert_called_once_with(query=mock.ANY) self.driver.client.edit_config.assert_called_once_with([mock.ANY, mock.ANY]) # Validate the query to get aggregate id query_call_args = self.driver.client.get.call_args ifaces = query_call_args[1]['query'] iface_a = list(ifaces)[0] iface_b = list(ifaces)[1] self.assertEqual(iface_a.name, 'foo1/1') self.assertEqual(iface_b.name, 'foo1/2') self.assertIsNone(iface_a.config) self.assertIsNone(iface_a.ethernet) self.assertIsNone(iface_b.config) self.assertIsNone(iface_b.ethernet) # Validate the edit config call edit_call_args_list = self.driver.client.edit_config.call_args_list _lacp = edit_call_args_list[0][0][0][0] ifaces = list(edit_call_args_list[0][0][0][1]) lacp_iface = list(_lacp.interfaces)[0] if_link_a = ifaces[0] if_link_b = ifaces[1] if_agg = ifaces[2] assert isinstance(if_link_a, interfaces.InterfaceEthernet) assert isinstance(if_link_b, interfaces.InterfaceEthernet) assert isinstance(if_agg, interfaces.InterfaceAggregate) assert isinstance(lacp_iface, lacp.LACPInterface) self.assertEqual(if_link_a.name, 'foo1/1') self.assertEqual(if_link_b.name, 'foo1/2') self.assertEqual(if_agg.name, 'Po10') for iface in (if_link_a, if_link_b): self.assertEqual(iface.config.operation, nc_op.REMOVE.value) self.assertEqual(iface.config.enabled, False) self.assertEqual(iface.config.mtu, 0) self.assertEqual(iface.config.description, '') self.assertEqual(iface.ethernet.switched_vlan.config.operation, nc_op.REMOVE.value) self.assertIsNone(iface.ethernet.config.aggregate_id) self.assertEqual(if_agg.name, 'Po10') self.assertEqual(if_agg.operation, nc_op.REMOVE.value) self.assertIsNone(if_agg.config) self.assertIsNone(if_agg.aggregation) self.assertEqual(lacp_iface.name, 'Po10') self.assertEqual(lacp_iface.operation, nc_op.REMOVE.value) self.assertIsNone(lacp_iface.config) def test_create_pre_configured_aggregate_port_vlan(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) binding_profile = { constants.LOCAL_LINK_INFO: [ {'port_id': 'foo1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}, {'port_id': 'foo1/2', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}], constants.LOCAL_GROUP_INFO: { 'id': uuidutils.generate_uuid(), 'name': 'PortGroup1', 'bond_mode': 'balance-rr', } } m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=40) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, binding_profile=binding_profile) m_pc.network = m_nc segment = { api.ID: uuidutils.generate_uuid(), api.PHYSICAL_NETWORK: m_nc.current['provider:physical_network'], api.NETWORK_TYPE: m_nc.current['provider:network_type'], api.SEGMENTATION_ID: m_nc.current['provider:segmentation_id']} links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.client.get.return_value = XML_IFACES_AGGREDATE_ID self.driver.create_port(m_pc, segment, links) self.driver.client.get.assert_called_once() self.driver.client.edit_config.assert_called_once() # Validate the query to get aggregate id query_call_args = self.driver.client.get.call_args ifaces = query_call_args[1]['query'] iface_a = list(ifaces)[0] iface_b = list(ifaces)[1] self.assertEqual(iface_a.name, 'foo1/1') self.assertEqual(iface_b.name, 'foo1/2') self.assertIsNone(iface_a.config) self.assertIsNone(iface_a.ethernet) self.assertIsNone(iface_b.config) self.assertIsNone(iface_b.ethernet) # Validate the edit config call edit_call_args_list = self.driver.client.edit_config.call_args_list ifaces = list(edit_call_args_list[0][0][0]) if_agg = ifaces[0] assert isinstance(if_agg, interfaces.InterfaceAggregate) self.assertEqual(if_agg.name, 'Po10') self.assertEqual(if_agg.operation, nc_op.MERGE.value) self.assertEqual(True, if_agg.config.enabled) self.assertEqual(if_agg.aggregation.switched_vlan.config.operation, nc_op.REPLACE.value) self.assertEqual( if_agg.aggregation.switched_vlan.config.interface_mode, constants.VLAN_MODE_ACCESS) self.assertEqual(if_agg.aggregation.switched_vlan.config.access_vlan, segment[api.SEGMENTATION_ID]) def test_update_pre_configured_aggregate_port_vlan(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) binding_profile = { constants.LOCAL_LINK_INFO: [ {'port_id': 'foo1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}, {'port_id': 'foo1/2', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}], constants.LOCAL_GROUP_INFO: { 'id': uuidutils.generate_uuid(), 'name': 'PortGroup1', 'bond_mode': 'balance-rr', } } m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15) m_nc.original = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, admin_state_up=False, binding_profile=binding_profile) m_pc.original = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, admin_state_up=True, binding_profile=binding_profile) m_pc.network = m_nc links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.client.get.return_value = XML_IFACES_AGGREDATE_ID self.driver.update_port(m_pc, links) self.driver.client.get.assert_called_once() self.driver.client.edit_config.assert_called_once() # Validate the edit config call edit_call_args_list = self.driver.client.edit_config.call_args_list ifaces = list(edit_call_args_list[0][0][0]) if_agg = ifaces[0] assert isinstance(if_agg, interfaces.InterfaceAggregate) self.assertEqual(if_agg.name, 'Po10') self.assertEqual(if_agg.operation, nc_op.MERGE.value) self.assertEqual(False, if_agg.config.enabled) def test_delete_pre_configured_aggregate_port_vlan(self): tenant_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid() project_id = uuidutils.generate_uuid() m_nc = mock.create_autospec(driver_context.NetworkContext) m_pc = mock.create_autospec(driver_context.PortContext) binding_profile = { constants.LOCAL_LINK_INFO: [ {'port_id': 'foo1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}, {'port_id': 'foo1/2', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}], constants.LOCAL_GROUP_INFO: { 'id': uuidutils.generate_uuid(), 'name': 'PortGroup1', 'bond_mode': 'balance-rr', } } m_nc.current = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15) m_nc.original = ml2_utils.get_test_network( id=network_id, tenant_id=tenant_id, project_id=project_id, network_type=n_const.TYPE_VLAN, segmentation_id=15) m_pc.current = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, admin_state_up=False, binding_profile=binding_profile) m_pc.original = ml2_utils.get_test_port( network_id=network_id, tenant_id=tenant_id, project_id=project_id, admin_state_up=True, binding_profile=binding_profile) m_pc.network = m_nc links = m_pc.current['binding:profile'][constants.LOCAL_LINK_INFO] self.driver.client.get.return_value = XML_IFACES_AGGREDATE_ID self.driver.delete_port(m_pc, links) self.driver.client.get.assert_called_once() self.driver.client.edit_config.assert_called_once() # Validate the edit config call edit_call_args_list = self.driver.client.edit_config.call_args_list ifaces = list(edit_call_args_list[0][0][0]) if_agg = ifaces[0] assert isinstance(if_agg, interfaces.InterfaceAggregate) self.assertEqual(if_agg.name, 'Po10') self.assertEqual(if_agg.operation, nc_op.MERGE.value) self.assertEqual(False, if_agg.config.enabled) self.assertEqual(if_agg.aggregation.switched_vlan.config.operation, nc_op.REMOVE.value) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/tests/unit/ironic_agent/0000775000175000017500000000000000000000000026652 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/ironic_agent/__init__.py0000664000175000017500000000000000000000000030751 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=networking-baremetal-6.4.0/networking_baremetal/tests/unit/ironic_agent/test_hashring_member_manager.py 22 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/ironic_agent/test_hashring_member_manager0000664000175000017500000000701500000000000034463 0ustar00zuulzuul00000000000000# Copyright 2017 Red Hat Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from oslo_utils import timeutils from oslotest import base from networking_baremetal.agent import ironic_neutron_agent def fake_notification(): return (mock.Mock(), 'publisher_id', 'event_type', {'id': 'agent_id', 'host': 'agent_host', 'timestamp': timeutils.utcnow_ts()}, 'metadata') class TestHashRingMemberManagerNotificationEndpoint(base.BaseTestCase): def setUp(self): super(TestHashRingMemberManagerNotificationEndpoint, self).setUp() self.member_manager = ( ironic_neutron_agent.HashRingMemberManagerNotificationEndpoint()) self.member_manager.members = [] self.old_timestamp = 1517874977 @mock.patch.object(ironic_neutron_agent.LOG, 'info', autospec=True) def test_notification_info_add_new_agent(self, mock_log): self.member_manager.hashring = mock.Mock() ctxt, publisher_id, event_type, payload, metadata = fake_notification() self.member_manager.info(ctxt, publisher_id, event_type, payload, metadata) self.member_manager.hashring.add_node.assert_called_with(payload['id']) self.assertEqual(payload, self.member_manager.members[0]) self.assertEqual(1, mock_log.call_count) def test_notification_info_update_timestamp(self): self.member_manager.hashring = mock.Mock() ctxt, publisher_id, event_type, payload, metadata = fake_notification() # Set an old timestamp, and insert into members payload['timestamp'] = self.old_timestamp self.member_manager.members.append(copy.deepcopy(payload)) # Reset timestamp, and simulate notification, add_node not called # Timestamp in member manager is updated. payload['timestamp'] = timeutils.utcnow_ts() self.assertNotEqual(payload['timestamp'], self.member_manager.members[0]['timestamp']) self.member_manager.info(ctxt, publisher_id, event_type, payload, metadata) self.member_manager.hashring.add_node.assert_not_called() self.assertEqual(payload['timestamp'], self.member_manager.members[0]['timestamp']) @mock.patch.object(ironic_neutron_agent.LOG, 'info', autospec=True) def test_remove_old_members(self, mock_log): self.member_manager.hashring = mock.Mock() # Add a member with an old timestamp, it is removed. ctxt, publisher_id, event_type, payload, metadata = fake_notification() payload['timestamp'] = self.old_timestamp self.member_manager.info(ctxt, publisher_id, event_type, payload, metadata) self.member_manager.hashring.remove_node.assert_called_with( payload['id']) self.assertEqual(0, len(self.member_manager.members)) self.assertEqual(2, mock_log.call_count) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/ironic_agent/test_ironic_agent.py0000664000175000017500000003534600000000000032737 0ustar00zuulzuul00000000000000# Copyright 2017 Red Hat Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from urllib import parse as urlparse from neutron.agent import rpc as agent_rpc from neutron.tests import base from neutron_lib import constants as n_const from openstack import connection from openstack import exceptions as sdk_exc from oslo_config import fixture as config_fixture from tooz import hashring from networking_baremetal.agent import ironic_neutron_agent from networking_baremetal import constants from networking_baremetal import ironic_client class FakePort1(object): def __init__(self, physnet='physnet1'): self.uuid = '11111111-2222-3333-4444-555555555555' self.node_id = '55555555-4444-3333-2222-111111111111' self.physical_network = physnet class FakePort2(object): def __init__(self, physnet='physnet2'): self.uuid = '11111111-aaaa-3333-4444-555555555555' self.node_id = '55555555-4444-3333-aaaa-111111111111' self.physical_network = physnet @mock.patch.object(ironic_client, '_get_ironic_session', autospec=True) @mock.patch.object(connection.Connection, 'baremetal', autospec=True) class TestBaremetalNeutronAgent(base.BaseTestCase): def setUp(self): super(TestBaremetalNeutronAgent, self).setUp() self.context = object() self.conf = self.useFixture(config_fixture.Config()) self.conf.config(transport_url='rabbit://user:password@host/') def test_get_template_node_state(self, mock_conn, mock_ir_client): self.agent = ironic_neutron_agent.BaremetalNeutronAgent() # Verify agent binary expected = constants.BAREMETAL_BINARY self.assertEqual(expected, self.agent.get_template_node_state( 'uuid')['binary']) # Verify agent_type is Baremetal Node expected = constants.BAREMETAL_AGENT_TYPE self.assertEqual(expected, self.agent.get_template_node_state( 'uuid')['agent_type']) # Verify topic expected = n_const.L2_AGENT_TOPIC self.assertEqual(expected, self.agent.get_template_node_state( 'uuid')['topic']) # Verify host expected = 'the_node_uuid' self.assertEqual(expected, self.agent.get_template_node_state( 'the_node_uuid')['host']) def test_report_state_one_node_one_port(self, mock_conn, mock_ir_client): self.agent = ironic_neutron_agent.BaremetalNeutronAgent() with mock.patch.object(self.agent.state_rpc, 'report_state', autospec=True) as mock_report_state: self.agent.ironic_client = mock_conn mock_conn.ports.return_value = iter([FakePort1()]) self.agent.agent_id = 'agent_id' self.agent.member_manager.hashring = hashring.HashRing( [self.agent.agent_id]) expected = { 'topic': n_const.L2_AGENT_TOPIC, 'start_flag': True, 'binary': constants.BAREMETAL_BINARY, 'host': '55555555-4444-3333-2222-111111111111', 'configurations': { 'bridge_mappings': { 'physnet1': 'yes' }, 'log_agent_heartbeats': False, }, 'agent_type': constants.BAREMETAL_AGENT_TYPE } self.agent._report_state() mock_report_state.assert_called_with(self.agent.context, expected) def test_report_state_with_log_agent_heartbeats(self, mock_conn, mock_ir_client): self.agent = ironic_neutron_agent.BaremetalNeutronAgent() with mock.patch.object(self.agent.state_rpc, 'report_state', autospec=True) as mock_report_state: self.conf.config(log_agent_heartbeats=True, group='AGENT') self.agent.ironic_client = mock_conn mock_conn.ports.return_value = iter([FakePort1()]) self.agent.agent_id = 'agent_id' self.agent.member_manager.hashring = hashring.HashRing( [self.agent.agent_id]) expected = { 'topic': n_const.L2_AGENT_TOPIC, 'start_flag': True, 'binary': constants.BAREMETAL_BINARY, 'host': '55555555-4444-3333-2222-111111111111', 'configurations': { 'bridge_mappings': { 'physnet1': 'yes' }, 'log_agent_heartbeats': True, }, 'agent_type': constants.BAREMETAL_AGENT_TYPE } self.agent._report_state() mock_report_state.assert_called_with(self.agent.context, expected) def test_start_flag_false_on_update_no_config_change(self, mock_conn, mock_ir_client): self.agent = ironic_neutron_agent.BaremetalNeutronAgent() with mock.patch.object(self.agent.state_rpc, 'report_state', autospec=True) as mock_report_state: self.agent.ironic_client = mock_conn mock_conn.ports.return_value = iter([FakePort1()]) self.agent.agent_id = 'agent_id' self.agent.member_manager.hashring = hashring.HashRing( [self.agent.agent_id]) expected = { 'topic': n_const.L2_AGENT_TOPIC, 'start_flag': 'PLACEHOLDER', 'binary': constants.BAREMETAL_BINARY, 'host': '55555555-4444-3333-2222-111111111111', 'configurations': { 'bridge_mappings': { 'physnet1': 'yes' }, 'log_agent_heartbeats': False, }, 'agent_type': constants.BAREMETAL_AGENT_TYPE } # First time report start_flag is True expected.update({'start_flag': True}) self.agent._report_state() mock_report_state.assert_called_with(self.agent.context, expected) # Subsequent times report start_flag is False mock_conn.ports.return_value = iter([FakePort1()]) expected.update({'start_flag': False}) self.agent._report_state() mock_report_state.assert_called_with(self.agent.context, expected) def test_start_flag_true_on_update_after_config_change(self, mock_conn, mock_ir_client): self.agent = ironic_neutron_agent.BaremetalNeutronAgent() with mock.patch.object(self.agent.state_rpc, 'report_state', autospec=True) as mock_report_state: self.agent.ironic_client = mock_conn mock_conn.ports.return_value = iter([FakePort1()]) self.agent.agent_id = 'agent_id' self.agent.member_manager.hashring = hashring.HashRing( [self.agent.agent_id]) expected = { 'topic': n_const.L2_AGENT_TOPIC, 'start_flag': 'PLACEHOLDER', 'binary': constants.BAREMETAL_BINARY, 'host': '55555555-4444-3333-2222-111111111111', 'configurations': { 'bridge_mappings': { 'physnet1': 'yes' }, 'log_agent_heartbeats': False, }, 'agent_type': constants.BAREMETAL_AGENT_TYPE } # First time report start_flag is True expected.update({'start_flag': True}) self.agent._report_state() mock_report_state.assert_called_with(self.agent.context, expected) # Subsequent times report start_flag is False mock_conn.ports.return_value = iter([FakePort1()]) expected.update({'start_flag': False}) self.agent._report_state() mock_report_state.assert_called_with(self.agent.context, expected) # After bridge_mapping config change start_flag is True once mock_conn.ports.return_value = iter( [FakePort1(physnet='new_physnet')]) expected.update({'configurations': { 'bridge_mappings': {'new_physnet': 'yes'}, 'log_agent_heartbeats': False}}) expected.update({'start_flag': True}) self.agent._report_state() mock_report_state.assert_called_with(self.agent.context, expected) # Subsequent times report start_flag is False mock_conn.ports.return_value = iter( [FakePort1(physnet='new_physnet')]) expected.update({'start_flag': False}) self.agent._report_state() mock_report_state.assert_called_with(self.agent.context, expected) def test_report_state_two_nodes_two_ports(self, mock_conn, mock_ir_client): self.agent = ironic_neutron_agent.BaremetalNeutronAgent() with mock.patch.object(self.agent.state_rpc, 'report_state', autospec=True) as mock_report_state: self.agent.ironic_client = mock_conn mock_conn.ports.return_value = iter([FakePort1(), FakePort2()]) self.agent.agent_id = 'agent_id' self.agent.member_manager.hashring = hashring.HashRing( [self.agent.agent_id]) expected1 = { 'topic': n_const.L2_AGENT_TOPIC, 'start_flag': True, 'binary': constants.BAREMETAL_BINARY, 'host': '55555555-4444-3333-2222-111111111111', 'configurations': { 'bridge_mappings': { 'physnet1': 'yes' }, 'log_agent_heartbeats': False, }, 'agent_type': constants.BAREMETAL_AGENT_TYPE } expected2 = { 'topic': n_const.L2_AGENT_TOPIC, 'start_flag': True, 'binary': constants.BAREMETAL_BINARY, 'host': '55555555-4444-3333-aaaa-111111111111', 'configurations': { 'bridge_mappings': { 'physnet2': 'yes' }, 'log_agent_heartbeats': False, }, 'agent_type': constants.BAREMETAL_AGENT_TYPE } self.agent._report_state() mock_report_state.assert_has_calls( [mock.call(self.agent.context, expected1), mock.call(self.agent.context, expected2)], any_order=True) @mock.patch.object(ironic_neutron_agent.LOG, 'exception', autospec=True) def test_ironic_port_list_fail(self, mock_log, mock_conn, mock_ir_client): self.agent = ironic_neutron_agent.BaremetalNeutronAgent() self.agent.ironic_client = mock_conn def mock_generator(details=None): raise sdk_exc.OpenStackCloudException() yield mock_conn.ports.side_effect = mock_generator self.agent._report_state() self.assertEqual(1, mock_log.call_count) @mock.patch.object(ironic_neutron_agent.LOG, 'exception', autospec=True) @mock.patch.object(agent_rpc, 'PluginReportStateAPI', autospec=True) def test_state_rpc_report_state_fail(self, mock_report_state, mock_log, mock_conn, mock_ir_client): self.agent = ironic_neutron_agent.BaremetalNeutronAgent() self.agent.agent_id = 'agent_id' self.agent.member_manager.hashring = hashring.HashRing( [self.agent.agent_id]) self.agent.ironic_client = mock_conn self.agent.state_rpc = mock_report_state mock_conn.ports.return_value = iter([FakePort1(), FakePort2()]) mock_report_state.report_state.side_effect = Exception() self.agent._report_state() self.assertEqual(1, mock_log.call_count) def test__get_notification_transport_url(self, mock_conn, mock_ir_client): self.assertEqual( 'rabbit://user:password@host/?amqp_auto_delete=true', ironic_neutron_agent._get_notification_transport_url()) self.conf.config(transport_url='rabbit://user:password@host:5672/') self.assertEqual( 'rabbit://user:password@host:5672/?amqp_auto_delete=true', ironic_neutron_agent._get_notification_transport_url()) self.conf.config(transport_url='rabbit://host:5672/') self.assertEqual( 'rabbit://host:5672/?amqp_auto_delete=true', ironic_neutron_agent._get_notification_transport_url()) self.conf.config(transport_url='rabbit://user:password@host/vhost') self.assertEqual( 'rabbit://user:password@host/vhost?amqp_auto_delete=true', ironic_neutron_agent._get_notification_transport_url()) self.conf.config( transport_url='rabbit://user:password@host/vhost?foo=bar') self.assertEqual( # NOTE(hjensas): Parse the url's when comparing, different versions # may sort the query different. urlparse.urlparse('rabbit://user:password@host/' 'vhost?foo=bar&amqp_auto_delete=true'), urlparse.urlparse( ironic_neutron_agent._get_notification_transport_url())) self.conf.config( transport_url=('rabbit://user:password@host/vhost?foo=bar&' 'amqp_auto_delete=false')) self.assertEqual( # NOTE(hjensas): Parse the url's when comparing, different versions # may sort the query different. urlparse.urlparse('rabbit://user:password@host' '/vhost?foo=bar&amqp_auto_delete=true'), urlparse.urlparse( ironic_neutron_agent._get_notification_transport_url())) def test__get_notification_transport_url_auto_delete_enabled( self, mock_conn, mock_ir_client): self.conf.config(amqp_auto_delete=True, group='oslo_messaging_rabbit') self.assertEqual( 'rabbit://user:password@host/', ironic_neutron_agent._get_notification_transport_url()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/tests/unit/openconfig/0000775000175000017500000000000000000000000026340 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/openconfig/__init__.py0000664000175000017500000000000000000000000030437 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/openconfig/test_interfaces.py0000664000175000017500000002221500000000000032076 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from xml.etree import ElementTree from oslotest import base from networking_baremetal import constants from networking_baremetal.openconfig.interfaces import aggregate from networking_baremetal.openconfig.interfaces import ethernet from networking_baremetal.openconfig.interfaces import interfaces from networking_baremetal.openconfig.vlan import vlan class TestInterfaces(base.BaseTestCase): @mock.patch.object(ethernet, 'InterfacesEthernetConfig', autospec=True) @mock.patch.object(vlan, 'VlanSwitchedVlan', autospec=True) def test_interfaces_ethernet(self, mock_sw_vlan, mock_eth_conf): mock_sw_vlan.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-switched-vlan')) mock_eth_conf.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-ethernet-config')) if_eth = ethernet.InterfacesEthernet() mock_sw_vlan.assert_called_with() element = if_eth.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = (f'' '' '' '') self.assertEqual(expected, xml_str) @mock.patch.object(aggregate, 'InterfacesAggregationConfig', autospec=True) @mock.patch.object(vlan, 'VlanSwitchedVlan', autospec=True) def test_interfaces_aggregate(self, mock_sw_vlan, mock_agg_conf): mock_sw_vlan.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-switched-vlan')) mock_agg_conf.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-aggregate-config')) if_aggregate = aggregate.InterfacesAggregation() mock_sw_vlan.assert_called_with() element = if_aggregate.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = (f'' '' '' '') self.assertEqual(expected, xml_str) @mock.patch.object(interfaces, 'InterfaceAggregate', autospec=True) @mock.patch.object(interfaces, 'InterfaceEthernet', autospec=True) def test_interfaces_interfaces(self, mock_iface_eth, mock_iface_aggregate): mock_iface_eth.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-ethernet')) mock_iface_aggregate.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-aggregate')) ifaces = interfaces.Interfaces() iface = ifaces.add('eth0/1') iface2 = ifaces.add('po10', interface_type='aggregate') mock_iface_eth.assert_called_with('eth0/1') mock_iface_aggregate.assert_called_with('po10') self.assertEqual([iface, iface2], ifaces.interfaces) element = ifaces.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = (f'' '' '' '') self.assertEqual(expected, xml_str) @mock.patch.object(ethernet, 'InterfacesEthernet', autospec=True) @mock.patch.object(interfaces, 'InterfaceConfig', autospec=True) def test_interfaces_interface_ethernet(self, mock_if_conf, mock_if_eth): mock_if_conf.return_value.to_xml_element.return_value = ( ElementTree.Element('fake_config')) mock_if_eth.return_value.to_xml_element.return_value = ( ElementTree.Element('fake_ethernet')) interface = interfaces.InterfaceEthernet('eth0/1') mock_if_conf.assert_called_with() mock_if_eth.assert_called_with() self.assertEqual('eth0/1', interface.name) self.assertEqual(mock_if_conf(), interface.config) self.assertEqual(mock_if_eth(), interface.ethernet) element = interface.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'eth0/1' '' '' '') self.assertEqual(expected, xml_str) not_string = 10 self.assertRaises(TypeError, interfaces.InterfaceEthernet, not_string) @mock.patch.object(aggregate, 'InterfacesAggregation', autospec=True) @mock.patch.object(interfaces, 'InterfaceConfig', autospec=True) def test_interfaces_interface_aggregate(self, mock_if_conf, mock_if_aggr): mock_if_conf.return_value.to_xml_element.return_value = ( ElementTree.Element('fake_config')) mock_if_aggr.return_value.to_xml_element.return_value = ( ElementTree.Element('fake_aggregation')) interface = interfaces.InterfaceAggregate('po10') mock_if_conf.assert_called_with() mock_if_aggr.assert_called_with() self.assertEqual('po10', interface.name) self.assertEqual(mock_if_conf(), interface.config) self.assertEqual(mock_if_aggr(), interface.aggregation) element = interface.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'po10' '' '' '') self.assertEqual(expected, xml_str) not_string = 10 self.assertRaises(TypeError, interfaces.InterfaceEthernet, not_string) def test_interfaces_interface_config(self): if_conf = interfaces.InterfaceConfig() self.assertEqual(constants.NetconfEditConfigOperation.MERGE.value, if_conf.operation) self.assertRaises(ValueError, interfaces.InterfaceConfig, **dict(operation='invalid')) self.assertRaises(TypeError, interfaces.InterfaceConfig, **dict(enabled='not_bool')) self.assertRaises(TypeError, interfaces.InterfaceConfig, **dict(description=10)) # Not string self.assertRaises(TypeError, interfaces.InterfaceConfig, **dict(mtu='not_int')) if_conf.name = 'test1' if_conf.enabled = True if_conf.description = 'Description' if_conf.mtu = 9000 element = if_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'test1' 'Description' 'true' '9000' '') self.assertEqual(expected, xml_str) del if_conf.name if_conf.operation = 'remove' if_conf.description = '' if_conf.mtu = 0 if_conf.enabled = False element = if_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' '' 'false' '0' '') self.assertEqual(expected, xml_str) def test_interfaces_interface_ethernet_config(self): eth_conf = ethernet.InterfacesEthernetConfig() self.assertEqual(constants.NetconfEditConfigOperation.MERGE.value, eth_conf.operation) self.assertRaises(ValueError, ethernet.InterfacesEthernetConfig, **dict(operation='invalid')) eth_conf.aggregate_id = 'po100' element = eth_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'po100' '') self.assertEqual(expected, xml_str) eth_conf = ethernet.InterfacesEthernetConfig() eth_conf.operation = 'remove' element = eth_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = '' self.assertEqual(expected, xml_str) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/openconfig/test_lacp.py0000664000175000017500000001154300000000000030674 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from xml.etree import ElementTree from oslotest import base from networking_baremetal import constants from networking_baremetal.openconfig.lacp import lacp class TestOpenConfigLACP(base.BaseTestCase): @mock.patch.object(lacp, 'LACPInterfaces', autospec=True) def test_openconfig_lacp(self, mock_lcap_ifaces): mock_lcap_ifaces.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-lacp-interfaces')) oc_lacp = lacp.LACP() mock_lcap_ifaces.assert_called_with() mock_lcap_ifaces.return_value.__len__.return_value = 1 element = oc_lacp.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = (f'' '' '') self.assertEqual(expected, xml_str) @mock.patch.object(lacp, 'LACPInterface', autospec=True) def test_openconfig_lacp_interfaces(self, mock_lacp_iface): mock_lacp_iface.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-lacp-interface')) oc_lacp_ifaces = lacp.LACPInterfaces() self.assertEqual([], oc_lacp_ifaces.interfaces) oc_lacp_iface = oc_lacp_ifaces.add('lacp-iface-name') mock_lacp_iface.assert_called_with('lacp-iface-name') self.assertEqual([oc_lacp_iface], oc_lacp_ifaces.interfaces) element = oc_lacp_ifaces.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' '' '') self.assertEqual(expected, xml_str) @mock.patch.object(lacp, 'LACPInterfaceConfig', autospec=True) def test_openconfig_lacp_interface(self, mock_lacp_if_conf): mock_lacp_if_conf.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-lacp-interface-config')) self.assertRaises(TypeError, lacp.LACPInterface, int(20)) oc_lacp_iface = lacp.LACPInterface('lacp-iface-name') self.assertEqual('lacp-iface-name', oc_lacp_iface.name) element = oc_lacp_iface.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'lacp-iface-name' '' '') self.assertEqual(expected, xml_str) oc_lacp_iface.operation = constants.NetconfEditConfigOperation.REMOVE element = oc_lacp_iface.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'lacp-iface-name' '' '') self.assertEqual(expected, xml_str) def test_openconfig_lacp_interface_config(self): self.assertRaises(ValueError, lacp.LACPInterfaceConfig, 'name', **dict(operation='invalid')) self.assertRaises(ValueError, lacp.LACPInterfaceConfig, 'name', **dict(interval='invalid')) self.assertRaises(ValueError, lacp.LACPInterfaceConfig, 'name', **dict(lacp_mode='invalid')) lacp_if_conf = lacp.LACPInterfaceConfig('lacp-iface-name') element = lacp_if_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'lacp-iface-name' 'SLOW' 'ACTIVE' '') self.assertEqual(expected, xml_str) lacp_if_conf.interval = constants.LACP_PERIOD_FAST lacp_if_conf.lacp_mode = constants.LACP_ACTIVITY_PASSIVE element = lacp_if_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'lacp-iface-name' 'FAST' 'PASSIVE' '') self.assertEqual(expected, xml_str) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/openconfig/test_network_instance.py0000664000175000017500000000444500000000000033335 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from xml.etree import ElementTree from oslotest import base from networking_baremetal.openconfig.network_instance import network_instance from networking_baremetal.openconfig.vlan import vlan class TestNetworkInstance(base.BaseTestCase): @mock.patch.object(network_instance, 'NetworkInstance', autospec=True) def test_network_instances(self, mock_net_instance): mock_net_instance.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-net-instance')) net_instances = network_instance.NetworkInstances() net_instance = net_instances.add('default') self.assertEqual([net_instance], net_instances.network_instances) element = net_instances.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = (f'' '' '') self.assertEqual(expected, xml_str) @mock.patch.object(vlan, 'Vlans', autospec=True) def test_network_instance(self, mock_oc_vlans): mock_oc_vlans.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-oc-vlans')) mock_oc_vlans.return_value.__len__.return_value = 1 net_instance = network_instance.NetworkInstance('default') self.assertEqual(mock_oc_vlans(), net_instance.vlans) element = net_instance.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'default' '' '') self.assertEqual(expected, xml_str) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/openconfig/test_vlan.py0000664000175000017500000001600400000000000030712 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from xml.etree import ElementTree from oslotest import base from networking_baremetal import constants from networking_baremetal.openconfig.vlan import vlan class TestVlan(base.BaseTestCase): @mock.patch.object(vlan, 'Vlan', autospec=True) def test_vlans(self, mock_vlan): mock_vlan.return_value.to_xml_element.side_effect = [ ElementTree.Element('fake-vlan-10'), ElementTree.Element('fake-vlan-20') ] oc_vlans = vlan.Vlans() oc_vlan = oc_vlans.add(10) mock_vlan.assert_called_with(10) self.assertEqual([oc_vlan], oc_vlans.vlans) remove_vlan = oc_vlans.remove(20) self.assertEqual([oc_vlan, remove_vlan], oc_vlans.vlans) element = oc_vlans.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = (f'' '' '' '') self.assertEqual(expected, xml_str) @mock.patch.object(vlan, 'VlanConfig', autospec=True) def test_vlan(self, mock_vlan_conf): mock_vlan_conf.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-vlan-conf')) oc_vlan = vlan.Vlan(10) self.assertEqual(constants.NetconfEditConfigOperation.MERGE.value, oc_vlan.operation) self.assertEqual(10, oc_vlan.vlan_id) self.assertRaises(TypeError, vlan.Vlan, 'not-int') self.assertRaises(ValueError, vlan.Vlan, 20, **dict(operation='invalid')) element = oc_vlan.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' '10' '' '') self.assertEqual(expected, xml_str) oc_vlan.operation = 'remove' element = oc_vlan.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' '10' '' '') self.assertEqual(expected, xml_str) def test_vlan_config(self): vlan_conf = vlan.VlanConfig() self.assertEqual(constants.NetconfEditConfigOperation.MERGE.value, vlan_conf.operation) self.assertRaises(ValueError, vlan.VlanConfig, **dict(operation='invalid')) self.assertRaises(TypeError, vlan.VlanConfig, **dict(vlan_id='not-int')) self.assertRaises(TypeError, vlan.VlanConfig, **dict(name=20)) # Not str self.assertRaises(ValueError, vlan.VlanConfig, **dict(status='invalid')) vlan_conf.vlan_id = 10 vlan_conf.name = 'Vlan10' vlan_conf.status = 'ACTIVE' element = vlan_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' '10' 'Vlan10' 'ACTIVE' '') self.assertEqual(expected, xml_str) vlan_conf.operation = 'delete' vlan_conf.status = 'SUSPENDED' element = vlan_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' '10' 'Vlan10' 'SUSPENDED' '') self.assertEqual(expected, xml_str) @mock.patch.object(vlan, 'VlanSwitchedConfig', autospec=True) def test_switched_vlan(self, mock_switched_vlan_conf): mock_switched_vlan_conf.return_value.to_xml_element.return_value = ( ElementTree.Element('fake-switched-vlan-config')) switched_vlan = vlan.VlanSwitchedVlan() element = switched_vlan.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = (f'' '' '') self.assertEqual(expected, xml_str) def test_switched_vlan_config(self): swithced_vlan_conf = vlan.VlanSwitchedConfig() self.assertEqual(constants.NetconfEditConfigOperation.MERGE.value, swithced_vlan_conf.operation) self.assertRaises(ValueError, vlan.VlanSwitchedConfig, **dict(operation='invalid')) self.assertRaises(ValueError, vlan.VlanSwitchedConfig, **dict(interface_mode='invalid')) self.assertRaises(TypeError, vlan.VlanSwitchedConfig, **dict(native_vlan='not-int')) self.assertRaises(TypeError, vlan.VlanSwitchedConfig, **dict(access_vlan='not-int')) swithced_vlan_conf.interface_mode = 'ACCESS' swithced_vlan_conf.access_vlan = 20 element = swithced_vlan_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'ACCESS' '20' '') self.assertEqual(expected, xml_str) del swithced_vlan_conf.access_vlan swithced_vlan_conf.interface_mode = 'TRUNK' swithced_vlan_conf.native_vlan = 30 swithced_vlan_conf.trunk_vlans.add('10..50') swithced_vlan_conf.trunk_vlans.add('99') swithced_vlan_conf.trunk_vlans.add(88) swithced_vlan_conf.trunk_vlans.add('200..300') element = swithced_vlan_conf.to_xml_element() xml_str = ElementTree.tostring(element).decode("utf-8") expected = ('' 'TRUNK' '30' '10..50' '99' '88' '200..300' '') self.assertEqual(expected, xml_str) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8411124 networking-baremetal-6.4.0/networking_baremetal/tests/unit/plugins/0000775000175000017500000000000000000000000025672 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/plugins/__init__.py0000664000175000017500000000000000000000000027771 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1724902937.845112 networking-baremetal-6.4.0/networking_baremetal/tests/unit/plugins/ml2/0000775000175000017500000000000000000000000026364 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/plugins/ml2/__init__.py0000664000175000017500000000000000000000000030463 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/plugins/ml2/test_baremetal_mech.py0000664000175000017500000006341200000000000032733 0ustar00zuulzuul00000000000000# Copyright 2017 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from neutron.db import provisioning_blocks from neutron.plugins.ml2 import driver_context from neutron.tests.unit.plugins.ml2 import _test_mech_agent as base from neutron_lib.api.definitions import portbindings from neutron_lib import constants as n_const from neutron_lib.plugins.ml2 import api from oslo_config import fixture as config_fixture from networking_baremetal import common from networking_baremetal import config from networking_baremetal import constants from networking_baremetal import exceptions from networking_baremetal.plugins.ml2 import baremetal_mech from networking_baremetal.tests.unit.plugins.ml2 import utils as ml2_utils class TestBaremetalMechDriver(base.AgentMechanismBaseTestCase): VIF_TYPE = portbindings.VIF_TYPE_OTHER VIF_DETAILS = None AGENT_TYPE = constants.BAREMETAL_AGENT_TYPE GOOD_CONFIGS = { 'bridge_mappings': {'fake_physical_network': 'fake_physnet'} } BAD_CONFIGS = { 'bridge_mappings': {'wrong_physical_network': 'wrong_physnet'} } AGENTS = [{'agent_type': AGENT_TYPE, 'alive': True, 'configurations': GOOD_CONFIGS, 'host': 'host'}] AGENTS_DEAD = [ {'agent_type': AGENT_TYPE, 'alive': False, 'configurations': GOOD_CONFIGS, 'host': 'dead_host'} ] AGENTS_BAD = [ {'agent_type': AGENT_TYPE, 'alive': False, 'configurations': GOOD_CONFIGS, 'host': 'bad_host_1'}, {'agent_type': AGENT_TYPE, 'alive': True, 'configurations': BAD_CONFIGS, 'host': 'bad_host_2'} ] VNIC_TYPE = portbindings.VNIC_BAREMETAL def setUp(self): super(TestBaremetalMechDriver, self).setUp() self.driver = baremetal_mech.BaremetalMechanismDriver() self.driver.initialize() def _make_port_ctx(self, agents): segments = [{api.ID: 'local_segment_id', api.PHYSICAL_NETWORK: 'fake_physical_network', api.NETWORK_TYPE: n_const.TYPE_FLAT}] return base.FakePortContext(self.AGENT_TYPE, agents, segments, vnic_type=self.VNIC_TYPE) def test_initialize(self): self.assertEqual([portbindings.VNIC_BAREMETAL], self.driver.supported_vnic_types) self.assertEqual(portbindings.VIF_TYPE_OTHER, self.driver.vif_type) def test_get_allowed_network_types(self): agent_mock = mock.Mock() allowed_network_types = self.driver.get_allowed_network_types( agent_mock) self.assertEqual(allowed_network_types, [n_const.TYPE_FLAT, n_const.TYPE_VLAN]) @mock.patch.object(provisioning_blocks, 'provisioning_complete', autospec=True) def test_update_port_postcommit_not_bound(self, mpb_pc): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network() m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port(network_id=m_nc.current['id']) m_pc.network = m_nc self.driver.update_port_postcommit(m_pc) self.assertFalse(mpb_pc.called) @mock.patch.object(provisioning_blocks, 'provisioning_complete', autospec=True) def test_update_port_postcommit_unsupported_vnic_type_not_bound( self, mpb_pc): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network() m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port( network_id=m_nc.current['id'], vnic_type=portbindings.VNIC_MACVTAP, vif_type=portbindings.VIF_TYPE_OTHER) m_pc.network = m_nc self.driver.update_port_postcommit(m_pc) self.assertFalse(mpb_pc.called) @mock.patch.object(provisioning_blocks, 'provisioning_complete', autospec=True) def test_update_port_postcommit_supported_vnic_type_bound( self, mpb_pc): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network() m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port( network_id=m_nc.current['id'], vnic_type=portbindings.VNIC_BAREMETAL, vif_type=portbindings.VIF_TYPE_OTHER) m_pc._plugin_context = 'plugin_context' m_pc.network = m_nc self.driver.update_port_postcommit(m_pc) mpb_pc.assert_called_once_with('plugin_context', m_pc.current['id'], 'port', 'BAREMETAL_DRV_ENTITIY') @mock.patch.object(provisioning_blocks, 'add_provisioning_component', autospec=True) def test_bind_port_unsupported_network_type(self, mpb_pc): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VXLAN) m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port( network_id=m_nc.current['id'], vnic_type=portbindings.VNIC_BAREMETAL, vif_type=portbindings.VIF_TYPE_OTHER) m_pc.network = m_nc m_pc.segments_to_bind = [ ml2_utils.get_test_segment(network_type=n_const.TYPE_VXLAN)] self.driver.bind_port(m_pc) self.assertFalse(mpb_pc.called) @mock.patch.object(provisioning_blocks, 'add_provisioning_component', autospec=True) def test_bind_port_unsupported_vnic_type(self, mpb_pc): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_FLAT) m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port( network_id=m_nc.current['id'], vnic_type='unsupported') m_pc.network = m_nc m_pc.segments_to_bind = [ ml2_utils.get_test_segment(network_type=n_const.TYPE_FLAT)] self.driver.bind_port(m_pc) self.assertFalse(mpb_pc.called) def test_empty_methods(self): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network() m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port(network_id=m_nc.current['id']) m_pc.network = m_nc.current m_sc = mock.create_autospec(driver_context.SubnetContext) m_sc.current = ml2_utils.get_test_subnet( network_id=m_nc.current['id']) m_sc.network = m_nc self.driver.create_network_precommit(m_nc) self.driver.create_network_postcommit(m_nc) self.driver.update_network_precommit(m_nc) self.driver.update_network_postcommit(m_nc) self.driver.delete_network_precommit(m_nc) self.driver.delete_network_postcommit(m_nc) self.driver.create_subnet_precommit(m_sc) self.driver.create_subnet_postcommit(m_sc) self.driver.update_subnet_precommit(m_sc) self.driver.update_subnet_postcommit(m_sc) self.driver.delete_subnet_precommit(m_sc) self.driver.delete_subnet_postcommit(m_sc) self.driver.create_port_precommit(m_pc) self.driver.create_port_postcommit(m_pc) self.driver.update_port_precommit(m_pc) self.driver.update_port_postcommit(m_pc) self.driver.delete_port_precommit(m_pc) self.driver.delete_port_postcommit(m_pc) class TestBaremetalMechDriverFakeDriver(base.AgentMechanismBaseTestCase): VIF_TYPE = portbindings.VIF_TYPE_OTHER VIF_DETAILS = None AGENT_TYPE = constants.BAREMETAL_AGENT_TYPE AGENT_CONF = {'bridge_mappings': {'fake_physical_network': 'fake_physnet'}} AGENTS = [{'agent_type': AGENT_TYPE, 'alive': True, 'configurations': AGENT_CONF, 'host': 'host'}] VNIC_TYPE = portbindings.VNIC_BAREMETAL def setUp(self): super(TestBaremetalMechDriverFakeDriver, self).setUp() mock_manager = mock.patch.object(common, 'driver_mgr', autospec=True) self.mock_manager = mock_manager.start() self.addCleanup(mock_manager.stop) self.mock_driver = mock.MagicMock() self.mock_manager.return_value = self.mock_driver self.conf = self.useFixture(config_fixture.Config()) self.conf.config(enabled_devices=['foo'], group='networking_baremetal') self.conf.register_opts(config._opts + config._device_opts, group='foo') self.conf.config(driver='test-driver', switch_id='aa:bb:cc:dd:ee:ff', switch_info='foo', physical_networks=['fake_physical_network'], group='foo') self.driver = baremetal_mech.BaremetalMechanismDriver() self.driver.initialize() self.mock_manager.assert_called_once_with('foo') self.mock_driver.load_config.assert_called_once() self.mock_driver.validate.assert_called_once() self.mock_manager.reset_mock() self.mock_driver.reset_mock() def _make_port_ctx(self, agents, profile): segments = [{api.ID: 'local_segment_id', api.PHYSICAL_NETWORK: 'fake_physical_network', api.NETWORK_TYPE: n_const.TYPE_FLAT}] return base.FakePortContext(self.AGENT_TYPE, agents, segments, vnic_type=self.VNIC_TYPE, profile=profile) def test__is_bound(self): m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port( network_id='network-id', vnic_type=portbindings.VNIC_BAREMETAL, vif_type=None) self.assertFalse(self.driver._is_bound(m_pc.current)) m_pc.current[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OTHER self.assertTrue(self.driver._is_bound(m_pc.current)) def test_create_network_postcommit_flat(self): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_FLAT) self.driver.create_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() def test_update_network_postcommit_flat(self): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_FLAT) self.driver.update_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() def test_delete_network_postcommit_flat(self): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_FLAT) self.driver.delete_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() def test_create_network_postcommit_vlan(self): # VLAN but no segmentation ID m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN) self.driver.create_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_manager.assert_not_called() # VLAN with segmentation ID, but not on physical network self.mock_manager.reset_mock() self.mock_driver.reset_mock() m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN, segmentation_id=10) self.driver.create_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() # VLAN with segmentation ID, on physical network self.mock_manager.reset_mock() self.mock_driver.reset_mock() m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN, segmentation_id=10, physical_network='fake_physical_network') self.driver.create_network_postcommit(m_nc) self.mock_manager.assert_called_once_with('foo') self.mock_driver.create_network.assert_called_once_with(m_nc) # Device VLAN management disabled in config self.conf.config(manage_vlans=False, group='foo') self.mock_manager.reset_mock() self.mock_driver.reset_mock() m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN, segmentation_id=10, physical_network='fake_physical_network') self.driver.create_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() def test_update_network_postcommit_vlan(self): # VLAN but no segmentation ID m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN) m_nc.original = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN) self.driver.update_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() # With physical network self.mock_manager.reset_mock() self.mock_driver.reset_mock() m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN, segmentation_id=10, physical_network='fake_physical_network') self.driver.update_network_postcommit(m_nc) self.mock_manager.assert_called_once_with('foo') self.mock_driver.update_network.assert_called_once_with(m_nc) # VLAN management disabled self.mock_manager.reset_mock() self.mock_driver.reset_mock() self.conf.config(manage_vlans=False, group='foo') self.driver.update_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() # Device not on physical network self.mock_manager.reset_mock() self.mock_driver.reset_mock() m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN, segmentation_id=10, physical_network='fake_physical_network') self.conf.config(physical_networks=['not-connected-physnet'], manage_vlans=True, group='foo') self.driver.update_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() def test_delete_network_postcommit_vlan(self): # VLAN but no segmentation ID m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN) m_nc.original = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN) self.driver.delete_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() # VLAN ID and matching physnet self.mock_manager.reset_mock() self.mock_driver.reset_mock() m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN, segmentation_id=10, physical_network='fake_physical_network') self.driver.delete_network_postcommit(m_nc) self.mock_manager.assert_called_once_with('foo') self.mock_driver.delete_network.assert_called_once_with(m_nc) # Not on physnet self.mock_manager.reset_mock() self.mock_driver.reset_mock() m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN, segmentation_id=10, physical_network='not-on-physnet') self.driver.delete_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() # VLAN management disabled self.mock_manager.reset_mock() self.mock_driver.reset_mock() self.conf.config(manage_vlans=False, group='foo') m_nc.current = ml2_utils.get_test_network( network_type=n_const.TYPE_VLAN, segmentation_id=10, physical_network='fake_physical_network') self.driver.delete_network_postcommit(m_nc) self.mock_manager.assert_not_called() self.mock_driver.assert_not_called() @mock.patch.object(provisioning_blocks, 'add_provisioning_component', autospec=True) def test_bind_port(self, mock_p_blocks): binding_profile = {} lli = binding_profile['local_link_information'] = [] lli.append({'port_id': 'test1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}) context = self._make_port_ctx(self.AGENTS, binding_profile) context._plugin_context = 'plugin_context' self.assertIsNone(context._bound_vif_type) self.driver.bind_port(context) self.mock_manager.assert_called_once_with('foo') self.mock_driver.create_port.assert_called_once_with( context, context.segments_to_bind[0], lli) self.assertEqual(context._bound_vif_type, self.driver.vif_type) @mock.patch.object(provisioning_blocks, 'add_provisioning_component', autospec=True) def test_no_device_does_bind_port(self, mock_p_blocks): binding_profile = {} lli = binding_profile['local_link_information'] = [] lli.append({'port_id': 'test1/1', 'switch_id': '11:11:11:11:11:11', 'switch_info': 'not-such-device'}) context = self._make_port_ctx(self.AGENTS, binding_profile) context._plugin_context = 'plugin_context' self.assertIsNone(context._bound_vif_type) self.driver.bind_port(context) self.mock_manager.assert_not_called() self.mock_driver.create_port.assert_not_called() self.assertIsNone(context._bound_vif_type) mock_p_blocks.assert_not_called() @mock.patch.object(provisioning_blocks, 'add_provisioning_component', autospec=True) def test_driver_load_error_does_not_bind_port(self, mock_p_blocks): binding_profile = {} lli = binding_profile['local_link_information'] = [] lli.append({'port_id': 'test1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}) context = self._make_port_ctx(self.AGENTS, binding_profile) context._plugin_context = 'plugin_context' self.mock_manager.side_effect = exceptions.DriverEntrypointLoadError( entry_point='entry_point', err='ERROR_MSG' ) self.assertIsNone(context._bound_vif_type) self.driver.bind_port(context) self.mock_manager.assert_called_once_with('foo') self.mock_driver.create_port.assert_not_called() # The port will not bind self.assertIsNone(context._bound_vif_type) @mock.patch.object(provisioning_blocks, 'add_provisioning_component', autospec=True) def test_not_on_physnet_does_not_bind_port(self, mock_p_blocks): binding_profile = {} lli = binding_profile['local_link_information'] = [] lli.append({'port_id': 'test1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}) context = self._make_port_ctx(self.AGENTS, binding_profile) context._plugin_context = 'plugin_context' self.conf.config(physical_networks='other_physnet', group='foo') self.assertIsNone(context._bound_vif_type) self.driver.bind_port(context) self.mock_manager.assert_not_called() self.mock_driver.create_port.assert_not_called() # The port will not bind self.assertIsNone(context._bound_vif_type) @mock.patch.object(provisioning_blocks, 'add_provisioning_component', autospec=True) def test_bond_mode_supported_bind_port(self, mock_p_blocks): binding_profile = {} lli = binding_profile['local_link_information'] = [] llg = binding_profile['local_group_information'] = {} llg['bond_mode'] = '802.3ad' lli.append({'port_id': 'test1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}) context = self._make_port_ctx(self.AGENTS, binding_profile) context._plugin_context = 'plugin_context' self.mock_driver.SUPPORTED_BOND_MODES = {'802.3ad'} self.assertIsNone(context._bound_vif_type) self.driver.bind_port(context) self.mock_manager.assert_called_once_with('foo') self.mock_driver.create_port.assert_called_once_with( context, context.segments_to_bind[0], lli) self.assertEqual(context._bound_vif_type, self.driver.vif_type) @mock.patch.object(provisioning_blocks, 'add_provisioning_component', autospec=True) def test_bond_mode_unsupported_does_not_bind_port(self, mock_p_blocks): binding_profile = {} lli = binding_profile['local_link_information'] = [] llg = binding_profile['local_group_information'] = {} llg['bond_mode'] = 'unsupported' lli.append({'port_id': 'test1/1', 'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}) context = self._make_port_ctx(self.AGENTS, binding_profile) context._plugin_context = 'plugin_context' self.mock_driver.SUPPORTED_BOND_MODES = {'802.3ad'} self.assertIsNone(context._bound_vif_type) self.driver.bind_port(context) self.mock_manager.assert_called_once_with('foo') self.mock_driver.create_port.assert_not_called() self.assertIsNone(context._bound_vif_type) @mock.patch.object(provisioning_blocks, 'add_provisioning_component', autospec=True) def test_no_port_id_does_not_bind_port(self, mock_p_blocks): binding_profile = {} lli = binding_profile['local_link_information'] = [] lli.append({'switch_id': 'aa:bb:cc:dd:ee:ff', 'switch_info': 'foo'}) context = self._make_port_ctx(self.AGENTS, binding_profile) context._plugin_context = 'plugin_context' self.assertIsNone(context._bound_vif_type) self.driver.bind_port(context) self.mock_manager.assert_not_called() self.mock_driver.create_port.assert_not_called() self.assertIsNone(context._bound_vif_type) @mock.patch.object(provisioning_blocks, 'provisioning_complete', autospec=True) def test_port_bound_update_port(self, mock_p_blocks): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network() m_nc.original = ml2_utils.get_test_network() m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port( network_id=m_nc.current['id'], vnic_type=portbindings.VNIC_BAREMETAL, vif_type=portbindings.VIF_TYPE_OTHER) m_pc.original = ml2_utils.get_test_port( network_id=m_nc.current['id'], vnic_type=portbindings.VNIC_BAREMETAL, vif_type=portbindings.VIF_TYPE_OTHER) m_pc.network = m_nc m_pc._plugin_context = 'plugin_context' m_pc._bound_vif_type = self.driver.vif_type self.driver.update_port_postcommit(m_pc) self.mock_manager.assert_called_once_with('foo') self.mock_driver.update_port.assert_called_once_with( m_pc, m_pc.current['binding:profile']['local_link_information']) def test_port_unbound_unplug_port(self): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network() m_nc.original = ml2_utils.get_test_network() m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port( network_id=m_nc.current['id'], vnic_type=None, vif_type=None) m_pc.original = ml2_utils.get_test_port( network_id=m_nc.current['id'], vnic_type=portbindings.VNIC_BAREMETAL, vif_type=portbindings.VIF_TYPE_OTHER) m_pc.network = m_nc self.driver.update_port_postcommit(m_pc) self.mock_manager.assert_called_once_with('foo') self.mock_driver.delete_port.assert_called_once_with( m_pc, m_pc.current['binding:profile']['local_link_information'], current=False) def test_delete_port(self): m_nc = mock.create_autospec(driver_context.NetworkContext) m_nc.current = ml2_utils.get_test_network() m_pc = mock.create_autospec(driver_context.PortContext) m_pc.current = ml2_utils.get_test_port( network_id=m_nc.current['id'], vnic_type=portbindings.VNIC_BAREMETAL, vif_type=portbindings.VIF_TYPE_OTHER) m_pc.network = m_nc self.driver.delete_port_postcommit(m_pc) self.mock_manager.assert_called_once_with('foo') self.mock_driver.delete_port.assert_called_once_with( m_pc, m_pc.current['binding:profile']['local_link_information'], current=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/networking_baremetal/tests/unit/plugins/ml2/utils.py0000664000175000017500000001213700000000000030102 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Mirantis, 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 oslo_utils import uuidutils def get_test_network(**kw): """Return a network object with appropriate attributes.""" result = { "provider:physical_network": kw.get("physical_network", "mynetwork"), "ipv6_address_scope": kw.get("ipv6_address_scope", None), "revision_number": kw.get("revision_number", 7), "port_security_enabled": kw.get("port_security_enabled", True), "mtu": kw.get("mtu", 1500), "id": kw.get("id", uuidutils.generate_uuid()), "router:external": kw.get("router:external", False), "availability_zone_hints": kw.get("availability_zone_hints", []), "availability_zones": kw.get("availability_zones", ["nova"]), "ipv4_address_scope": kw.get("ipv4_address_scope", None), "shared": kw.get("shared", False), "project_id": kw.get("project_id", uuidutils.generate_uuid()), "status": kw.get("status", "ACTIVE"), "subnets": kw.get("subnets", []), "description": kw.get("description", ""), "tags": kw.get("tags", []), "provider:segmentation_id": kw.get("segmentation_id", 113), "name": kw.get("name", "private"), "admin_state_up": kw.get("admin_state_up", True), "tenant_id": kw.get("tenant_id", uuidutils.generate_uuid()), "provider:network_type": kw.get("network_type", "flat") } return result def get_test_port(network_id, **kw): """Return a port object with appropriate attributes.""" result = { "status": kw.get("status", "DOWN"), "binding:host_id": kw.get("host_id", "aaa.host"), "description": kw.get("description", ""), "allowed_address_pairs": kw.get("allowed_address_pairs", []), "tags": kw.get("tags", []), "extra_dhcp_opts": kw.get("extra_dhcp_opts", []), "device_owner": kw.get("device_owner", "baremetal:host"), "revision_number": kw.get("revision_number", 7), "port_security_enabled": kw.get("port_security_enabled", False), "binding:profile": kw.get("binding_profile", {'local_link_information': [ {'switch_info': 'foo', 'port_id': 'Gig0/1', 'switch_id': 'aa:bb:cc:dd:ee:ff'}]}), "fixed_ips": kw.get("fixed_ips", []), "id": kw.get("id", uuidutils.generate_uuid()), "security_groups": kw.get("security_groups", []), "device_id": kw.get("device_id", ""), "name": kw.get("name", "Port1"), "admin_state_up": kw.get("admin_state_up", True), "network_id": network_id, "tenant_id": kw.get("tenant_id", uuidutils.generate_uuid()), "binding:vif_details": kw.get("vif_details", {}), "binding:vnic_type": kw.get("vnic_type", "baremetal"), "binding:vif_type": kw.get("vif_type", "unbound"), "mac_address": kw.get("mac_address", "fa:16:3e:c2:2a:8f"), "project_id": kw.get("project_id", uuidutils.generate_uuid()) } return result def get_test_subnet(network_id, **kw): """Return a subnet object with appropriate attributes.""" result = { "service_types": kw.get("service_types", []), "description": kw.get("description", ""), "enable_dhcp": kw.get("enable_dhcp", True), "tags": kw.get("tags", []), "network_id": network_id, "tenant_id": kw.get("tenant_id", uuidutils.generate_uuid()), "dns_nameservers": kw.get("dns_nameservers", []), "gateway_ip": kw.get("gateway_ip", "10.1.0.1"), "ipv6_ra_mode": kw.get("ipv6_ra_mode", None), "allocation_pools": kw.get("allocation_pools", [{"start": "10.1.0.2", "end": "10.1.0.62"}]), "host_routes": kw.get("host_routes", []), "revision_number": kw.get("revision_number", 7), "ip_version": kw.get("ip_version", 4), "ipv6_address_mode": kw.get("ipv6_address_mode", None), "cidr": kw.get("cidr", "10.1.0.0/26"), "project_id": kw.get("project_id", uuidutils.generate_uuid()), "id": kw.get("id", uuidutils.generate_uuid()), "subnetpool_id": kw.get("subnetpool_id", uuidutils.generate_uuid()), "name": kw.get("name", "subnet0") } return result def get_test_segment(**kw): result = { 'segmentation_id': kw.get('segmentation_id', '123'), 'network_type': kw.get('network_type', 'flat'), 'id': uuidutils.generate_uuid() } return result ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8371127 networking-baremetal-6.4.0/networking_baremetal.egg-info/0000775000175000017500000000000000000000000023562 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/networking_baremetal.egg-info/PKG-INFO0000664000175000017500000000331300000000000024657 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: networking-baremetal Version: 6.4.0 Summary: Neutron plugin that provides deep Ironic/Neutron integration. Home-page: https://docs.openstack.org/networking-baremetal/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: networking-baremetal plugin --------------------------- This project's goal is to provide deep integration between the Networking service and the Bare Metal service and advanced networking features like notifications of port status changes and routed networks support in clouds with Bare Metal service. * Free software: Apache license * Documentation: http://docs.openstack.org/networking-baremetal/latest * Source: http://opendev.org/openstack/networking-baremetal * Bugs: https://bugs.launchpad.net/networking-baremetal * Release notes: https://docs.openstack.org/releasenotes/networking-baremetal/ Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/networking_baremetal.egg-info/SOURCES.txt0000664000175000017500000001333400000000000025452 0ustar00zuulzuul00000000000000.stestr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini devstack/plugin.sh devstack/settings devstack/lib/networking-baremetal doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/configuration/index.rst doc/source/configuration/ironic-neutron-agent/config.rst doc/source/configuration/ironic-neutron-agent/index.rst doc/source/configuration/ironic-neutron-agent/sample-config.rst doc/source/configuration/ml2/index.rst doc/source/configuration/ml2/device_drivers/common_config.rst doc/source/configuration/ml2/device_drivers/index.rst doc/source/configuration/ml2/device_drivers/netconf-openconfig.rst doc/source/contributor/index.rst doc/source/contributor/quickstart-multitenant.rst doc/source/contributor/quickstart-netconf-openconfig.rst doc/source/contributor/quickstart.rst doc/source/install/index.rst networking_baremetal/__init__.py networking_baremetal/_i18n.py networking_baremetal/common.py networking_baremetal/config.py networking_baremetal/constants.py networking_baremetal/exceptions.py networking_baremetal/ironic_client.py networking_baremetal.egg-info/PKG-INFO networking_baremetal.egg-info/SOURCES.txt networking_baremetal.egg-info/dependency_links.txt networking_baremetal.egg-info/entry_points.txt networking_baremetal.egg-info/not-zip-safe networking_baremetal.egg-info/pbr.json networking_baremetal.egg-info/requires.txt networking_baremetal.egg-info/top_level.txt networking_baremetal/agent/__init__.py networking_baremetal/agent/ironic_neutron_agent.py networking_baremetal/drivers/__init__.py networking_baremetal/drivers/base.py networking_baremetal/drivers/netconf/openconfig.py networking_baremetal/openconfig/__init__.py networking_baremetal/openconfig/interfaces/__init__.py networking_baremetal/openconfig/interfaces/aggregate.py networking_baremetal/openconfig/interfaces/ethernet.py networking_baremetal/openconfig/interfaces/interfaces.py networking_baremetal/openconfig/interfaces/types.py networking_baremetal/openconfig/lacp/__init__.py networking_baremetal/openconfig/lacp/lacp.py networking_baremetal/openconfig/lacp/types.py networking_baremetal/openconfig/network_instance/__init__.py networking_baremetal/openconfig/network_instance/network_instance.py networking_baremetal/openconfig/vlan/__init__.py networking_baremetal/openconfig/vlan/types.py networking_baremetal/openconfig/vlan/vlan.py networking_baremetal/plugins/__init__.py networking_baremetal/plugins/ml2/__init__.py networking_baremetal/plugins/ml2/baremetal_mech.py networking_baremetal/tests/__init__.py networking_baremetal/tests/base.py networking_baremetal/tests/unit/__init__.py networking_baremetal/tests/unit/drivers/__init__.py networking_baremetal/tests/unit/drivers/netconf/__init__.py networking_baremetal/tests/unit/drivers/netconf/test_openconfig.py networking_baremetal/tests/unit/ironic_agent/__init__.py networking_baremetal/tests/unit/ironic_agent/test_hashring_member_manager.py networking_baremetal/tests/unit/ironic_agent/test_ironic_agent.py networking_baremetal/tests/unit/openconfig/__init__.py networking_baremetal/tests/unit/openconfig/test_interfaces.py networking_baremetal/tests/unit/openconfig/test_lacp.py networking_baremetal/tests/unit/openconfig/test_network_instance.py networking_baremetal/tests/unit/openconfig/test_vlan.py networking_baremetal/tests/unit/plugins/__init__.py networking_baremetal/tests/unit/plugins/ml2/__init__.py networking_baremetal/tests/unit/plugins/ml2/test_baremetal_mech.py networking_baremetal/tests/unit/plugins/ml2/utils.py releasenotes/notes/.placeholder releasenotes/notes/add-initial-note-8f08fd95b0149b2c.yaml releasenotes/notes/agent-notification-auto-delete-queues-a3782fbeea2b57b1.yaml releasenotes/notes/device-manager-driver-interface-741703fbfc063780.yaml releasenotes/notes/distributed-agent-hashring-6b623a7a9caf0425.yaml releasenotes/notes/drop-py-2-7-2249129e616bb1e5.yaml releasenotes/notes/fix-conflict-with-ngs-41a862c292718c3b.yaml releasenotes/notes/fix-exception-handling-openstacksdk-d3eff6f9fe9ea42f.yaml releasenotes/notes/fix-member-manager-notification-queue-not-consumed-449738d4fd799634.yaml releasenotes/notes/fix_autodelete_for_quorum_queues-e001f2d8d8ae780b.yaml releasenotes/notes/ironic-neutron-agent-291f8aad7d53f06c.yaml releasenotes/notes/mech-agent-driver-ffc361e528668f8e.yaml releasenotes/notes/netconf-openconfig-device-driver-8fc15c9c2dc4bf17.yaml releasenotes/notes/netconf-openconfig-device-driver-lacp-support-ed9e1bc0eb784c9b.yaml releasenotes/notes/netconf-openconfig-device-driver-pre-configured-link-aggregates-2dcba8f96500d159.yaml releasenotes/notes/openconfig-library-5ecd1f158666c6c5.yaml releasenotes/notes/replace-ironicclient-with-openstacksdk-75d1edd705571f94.yaml releasenotes/notes/sighup-service-reloads-configs-11cd374cc33aac83.yaml releasenotes/notes/vlan-type-support-mech-driver-31f907c76dc44923.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst releasenotes/source/zed.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/flake8wrap.sh tools/run_bashate.sh tools/config/networking-baremetal-common-device-driver-opts.conf tools/config/networking-baremetal-ironic-neutron-agent.conf tools/config/networking-baremetal-netconf-openconfig-driver-opts.conf zuul.d/networking-baremetal-jobs.yaml zuul.d/project.yaml././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/networking_baremetal.egg-info/dependency_links.txt0000664000175000017500000000000100000000000027630 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/networking_baremetal.egg-info/entry_points.txt0000664000175000017500000000133700000000000027064 0ustar00zuulzuul00000000000000[console_scripts] ironic-neutron-agent = networking_baremetal.agent.ironic_neutron_agent:main [networking_baremetal.drivers] netconf-openconfig = networking_baremetal.drivers.netconf.openconfig:NetconfOpenConfigDriver [neutron.ml2.mechanism_drivers] baremetal = networking_baremetal.plugins.ml2.baremetal_mech:BaremetalMechanismDriver [oslo.config.opts] baremetal = networking_baremetal.config:list_opts common-device-driver-opts = networking_baremetal.config:list_common_device_driver_opts ironic-client = networking_baremetal.ironic_client:list_opts ironic-neutron-agent = networking_baremetal.agent.ironic_neutron_agent:list_opts netconf-openconfig-driver-opts = networking_baremetal.drivers.netconf.openconfig:list_driver_opts ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/networking_baremetal.egg-info/not-zip-safe0000664000175000017500000000000100000000000026010 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/networking_baremetal.egg-info/pbr.json0000664000175000017500000000005600000000000025241 0ustar00zuulzuul00000000000000{"git_version": "0bf02fb", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/networking_baremetal.egg-info/requires.txt0000664000175000017500000000037700000000000026171 0ustar00zuulzuul00000000000000keystoneauth1>=3.14.0 ncclient>=0.6.9 neutron-lib>=1.28.0 neutron>=14.0.0.0b1 openstacksdk>=0.31.2 oslo.config>=5.2.0 oslo.i18n>=3.15.3 oslo.log>=3.36.0 oslo.messaging>=5.29.0 oslo.service>=1.40.2 oslo.utils>=3.40.2 pbr>=3.1.1 tenacity>=6.0.0 tooz>=2.5.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902937.0 networking-baremetal-6.4.0/networking_baremetal.egg-info/top_level.txt0000664000175000017500000000002500000000000026311 0ustar00zuulzuul00000000000000networking_baremetal ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8251135 networking-baremetal-6.4.0/releasenotes/0000775000175000017500000000000000000000000020356 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1724902937.845112 networking-baremetal-6.4.0/releasenotes/notes/0000775000175000017500000000000000000000000021506 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000023757 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/add-initial-note-8f08fd95b0149b2c.yaml0000664000175000017500000000052600000000000027741 0ustar00zuulzuul00000000000000--- prelude: > This is the initial release of the networking-baremetal. The project includes the ``baremetal`` ml2 mechanism driver performing binding of the Networking service ports with ``binding_vnic_type=baremetal`` in flat networks. It also includes the devstack plugin to simplify the development setup and testing. ././@PaxHeader0000000000000000000000000000021100000000000011447 xustar0000000000000000115 path=networking-baremetal-6.4.0/releasenotes/notes/agent-notification-auto-delete-queues-a3782fbeea2b57b1.yaml 22 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/agent-notification-auto-delete-queues-a3782fbeea2b57b10000664000175000017500000000303300000000000033275 0ustar00zuulzuul00000000000000--- upgrade: - | To fix `bug: 2004933 `_ oslo.messaging notification queues are now renamed and created with ``amqp_auto_delete=true``. When upgrading the agent old queues should be deleted to free up message broker resources. Previous queue that can be deleted are named ``ironic-neutron-agent-heartbeat.info``. There may also be queues with uuid of previous agent instances as name, these can also safely be deleted. (Look in the agent logs for relevant agent uuids). On rabbitmq queues can be deleted via the web console. For example with curl:: curl -i -u username:password \ -H "content-type:application/json" -XDELETE \ http://:/api/queues// Another example with vhost: '/' deleting the ironic-neutron-agent-heartbeat.info queue:: curl -i -u username:password \ -H "content-type:application/json" \ -XDELETE \ http://172.20.0.1:15672/api/queues/%2F/ironic-neutron-agent-heartbeat.info .. Note:: In the example above the vhost is ``/``. To ensure the vhost is correctly encoded the use of ``%2F``, instead of ``/`` is required. fixes: - | Fixes an issue where old oslo.messaging notification pool queues remained in the broker without any consumer after agent restart. The notification queues will now be created with ``amqp_auto_delete=true``. See `bug: 2004933 `_. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/device-manager-driver-interface-741703fbfc063780.yaml0000664000175000017500000000033000000000000032540 0ustar00zuulzuul00000000000000--- features: - | A device management driver interface using stevedore for dynamic loading has been added. The base driver includes two abstract base classes `BaseDeviceDriver` and `BaseDeviceClient`. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/distributed-agent-hashring-6b623a7a9caf0425.yaml0000664000175000017500000000056700000000000032030 0ustar00zuulzuul00000000000000--- features: - | Adds support for load distribution when multiple instances of the networking-baremetal agent are running. Each instance will manage a subset of bare metal nodes. In case one or more instances of networking-baremetal agent is lost, the remaining instances will take over the bare metal nodes previously managed by the lost instance(s). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/drop-py-2-7-2249129e616bb1e5.yaml0000664000175000017500000000034600000000000026345 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of Networking Baremetal to support Python 2.7 is OpenStack Train. The minimum version of Python now supported by Networking Baremetal is Python 3.6. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/fix-conflict-with-ngs-41a862c292718c3b.yaml0000664000175000017500000000024200000000000030566 0ustar00zuulzuul00000000000000--- fixes: - | Fixes an issue when networking-baremetal try to bind port that should be handled by other ml2 driver like networking-generic-switch. ././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=networking-baremetal-6.4.0/releasenotes/notes/fix-exception-handling-openstacksdk-d3eff6f9fe9ea42f.yaml 22 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/fix-exception-handling-openstacksdk-d3eff6f9fe9ea42f.y0000664000175000017500000000050700000000000033475 0ustar00zuulzuul00000000000000--- fixes: - | Fixed the incorrect handling of exceptions from openstacksdk when querying the list of ports from ironic, that caused the agent to stop reporting its state. Also when there are problems querying ports, agent now does not report an empty state, and rather waits for the next iteration to retry. ././@PaxHeader0000000000000000000000000000022600000000000011455 xustar0000000000000000128 path=networking-baremetal-6.4.0/releasenotes/notes/fix-member-manager-notification-queue-not-consumed-449738d4fd799634.yaml 22 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/fix-member-manager-notification-queue-not-consumed-4490000664000175000017500000000125700000000000033636 0ustar00zuulzuul00000000000000--- fixes: - | Fixes an issue causing heavy RAM (and/or-storage) usage on the message broker back-end. The ``ironic-neutron-agent`` uses oslo.messaging notifications, with all notification listeners using pools. Since all listeners are using pools the default notification queue in messaging is not consumed (only the pool queues are consumed). The default notification queue was continuously growing, consuming more and more resources on the messaging back-end. See `oslo.messaging bug: 1814544 `_ and `bug: 2004938 `_ for more details. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/fix_autodelete_for_quorum_queues-e001f2d8d8ae780b.yaml0000664000175000017500000000041500000000000033532 0ustar00zuulzuul00000000000000--- fixes: - | Fixes a bug which tried to force delete queues when quorum queues are enabled. Quorum queues do not support the auto-delete feature. See the `bugreport 2046962 `_ for details. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/ironic-neutron-agent-291f8aad7d53f06c.yaml0000664000175000017500000000107300000000000030741 0ustar00zuulzuul00000000000000--- features: - Add neutron agent ``ironic-neutron-agent`` to enable integration with neutron routed provider networks. The ml2 agent reports the state of ironic ports associated with ironic nodes to neutron, it populates the bridge_mappings configuration for each ironic node. The agent data can be used by the neutron segments plug-in in conjunction with neutron ml2 mechanism driver to ensure that port binding and ipam ip address allocations are taken from subnets associated with physical network segments available to the ironic port. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/mech-agent-driver-ffc361e528668f8e.yaml0000664000175000017500000000036600000000000030141 0ustar00zuulzuul00000000000000--- features: - Baremetal ml2 mechanism driver integration with the L2 agent. This enables the ml2 mechanism driver to use the agent_db data when binding ports. E.g the bridge mappings to enable binding on routed provider networks. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/netconf-openconfig-device-driver-8fc15c9c2dc4bf17.yaml0000664000175000017500000000045100000000000033261 0ustar00zuulzuul00000000000000--- features: - | Added an `OpenConfig `__ based device driver (driver name: ``netconf-openconfig``) using Network Configuration Protocol (**NETCONF**). Implements network create, delete and update functionality as well as port create, delete and update. ././@PaxHeader0000000000000000000000000000022100000000000011450 xustar0000000000000000123 path=networking-baremetal-6.4.0/releasenotes/notes/netconf-openconfig-device-driver-lacp-support-ed9e1bc0eb784c9b.yaml 22 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/netconf-openconfig-device-driver-lacp-support-ed9e1bc00000664000175000017500000000020400000000000033655 0ustar00zuulzuul00000000000000--- features: - | Added support to configure LACP (802.3ad) link-aggregates in the ``netconf-openconfig`` device driver. ././@PaxHeader0000000000000000000000000000024300000000000011454 xustar0000000000000000141 path=networking-baremetal-6.4.0/releasenotes/notes/netconf-openconfig-device-driver-pre-configured-link-aggregates-2dcba8f96500d159.yaml 22 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/netconf-openconfig-device-driver-pre-configured-link-a0000664000175000017500000000035000000000000034016 0ustar00zuulzuul00000000000000--- features: - | Added support for *pre-configured* link-aggregates in the ``netconf-openconfig`` device driver. This is useful for the following linux bond modes: * balance-rr * balance-xor * broadcast ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/openconfig-library-5ecd1f158666c6c5.yaml0000664000175000017500000000160200000000000030410 0ustar00zuulzuul00000000000000--- features: - | `OpenConfig `_ YANG data model python bindings. Bindings for a subset of the OpenConfig YANG models has been added, these bindings can be used to build a structured configuration that can be serialized and sent to a network device (switch) where it will be parsed and applied. Serialization to XML which can be used with Network Configuration Protocol (NETCONF) has been implemented. The bindings is only a small subset of the following YANG models, it implements what is required to provide a good feature-set for BMaaS use case. * \http://openconfig.net/yang/interfaces * \http://openconfig.net/yang/interfaces/ethernet * \http://openconfig.net/yang/vlan * \http://openconfig.net/yang/network-instance * \http://openconfig.net/yang/interfaces/aggregate * \http://openconfig.net/yang/lacp ././@PaxHeader0000000000000000000000000000021200000000000011450 xustar0000000000000000116 path=networking-baremetal-6.4.0/releasenotes/notes/replace-ironicclient-with-openstacksdk-75d1edd705571f94.yaml 22 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/replace-ironicclient-with-openstacksdk-75d1edd705571f90000664000175000017500000000170300000000000033250 0ustar00zuulzuul00000000000000--- upgrade: - | Operators using ironic-neutron-agent with ``noauth`` authentication strategy (i.e standalone ironic without keystone) must update the configuration. Replace ``[ironic]/auth_strategy = noauth`` with ``[ironic]/auth_type = none`` and set the ``[ironic]/endpoint_override`` option accordingly. deprecations: - | With the switch from ironicclient to openstacksdk the following options has been deprecated. * ``[ironic]/ironic_url`` replaced by ``[ironic]/endpoint_override`` * ``[ironic]/os_region`` replaced by ``[ironic]/region_name`` * ``[ironic]/retry_interval`` replaced by ``[ironic]/status_code_retries`` * ``[ironic]/max_retries`` replaced by ``[ironic]/status_code_retry_delay`` * ``[ironic]/auth_strategy`` is **ignored**, please use ``[ironic]/auth_type`` instead. other: - | Communication with ironic is now using openstacksdk, removing the dependency on ironicclient. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/sighup-service-reloads-configs-11cd374cc33aac83.yaml0000664000175000017500000000103200000000000032660 0ustar00zuulzuul00000000000000--- features: - | Issuing a SIGHUP (e.g. ``pkill -HUP ironic-neutron-agent``) to the agent service will cause the service to reload and use any changed values for *mutable* configuration options. Mutable configuration options are indicated as such in the `sample configuration file `_ by ``Note: This option can be changed without restarting``. A warning is logged for any changes to immutable configuration options. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/notes/vlan-type-support-mech-driver-31f907c76dc44923.yaml0000664000175000017500000000133500000000000032314 0ustar00zuulzuul00000000000000--- features: - | Add support for type ``vlan`` networks in baremetal ml2 mechanism driver. This enables binding on networks using vlans for segmentation. It is only setting type ``vlan`` as supported. The intent is to use this in combination with another neutron mechanism driver that actually knows how to configure the network devices. .. Note:: The driver will **not** do anything to **set up** the correct **vlan tagging** in the network infrastructure such as switches or baremetal node ports. Another ml2 mechanism driver, or some other implementation, must be enabled to perform the necessary configuration on network devices. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8491118 networking-baremetal-6.4.0/releasenotes/source/0000775000175000017500000000000000000000000021656 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/2023.1.rst0000664000175000017500000000020200000000000023127 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000023130 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000023130 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8491118 networking-baremetal-6.4.0/releasenotes/source/_static/0000775000175000017500000000000000000000000023304 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000025555 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8491118 networking-baremetal-6.4.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000024013 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000026264 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/conf.py0000664000175000017500000002162100000000000023157 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # Networking Baremetal Release Notes documentation build configuration file, # created by sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. copyright = '2017, The Networking Baremetal team' # 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. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # openstackdocstheme options openstackdocs_repo_name = 'openstack/networking-baremetal' openstackdocs_use_storyboard = False # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'NetworkingBaremetalReleaseNotesdoc' # -- 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 = [ ('index', 'NetworkingBaremetalReleaseNotes.tex', 'Networking Baremetal Release Notes Documentation', 'Networking Baremetal Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'networkingbaremetalreleasenotes', 'Networking Baremetal Release Notes Documentation', ['Networking Baremetal Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'NetworkingBaremetalReleaseNotes', 'Networking Baremetal Release Notes Documentation', 'networking Baremetal Developers', 'networkingbaremetalreleasenotes', 'Neutron plugin that provides deep Ironic/Neutron integration.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/index.rst0000664000175000017500000000043400000000000023520 0ustar00zuulzuul00000000000000=================================== networking_baremetal Release Notes =================================== .. toctree:: :maxdepth: 1 unreleased 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/pike.rst0000664000175000017500000000021700000000000023340 0ustar00zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/queens.rst0000664000175000017500000000022300000000000023705 0ustar00zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000023532 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/stein.rst0000664000175000017500000000022100000000000023525 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/train.rst0000664000175000017500000000026100000000000023524 0ustar00zuulzuul00000000000000=========================================== Train Series (1.4.0 - 1.4.x) Release Notes =========================================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/unreleased.rst0000664000175000017500000000016000000000000024534 0ustar00zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000023734 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/victoria.rst0000664000175000017500000000022000000000000024222 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/wallaby.rst0000664000175000017500000000021400000000000024040 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/xena.rst0000664000175000017500000000020000000000000023333 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000023337 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/releasenotes/source/zed.rst0000664000175000017500000000017400000000000023174 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/requirements.txt0000664000175000017500000000123000000000000021145 0ustar00zuulzuul00000000000000# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. ncclient>=0.6.9 # Apache-2.0 neutron-lib>=1.28.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.utils>=3.40.2 # Apache-2.0 oslo.messaging>=5.29.0 # Apache-2.0 oslo.service>=1.40.2 # Apache-2.0 pbr>=3.1.1 # Apache-2.0 openstacksdk>=0.31.2 # Apache-2.0 tooz>=2.5.1 # Apache-2.0 neutron>=14.0.0.0b1 # Apache-2.0 tenacity>=6.0.0 # Apache-2.0 keystoneauth1>=3.14.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8531115 networking-baremetal-6.4.0/setup.cfg0000664000175000017500000000333400000000000017511 0ustar00zuulzuul00000000000000[metadata] name = networking-baremetal summary = Neutron plugin that provides deep Ironic/Neutron integration. description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/networking-baremetal/latest/ python_requires = >=3.8 classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 [files] packages = networking_baremetal [entry_points] oslo.config.opts = ironic-neutron-agent = networking_baremetal.agent.ironic_neutron_agent:list_opts ironic-client = networking_baremetal.ironic_client:list_opts baremetal = networking_baremetal.config:list_opts common-device-driver-opts = networking_baremetal.config:list_common_device_driver_opts netconf-openconfig-driver-opts = networking_baremetal.drivers.netconf.openconfig:list_driver_opts console_scripts = ironic-neutron-agent = networking_baremetal.agent.ironic_neutron_agent:main neutron.ml2.mechanism_drivers = baremetal = networking_baremetal.plugins.ml2.baremetal_mech:BaremetalMechanismDriver networking_baremetal.drivers = netconf-openconfig = networking_baremetal.drivers.netconf.openconfig:NetconfOpenConfigDriver [codespell] quiet-level = 4 ignore-words-list = assertIn [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/setup.py0000664000175000017500000000127100000000000017400 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/test-requirements.txt0000664000175000017500000000032200000000000022123 0ustar00zuulzuul00000000000000coverage>=4.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD testtools>=2.2.0 # MIT stestr>=2.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD ncclient>=0.6.9 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8491118 networking-baremetal-6.4.0/tools/0000775000175000017500000000000000000000000017025 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8531115 networking-baremetal-6.4.0/tools/config/0000775000175000017500000000000000000000000020272 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/tools/config/networking-baremetal-common-device-driver-opts.conf0000664000175000017500000000020600000000000032257 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/neutron/plugins/ml2/common_device_driver.ini.sample wrap_width = 79 namespace = common-device-driver-opts ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/tools/config/networking-baremetal-ironic-neutron-agent.conf0000664000175000017500000000026000000000000031325 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/neutron/plugins/ml2/ironic_neutron_agent.ini.sample wrap_width = 79 namespace = ironic-neutron-agent namespace = oslo.log namespace = ironic-client ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/tools/config/networking-baremetal-netconf-openconfig-driver-opts.conf0000664000175000017500000000022700000000000033316 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/neutron/plugins/ml2/netconf_openconfig_device_driver.ini.sample wrap_width = 79 namespace = netconf-openconfig-driver-opts ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/tools/flake8wrap.sh0000775000175000017500000000073500000000000021435 0ustar00zuulzuul00000000000000#!/bin/bash # # A simple wrapper around flake8 which makes it possible # to ask it to only verify files changed in the current # git HEAD patch. # # Intended to be invoked via tox: # # tox -epep8 -- -HEAD # if test "x$1" = "x-HEAD" ; then shift files=$(git diff --name-only HEAD~1 | tr '\n' ' ') echo "Running flake8 on ${files}" diff -u --from-file /dev/null ${files} | flake8 --diff "$@" else echo "Running flake8 on all files" exec flake8 "$@" fi ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/tools/run_bashate.sh0000775000175000017500000000242000000000000021655 0ustar00zuulzuul00000000000000#!/bin/bash # # 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. find "$@" -not \( -type d -name .?\* -prune \) \ -type f \ -not -name \*.swp \ -not -name \*~ \ -not -name \*.xml \ -not -name \*.template \ -not -name \*.py \ \( \ -name \*.sh -or \ -wholename \*/devstack/lib/\* -or \ -wholename \*/tools/\* \ \) \ -print0 | xargs -0 bashate -v -iE006 -eE005,E042 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/tox.ini0000664000175000017500000000724400000000000017207 0ustar00zuulzuul00000000000000[tox] minversion = 4.4.0 envlist = py3,pep8 ignore_basepython_conflict=true [testenv] constrain_package_deps = true usedevelop = True setenv = PYTHONWARNINGS=default::DeprecationWarning deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run {posargs} passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY [testenv:pep8] deps = bashate~=2.1.0 # Apache-2.0 flake8-import-order~=0.18.0 # LGPLv3 hacking~=6.1.0 # Apache-2.0 pycodestyle>=2.0.0,<3.0.0 # MIT allowlist_externals = bash {toxinidir}/tools/run_bashate.sh commands = bash tools/flake8wrap.sh {posargs} # Run bashate during pep8 runs to ensure violations are caught by # the check and gate queues. {toxinidir}/tools/run_bashate.sh {toxinidir}/devstack [testenv:venv] commands = {posargs} [testenv:cover] setenv = VIRTUAL_ENV={envdir} LANGUAGE=en_US PYTHON=coverage run --source networking_baremetal --parallel-mode commands = coverage erase stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report --omit='*test*' [testenv:docs] setenv = PYTHONHASHSEED=0 sitepackages = False deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:pdf-docs] allowlist_externals = make setenv = PYTHONHASHSEED=0 sitepackages = False deps = {[testenv:docs]deps} commands = sphinx-build -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:releasenotes] usedevelop = False deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:genconfig] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt commands = oslo-config-generator --config-file=tools/config/networking-baremetal-ironic-neutron-agent.conf oslo-config-generator --config-file=tools/config/networking-baremetal-common-device-driver-opts.conf oslo-config-generator --config-file=tools/config/networking-baremetal-netconf-openconfig-driver-opts.conf [testenv:debug] commands = oslo_debug_helper -t networking_baremetal/tests/unit {posargs} [flake8] show-source = True # E123, E125 skipped as they are invalid PEP-8. # [W503] Line break occurred before a binary operator. Conflicts with W504. ignore = E123,E125,W503 # [H106] Don't put vim configuration in source files. # [H203] Use assertIs(Not)None to check for None. # [H204] Use assert(Not)Equal to check for equality. # [H205] Use assert(Greater|Less)(Equal) for comparison. # [H210] Require 'autospec', 'spec', or 'spec_set' in mock.patch/mock.patch.object calls # [H904] Delay string interpolations at logging calls. enable-extensions=H106,H203,H204,H205,H210,H904 builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build import-order-style = pep8 application-import-names = networking_baremetal filename = *.py per-file-ignores = networking_baremetal/agent/ironic_neutron_agent.py:E402 [testenv:codespell] description = Run codespell to check spelling deps = codespell # note(JayF): {posargs} lets us run `tox -ecodespell -- -w` to get codespell # to correct spelling issues in our code it's aware of. commands = codespell {posargs}././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1724902937.8531115 networking-baremetal-6.4.0/zuul.d/0000775000175000017500000000000000000000000017106 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/zuul.d/networking-baremetal-jobs.yaml0000664000175000017500000000501400000000000025046 0ustar00zuulzuul00000000000000- job: name: networking-baremetal-multitenant-vlans parent: ironic-base irrelevant-files: - ^.*\.rst$ - ^doc/.*$ - ^setup.cfg$ - ^test-requirements.txt$ - ^tools/.*$ - ^tox.ini$ required-projects: - openstack/networking-generic-switch - openstack/networking-baremetal vars: tempest_test_timeout: 2400 devstack_plugins: networking-generic-switch: https://opendev.org/openstack/networking-generic-switch networking-baremetal: https://opendev.org/openstack/networking-baremetal devstack_localrc: BUILD_TIMEOUT: 2400 ENABLE_TENANT_VLANS: True IRONIC_DEFAULT_DEPLOY_INTERFACE: direct IRONIC_DEFAULT_RESCUE_INTERFACE: "" IRONIC_ENABLED_NETWORK_INTERFACES: flat,neutron IRONIC_NETWORK_INTERFACE: neutron IRONIC_PROVISION_NETWORK_NAME: ironic-provision IRONIC_PROVISION_PROVIDER_NETWORK_TYPE: vlan IRONIC_PROVISION_SUBNET_GATEWAY: 10.0.5.1 IRONIC_PROVISION_SUBNET_PREFIX: 10.0.5.0/24 IRONIC_TEMPEST_BUILD_TIMEOUT: 2400 IRONIC_TEMPEST_WHOLE_DISK_IMAGE: True IRONIC_USE_LINK_LOCAL: True IRONIC_USE_NEUTRON_SEGMENTS: True IRONIC_VM_COUNT: 3 IRONIC_VM_EPHEMERAL_DISK: 0 IRONIC_AUTOMATED_CLEAN_ENABLED: False OVS_PHYSICAL_BRIDGE: brbm Q_USE_PROVIDERNET_FOR_PUBLIC: True PUBLIC_PHYSICAL_NETWORK: public OVS_BRIDGE_MAPPINGS: mynetwork:brbm,public:br-ex PHYSICAL_NETWORK: mynetwork Q_ML2_TENANT_NETWORK_TYPE: vlan Q_PLUGIN: ml2 Q_SERVICE_PLUGIN_CLASSES: neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,segments Q_USE_DEBUG_COMMAND: True SWIFT_ENABLE_TEMPURLS: True SWIFT_TEMPURL_KEY: secretkey TENANT_VLAN_RANGE: 100:150 EBTABLES_RACE_FIX: True NEUTRON_PORT_SECURITY: False devstack_services: s-account: True s-container: True s-object: True s-proxy: True generic_switch: True networking_baremetal: True ir-neutronagt: True neutron-api: False neutron-agent: False neutron-dhcp: False neutron-l3: False neutron-metadata-agent: False neutron-metering: False q-agt: True q-dhcp: True q-l3: True q-meta: True q-metering: True q-svc: True - job: name: networking-baremetal-tox-codespell parent: openstack-tox timeout: 7200 vars: tox_envlist: codespell././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1724902911.0 networking-baremetal-6.4.0/zuul.d/project.yaml0000664000175000017500000000057100000000000021443 0ustar00zuulzuul00000000000000- project: templates: - check-requirements - openstack-python3-jobs-neutron - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - networking-baremetal-multitenant-vlans - networking-baremetal-tox-codespell: voting: false gate: jobs: - networking-baremetal-multitenant-vlans