././@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/1Po10foo1/2Po10
'''
XML_AGGREGATE_IFACES = f'''
Po5Po7Po9foo1/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