├── .gitignore ├── .gitreview ├── .stestr.conf ├── CONTRIBUTING.rst ├── LICENSE ├── README.rst ├── devstack ├── exercise.py ├── exercise.sh ├── plugin.sh ├── settings └── upgrade │ ├── resources.sh │ ├── settings │ └── upgrade.sh ├── doc ├── requirements.txt └── source │ ├── _exts │ └── netmiko_device_commands.py │ ├── conf.py │ ├── configuration.rst │ ├── contributing.rst │ ├── dev │ └── dev-quickstart.rst │ ├── include │ └── configure-neutron.rst │ ├── index.rst │ ├── installation.rst │ ├── netmiko-device-commands.rst │ └── supported-devices.rst ├── etc └── neutron │ └── plugins │ └── ml2 │ └── ml2_conf_genericswitch.ini.sample ├── networking_generic_switch ├── __init__.py ├── _i18n.py ├── batching.py ├── config.py ├── devices │ ├── __init__.py │ ├── netmiko_devices │ │ ├── __init__.py │ │ ├── arista.py │ │ ├── aruba.py │ │ ├── brocade.py │ │ ├── cisco.py │ │ ├── cisco300.py │ │ ├── cumulus.py │ │ ├── dell.py │ │ ├── fake.py │ │ ├── hpe.py │ │ ├── huawei.py │ │ ├── huawei_vrpv8.py │ │ ├── juniper.py │ │ ├── mellanox_mlnxos.py │ │ ├── nokia.py │ │ ├── ovs.py │ │ ├── pluribus.py │ │ ├── ruijie.py │ │ ├── smc.py │ │ └── sonic.py │ └── utils.py ├── exceptions.py ├── generic_switch_mech.py ├── locking.py ├── tests │ ├── __init__.py │ └── unit │ │ ├── __init__.py │ │ ├── devices │ │ ├── __init__.py │ │ └── test_utils.py │ │ ├── netmiko │ │ ├── __init__.py │ │ ├── test_arista_eos.py │ │ ├── test_aruba.py │ │ ├── test_brocade_fastiron.py │ │ ├── test_cisco_300.py │ │ ├── test_cisco_ios.py │ │ ├── test_cisco_nxos.py │ │ ├── test_cumulus.py │ │ ├── test_cumulus_nvue.py │ │ ├── test_dell.py │ │ ├── test_huawei.py │ │ ├── test_huawei_vrpv8.py │ │ ├── test_juniper.py │ │ ├── test_netmiko_base.py │ │ ├── test_nokia_srl.py │ │ ├── test_ovs_linux.py │ │ ├── test_pluribus.py │ │ ├── test_smc.py │ │ └── test_sonic.py │ │ ├── test_batching.py │ │ ├── test_config.py │ │ ├── test_devices.py │ │ ├── test_generic_switch_mech.py │ │ └── test_locking.py ├── trunk_driver.py └── utils.py ├── pyproject.toml ├── releasenotes ├── notes │ ├── .placeholder │ ├── add-aruba-support-463a90b0b150b9af.yaml │ ├── add-cisco-nx-os-support-8046a33107e2a670.yaml │ ├── add-cumulus-nclu-support-ddcffa604c3e1b18.yaml │ ├── add-cumulus-nvue-support-2207d67edc12e866.yaml │ ├── add-dellos10-support-c6426372f960ded4.yaml │ ├── add-initial-note-ae1c9f9709c2e66f.yaml │ ├── add-mellanox-mlnx-os-switch-support-a4bf0661cd27fec7.yaml │ ├── add-ngs_save_configuration-180c2145f08e54d2.yaml │ ├── add-ngs_ssh_disabled_algorithms-dfe3e805f480ce90.yaml │ ├── add-sonic-os-switch-support-73fcaf3acdc8c1d0.yaml │ ├── add-supermicro-support-8cc6c6b2265e474b.yaml │ ├── add-switchmode-option-to-dell-powerconnect-87718a84430444ef.yaml │ ├── add-vlan-and-port-allowlist-0b1c29967210fbfb.yaml │ ├── add_alias_for_hpe_comware-0eb9a016f0c992df.yaml │ ├── add_netmiko_hpe_comware_device-c39be5d96943c6fd.yaml │ ├── add_ngs_default_vlan-ab09e0f4fd7ce897.yaml │ ├── batching-12d9005924fd9d74.yaml │ ├── config-dir-d5f59b536c110841.yaml │ ├── cumulus-802.3ad-da9bffe131995f98.yaml │ ├── delete_network_postcommit-more-defensive-19929702ba7f19fb.yaml │ ├── dell-powerconnect-5ce572b9fb2702d3.yaml │ ├── disable-inactive-ports-bd6c42ceb232aab2.yaml │ ├── drop-py-2-7-76d7a678dc042bd6.yaml │ ├── fix-junos-syntax-27bb18dc737d776b.yaml │ ├── fix-netmiko-and-ngs-33c79b55ff7e2fd7.yaml │ ├── fix-netmiko-info-leakage-423c4c59b924c06f.yaml │ ├── fixes-netmiko-regression-binding-port-groups-af6978a199a381b1.yaml │ ├── juniper-92d75d3086cf78a2.yaml │ ├── junos-retry-warnings-f2b004fe99d7770d.yaml │ ├── log-unknown-ngs-options-8a385406055ccc98.yaml │ ├── manage-vlans-c75e4c2e9b9b3403.yaml │ ├── net-add-del-failure-f4ea1118bc1f9d28.yaml │ ├── netmiko-session-logging-9834fcdb8d6e5bb3.yaml │ ├── network-name-format-075f5757d599ac92.yaml │ ├── ngs-stress-78f9e993e62e2e36.yaml │ ├── nokia-srl-support-52ea2a445f4b24d4.yaml │ ├── normalize-ngs-macaddr-1ff0b6be7a53087a.yaml │ ├── remove-del_network-transitional-2f5742f7cafa2276.yaml │ ├── remove-py38-18af38f2ee386abc.yaml │ ├── ruijie-netmiko-driver-14e521fc36ede897.yaml │ ├── support-multiple-links-in-port-group-59a11c2c2da73065.yaml │ └── vlan-aware-vms-3923cc17254829e9.yaml └── source │ ├── 2023.1.rst │ ├── 2023.2.rst │ ├── 2024.1.rst │ ├── 2024.2.rst │ ├── 2025.1.rst │ ├── _static │ └── .placeholder │ ├── _templates │ └── .placeholder │ ├── conf.py │ ├── index.rst │ ├── queens.rst │ ├── rocky.rst │ ├── stein.rst │ ├── train.rst │ ├── unreleased.rst │ ├── ussuri.rst │ ├── victoria.rst │ ├── wallaby.rst │ ├── xena.rst │ ├── yoga.rst │ └── zed.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── tempest_plugin ├── README.rst ├── __init__.py ├── config.py ├── plugin.py └── tests │ ├── __init__.py │ ├── common │ ├── __init__.py │ └── ovs_lib.py │ └── scenario │ ├── __init__.py │ └── test_ngs_basic_ops.py ├── test-requirements.txt ├── tools ├── flake8wrap.sh ├── ngs-stress │ ├── README.rst │ └── ngs_stress.py └── run_bashate.sh ├── tox.ini └── zuul.d ├── networking-generic-switch-jobs.yaml └── project.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.py[co] 3 | 4 | # Sphinx 5 | _build 6 | doc/source/contributor/api/ 7 | 8 | # release notes build 9 | releasenotes/build 10 | 11 | # Packaging artefacts 12 | *.egg 13 | *.egg-info 14 | dist 15 | build 16 | eggs 17 | .eggs 18 | parts 19 | var 20 | sdist 21 | develop-eggs 22 | .installed.cfg 23 | 24 | # Testing artefacts 25 | .stestr 26 | .tox 27 | .venv 28 | .coverage 29 | cover 30 | 31 | # Others 32 | .*.swp 33 | AUTHORS 34 | ChangeLog 35 | .idea 36 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/networking-generic-switch.git 5 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=${TESTS_DIR:-./networking_generic_switch/tests/unit/} 3 | top_dir=./ 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ######################################### 2 | Contributing to Networking-generic-switch 3 | ######################################### 4 | 5 | If you would like to contribute to the development of GenericSwitch project, you must follow the 6 | general OpenStack community procedures documented at: 7 | 8 | https://docs.openstack.org/infra/manual/developers.html#development-workflow 9 | 10 | Pull requests submitted through GitHub will be ignored. 11 | 12 | Contributor License Agreement 13 | ============================= 14 | 15 | .. index:: 16 | single: license; agreement 17 | 18 | In order to contribute to the GenericSwitch project, you need to have 19 | signed OpenStack's contributor's agreement. 20 | 21 | .. seealso:: 22 | 23 | * https://docs.openstack.org/infra/manual/developers.html 24 | * https://wiki.openstack.org/CLA 25 | 26 | Related Projects 27 | ================ 28 | 29 | * https://docs.openstack.org/neutron/latest 30 | * https://docs.openstack.org/ironic/latest 31 | 32 | 33 | Project Hosting Details 34 | ======================= 35 | 36 | Bug tracker 37 | https://bugs.launchpad.net/networking-generic-switch 38 | 39 | Code Hosting 40 | https://opendev.org/openstack/networking-generic-switch 41 | 42 | Code Review 43 | https://review.opendev.org/#/q/status:open+project:openstack/networking-generic-switch,n,z 44 | 45 | 46 | Creating new device plugins 47 | =========================== 48 | 49 | #. Subclass the abstract class 50 | ``networking_generic_switch.devices.GenericSwitch`` 51 | and implement all the abstract methods it defines. 52 | 53 | * Your class must accept as first argument a dictionary that contains 54 | all fields given in the device config section of the ML2 plugin config. 55 | This will be available as ``self.config`` in the instantiated object. 56 | The second argument is the device name as specified in the configuration. 57 | It is recommended to accept and pass ``*args`` and ``**kwargs`` to the 58 | __init__ method of the parent class: this helps to stay compatible with 59 | future changes of the base class. 60 | 61 | #. Register your class under ``generic_switch.devices`` entrypoint. 62 | #. Add your device config to the plugin configuration file 63 | (``/etc/neutron/plugins/ml2/ml2_conf_genericswitch.ini`` by default). 64 | The only required option is ``device_type`` that must be equal to the 65 | entrypoint you have registered your plugin under, as it is used for plugin 66 | lookup (see provided ``Netmiko``-based plugins for example). 67 | 68 | 69 | Additional Contributor Resources 70 | ================================ 71 | GenericSwitch is a member of the Bare Metal (ironic) program in OpenStack. 72 | Development of GenericSwitch follows many ironic conventions. 73 | 74 | The `Ironic developer quickstart _` 75 | has some relevant information -- particularly on unit testing with tox, 76 | integration testing with devstack, and other information that may be useful 77 | for GenericSwitch developers. As the documentation is written targeting 78 | Ironic, it should only be used as a general guideline. 79 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============================================ 2 | Networking-generic-switch Neutron ML2 driver 3 | ============================================ 4 | 5 | .. image:: https://governance.openstack.org/tc/badges/networking-generic-switch.svg 6 | :target: https://governance.openstack.org/tc/reference/tags/index.html 7 | 8 | This is a Modular Layer 2 `Neutron Mechanism driver 9 | `_. The mechanism driver is 10 | responsible for applying configuration information to hardware equipment. 11 | ``GenericSwitch`` provides a pluggable framework to implement 12 | functionality required for use-cases like OpenStack Ironic multi-tenancy mode. 13 | It abstracts applying changes to all switches managed by this ML2 plugin 14 | and handling ``local_link_information`` field of Neutron port. 15 | 16 | Networking-generic-switch is distributed under the terms of the Apache License, 17 | Version 2.0. The full terms and conditions of this license are detailed in the 18 | LICENSE file. 19 | 20 | Project resources 21 | ~~~~~~~~~~~~~~~~~ 22 | 23 | * Documentation: https://docs.openstack.org/networking-generic-switch/latest/ 24 | * Source: https://opendev.org/openstack/networking-generic-switch 25 | * Bugs: https://bugs.launchpad.net/networking-generic-switch 26 | * Release notes: https://docs.openstack.org/releasenotes/networking-generic-switch/ 27 | 28 | For information on how to contribute to Networking-generic-switch, see 29 | https://docs.openstack.org/networking-generic-switch/latest/contributing.html. 30 | -------------------------------------------------------------------------------- /devstack/exercise.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | import argparse 17 | import os 18 | import sys 19 | 20 | from keystoneauth1 import identity 21 | from keystoneauth1 import session 22 | from neutronclient.v2_0 import client 23 | 24 | 25 | parser = argparse.ArgumentParser(description='GenericSwitch functional test') 26 | parser.add_argument('--switch_name', 27 | type=str, 28 | required=True, 29 | help='Name of the switch') 30 | parser.add_argument('--switch_id', 31 | type=str, 32 | required=True, 33 | help='Switch id to create a local_link_information with') 34 | parser.add_argument('--port', 35 | type=str, 36 | required=True, 37 | help='OVS port to manage') 38 | parser.add_argument('--network', 39 | type=str, 40 | default='private', 41 | help='Neutron network name to create port in') 42 | parser.add_argument('--auth-url', 43 | type=str, 44 | default='http://127.0.0.1:5000/v3', 45 | help='Keystone auth URL endpoint') 46 | parser.add_argument('--username', 47 | type=str, 48 | default='admin', 49 | help='Keystone user name, must have admin access') 50 | parser.add_argument('--password', 51 | type=str, 52 | default='admin', 53 | help='Keystone user password, must have admin access') 54 | parser.add_argument('--project-name', 55 | type=str, 56 | default='admin', 57 | help='Keystone user project name, must have admin access') 58 | parser.add_argument('--user-domain-id', 59 | type=str, 60 | help='Keystone user domain ID') 61 | parser.add_argument('--project-domain-id', 62 | type=str, 63 | help='Keystone project domain ID') 64 | opts = parser.parse_args() 65 | 66 | auth_params = { 67 | "username": os.environ.get("OS_USERNAME", opts.username), 68 | "password": os.environ.get("OS_PASSWORD", opts.password), 69 | "tenant_name": os.environ.get("OS_PROJECT_NAME", opts.project_name), 70 | } 71 | 72 | user_domain_id = os.environ.get("OS_USER_DOMAIN_ID", opts.user_domain_id) 73 | if user_domain_id: 74 | auth_params["user_domain_id"] = user_domain_id 75 | project_domain_id = os.environ.get("OS_PROJECT_DOMAIN_ID", 76 | opts.project_domain_id) 77 | if project_domain_id: 78 | auth_params["project_domain_id"] = project_domain_id 79 | 80 | 81 | auth = identity.Password(os.environ.get("OS_AUTH_URL", opts.auth_url), 82 | **auth_params) 83 | try: 84 | sess = session.Session(auth=auth) 85 | nc = client.Client(session=sess) 86 | 87 | network_name = opts.network 88 | 89 | network = nc.list_networks(name=network_name)['networks'][0] 90 | print(network['provider:segmentation_id']) 91 | 92 | create_body = { 93 | 'port': 94 | {'network_id': network['id'], 95 | 'admin_state_up': True, 96 | 'name': 'generic_switch_test' 97 | } 98 | } 99 | port_id = nc.create_port(create_body)['port']['id'] 100 | host = nc.list_agents( 101 | agent_type='Open vSwitch agent')['agents'][0]['host'] 102 | update_body = { 103 | 'port': { 104 | 'device_owner': 'baremetal:none', 105 | 'device_id': 'fake-instance-uuid', 106 | 'admin_state_up': True, 107 | 'binding:vnic_type': 'baremetal', 108 | 'binding:host_id': host, 109 | 'binding:profile': { 110 | 'local_link_information': [{ 111 | 'switch_info': opts.switch_name, 112 | 'switch_id': opts.switch_id, 113 | 'port_id': opts.port}] 114 | } 115 | } 116 | } 117 | 118 | nc.update_port(port_id, update_body) 119 | except Exception as exc: 120 | msg = "Failed to create and update port, exception is %s" % exc 121 | sys.exit(msg) 122 | -------------------------------------------------------------------------------- /devstack/exercise.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | GENERIC_SWITCH_TEST_BRIDGE=genericswitch 6 | GENERIC_SWITCH_TEST_PORT_NAME=${GENERIC_SWITCH_PORT_NAME:-p_01} 7 | NEUTRON_GENERIC_SWITCH_TEST_PORT_NAME=generic_switch_test 8 | 9 | function clear_resources { 10 | sudo ovs-vsctl --if-exists del-port $GENERIC_SWITCH_TEST_PORT_NAME 11 | if neutron port-show $NEUTRON_GENERIC_SWITCH_TEST_PORT_NAME; then 12 | neutron port-delete $NEUTRON_GENERIC_SWITCH_TEST_PORT_NAME 13 | fi 14 | } 15 | 16 | function wait_for_openvswitch_agent { 17 | local openvswitch_agent 18 | local retries=10 19 | local retry_delay=20; 20 | local status=false 21 | openvswitch_agent="Open vSwitch agent" 22 | 23 | while [[ $retries -ge 0 ]]; do 24 | if neutron agent-list --fields agent_type | grep -q "Open vSwitch agent"; then 25 | status=true 26 | break 27 | fi 28 | retries=$((retries - 1)) 29 | echo "$openvswitch_agent is not yet registered. $retries left." 30 | sleep $retry_delay 31 | done 32 | if ! $status; then 33 | echo "$openvswitch_agent is not started in $((retries * retry_delay))" 34 | fi 35 | } 36 | 37 | clear_resources 38 | 39 | sudo ovs-vsctl add-port $GENERIC_SWITCH_TEST_BRIDGE $GENERIC_SWITCH_TEST_PORT_NAME 40 | sudo ovs-vsctl clear port $GENERIC_SWITCH_TEST_PORT_NAME tag 41 | 42 | switch_id=$(ip link show dev $GENERIC_SWITCH_TEST_BRIDGE | egrep -o "ether [A-Za-z0-9:]+"|sed "s/ether\ //") 43 | 44 | wait_for_openvswitch_agent 45 | 46 | # create and update Neutron port 47 | expected_tag=$(python ${DIR}/exercise.py --switch_name $GENERIC_SWITCH_TEST_BRIDGE --port $GENERIC_SWITCH_TEST_PORT_NAME --switch_id=$switch_id) 48 | 49 | new_tag=$(sudo ovs-vsctl get port $GENERIC_SWITCH_TEST_PORT_NAME tag) 50 | 51 | clear_resources 52 | 53 | if [ "${new_tag}" != "${expected_tag}" ]; then 54 | echo "FAIL: OVS port tag is not set correctly!" 55 | exit 1 56 | else 57 | echo "SUCCESS: OVS port tag is set correctly" 58 | fi 59 | -------------------------------------------------------------------------------- /devstack/settings: -------------------------------------------------------------------------------- 1 | # settings file for generic_switch 2 | enable_service generic_switch 3 | -------------------------------------------------------------------------------- /devstack/upgrade/resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | set -o errexit 16 | 17 | source $GRENADE_DIR/grenaderc 18 | source $GRENADE_DIR/functions 19 | 20 | GENERIC_SWITCH_DEVSTACK_DIR=$(cd $(dirname "$0")/.. && pwd) 21 | source $GENERIC_SWITCH_DEVSTACK_DIR/plugin.sh 22 | 23 | set -o xtrace 24 | 25 | 26 | function early_create { 27 | : 28 | } 29 | 30 | function create { 31 | # TODO(vsaienko) Add resources tests. 32 | : 33 | } 34 | 35 | function verify { 36 | : 37 | } 38 | 39 | function verify_noapi { 40 | : 41 | } 42 | 43 | function destroy { 44 | : 45 | } 46 | 47 | # Dispatcher 48 | case $1 in 49 | "early_create") 50 | early_create 51 | ;; 52 | "create") 53 | create 54 | ;; 55 | "verify_noapi") 56 | verify_noapi 57 | ;; 58 | "verify") 59 | verify 60 | ;; 61 | "destroy") 62 | destroy 63 | ;; 64 | "force_destroy") 65 | set +o errexit 66 | destroy 67 | ;; 68 | esac 69 | -------------------------------------------------------------------------------- /devstack/upgrade/settings: -------------------------------------------------------------------------------- 1 | register_project_for_upgrade networking-generic-switch 2 | 3 | devstack_localrc base enable_service generic_switch 4 | 5 | devstack_localrc target enable_service generic_switch 6 | -------------------------------------------------------------------------------- /devstack/upgrade/upgrade.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # ``upgrade networking-generic-switch`` 4 | 5 | echo "*********************************************************************" 6 | echo "Begin $0" 7 | echo "*********************************************************************" 8 | 9 | # Clean up any resources that may be in use 10 | cleanup() { 11 | set +o errexit 12 | 13 | echo "*********************************************************************" 14 | echo "ERROR: Abort $0" 15 | echo "*********************************************************************" 16 | 17 | # Kill ourselves to signal any calling process 18 | trap 2; kill -2 $$ 19 | } 20 | 21 | trap cleanup SIGHUP SIGINT SIGTERM 22 | 23 | # Keep track of the grenade directory 24 | RUN_DIR=$(cd $(dirname "$0") && pwd) 25 | 26 | # Source params 27 | source $GRENADE_DIR/grenaderc 28 | 29 | # Import common functions 30 | source $GRENADE_DIR/functions 31 | 32 | # This script exits on an error so that errors don't compound and you see 33 | # only the first error that occurred. 34 | set -o errexit 35 | 36 | # Upgrade networking-generic-switch 37 | # ================================= 38 | 39 | # Duplicate some setup bits from target DevStack 40 | source $TARGET_DEVSTACK_DIR/stackrc 41 | source $TARGET_DEVSTACK_DIR/lib/tls 42 | source $TARGET_DEVSTACK_DIR/lib/nova 43 | source $TARGET_DEVSTACK_DIR/lib/apache 44 | source $TARGET_DEVSTACK_DIR/lib/keystone 45 | source $TARGET_DEVSTACK_DIR/lib/neutron 46 | 47 | 48 | GENERIC_SWITCH_DEVSTACK_DIR=$(dirname "$0")/.. 49 | source $GENERIC_SWITCH_DEVSTACK_DIR/plugin.sh 50 | 51 | neutron_plugin_configure_common 52 | Q_PLUGIN_CONF_FILE=$Q_PLUGIN_CONF_PATH/$Q_PLUGIN_CONF_FILENAME 53 | if [ "$Q_AGENT" == "linuxbridge" ]; then 54 | AGENT_BINARY=${AGENT_BINARY:-"$NEUTRON_BIN_DIR/neutron-linuxbridge-agent"} 55 | else 56 | # fall back to openvswitch as the default 57 | AGENT_BINARY=${AGENT_BINARY:-"$NEUTRON_BIN_DIR/neutron-openvswitch-agent"} 58 | fi 59 | 60 | # Print the commands being run so that we can see the command that triggers 61 | # an error. It is also useful for following allowing as the install occurs. 62 | set -o xtrace 63 | 64 | 65 | stack_install_service generic_switch 66 | 67 | # calls upgrade-networking-generic-switch for specific release 68 | upgrade_project networking-generic-switch $RUN_DIR $BASE_DEVSTACK_BRANCH $TARGET_DEVSTACK_BRANCH 69 | 70 | # Also set in grenade but isn't picked up 71 | AGENT_METERING_BINARY=${AGENT_METERING_BINARY:-"$NEUTRON_BIN_DIR/neutron-metering-agent"} 72 | METERING_AGENT_CONF_FILENAME=${METERING_AGENT_CONF_FILENAME:-"/etc/neutron/services/metering/metering_agent.ini"} 73 | 74 | # NOTE(vsaienko) restart neutron to use new networking-generic-switch 75 | neutron_server_config_add ${GENERIC_SWITCH_INI_FILE} 76 | stop_neutron 77 | # Start neutron and agents 78 | start_neutron_service_and_check 79 | start_neutron_agents 80 | 81 | echo "*********************************************************************" 82 | echo "SUCCESS: End $0" 83 | echo "*********************************************************************" 84 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | reno>=3.1.0 # Apache-2.0 2 | sphinx>=2.0.0,!=2.1.0 # BSD 3 | sphinxcontrib-pecanwsme>=0.8.0 # Apache-2.0 4 | sphinxcontrib-seqdiag>=0.8.4 # BSD 5 | openstackdocstheme>=2.2.1 # Apache-2.0 6 | sphinxcontrib-apidoc>=0.2.0 # BSD 7 | -------------------------------------------------------------------------------- /doc/source/_exts/netmiko_device_commands.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import stevedore 4 | 5 | from docutils import nodes 6 | from docutils.parsers import rst 7 | from docutils.statemachine import ViewList 8 | from sphinx.util.nodes import nested_parse_with_titles 9 | 10 | command_descriptions = { 11 | 'ADD_NETWORK': 'A tuple of command strings used to add a VLAN', 12 | 'DELETE_NETWORK': 'A tuple of command strings used to delete a VLAN', 13 | 'PLUG_PORT_TO_NETWORK': 'A tuple of command strings used to configure a \ 14 | port to connect to a specific VLAN', 15 | 'DELETE_PORT': 'A tuple of command strings used to remove a port from the\ 16 | VLAN', 17 | 'NETMIKO_DEVICE_TYPE': 'Netmiko Supported device type', 18 | 'ADD_NETWORK_TO_TRUNK': 'Adds a network to a trunk port.', 19 | 'REMOVE_NETWORK_FROM_TRUNK': 'Removes a network from a trunk port.', 20 | 'ENABLE_PORT': 'Enables the port', 21 | 'DISABLE_PORT': 'Shuts down the port', 22 | 'ERROR_MSG_PATTERNS': 'A tuple of regular expressions. These patterns are\ 23 | used to match and handle error messages returned by the switch.', 24 | 'SET_NATIVE_VLAN': 'Sets a specified native VLAN', 25 | 'DELETE_NATIVE_VLAN': 'Removes the native VLAN', 26 | 'SAVE_CONFIGURATION': 'Saves the configuration', 27 | 'SET_NATIVE_VLAN_BOND': 'Sets the native VLAN for the bond interface', 28 | 'DELETE_NATIVE_VLAN_BOND': 'Unsets the native VLAN for the bond \ 29 | interface', 30 | 'ADD_NETWORK_TO_BOND_TRUNK': 'Adds a VLAN to the bond interface for \ 31 | trunking', 32 | 'DELETE_NETWORK_ON_BOND_TRUNK': 'Removes a VLAN from the bond interface \ 33 | for trunking', 34 | 'PLUG_PORT_TO_NETWORK_GENERAL': 'Allows the VLAN and lets it carry \ 35 | untagged frames', 36 | 'DELETE_PORT_GENERAL': 'Removes VLAN from allowed list and stops allowing\ 37 | it to carry untagged frames', 38 | 'QUERY_PORT': 'Shows details about the switch for that port', 39 | 'PLUG_BOND_TO_NETWORK': 'Adds bond to the bridge as a port for the VLAN', 40 | 'UNPLUG_BOND_FROM_NETWORK': 'Removes bond\'s access VLAN assignment', 41 | 'ENABLE_BOND': 'Enables bond interface by removing link down state', 42 | 'DISABLE_BOND': 'Disables bond interface by setting its link state to \ 43 | down', 44 | } 45 | 46 | 47 | class DeviceCommandsDirective(rst.Directive): 48 | 49 | def parse_tuples(value): 50 | """Parses the value in the tuples and returns a list of its contents""" 51 | tuple_values = [] 52 | for elt in value.elts: 53 | # Parsing if the item in the tuple is a function call 54 | if isinstance(elt, ast.Call): 55 | func_name = '' 56 | if isinstance(elt.func, ast.Attribute): 57 | func_name = f"{ast.unparse(elt.func.value)}.{elt.func.attr}" 58 | elif isinstance(elt.func, ast.Name): 59 | func_name = elt.func.id 60 | args = [ast.literal_eval(arg) for arg in elt.args] 61 | call_command = str(func_name) + '(\'' + str(args[0]) + '\')' 62 | tuple_values.append(call_command) 63 | 64 | else: 65 | tuple_values.append(ast.literal_eval(elt)) 66 | return tuple_values 67 | 68 | def parse_file(file_content, filename): 69 | """Uses ast to split document body into nodes and parse them""" 70 | tree = ast.parse(file_content, filename=filename) 71 | classes = {} 72 | for node in tree.body: 73 | if isinstance(node, ast.ClassDef): 74 | device_name = node.name 75 | cli_commands = {} 76 | docstring = ast.get_docstring(node) 77 | 78 | if docstring: 79 | cli_commands['__doc__'] = docstring 80 | # Iterates through nodes, checks for type of node and extracts the value 81 | for subnode in node.body: 82 | if isinstance(subnode, ast.Assign): 83 | for target in subnode.targets: 84 | command_name = target.id 85 | if isinstance(target, ast.Name): 86 | ast_type = subnode.value 87 | if isinstance(ast_type, ast.Tuple): 88 | cli_commands[command_name] = DeviceCommandsDirective.parse_tuples(ast_type) 89 | else: 90 | cli_commands[command_name] = ast.literal_eval(ast_type) 91 | if cli_commands: 92 | classes[device_name] = cli_commands 93 | return classes 94 | 95 | def format_output(switch_details): 96 | """Formats output that is to be displayed""" 97 | formatted_output = ViewList() 98 | if '__doc__' in switch_details: 99 | for line in switch_details['__doc__'].splitlines(): 100 | formatted_output.append(f" {line}", "") 101 | formatted_output.append("", "") 102 | del switch_details['__doc__'] 103 | for command_name, cli_commands in switch_details.items(): 104 | desc = command_descriptions.get(command_name, 'No description provided') 105 | formatted_output.append(f" - {command_name}: {desc}", "") 106 | formatted_output.append(f" - CLI commands:", "") 107 | if isinstance(cli_commands, list): 108 | if cli_commands: 109 | for command in cli_commands: 110 | formatted_output.append(f" - {command}", "") 111 | else: 112 | formatted_output.append(f" - No cli commands for this switch command", "") 113 | else: 114 | formatted_output.append(f" - {cli_commands}", "") 115 | return formatted_output 116 | 117 | def run(self): 118 | """Loads the files, parses them and formats the output""" 119 | manager = stevedore.ExtensionManager( 120 | namespace='generic_switch.devices', 121 | invoke_on_load=False, 122 | ) 123 | output_lines = ViewList() 124 | output_lines.append("Switches", "") 125 | output_lines.append("========", "") 126 | 127 | for file_loader in manager.extensions: 128 | switch = file_loader.plugin 129 | module = inspect.getmodule(switch) 130 | file_content = inspect.getsource(module) 131 | filename = module.__file__ 132 | parsed_device_file = DeviceCommandsDirective.parse_file(file_content, filename) 133 | switch_name = switch.__name__ 134 | output_lines.append(f"{switch_name}:", "") 135 | subheading_characters = "^" 136 | subheading = subheading_characters * (len(switch_name) + 1) 137 | output_lines.append(subheading, "") 138 | 139 | if switch_name in parsed_device_file: 140 | switch_details = parsed_device_file[switch_name] 141 | output_lines.extend(DeviceCommandsDirective.format_output(switch_details)) 142 | output_lines.append("", "") 143 | 144 | node = nodes.section() 145 | node.document = self.state.document 146 | nested_parse_with_titles(self.state, output_lines, node) 147 | return node.children 148 | 149 | def setup(app): 150 | app.add_directive('netmiko-device-commands', DeviceCommandsDirective) 151 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 11 | # implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import sys 17 | 18 | sys.path.insert(0, os.path.abspath('../..')) 19 | sys.path.insert(0, os.path.join(os.path.abspath('.'), '_exts')) 20 | # -- General configuration ---------------------------------------------------- 21 | 22 | # Add any Sphinx extension module names here, as strings. They can be 23 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 24 | extensions = [ 25 | 'sphinxcontrib.apidoc', 26 | 'openstackdocstheme', 27 | 'netmiko_device_commands' 28 | ] 29 | 30 | # openstackdocstheme options 31 | openstackdocs_repo_name = 'openstack/networking-generic-switch' 32 | openstackdocs_pdf_link = True 33 | openstackdocs_use_storyboard = False 34 | 35 | # autodoc generation is a bit aggressive and a nuisance when doing heavy 36 | # text edit cycles. 37 | # execute "export SPHINX_DEBUG=1" in your terminal to disable 38 | 39 | # The suffix of source filenames. 40 | source_suffix = '.rst' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | copyright = '2017, Openstack Foundation' 47 | 48 | # If true, '()' will be appended to :func: etc. cross-reference text. 49 | add_function_parentheses = True 50 | 51 | # If true, the current module name will be prepended to all description 52 | # unit titles (such as .. function::). 53 | add_module_names = True 54 | 55 | # The name of the Pygments (syntax highlighting) style to use. 56 | pygments_style = 'native' 57 | 58 | # -- Options for HTML output -------------------------------------------------- 59 | 60 | # The theme to use for HTML and HTML Help pages. Major themes that come with 61 | # Sphinx are currently 'default' and 'sphinxdoc'. 62 | # html_theme_path = ["."] 63 | # html_static_path = ['static'] 64 | html_theme = 'openstackdocs' 65 | 66 | # Output file base name for HTML help builder. 67 | htmlhelp_basename = 'networking-generic-switchdoc' 68 | 69 | latex_use_xindy = False 70 | 71 | # Grouping the document tree into LaTeX files. List of tuples 72 | # (source start file, target name, title, author, documentclass 73 | # [howto/manual]). 74 | latex_documents = [ 75 | ('index', 76 | 'doc-networking-generic-switch.tex', 77 | 'Networking Generic Switch Documentation', 78 | 'OpenStack Foundation', 'manual'), 79 | ] 80 | 81 | # Example configuration for intersphinx: refer to the Python standard library. 82 | #intersphinx_mapping = {'http://docs.python.org/': None} 83 | 84 | # -- sphinxcontrib.apidoc configuration -------------------------------------- 85 | 86 | apidoc_module_dir = '../../networking_generic_switch' 87 | apidoc_output_dir = 'contributor/api' 88 | apidoc_excluded_paths = [ 89 | 'tests', 90 | 'devices/netmiko_devices', 91 | ] 92 | -------------------------------------------------------------------------------- /doc/source/contributing.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | .. include:: ../../CONTRIBUTING.rst 5 | -------------------------------------------------------------------------------- /doc/source/dev/dev-quickstart.rst: -------------------------------------------------------------------------------- 1 | .. _dev-quickstart: 2 | 3 | ===================== 4 | Developer Quick-Start 5 | ===================== 6 | 7 | This is a quick walk through to get you started developing code for 8 | Networking-generic-switch. This assumes you are already familiar with 9 | submitting code reviews to an OpenStack project. 10 | 11 | ================================================= 12 | Deploying Networking-generic-switch with DevStack 13 | ================================================= 14 | 15 | DevStack may be configured to deploy Networking-generic-switch, setup Neutron to 16 | use the Networking-generic-switch ML2 driver. It is highly recommended 17 | to deploy on an expendable virtual machine and not on your personal work 18 | station. Deploying Networking-generic-switch with DevStack requires a machine 19 | running Ubuntu 14.04 (or later) or Fedora 20 (or later). 20 | 21 | .. seealso:: 22 | 23 | https://docs.openstack.org/devstack/latest/ 24 | 25 | Devstack will no longer create the user 'stack' with the desired 26 | permissions, but does provide a script to perform the task:: 27 | 28 | git clone https://github.com/openstack-dev/devstack.git devstack 29 | sudo ./devstack/tools/create-stack-user.sh 30 | 31 | Switch to the stack user and clone DevStack:: 32 | 33 | sudo su - stack 34 | git clone https://github.com/openstack-dev/devstack.git devstack 35 | 36 | Create devstack/local.conf with minimal settings required to enable 37 | Networking-generic-switch. Here is an example of local.conf: 38 | 39 | 40 | .. code-block:: ini 41 | 42 | [[local|localrc]] 43 | # Set credentials 44 | ADMIN_PASSWORD=secrete 45 | DATABASE_PASSWORD=secrete 46 | RABBIT_PASSWORD=secrete 47 | SERVICE_PASSWORD=secrete 48 | SERVICE_TOKEN=secrete 49 | 50 | # Enable minimal required services 51 | ENABLED_SERVICES="dstat,mysql,rabbit,key,q-svc,q-agt,q-dhcp" 52 | 53 | # Enable networking-generic-switch plugin 54 | enable_plugin networking-generic-switch https://review.openstack.org/openstack/networking-generic-switch 55 | 56 | # Configure Neutron 57 | OVS_PHYSICAL_BRIDGE=brbm 58 | PHYSICAL_NETWORK=mynetwork 59 | Q_PLUGIN=ml2 60 | ENABLE_TENANT_VLANS=True 61 | Q_ML2_TENANT_NETWORK_TYPE=vlan 62 | TENANT_VLAN_RANGE=100:150 63 | 64 | # Configure logging 65 | LOGFILE=$HOME/devstack.log 66 | LOGDIR=$HOME/logs 67 | 68 | Run stack.sh:: 69 | 70 | ./stack.sh 71 | 72 | Source credentials:: 73 | 74 | source ~/devstack/openrc admin admin 75 | 76 | 77 | Test with OVS 78 | ------------- 79 | 80 | Launch exercise.sh from networking-generic-switch. This script 81 | creates port in Neutron/update it with local_link_information and 82 | verifies that ovs port has been assigned to correct VLAN:: 83 | 84 | bash ~/networking-generic-switch/devstack/exercise.sh 85 | 86 | 87 | Test with real hardware 88 | ----------------------- 89 | 90 | Add information about hardware switch to Networking-generic-switch 91 | config ``/etc/neutron/plugins/ml2/ml2_conf_genericswitch.ini`` and 92 | restart Neutron server: 93 | 94 | .. code-block:: ini 95 | 96 | [genericswitch:cisco_switch_1] 97 | device_type = netmiko_cisco_ios 98 | ip = 1.2.3.4 99 | username = cisco 100 | password = cisco 101 | secret = enable_password 102 | 103 | 104 | Get current configuration of the port on the switch, for example for 105 | Cisco IOS device: 106 | 107 | .. code-block:: ini 108 | 109 | sh running-config int gig 0/12 110 | Building configuration... 111 | 112 | Current configuration : 283 bytes 113 | ! 114 | interface GigabitEthernet0/12 115 | switchport mode access 116 | end 117 | 118 | Run exercise.py to create/update Neutron port. It will print VLAN id to be 119 | assigned: 120 | 121 | .. code-block:: ini 122 | 123 | $ neutron net-create test 124 | $ python ~/networking-generic-switch/devstack/exercise.py --switch_name cisco_switch_1 --port Gig0/12 --switch_id=06:58:1f:e7:b4:44 --network test 125 | 126 126 | 127 | 128 | Verify that VLAN has been changed on the switch port, for example for 129 | Cisco IOS device: 130 | 131 | .. code-block:: ini 132 | 133 | sh running-config int gig 0/12 134 | Building configuration... 135 | 136 | Current configuration : 311 bytes 137 | ! 138 | interface GigabitEthernet0/12 139 | switchport access vlan 126 140 | switchport mode access 141 | end 142 | -------------------------------------------------------------------------------- /doc/source/include/configure-neutron.rst: -------------------------------------------------------------------------------- 1 | Enable genericswitch mechanism driver in Neutron 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | To enable mechanism drivers in the ML2 plug-in, edit the 5 | ``/etc/neutron/plugins/ml2/ml2_conf.ini`` file on the neutron server:: 6 | 7 | [ml2] 8 | mechanism_drivers = ovs,genericswitch 9 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to networking-generic-switch's documentation! 2 | ===================================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | supported-devices 8 | installation 9 | configuration 10 | dev/dev-quickstart 11 | contributing 12 | netmiko-device-commands 13 | contributor/api/modules 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /doc/source/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | This sections describes how to install and configure networking-generic-switch 6 | plugin. 7 | 8 | At the command line:: 9 | 10 | $ pip install networking-generic-switch 11 | 12 | Or, if you have virtualenvwrapper installed:: 13 | 14 | $ mkvirtualenv networking-generic-switch 15 | $ pip install networking-generic-switch 16 | 17 | .. include:: include/configure-neutron.rst 18 | -------------------------------------------------------------------------------- /doc/source/netmiko-device-commands.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Netmiko Device Commands 3 | ======================= 4 | 5 | This section contains details of the commands capable of being executed 6 | by the switches and the CLI commands sent to the switch for each command 7 | module that is selected. 8 | 9 | .. netmiko-device-commands:: 10 | -------------------------------------------------------------------------------- /doc/source/supported-devices.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Supported Devices 3 | ================= 4 | 5 | The following devices are supported by this plugin: 6 | 7 | * Arista EOS 8 | * ArubaOS-CX switches 9 | * Brocade ICX (FastIron) 10 | * Cisco 300-series switches 11 | * Cisco IOS switches 12 | * Cisco NX-OS switches (Nexus) 13 | * Cumulus Linux (via NCLU) 14 | * Cumulus Linux (via NVUE) 15 | * Dell Force10 (netmiko_dell_force10) 16 | * Dell OS10 (netmiko_dell_os10) 17 | * Dell PowerConnect 18 | * HPE 5900 Series switches 19 | * Huawei switches 20 | * Juniper Junos OS switches 21 | * OpenVSwitch 22 | * Ruijie switches 23 | * SONiC switches 24 | * Supermicro switches 25 | 26 | This Mechanism Driver architecture allows easily to add more devices 27 | of any type. 28 | 29 | :: 30 | 31 | OpenStack Neutron v2.0 => ML2 plugin => Generic Mechanism Driver => Device plugin 32 | 33 | These device plugins use `Netmiko `_ 34 | library, which in turn uses `Paramiko` library to access and configure 35 | the switches via the SSH protocol. 36 | 37 | Cisco Nexus (netmiko_cisco_nxos) 38 | -------------------------------- 39 | 40 | Known working firmware versions: 10.3.7 41 | 42 | Notes: 43 | 44 | * Default state for switches is well suited for networking-generic-switch 45 | as long as SSH is utilized *and* the underlying role provided to the 46 | account permits configuration of switchports. 47 | * Pre-configuration of upstream network trunk ports to the neutron networking 48 | nodes is advisable, however the ``ngs_trunk_ports`` setting should be 49 | suitable for most users as well. 50 | * Use of an "enable" secret through the ``secret`` configuration option has 51 | not been tested. 52 | 53 | Dell Force10 OS9 (netmiko_dell_force10) 54 | --------------------------------------- 55 | 56 | Known working firmware versions: 9.13.0.0 57 | 58 | Notes: 59 | 60 | * Dell Force10 Simulator for 9.13.0 lacks the ability to set a switchport 61 | mode to trunk, which prevents automated or even semi-automated testing. 62 | That being said, creating VLANs and tagging/untagging works as expected. 63 | * Uplink switchports to the rest of the network fabric must be configured in 64 | advance if the ``ngs_trunk_ports`` switch device level configuration 65 | option is *not* utilized. 66 | * Use of SSH is expected and must be configured on the remote switch. 67 | * Set each port to "switchport" to enable L2 switchport mode. 68 | * Use of an "enable" secret through the switch level configuration option 69 | ``secret`` was the tested path. Depending on precise switch configuration 70 | and access control modeling, it may be possible to use without an enable 71 | secret, but that has not been tested. 72 | 73 | Known Issues: 74 | 75 | * `bug 2100641 `_ is 76 | alieviated by setting a port to "switchport" *before* attempting to utilize 77 | networking-generic-switch. 78 | 79 | Dell Force10 OS10 (netmiko_dell_os10) 80 | ------------------------------------- 81 | 82 | Known working firmware version: 10.6.0.2.74 83 | 84 | Notes: 85 | 86 | * Uplink switchports may need to be configured as Trunk ports prior to the 87 | use of networking-generic-switch through a "switchport mode trunk" command. 88 | Further specific trunk configuration may be necessary, however NGS can 89 | leverage the ``ngs_trunk_ports`` configuration option and does appropriately 90 | tag switchports as permitted when creating/deleting attachments. 91 | * Password authentication for networking-generic-switch needs to be setup in 92 | advance, specifically "ip ssh server enable" and 93 | "ip ssh server password-authentication" commands. 94 | * This driver was tested *without* the use of an enable secret to 95 | permit a higher level of configuration access within the Switch. 96 | 97 | Sonic - Community Distribution (netmiko_sonic) 98 | ---------------------------------------------- 99 | 100 | Known working firmware version: master branch - March 2025 101 | 102 | Notes: 103 | 104 | * The driver expects to be able to SSH into the switch running 105 | SONiC, execute sudo, and then execute configuration commands. 106 | * Ports *must* be in Layer-2 mode. As such, 107 | ``sudo config interface ip remove $INTERFACE $IP_ADDRESS/$CIDR`` 108 | and ``sudo config switchport mode access $INTERFACE`` commands 109 | may be required. 110 | * Uplink switch ports should be configured in advance with the 111 | ``sudo config switchport mode trunk $INTERFACE`` command. 112 | Testing for the configuraiton utilized this advanced state 113 | configuration of the trunk uplink ports with the ``ngs_trunk_ports`` 114 | configuration option for Networking-Generic-Switch. 115 | -------------------------------------------------------------------------------- /etc/neutron/plugins/ml2/ml2_conf_genericswitch.ini.sample: -------------------------------------------------------------------------------- 1 | [genericswitch:c2970G] 2 | device_type=netmiko_cisco_ios 3 | ip=1.2.3.4 4 | username=user 5 | password=cisco_password 6 | secret=enable_password 7 | -------------------------------------------------------------------------------- /networking_generic_switch/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/networking_generic_switch/__init__.py -------------------------------------------------------------------------------- /networking_generic_switch/_i18n.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 3 | # not use this file except in compliance with the License. You may obtain 4 | # a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # License for the specific language governing permissions and limitations 12 | # under the License. 13 | 14 | import oslo_i18n 15 | 16 | DOMAIN = "networking_generic_switch" 17 | 18 | _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) 19 | 20 | # The primary translation function using the well-known name "_" 21 | _ = _translators.primary 22 | -------------------------------------------------------------------------------- /networking_generic_switch/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import glob 16 | import os 17 | 18 | from oslo_config import cfg 19 | from oslo_log import log as logging 20 | 21 | CONF = cfg.CONF 22 | LOG = logging.getLogger(__name__) 23 | 24 | coordination_opts = [ 25 | cfg.StrOpt('backend_url', 26 | secret=True, 27 | help='The backend URL to use for distributed coordination.'), 28 | cfg.IntOpt('acquire_timeout', 29 | min=0, 30 | default=60, 31 | help='Timeout in seconds after which an attempt to grab a lock ' 32 | 'is failed. Value of 0 is forever.'), 33 | ] 34 | 35 | ngs_opts = [ 36 | cfg.StrOpt('session_log_file', 37 | default=None, 38 | help='Netmiko session log file.') 39 | ] 40 | 41 | CONF.register_opts(coordination_opts, group='ngs_coordination') 42 | CONF.register_opts(ngs_opts, group='ngs') 43 | 44 | 45 | def config_files(): 46 | """Generate which yields all config files in the required order""" 47 | for config_file in CONF.config_file: 48 | yield config_file 49 | for config_dir in CONF.config_dir: 50 | config_dir_glob = os.path.join(config_dir, '*.conf') 51 | for config_file in sorted(glob.glob(config_dir_glob)): 52 | yield config_file 53 | 54 | 55 | def get_devices(): 56 | """Parse supplied config files and fetch defined supported devices.""" 57 | 58 | device_tag = 'genericswitch:' 59 | devices = {} 60 | 61 | for filename in config_files(): 62 | LOG.debug(f'Searching for genericswitch config in: {filename}') 63 | sections = {} 64 | parser = cfg.ConfigParser(filename, sections) 65 | try: 66 | parser.parse() 67 | except IOError: 68 | continue 69 | for parsed_item, parsed_value in sections.items(): 70 | if parsed_item.startswith(device_tag): 71 | LOG.debug(f'Found genericswitch config: {parsed_item}') 72 | dev_id = parsed_item.partition(device_tag)[2] 73 | device_cfg = {k: v[0] for k, v 74 | in parsed_value.items()} 75 | devices[dev_id] = device_cfg 76 | 77 | return devices 78 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/arista.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class AristaEos(netmiko_devices.NetmikoSwitch): 19 | ADD_NETWORK = ( 20 | 'vlan {segmentation_id}', 21 | 'name {network_name}', 22 | ) 23 | 24 | DELETE_NETWORK = ( 25 | 'no vlan {segmentation_id}', 26 | ) 27 | 28 | PLUG_PORT_TO_NETWORK = ( 29 | 'interface {port}', 30 | 'switchport mode access', 31 | 'switchport access vlan {segmentation_id}', 32 | ) 33 | 34 | DELETE_PORT = ( 35 | 'interface {port}', 36 | 'no switchport access vlan {segmentation_id}', 37 | 'no switchport mode trunk', 38 | 'switchport trunk allowed vlan none' 39 | ) 40 | 41 | SET_NATIVE_VLAN = ( 42 | 'interface {port}', 43 | 'switchport mode trunk', 44 | 'switchport trunk native vlan {segmentation_id}', 45 | 'switchport trunk allowed vlan add {segmentation_id}' 46 | ) 47 | 48 | DELETE_NATIVE_VLAN = ( 49 | 'interface {port}', 50 | 'no switchport trunk native vlan {segmentation_id}', 51 | 'switchport trunk allowed vlan remove {segmentation_id}', 52 | ) 53 | 54 | ADD_NETWORK_TO_TRUNK = ( 55 | 'interface {port}', 56 | 'switchport trunk allowed vlan add {segmentation_id}' 57 | ) 58 | 59 | REMOVE_NETWORK_FROM_TRUNK = ( 60 | 'interface {port}', 61 | 'switchport trunk allowed vlan remove {segmentation_id}' 62 | ) 63 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/aruba.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 VEXXHOST, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class ArubaOSCX(netmiko_devices.NetmikoSwitch): 19 | """Built for ArubaOS-CX""" 20 | 21 | ADD_NETWORK = ( 22 | 'vlan {segmentation_id}', 23 | 'name {network_name}', 24 | ) 25 | 26 | DELETE_NETWORK = ( 27 | 'no vlan {segmentation_id}', 28 | ) 29 | 30 | PLUG_PORT_TO_NETWORK = ( 31 | 'interface {port}', 32 | 'no routing', 33 | 'vlan access {segmentation_id}', 34 | ) 35 | 36 | DELETE_PORT = ( 37 | 'interface {port}', 38 | 'no vlan access {segmentation_id}', 39 | ) 40 | 41 | ADD_NETWORK_TO_TRUNK = ( 42 | "interface {port}", 43 | "no routing", 44 | "vlan trunk allowed {segmentation_id}", 45 | ) 46 | 47 | REMOVE_NETWORK_FROM_TRUNK = ( 48 | "interface {port}", 49 | "no vlan trunk allowed {segmentation_id}", 50 | ) 51 | 52 | ENABLE_PORT = ( 53 | "interface {port}", 54 | "no shutdown", 55 | ) 56 | 57 | DISABLE_PORT = ( 58 | "interface {port}", 59 | "shutdown", 60 | ) 61 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/brocade.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Servers.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | import re 17 | 18 | from oslo_log import log as logging 19 | 20 | from networking_generic_switch.devices import netmiko_devices 21 | 22 | 23 | LOG = logging.getLogger(__name__) 24 | 25 | 26 | class BrocadeFastIron(netmiko_devices.NetmikoSwitch): 27 | ADD_NETWORK = ( 28 | 'vlan {segmentation_id} by port', 29 | 'name {network_name}', 30 | ) 31 | 32 | DELETE_NETWORK = ( 33 | 'no vlan {segmentation_id}', 34 | ) 35 | 36 | PLUG_PORT_TO_NETWORK = ( 37 | 'vlan {segmentation_id} by port', 38 | 'untagged ether {port}', 39 | ) 40 | 41 | DELETE_PORT = ( 42 | 'vlan {segmentation_id} by port', 43 | 'no untagged ether {port}', 44 | ) 45 | 46 | QUERY_PORT = ( 47 | 'show interfaces ether {port} | include VLAN', 48 | ) 49 | 50 | @staticmethod 51 | def _process_raw_output(raw_output): 52 | PATTERN = "Member of L2 VLAN ID (\\d+), port is untagged" 53 | match = re.search(PATTERN, raw_output) 54 | if not match: 55 | return None 56 | return match.group(1) # vlan_id 57 | 58 | def get_wrong_vlan(self, port): 59 | raw_output = self.send_commands_to_device( 60 | self._format_commands(self.QUERY_PORT, port=port) 61 | ) 62 | return self._process_raw_output(str(raw_output)) 63 | 64 | def clean_port_vlan_if_necessary(self, port): 65 | wrong_vlan = self.get_wrong_vlan(port) 66 | if not wrong_vlan: 67 | return 68 | if str(wrong_vlan) == '1': 69 | return 70 | LOG.warning( 71 | 'Port %s is used in a wrong vlan %s, clean it', 72 | port, 73 | str(wrong_vlan) 74 | ) 75 | self.delete_port(port, wrong_vlan) 76 | 77 | @netmiko_devices.check_output('plug port') 78 | def plug_port_to_network(self, port, segmentation_id): 79 | self.clean_port_vlan_if_necessary(port) 80 | return super(BrocadeFastIron, self).plug_port_to_network( 81 | port, segmentation_id) 82 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/cisco.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # Copyright 2022 Baptiste Jonglez, Inria 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from networking_generic_switch.devices import netmiko_devices 17 | 18 | 19 | class CiscoIos(netmiko_devices.NetmikoSwitch): 20 | ADD_NETWORK = ( 21 | 'vlan {segmentation_id}', 22 | 'name {network_name}', 23 | ) 24 | 25 | DELETE_NETWORK = ( 26 | 'no vlan {segmentation_id}', 27 | ) 28 | 29 | PLUG_PORT_TO_NETWORK = ( 30 | 'interface {port}', 31 | 'switchport mode access', 32 | 'switchport access vlan {segmentation_id}', 33 | ) 34 | 35 | DELETE_PORT = ( 36 | 'interface {port}', 37 | 'no switchport access vlan {segmentation_id}', 38 | 'no switchport mode trunk', 39 | 'switchport trunk allowed vlan none' 40 | ) 41 | 42 | SET_NATIVE_VLAN = ( 43 | 'interface {port}', 44 | 'switchport mode trunk', 45 | 'switchport trunk native vlan {segmentation_id}', 46 | 'switchport trunk allowed vlan add {segmentation_id}', 47 | ) 48 | 49 | DELETE_NATIVE_VLAN = ( 50 | 'interface {port}', 51 | 'no switchport mode trunk', 52 | 'no switchport trunk native vlan {segmentation_id}', 53 | 'switchport trunk allowed vlan remove {segmentation_id}', 54 | ) 55 | 56 | ADD_NETWORK_TO_TRUNK = ( 57 | 'interface {port}', 58 | 'switchport mode trunk', 59 | 'switchport trunk allowed vlan add {segmentation_id}', 60 | ) 61 | 62 | REMOVE_NETWORK_FROM_TRUNK = ( 63 | 'interface {port}', 64 | 'switchport trunk allowed vlan remove {segmentation_id}', 65 | ) 66 | 67 | 68 | class CiscoNxOS(netmiko_devices.NetmikoSwitch): 69 | """Netmiko device driver for Cisco Nexus switches running NX-OS.""" 70 | 71 | ADD_NETWORK = ( 72 | 'vlan {segmentation_id}', 73 | 'name {network_name}', 74 | 'exit', 75 | ) 76 | 77 | DELETE_NETWORK = ( 78 | 'no vlan {segmentation_id}', 79 | ) 80 | 81 | PLUG_PORT_TO_NETWORK = ( 82 | 'interface {port}', 83 | 'switchport mode access', 84 | 'switchport access vlan {segmentation_id}', 85 | 'exit', 86 | ) 87 | 88 | DELETE_PORT = ( 89 | 'interface {port}', 90 | 'no switchport access vlan', 91 | 'exit', 92 | ) 93 | 94 | ADD_NETWORK_TO_TRUNK = ( 95 | 'interface {port}', 96 | 'switchport mode trunk', 97 | 'switchport trunk allowed vlan add {segmentation_id}', 98 | 'exit', 99 | ) 100 | 101 | REMOVE_NETWORK_FROM_TRUNK = ( 102 | 'interface {port}', 103 | 'switchport trunk allowed vlan remove {segmentation_id}', 104 | 'exit', 105 | ) 106 | 107 | ENABLE_PORT = ( 108 | 'interface {port}', 109 | 'no shutdown', 110 | 'exit', 111 | ) 112 | 113 | DISABLE_PORT = ( 114 | 'interface {port}', 115 | 'shutdown', 116 | 'exit', 117 | ) 118 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/cisco300.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | # NOTE(TheJulia): The Cisco 300 is not Cisco IOS based and as such 18 | # the command line behaves a little differently, such as setting 19 | # terminal behavior. Command syntax varies slightly. i.e. no name 20 | # command within the vlan configuration. 21 | 22 | 23 | class Cisco300(netmiko_devices.NetmikoSwitch): 24 | ADD_NETWORK = ( 25 | 'vlan {segmentation_id}', 26 | ) 27 | 28 | DELETE_NETWORK = ( 29 | 'no vlan {segmentation_id}', 30 | ) 31 | 32 | PLUG_PORT_TO_NETWORK = ( 33 | 'interface {port}', 34 | 'switchport mode access', 35 | 'switchport access vlan {segmentation_id}', 36 | ) 37 | 38 | DELETE_PORT = ( 39 | 'interface {port}', 40 | 'no switchport access vlan', 41 | 'switchport trunk allowed vlan remove all', 42 | ) 43 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/dell.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import re 16 | 17 | from networking_generic_switch.devices import netmiko_devices 18 | from networking_generic_switch import exceptions as exc 19 | 20 | 21 | class DellOS10(netmiko_devices.NetmikoSwitch): 22 | """Netmiko device driver for Dell PowerSwitch switches.""" 23 | 24 | ADD_NETWORK = ( 25 | "interface vlan {segmentation_id}", 26 | "description {network_name}", 27 | "exit", 28 | ) 29 | 30 | DELETE_NETWORK = ( 31 | "no interface vlan {segmentation_id}", 32 | "exit", 33 | ) 34 | 35 | PLUG_PORT_TO_NETWORK = ( 36 | "interface {port}", 37 | "switchport mode access", 38 | "switchport access vlan {segmentation_id}", 39 | "exit", 40 | ) 41 | 42 | DELETE_PORT = ( 43 | "interface {port}", 44 | "no switchport access vlan", 45 | "exit", 46 | ) 47 | 48 | ADD_NETWORK_TO_TRUNK = ( 49 | "interface {port}", 50 | "switchport mode trunk", 51 | "switchport trunk allowed vlan {segmentation_id}", 52 | "exit", 53 | ) 54 | 55 | REMOVE_NETWORK_FROM_TRUNK = ( 56 | "interface {port}", 57 | "no switchport trunk allowed vlan {segmentation_id}", 58 | "exit", 59 | ) 60 | 61 | SET_NATIVE_VLAN = ( 62 | 'interface {port}', 63 | 'switchport mode trunk', 64 | 'switchport access vlan {segmentation_id}', 65 | ) 66 | 67 | DELETE_NATIVE_VLAN = ( 68 | 'interface {port}', 69 | 'no switchport access vlan', 70 | ) 71 | 72 | ENABLE_PORT = ( 73 | "interface {port}", 74 | "no shutdown", 75 | "exit", 76 | ) 77 | 78 | DISABLE_PORT = ( 79 | "interface {port}", 80 | "shutdown", 81 | "exit", 82 | ) 83 | 84 | ERROR_MSG_PATTERNS = () 85 | """Sequence of error message patterns. 86 | 87 | Sequence of re.RegexObject objects representing patterns to check for in 88 | device output that indicate a failure to apply configuration. 89 | """ 90 | 91 | 92 | class DellNos(netmiko_devices.NetmikoSwitch): 93 | """Netmiko device driver for Dell Force10 (OS9) switches.""" 94 | 95 | ADD_NETWORK = ( 96 | 'interface vlan {segmentation_id}', 97 | # It's not possible to set the name on OS9: the field takes 32 98 | # chars max, and cannot begin with a number. Let's set the 99 | # description and leave the name empty. 100 | 'description {network_name}', 101 | 'exit', 102 | ) 103 | 104 | DELETE_NETWORK = ( 105 | 'no interface vlan {segmentation_id}', 106 | 'exit', 107 | ) 108 | 109 | PLUG_PORT_TO_NETWORK = ( 110 | 'interface vlan {segmentation_id}', 111 | 'untagged {port}', 112 | 'exit', 113 | ) 114 | 115 | DELETE_PORT = ( 116 | 'interface vlan {segmentation_id}', 117 | 'no untagged {port}', 118 | 'exit', 119 | ) 120 | 121 | ADD_NETWORK_TO_TRUNK = ( 122 | 'interface vlan {segmentation_id}', 123 | 'tagged {port}', 124 | 'exit', 125 | ) 126 | 127 | REMOVE_NETWORK_FROM_TRUNK = ( 128 | 'interface vlan {segmentation_id}', 129 | 'no tagged {port}', 130 | 'exit', 131 | ) 132 | 133 | 134 | class DellPowerConnect(netmiko_devices.NetmikoSwitch): 135 | """Netmiko device driver for Dell PowerConnect switches.""" 136 | 137 | def _switch_to_general_mode(self): 138 | self.PLUG_PORT_TO_NETWORK = self.PLUG_PORT_TO_NETWORK_GENERAL 139 | self.DELETE_PORT = self.DELETE_PORT_GENERAL 140 | 141 | def __init__(self, device_cfg, *args, **kwargs): 142 | super(DellPowerConnect, self).__init__(device_cfg, *args, **kwargs) 143 | port_mode = self.ngs_config['ngs_switchport_mode'] 144 | switchport_mode = { 145 | 'general': self._switch_to_general_mode, 146 | 'access': lambda: () 147 | } 148 | 149 | def on_invalid_switchmode(): 150 | raise exc.GenericSwitchConfigException( 151 | option="ngs_switchport_mode", 152 | allowed_options=switchport_mode.keys() 153 | ) 154 | 155 | switchport_mode.get(port_mode.lower(), on_invalid_switchmode)() 156 | 157 | ADD_NETWORK = ( 158 | 'vlan database', 159 | 'vlan {segmentation_id}', 160 | 'exit', 161 | ) 162 | 163 | DELETE_NETWORK = ( 164 | 'vlan database', 165 | 'no vlan {segmentation_id}', 166 | 'exit', 167 | ) 168 | 169 | PLUG_PORT_TO_NETWORK_GENERAL = ( 170 | 'interface {port}', 171 | 'switchport general allowed vlan add {segmentation_id} untagged', 172 | 'switchport general pvid {segmentation_id}', 173 | 'exit', 174 | ) 175 | 176 | PLUG_PORT_TO_NETWORK = ( 177 | 'interface {port}', 178 | 'switchport access vlan {segmentation_id}', 179 | 'exit', 180 | ) 181 | 182 | DELETE_PORT_GENERAL = ( 183 | 'interface {port}', 184 | 'switchport general allowed vlan remove {segmentation_id}', 185 | 'no switchport general pvid', 186 | 'exit', 187 | ) 188 | 189 | DELETE_PORT = ( 190 | 'interface {port}', 191 | 'switchport access vlan none', 192 | 'exit', 193 | ) 194 | 195 | ADD_NETWORK_TO_TRUNK = ( 196 | 'interface {port}', 197 | 'switchport general allowed vlan add {segmentation_id} tagged', 198 | 'exit', 199 | ) 200 | 201 | REMOVE_NETWORK_FROM_TRUNK = ( 202 | 'interface {port}', 203 | 'switchport general allowed vlan remove {segmentation_id}', 204 | 'exit', 205 | ) 206 | 207 | ERROR_MSG_PATTERNS = ( 208 | re.compile(r'\% Incomplete command'), 209 | re.compile(r'VLAN was not created by user'), 210 | re.compile(r'Configuration Database locked by another application \- ' 211 | r'try later'), 212 | re.compile(r'Port is not in Layer-2 mode'), 213 | ) 214 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/fake.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import contextlib 14 | import random 15 | import time 16 | 17 | from oslo_log import log as logging 18 | 19 | from networking_generic_switch.devices import netmiko_devices 20 | 21 | 22 | LOG = logging.getLogger(__name__) 23 | 24 | 25 | class FakeConnection(object): 26 | """A Fake netmiko connection object.""" 27 | 28 | def __init__(self, device): 29 | self.device = device 30 | 31 | def enable(self): 32 | pass 33 | 34 | def send_config_set(self, config_commands, cmd_verify): 35 | # Allow adding a random sleep to commands. 36 | if self.device.ngs_config.get('ngs_fake_sleep_max_s'): 37 | sleep_min_s = self.device.ngs_config.get('ngs_fake_sleep_min_s', 0) 38 | sleep_max_s = self.device.ngs_config['ngs_fake_sleep_max_s'] 39 | sleep_duration = random.uniform(float(sleep_min_s), 40 | float(sleep_max_s)) 41 | time.sleep(sleep_duration) 42 | 43 | # Allow injecting random failures. 44 | if self.device.ngs_config.get('ngs_fake_failure_prob'): 45 | failure_prob = self.device.ngs_config['ngs_fake_failure_prob'] 46 | if random.random() < float(failure_prob): 47 | raise Exception("Random failure!") 48 | 49 | for cmd in config_commands: 50 | LOG.info("%s", cmd) 51 | return "Success!" 52 | 53 | def save_config(self): 54 | pass 55 | 56 | def send_command(self, command): 57 | LOG.info("%s", command) 58 | return "Success!" 59 | 60 | 61 | class Fake(netmiko_devices.NetmikoSwitch): 62 | """Netmiko device driver for Fake switches.""" 63 | 64 | NETMIKO_DEVICE_TYPE = "linux" 65 | 66 | ADD_NETWORK = ( 67 | "add network {segmentation_id}", 68 | ) 69 | 70 | DELETE_NETWORK = ( 71 | "delete network {segmentation_id}", 72 | ) 73 | 74 | PLUG_PORT_TO_NETWORK = ( 75 | "plug port {port} to network {segmentation_id}", 76 | ) 77 | 78 | DELETE_PORT = ( 79 | "delete port {port}", 80 | ) 81 | 82 | ADD_NETWORK_TO_TRUNK = ( 83 | "add network {segmentation_id} to trunk {port}", 84 | ) 85 | 86 | REMOVE_NETWORK_FROM_TRUNK = ( 87 | "remove network {segmentation_id} from trunk {port}", 88 | ) 89 | 90 | ENABLE_PORT = ( 91 | "enable {port}", 92 | ) 93 | 94 | DISABLE_PORT = ( 95 | "disable {port}", 96 | ) 97 | 98 | ERROR_MSG_PATTERNS = () 99 | """Sequence of error message patterns. 100 | 101 | Sequence of re.RegexObject objects representing patterns to check for in 102 | device output that indicate a failure to apply configuration. 103 | """ 104 | 105 | @contextlib.contextmanager 106 | def _get_connection(self): 107 | """Context manager providing a netmiko SSH connection object.""" 108 | yield FakeConnection(self) 109 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/hpe.py: -------------------------------------------------------------------------------- 1 | # (c) Copyright 2017-2018 SUSE LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class HpeComware(netmiko_devices.NetmikoSwitch): 19 | ADD_NETWORK = ( 20 | 'vlan {segmentation_id}', 21 | ) 22 | 23 | DELETE_NETWORK = ( 24 | 'undo vlan {segmentation_id}', 25 | ) 26 | 27 | PLUG_PORT_TO_NETWORK = ( 28 | 'interface {port}', 29 | 'port link-type access', 30 | 'port access vlan {segmentation_id}' 31 | ) 32 | 33 | DELETE_PORT = ( 34 | 'interface {port}', 35 | 'undo port access vlan' 36 | ) 37 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/huawei.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Huawei Technologies Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class Huawei(netmiko_devices.NetmikoSwitch): 19 | """For Huawei Network Operating System VRP V3 and V5.""" 20 | ADD_NETWORK = ( 21 | 'vlan {segmentation_id}', 22 | ) 23 | 24 | DELETE_NETWORK = ( 25 | 'undo vlan {segmentation_id}', 26 | ) 27 | 28 | PLUG_PORT_TO_NETWORK = ( 29 | 'interface {port}', 30 | 'port link-type access', 31 | 'port default vlan {segmentation_id}', 32 | ) 33 | 34 | DELETE_PORT = ( 35 | 'interface {port}', 36 | 'undo port default vlan {segmentation_id}', 37 | ) 38 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/huawei_vrpv8.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Huawei Technologies Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class Huawei(netmiko_devices.NetmikoSwitch): 19 | """For Huawei Next-Generation Network Operating System VRP V8.""" 20 | ADD_NETWORK = ( 21 | 'vlan {segmentation_id}', 22 | 'commit', 23 | ) 24 | 25 | DELETE_NETWORK = ( 26 | 'undo vlan {segmentation_id}', 27 | 'commit', 28 | ) 29 | 30 | PLUG_PORT_TO_NETWORK = ( 31 | 'interface {port}', 32 | 'port link-type access', 33 | 'port default vlan {segmentation_id}', 34 | 'commit', 35 | ) 36 | 37 | DELETE_PORT = ( 38 | 'interface {port}', 39 | 'undo port default vlan {segmentation_id}', 40 | 'commit', 41 | ) 42 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/mellanox_mlnxos.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 IBM Corp 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class MellanoxMlnxOS(netmiko_devices.NetmikoSwitch): 19 | ADD_NETWORK = ( 20 | 'vlan {segmentation_id}', 21 | 'name {network_id}', 22 | ) 23 | 24 | DELETE_NETWORK = ( 25 | 'no vlan {segmentation_id}', 26 | ) 27 | 28 | PLUG_PORT_TO_NETWORK = ( 29 | 'interface ethernet {port}', 30 | 'switchport mode access', 31 | 'switchport access vlan {segmentation_id}', 32 | ) 33 | 34 | DELETE_PORT = ( 35 | 'interface ethernet {port}', 36 | 'no switchport access vlan', 37 | 'no switchport mode' 38 | ) 39 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/nokia.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Nokia 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from oslo_log import log as logging 16 | 17 | from networking_generic_switch._i18n import _ 18 | from networking_generic_switch.devices import netmiko_devices 19 | from networking_generic_switch.devices import utils as device_utils 20 | from networking_generic_switch import exceptions as exc 21 | from networking_generic_switch import locking as ngs_lock 22 | 23 | LOG = logging.getLogger(__name__) 24 | 25 | 26 | class NokiaSRL(netmiko_devices.NetmikoSwitch): 27 | ADD_NETWORK = ( 28 | 'set tunnel-interface vxlan0 vxlan-interface {segmentation_id} ' 29 | 'type bridged', 30 | 'set tunnel-interface vxlan0 vxlan-interface {segmentation_id} ' 31 | 'ingress vni {segmentation_id}', 32 | 'set tunnel-interface vxlan0 vxlan-interface {segmentation_id} ' 33 | 'egress source-ip use-system-ipv4-address', 34 | 'set network-instance mac-vrf-{segmentation_id} type mac-vrf', 35 | 'set network-instance mac-vrf-{segmentation_id} description ' 36 | 'OS-Network-ID-{network_name}', 37 | 'set network-instance mac-vrf-{segmentation_id} vxlan-interface ' 38 | 'vxlan0.{segmentation_id}', 39 | 'set network-instance mac-vrf-{segmentation_id} protocols bgp-evpn ' 40 | 'bgp-instance 1 vxlan-interface vxlan0.{segmentation_id}', 41 | 'set network-instance mac-vrf-{segmentation_id} protocols bgp-evpn ' 42 | 'bgp-instance 1 evi {segmentation_id}', 43 | 'set network-instance mac-vrf-{segmentation_id} protocols bgp-evpn ' 44 | 'bgp-instance 1 ecmp 8', 45 | 'set network-instance mac-vrf-{segmentation_id} protocols bgp-vpn ' 46 | 'bgp-instance 1 route-target export-rt target:1:{segmentation_id}', 47 | 'set network-instance mac-vrf-{segmentation_id} protocols bgp-vpn ' 48 | 'bgp-instance 1 route-target import-rt target:1:{segmentation_id}', 49 | ) 50 | 51 | DELETE_NETWORK = ( 52 | 'delete network-instance mac-vrf-{segmentation_id}', 53 | 'delete tunnel-interface vxlan0 vxlan-interface {segmentation_id}', 54 | ) 55 | 56 | PLUG_PORT_TO_NETWORK = ( 57 | 'set interface {port} subinterface {segmentation_id} type bridged', 58 | 'set network-instance mac-vrf-{segmentation_id} interface ' 59 | '{port}.{segmentation_id}', 60 | ) 61 | 62 | DELETE_PORT = ( 63 | 'delete network-instance mac-vrf-{segmentation_id} interface ' 64 | '{port}.{segmentation_id}', 65 | 'delete interface {port} subinterface {segmentation_id}', 66 | ) 67 | 68 | def send_commands_to_device(self, cmd_set): 69 | if not cmd_set: 70 | LOG.debug('Nothing to execute') 71 | return 72 | 73 | try: 74 | with ngs_lock.PoolLock(self.locker, **self.lock_kwargs): 75 | with self._get_connection() as net_connect: 76 | output = self.send_config_set(net_connect, cmd_set) 77 | # A commit is required with Nokia SRL before saving 78 | output += self.commit(net_connect) 79 | self.save_configuration(net_connect) 80 | except exc.GenericSwitchException: 81 | # Reraise without modification exceptions originating from this 82 | # module. 83 | raise 84 | except Exception as e: 85 | LOG.error(_("Device: %(device)s, error: %(error)s"), { 86 | 'device': device_utils.sanitise_config(self.config), 87 | 'error': e}) 88 | raise exc.GenericSwitchNetmikoConnectError() 89 | 90 | LOG.debug(output) 91 | return output 92 | 93 | def commit(self, net_connect) -> str: 94 | '''Try to commit the Nokia SRL configuration. 95 | 96 | :param net_connect: a netmiko connection object. 97 | ''' 98 | output = '' 99 | try: 100 | output = net_connect.commit() 101 | except AttributeError: 102 | LOG.warning( 103 | ' Committing config should be supported for Nokia SRL' 104 | ' Please verify Nokia SRL support in netmiko' 105 | ) 106 | return output 107 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/ovs.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class OvsLinux(netmiko_devices.NetmikoSwitch): 19 | 20 | PLUG_PORT_TO_NETWORK = ( 21 | 'ovs-vsctl set port {port} vlan_mode=access', 22 | 'ovs-vsctl set port {port} tag={segmentation_id}', 23 | ) 24 | 25 | DELETE_PORT = ( 26 | 'ovs-vsctl clear port {port} tag', 27 | 'ovs-vsctl clear port {port} trunks', 28 | 'ovs-vsctl clear port {port} vlan_mode' 29 | ) 30 | 31 | SET_NATIVE_VLAN = ( 32 | 'ovs-vsctl set port {port} vlan_mode=native-untagged', 33 | 'ovs-vsctl set port {port} tag={segmentation_id}', 34 | 'ovs-vsctl add port {port} trunks {segmentation_id}', 35 | ) 36 | 37 | DELETE_NATIVE_VLAN = ( 38 | 'ovs-vsctl clear port {port} vlan_mode', 39 | 'ovs-vsctl clear port {port} tag', 40 | 'ovs-vsctl remove port {port} trunks {segmentation_id}', 41 | ) 42 | 43 | SET_NATIVE_VLAN_BOND = ( 44 | 'ovs-vsctl set port {bond} vlan_mode=native-untagged', 45 | 'ovs-vsctl set port {bond} tag={segmentation_id}', 46 | 'ovs-vsctl add port {bond} trunks {segmentation_id}', 47 | ) 48 | 49 | DELETE_NATIVE_VLAN_BOND = ( 50 | 'ovs-vsctl clear port {bond} vlan_mode', 51 | 'ovs-vsctl clear port {bond} tag', 52 | 'ovs-vsctl remove port {bond} trunks {segmentation_id}', 53 | ) 54 | 55 | ADD_NETWORK_TO_TRUNK = ( 56 | 'ovs-vsctl add port {port} trunks {segmentation_id}', 57 | ) 58 | 59 | REMOVE_NETWORK_FROM_TRUNK = ( 60 | 'ovs-vsctl remove port {port} trunks {segmentation_id}', 61 | ) 62 | 63 | ADD_NETWORK_TO_BOND_TRUNK = ( 64 | 'ovs-vsctl add port {bond} trunks {segmentation_id}', 65 | ) 66 | 67 | DELETE_NETWORK_ON_BOND_TRUNK = ( 68 | 'ovs-vsctl remove port {bond} trunks {segmentation_id}', 69 | ) 70 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/pluribus.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 EscherCloud. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class Pluribus(netmiko_devices.NetmikoSwitch): 19 | ADD_NETWORK = ( 20 | 'vlan-create id {segmentation_id} scope fabric\ 21 | ports none description {network_name} auto-vxlan', 22 | ) 23 | 24 | DELETE_NETWORK = ( 25 | 'vlan-delete id {segmentation_id}', 26 | ) 27 | 28 | PLUG_PORT_TO_NETWORK = ( 29 | 'vlan-port-remove vlan-range all ports {port}', 30 | 'port-vlan-add port {port} untagged-vlan {segmentation_id}', 31 | ) 32 | 33 | DELETE_PORT = ( 34 | 'vlan-port-remove vlan-range all ports {port}', 35 | ) 36 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/ruijie.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class Ruijie(netmiko_devices.NetmikoSwitch): 19 | ADD_NETWORK = ( 20 | 'vlan {segmentation_id}', 21 | 'name {network_name}', 22 | ) 23 | 24 | DELETE_NETWORK = ( 25 | 'no vlan {segmentation_id}', 26 | ) 27 | 28 | PLUG_PORT_TO_NETWORK = ( 29 | 'interface {port}', 30 | 'switchport mode access', 31 | 'switchport access vlan {segmentation_id}', 32 | ) 33 | 34 | DELETE_PORT = ( 35 | 'interface {port}', 36 | 'no switchport access vlan {segmentation_id}', 37 | 'no switchport mode trunk', 38 | 'switchport trunk allowed vlan none' 39 | ) 40 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/smc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Nscale. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from networking_generic_switch.devices import netmiko_devices 16 | 17 | 18 | class SupermicroSmis(netmiko_devices.NetmikoSwitch): 19 | """A class to represent a Supermicro SMIS switch.""" 20 | 21 | ADD_NETWORK = ( 22 | 'vlan {segmentation_id}', 23 | 'name {network_name}', 24 | ) 25 | 26 | DELETE_NETWORK = ( 27 | 'no vlan {segmentation_id}', 28 | ) 29 | 30 | PLUG_PORT_TO_NETWORK = ( 31 | 'interface {port}', 32 | 'switchport mode access', 33 | 'switchport access vlan {segmentation_id}', 34 | ) 35 | 36 | DELETE_PORT = ( 37 | 'interface {port}', 38 | 'no switchport access vlan {segmentation_id}', 39 | 'no switchport mode trunk', 40 | 'switchport trunk allowed vlan none' 41 | ) 42 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/netmiko_devices/sonic.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 James Denton 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | import re 15 | 16 | from networking_generic_switch.devices import netmiko_devices 17 | 18 | 19 | class Sonic(netmiko_devices.NetmikoSwitch): 20 | """Built for SONiC 3.x 21 | 22 | Note for this switch you want config like this, 23 | where secret is the password needed for sudo su: 24 | 25 | .. code-block:: ini 26 | 27 | [genericswitch:] 28 | device_type = netmiko_sonic 29 | ip = 30 | username = 31 | password = 32 | secret = 33 | ngs_physical_networks = physnet1 34 | ngs_max_connections = 1 35 | ngs_port_default_vlan = 123 36 | ngs_disable_inactive_ports = False 37 | """ 38 | NETMIKO_DEVICE_TYPE = "linux" 39 | 40 | ADD_NETWORK = ( 41 | 'config vlan add {segmentation_id}', 42 | ) 43 | 44 | DELETE_NETWORK = ( 45 | 'config vlan del {segmentation_id}', 46 | ) 47 | 48 | PLUG_PORT_TO_NETWORK = ( 49 | 'config vlan member add -u {segmentation_id} {port}', 50 | ) 51 | 52 | DELETE_PORT = ( 53 | 'config vlan member del {segmentation_id} {port}', 54 | ) 55 | 56 | ADD_NETWORK_TO_TRUNK = ( 57 | 'config vlan member add {segmentation_id} {port}', 58 | ) 59 | 60 | REMOVE_NETWORK_FROM_TRUNK = ( 61 | 'config vlan member del {segmentation_id} {port}', 62 | ) 63 | 64 | SAVE_CONFIGURATION = ( 65 | 'config save -y', 66 | ) 67 | 68 | ERROR_MSG_PATTERNS = ( 69 | re.compile(r'VLAN[0-9]+ doesn\'t exist'), 70 | re.compile(r'Invalid Vlan Id , Valid Range : 1 to 4094'), 71 | re.compile(r'Interface name is invalid!!'), 72 | re.compile(r'No such command'), 73 | ) 74 | 75 | def send_config_set(self, net_connect, cmd_set): 76 | """Send a set of configuration lines to the device. 77 | 78 | :param net_connect: a netmiko connection object. 79 | :param cmd_set: a list of configuration lines to send. 80 | :returns: The output of the configuration commands. 81 | """ 82 | net_connect.enable() 83 | 84 | # Don't exit configuration mode, as config save requires 85 | # root permissions. 86 | return net_connect.send_config_set(config_commands=cmd_set, 87 | cmd_verify=False, 88 | exit_config_mode=False) 89 | -------------------------------------------------------------------------------- /networking_generic_switch/devices/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from oslo_config import cfg 16 | 17 | 18 | CONF = cfg.CONF 19 | 20 | 21 | def get_switch_device(switches, switch_info=None, 22 | ngs_mac_address=None): 23 | """Return switch device by specified identifier. 24 | 25 | Returns switch device from switches array that matched with any of 26 | passed identifiers. ngs_mac_address takes precedence over switch_info, 27 | if didn't match any address based on mac fallback to switch_info. 28 | 29 | :param switch_info: hostname of the switch or any other switch identifier. 30 | :param ngs_mac_address: Normalized mac address of the switch. 31 | :returns: switch device matches by specified identifier or None. 32 | """ 33 | 34 | if ngs_mac_address: 35 | for sw_info, switch in switches.items(): 36 | mac_address = switch.ngs_config.get('ngs_mac_address') 37 | if mac_address and mac_address.lower() == ngs_mac_address.lower(): 38 | return switch 39 | if switch_info: 40 | return switches.get(switch_info) 41 | 42 | 43 | def sanitise_config(config): 44 | """Return a sanitised configuration of a switch device. 45 | 46 | :param config: a configuration dict to sanitise. 47 | :returns: a copy of the configuration, with sensitive fields removed. 48 | """ 49 | sanitised_fields = {"password", "ip", "device_type", "username", 50 | "session_log"} 51 | return { 52 | key: "******" if key in sanitised_fields else value 53 | for key, value in config.items() 54 | } 55 | 56 | 57 | def get_hostname(): 58 | """Helper to allow isolation of CONF.host and plugin loading.""" 59 | return CONF.host 60 | -------------------------------------------------------------------------------- /networking_generic_switch/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from neutron_lib import exceptions 16 | 17 | from networking_generic_switch._i18n import _ 18 | 19 | 20 | class GenericSwitchException(exceptions.NeutronException): 21 | message = _("%(method)s failed.") 22 | 23 | 24 | class GenericSwitchConfigException(exceptions.NeutronException): 25 | message = _("%(option)s must be one of: %(allowed_options)s") 26 | 27 | 28 | class GenericSwitchEntrypointLoadError(GenericSwitchException): 29 | message = _("Failed to load entrypoint %(ep)s: %(err)s") 30 | 31 | 32 | class GenericSwitchNetworkNameFormatInvalid(GenericSwitchException): 33 | message = _("Invalid value for 'ngs_network_name_format': " 34 | "%(name_format)s. Valid format options include 'network_id' " 35 | "and 'segmentation_id'") 36 | 37 | 38 | class GenericSwitchNetmikoMethodError(GenericSwitchException): 39 | message = _("Can not parse arguments: commands %(cmds)s, args %(args)s") 40 | 41 | 42 | class GenericSwitchNetmikoNotSupported(GenericSwitchException): 43 | message = _("Netmiko does not support device type %(device_type)s") 44 | 45 | 46 | class GenericSwitchNetmikoConnectError(GenericSwitchException): 47 | message = _("Failed to connect to Netmiko switch. " 48 | "Please contact your administrator.") 49 | 50 | 51 | class GenericSwitchNetmikoConfigError(GenericSwitchException): 52 | message = _("Netmiko switch configuration operation failed. " 53 | "Please contact your administrator.") 54 | 55 | 56 | class GenericSwitchBatchError(GenericSwitchException): 57 | message = _("Batching error: %(device)s, error: %(error)s") 58 | 59 | 60 | class GenericSwitchNotSupported(GenericSwitchException): 61 | message = _("Requested feature %(feature)s is not supported by " 62 | "networking-generic-switch on the %(switch)s. %(error)s") 63 | -------------------------------------------------------------------------------- /networking_generic_switch/locking.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import itertools 16 | 17 | from oslo_log import log as logging 18 | import tenacity 19 | from tooz import coordination 20 | 21 | LOG = logging.getLogger(__name__) 22 | 23 | 24 | class PoolLock(object): 25 | """Tooz lock wrapper for pools of locks 26 | 27 | If tooz coordinator is provided, it will attempt to grab any lock 28 | from a predefined set of names, with configurable set size (lock pool), 29 | and keep attempting for until given timeout is reached. 30 | 31 | """ 32 | 33 | def __init__(self, coordinator, locks_pool_size=1, locks_prefix='ngs-', 34 | timeout=0): 35 | self.coordinator = coordinator 36 | self.locks_prefix = locks_prefix 37 | self.lock_names = ("{}{}".format(locks_prefix, i) 38 | for i in range(locks_pool_size)) 39 | self.locks_pool_size = locks_pool_size 40 | self.timeout = timeout 41 | 42 | def __enter__(self): 43 | self.lock = False 44 | if not self.coordinator: 45 | return self 46 | 47 | LOG.debug("Trying to acquire lock for %s", self.locks_prefix) 48 | names = itertools.cycle(self.lock_names) 49 | retry_kwargs = {'wait': tenacity.wait_random(min=0, max=1), 50 | 'reraise': True} 51 | if self.timeout: 52 | retry_kwargs['stop'] = tenacity.stop_after_delay(self.timeout) 53 | 54 | @tenacity.retry(**retry_kwargs) 55 | def grab_lock_from_pool(): 56 | name = next(names) 57 | # NOTE(pas-ha) currently all tooz backends support locking API. 58 | # In case this changes, this should be wrapped to not respin 59 | # lock grabbing on NotImplemented exception. 60 | lock = self.coordinator.get_lock(name.encode()) 61 | locked = lock.acquire(blocking=False) 62 | if not locked: 63 | raise coordination.LockAcquireFailed( 64 | "Failed to acquire lock %s" % name) 65 | return lock 66 | 67 | try: 68 | self.lock = grab_lock_from_pool() 69 | except Exception: 70 | msg = ("Failed to acquire any of %s locks for %s " 71 | "for a netmiko action in %s seconds. " 72 | "Try increasing acquire_timeout." % ( 73 | self.locks_pool_size, self.locks_prefix, 74 | self.timeout)) 75 | LOG.error(msg, exc_info=True) 76 | raise 77 | return self 78 | 79 | def __exit__(self, exc_type, exc_val, exc_tb): 80 | if self.lock: 81 | self.lock.release() 82 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/networking_generic_switch/tests/__init__.py -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/networking_generic_switch/tests/unit/__init__.py -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/devices/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/networking_generic_switch/tests/unit/devices/__init__.py -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/devices/test_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | import unittest 17 | 18 | from networking_generic_switch import devices 19 | from networking_generic_switch.devices import utils as device_utils 20 | 21 | 22 | class TestDevices(unittest.TestCase): 23 | 24 | def setUp(self): 25 | self.devices = { 26 | 'A': devices.device_manager({"device_type": 'netmiko_ovs_linux'}), 27 | 'B': devices.device_manager({"device_type": 'netmiko_cisco_ios', 28 | "ngs_mac_address": 29 | 'aa:bb:cc:dd:ee:ff'}), 30 | } 31 | 32 | def test_get_switch_device_match(self): 33 | self.assertEqual(device_utils.get_switch_device( 34 | self.devices, switch_info='A'), self.devices['A']) 35 | self.assertEqual(device_utils.get_switch_device( 36 | self.devices, ngs_mac_address='aa:bb:cc:dd:ee:ff'), 37 | self.devices['B']) 38 | 39 | def test_get_switch_device_match_priority(self): 40 | self.assertEqual(device_utils.get_switch_device( 41 | self.devices, switch_info='A', 42 | ngs_mac_address='aa:bb:cc:dd:ee:ff'), 43 | self.devices['B']) 44 | 45 | def test_get_switch_device_match_mac_ignore_case(self): 46 | self.assertEqual(device_utils.get_switch_device( 47 | self.devices, switch_info='A', 48 | ngs_mac_address='AA:BB:CC:DD:EE:FF'), 49 | self.devices['B']) 50 | 51 | def test_get_switch_device_no_match(self): 52 | self.assertIsNone(device_utils.get_switch_device( 53 | self.devices, switch_info='C')) 54 | self.assertIsNone(device_utils.get_switch_device( 55 | self.devices, ngs_mac_address='11:22:33:44:55:66')) 56 | 57 | def test_get_switch_device_fallback_to_switch_info(self): 58 | self.assertEqual(self.devices['A'], device_utils.get_switch_device( 59 | self.devices, switch_info='A', 60 | ngs_mac_address='11:22:33:44:55:77')) 61 | 62 | def test_sanitise_config(self): 63 | config = {'username': 'fake-user', 'password': 'fake-password', 64 | 'ip': '123.456.789', 'session_log': '/some/path/here', 65 | "device_type": "my_device"} 66 | result = device_utils.sanitise_config(config) 67 | expected = {k: '******' for k in config} 68 | self.assertEqual(expected, result) 69 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/networking_generic_switch/tests/unit/netmiko/__init__.py -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_aruba.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 VEXXHOST, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from networking_generic_switch.devices.netmiko_devices import aruba 18 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 19 | 20 | 21 | class TestNetmikoAruba(test_netmiko_base.NetmikoSwitchTestBase): 22 | 23 | def _make_switch_device(self, extra_cfg={}): 24 | device_cfg = {'device_type': 'netmiko_aruba_os'} 25 | device_cfg.update(extra_cfg) 26 | return aruba.ArubaOSCX(device_cfg) 27 | 28 | def test_constants(self): 29 | self.assertIsNone(self.switch.SAVE_CONFIGURATION) 30 | 31 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 32 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 33 | def test_add_network(self, m_exec): 34 | self.switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 35 | m_exec.assert_called_with( 36 | self.switch, 37 | ['vlan 33', 'name 0ae071f55be943e480eae41fefe85b21']) 38 | 39 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 40 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 41 | def test_del_network(self, mock_exec): 42 | self.switch.del_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 43 | mock_exec.assert_called_with(self.switch, ['no vlan 33']) 44 | 45 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 46 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 47 | def test_plug_port_to_network(self, mock_exec): 48 | self.switch.plug_port_to_network(3333, 33) 49 | mock_exec.assert_called_with( 50 | self.switch, 51 | ['interface 3333', 'no routing', 'vlan access 33']) 52 | 53 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 54 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 55 | def test_plug_port_to_network_disable_inactive(self, m_sctd): 56 | switch = self._make_switch_device( 57 | {'ngs_disable_inactive_ports': 'true'}) 58 | switch.plug_port_to_network(3333, 33) 59 | m_sctd.assert_called_with( 60 | switch, 61 | ['interface 3333', 'no shutdown', 62 | 'interface 3333', 'no routing', 'vlan access 33']) 63 | 64 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 65 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 66 | def test_delete_port(self, mock_exec): 67 | self.switch.delete_port(3333, 33) 68 | mock_exec.assert_called_with( 69 | self.switch, ['interface 3333', 'no vlan access 33']) 70 | 71 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 72 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 73 | def test_delete_port_disable_inactive(self, m_sctd): 74 | switch = self._make_switch_device( 75 | {'ngs_disable_inactive_ports': 'true'}) 76 | switch.delete_port(3333, 33) 77 | m_sctd.assert_called_with( 78 | switch, 79 | ['interface 3333', 'no vlan access 33', 80 | 'interface 3333', 'shutdown']) 81 | 82 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 83 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 84 | def test_add_network_with_trunk_ports(self, mock_exec): 85 | switch = self._make_switch_device({'ngs_trunk_ports': 'port1, port2'}) 86 | switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 87 | mock_exec.assert_called_with( 88 | switch, 89 | ['vlan 33', 90 | 'name 0ae071f55be943e480eae41fefe85b21', 91 | 'interface port1', 'no routing', 'vlan trunk allowed 33', 92 | 'interface port2', 'no routing', 'vlan trunk allowed 33']) 93 | 94 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 95 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 96 | def test_del_network_with_trunk_ports(self, mock_exec): 97 | switch = self._make_switch_device({'ngs_trunk_ports': 'port1, port2'}) 98 | switch.del_network(33, '0ae071f55be943e480eae41fefe85b21') 99 | mock_exec.assert_called_with( 100 | switch, 101 | ['interface port1', 'no vlan trunk allowed 33', 102 | 'interface port2', 'no vlan trunk allowed 33', 103 | 'no vlan 33']) 104 | 105 | def test__format_commands(self): 106 | cmd_set = self.switch._format_commands( 107 | aruba.ArubaOSCX.ADD_NETWORK, 108 | segmentation_id=22, 109 | network_id=22, 110 | network_name='vlan-22') 111 | self.assertEqual(cmd_set, ['vlan 22', 'name vlan-22']) 112 | 113 | cmd_set = self.switch._format_commands( 114 | aruba.ArubaOSCX.DELETE_NETWORK, 115 | segmentation_id=22) 116 | self.assertEqual(cmd_set, ['no vlan 22']) 117 | 118 | cmd_set = self.switch._format_commands( 119 | aruba.ArubaOSCX.PLUG_PORT_TO_NETWORK, 120 | port=3333, 121 | segmentation_id=33) 122 | plug_exp = ['interface 3333', 'no routing', 'vlan access 33'] 123 | self.assertEqual(plug_exp, cmd_set) 124 | 125 | cmd_set = self.switch._format_commands( 126 | aruba.ArubaOSCX.DELETE_PORT, 127 | port=3333, 128 | segmentation_id=33) 129 | del_exp = ['interface 3333', 'no vlan access 33'] 130 | self.assertEqual(del_exp, cmd_set) 131 | 132 | cmd_set = self.switch._format_commands( 133 | aruba.ArubaOSCX.ADD_NETWORK_TO_TRUNK, 134 | port=3333, 135 | segmentation_id=33) 136 | trunk_exp = ['interface 3333', 'no routing', 'vlan trunk allowed 33'] 137 | self.assertEqual(trunk_exp, cmd_set) 138 | cmd_set = self.switch._format_commands( 139 | aruba.ArubaOSCX.REMOVE_NETWORK_FROM_TRUNK, 140 | port=3333, 141 | segmentation_id=33) 142 | self.assertEqual(cmd_set, 143 | ['interface 3333', 'no vlan trunk allowed 33']) 144 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_brocade_fastiron.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Servers.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | # based on test_cisco_ios.py by Mirantis 15 | 16 | 17 | from unittest import mock 18 | 19 | from networking_generic_switch.devices.netmiko_devices import brocade 20 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 21 | 22 | 23 | class TestNetmikoBrocadeFastIron(test_netmiko_base.NetmikoSwitchTestBase): 24 | 25 | def _make_switch_device(self): 26 | device_cfg = {'device_type': 'netmiko_brocade_fastiron'} 27 | return brocade.BrocadeFastIron(device_cfg) 28 | 29 | def test_constants(self): 30 | self.assertIsNone(self.switch.SAVE_CONFIGURATION) 31 | 32 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 33 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 34 | def test_add_network(self, m_exec): 35 | self.switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 36 | m_exec.assert_called_with( 37 | self.switch, 38 | ['vlan 33 by port', 'name 0ae071f55be943e480eae41fefe85b21']) 39 | 40 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 41 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 42 | def test_del_network(self, mock_exec): 43 | self.switch.del_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 44 | mock_exec.assert_called_with(self.switch, ['no vlan 33']) 45 | 46 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 47 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 48 | def test_plug_port_to_network(self, mock_exec): 49 | self.switch.plug_port_to_network(3333, 33) 50 | mock_exec.assert_called_with( 51 | self.switch, 52 | ['vlan 33 by port', 'untagged ether 3333']) 53 | 54 | def test_plug_port_to_network_with_cleaning(self): 55 | with mock.patch( 56 | 'networking_generic_switch.devices.netmiko_devices.' 57 | 'NetmikoSwitch.send_commands_to_device', autospec=True 58 | ) as m_exec: 59 | m_exec.return_value = "Member of L2 VLAN ID 22, port is untagged." 60 | self.switch.plug_port_to_network(3333, 33) 61 | m_exec.assert_has_calls([ 62 | mock.call(self.switch, 63 | ['show interfaces ether 3333 | include VLAN']), 64 | mock.call(self.switch, 65 | ['vlan 22 by port', 'no untagged ether 3333']), 66 | mock.call(self.switch, 67 | ['vlan 33 by port', 'untagged ether 3333']), 68 | ]) 69 | 70 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 71 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 72 | def test_delete_port(self, mock_exec): 73 | self.switch.delete_port(3333, 33) 74 | mock_exec.assert_called_with( 75 | self.switch, 76 | ['vlan 33 by port', 'no untagged ether 3333']) 77 | 78 | def test__format_commands(self): 79 | cmd_set = self.switch._format_commands( 80 | brocade.BrocadeFastIron.ADD_NETWORK, 81 | segmentation_id=22, 82 | network_id=22, 83 | network_name='vlan-22') 84 | self.assertEqual(cmd_set, ['vlan 22 by port', 'name vlan-22']) 85 | 86 | cmd_set = self.switch._format_commands( 87 | brocade.BrocadeFastIron.DELETE_NETWORK, 88 | segmentation_id=22) 89 | self.assertEqual(cmd_set, ['no vlan 22']) 90 | 91 | def test__process_raw_output_empty(self): 92 | self.assertIsNone(self.switch._process_raw_output('')) 93 | 94 | def test__process_raw_output_tagged(self): 95 | self.assertIsNone(self.switch._process_raw_output( 96 | ' Member of 1 L2 VLANs, port is tagged, port state is FORWARDING' 97 | )) 98 | 99 | def test__process_raw_output_untagged(self): 100 | self.assertEqual(self.switch._process_raw_output( 101 | ' Member of L2 VLAN ID 22, port is untagged, port state is...' 102 | ), '22') 103 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_cisco_300.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from networking_generic_switch.devices.netmiko_devices import cisco300 as cisco 18 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 19 | 20 | 21 | class TestNetmikoCisco300(test_netmiko_base.NetmikoSwitchTestBase): 22 | 23 | def _make_switch_device(self): 24 | device_cfg = {'device_type': 'netmiko_cisco_s300'} 25 | return cisco.Cisco300(device_cfg) 26 | 27 | def test_constants(self): 28 | self.assertIsNone(self.switch.SAVE_CONFIGURATION) 29 | 30 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 31 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 32 | def test_add_network(self, m_exec): 33 | self.switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 34 | m_exec.assert_called_with( 35 | self.switch, 36 | ['vlan 33']) 37 | 38 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 39 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 40 | def test_del_network(self, mock_exec): 41 | self.switch.del_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 42 | mock_exec.assert_called_with(self.switch, ['no vlan 33']) 43 | 44 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 45 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 46 | def test_plug_port_to_network(self, mock_exec): 47 | self.switch.plug_port_to_network(3333, 33) 48 | mock_exec.assert_called_with( 49 | self.switch, 50 | ['interface 3333', 'switchport mode access', 51 | 'switchport access vlan 33']) 52 | 53 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 54 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 55 | def test_delete_port(self, mock_exec): 56 | self.switch.delete_port(3333, 33) 57 | mock_exec.assert_called_with( 58 | self.switch, 59 | ['interface 3333', 'no switchport access vlan', 60 | 'switchport trunk allowed vlan remove all']) 61 | 62 | def test__format_commands(self): 63 | cmd_set = self.switch._format_commands( 64 | cisco.Cisco300.ADD_NETWORK, 65 | segmentation_id=22, 66 | network_id=22) 67 | self.assertEqual(cmd_set, ['vlan 22']) 68 | 69 | cmd_set = self.switch._format_commands( 70 | cisco.Cisco300.DELETE_NETWORK, 71 | segmentation_id=22) 72 | self.assertEqual(cmd_set, ['no vlan 22']) 73 | 74 | cmd_set = self.switch._format_commands( 75 | cisco.Cisco300.PLUG_PORT_TO_NETWORK, 76 | port=3333, 77 | segmentation_id=33) 78 | plug_exp = ['interface 3333', 'switchport mode access', 79 | 'switchport access vlan 33'] 80 | self.assertEqual(plug_exp, cmd_set) 81 | 82 | cmd_set = self.switch._format_commands( 83 | cisco.Cisco300.DELETE_PORT, 84 | port=3333, 85 | segmentation_id=33) 86 | del_exp = ['interface 3333', 87 | 'no switchport access vlan', 88 | 'switchport trunk allowed vlan remove all'] 89 | self.assertEqual(del_exp, cmd_set) 90 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_cisco_nxos.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Baptiste Jonglez, Inria 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from networking_generic_switch.devices.netmiko_devices import cisco 18 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 19 | 20 | 21 | class TestNetmikoCiscoNxOS(test_netmiko_base.NetmikoSwitchTestBase): 22 | 23 | def _make_switch_device(self): 24 | device_cfg = {'device_type': 'netmiko_cisco_nxos'} 25 | return cisco.CiscoNxOS(device_cfg) 26 | 27 | def test_constants(self): 28 | self.assertIsNone(self.switch.SAVE_CONFIGURATION) 29 | 30 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 31 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 32 | def test_add_network(self, m_exec): 33 | self.switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 34 | m_exec.assert_called_with( 35 | self.switch, 36 | ['vlan 33', 'name 0ae071f55be943e480eae41fefe85b21', 'exit']) 37 | 38 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 39 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 40 | def test_del_network(self, mock_exec): 41 | self.switch.del_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 42 | mock_exec.assert_called_with(self.switch, ['no vlan 33']) 43 | 44 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 45 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 46 | def test_plug_port_to_network(self, mock_exec): 47 | self.switch.plug_port_to_network(3333, 33) 48 | mock_exec.assert_called_with( 49 | self.switch, 50 | ['interface 3333', 'switchport mode access', 51 | 'switchport access vlan 33', 'exit']) 52 | 53 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 54 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 55 | def test_delete_port(self, mock_exec): 56 | self.switch.delete_port(3333, 33) 57 | mock_exec.assert_called_with( 58 | self.switch, 59 | ['interface 3333', 'no switchport access vlan', 'exit']) 60 | 61 | def test__format_commands(self): 62 | cmd_set = self.switch._format_commands( 63 | cisco.CiscoNxOS.ADD_NETWORK, 64 | segmentation_id=22, 65 | network_id=22, 66 | network_name='vlan-22') 67 | self.assertEqual(cmd_set, ['vlan 22', 'name vlan-22', 'exit']) 68 | 69 | cmd_set = self.switch._format_commands( 70 | cisco.CiscoNxOS.DELETE_NETWORK, 71 | segmentation_id=22) 72 | self.assertEqual(cmd_set, ['no vlan 22']) 73 | 74 | cmd_set = self.switch._format_commands( 75 | cisco.CiscoNxOS.PLUG_PORT_TO_NETWORK, 76 | port=3333, 77 | segmentation_id=33) 78 | plug_exp = ['interface 3333', 'switchport mode access', 79 | 'switchport access vlan 33', 'exit'] 80 | self.assertEqual(plug_exp, cmd_set) 81 | 82 | cmd_set = self.switch._format_commands( 83 | cisco.CiscoNxOS.DELETE_PORT, 84 | port=3333, 85 | segmentation_id=33) 86 | del_exp = ['interface 3333', 'no switchport access vlan', 'exit'] 87 | self.assertEqual(del_exp, cmd_set) 88 | 89 | cmd_set = self.switch._format_commands( 90 | cisco.CiscoNxOS.ADD_NETWORK_TO_TRUNK, 91 | port=3333, 92 | segmentation_id=33) 93 | add_trunk_exp = ['interface 3333', 'switchport mode trunk', 94 | 'switchport trunk allowed vlan add 33', 'exit'] 95 | self.assertEqual(add_trunk_exp, cmd_set) 96 | 97 | cmd_set = self.switch._format_commands( 98 | cisco.CiscoNxOS.REMOVE_NETWORK_FROM_TRUNK, 99 | port=3333, 100 | segmentation_id=33) 101 | del_trunk_exp = ['interface 3333', 102 | 'switchport trunk allowed vlan remove 33', 103 | 'exit'] 104 | self.assertEqual(del_trunk_exp, cmd_set) 105 | 106 | cmd_set = self.switch._format_commands( 107 | cisco.CiscoNxOS.ENABLE_PORT, 108 | port=3333) 109 | enable_exp = ['interface 3333', 'no shutdown', 'exit'] 110 | self.assertEqual(enable_exp, cmd_set) 111 | 112 | cmd_set = self.switch._format_commands( 113 | cisco.CiscoNxOS.DISABLE_PORT, 114 | port=3333) 115 | disable_exp = ['interface 3333', 'shutdown', 'exit'] 116 | self.assertEqual(disable_exp, cmd_set) 117 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_huawei.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Huawei Technologies Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from networking_generic_switch.devices.netmiko_devices import huawei 18 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 19 | 20 | 21 | class TestNetmikoHuawei(test_netmiko_base.NetmikoSwitchTestBase): 22 | 23 | def _make_switch_device(self): 24 | device_cfg = {'device_type': 'netmiko_huawei'} 25 | return huawei.Huawei(device_cfg) 26 | 27 | def test_constants(self): 28 | self.assertIsNone(self.switch.SAVE_CONFIGURATION) 29 | 30 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 31 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 32 | def test_add_network(self, m_exec): 33 | self.switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 34 | m_exec.assert_called_with( 35 | self.switch, 36 | ['vlan 33']) 37 | 38 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 39 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 40 | def test_del_network(self, mock_exec): 41 | self.switch.del_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 42 | mock_exec.assert_called_with( 43 | self.switch, 44 | ['undo vlan 33']) 45 | 46 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 47 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 48 | def test_plug_port_to_network(self, mock_exec): 49 | self.switch.plug_port_to_network(3333, 33) 50 | mock_exec.assert_called_with( 51 | self.switch, 52 | ['interface 3333', 53 | 'port link-type access', 54 | 'port default vlan 33']) 55 | 56 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 57 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 58 | def test_delete_port(self, mock_exec): 59 | self.switch.delete_port(3333, 33) 60 | mock_exec.assert_called_with( 61 | self.switch, 62 | ['interface 3333', 63 | 'undo port default vlan 33']) 64 | 65 | def test__format_commands(self): 66 | cmd_set = self.switch._format_commands( 67 | huawei.Huawei.ADD_NETWORK, 68 | segmentation_id=22, 69 | network_id=22) 70 | self.assertEqual(cmd_set, ['vlan 22']) 71 | 72 | cmd_set = self.switch._format_commands( 73 | huawei.Huawei.DELETE_NETWORK, 74 | segmentation_id=22) 75 | self.assertEqual(cmd_set, ['undo vlan 22']) 76 | 77 | cmd_set = self.switch._format_commands( 78 | huawei.Huawei.PLUG_PORT_TO_NETWORK, 79 | port=3333, 80 | segmentation_id=33) 81 | self.assertEqual(cmd_set, 82 | ['interface 3333', 83 | 'port link-type access', 84 | 'port default vlan 33']) 85 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_huawei_vrpv8.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Huawei Technologies Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from networking_generic_switch.devices.netmiko_devices import huawei_vrpv8 18 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 19 | 20 | 21 | class TestNetmikoHuawei_vrpv8(test_netmiko_base.NetmikoSwitchTestBase): 22 | 23 | def _make_switch_device(self, extra_cfg={}): 24 | device_cfg = {'device_type': 'netmiko_huawei'} 25 | device_cfg.update(extra_cfg) 26 | return huawei_vrpv8.Huawei(device_cfg) 27 | 28 | def test_constants(self): 29 | self.assertIsNone(self.switch.SAVE_CONFIGURATION) 30 | 31 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 32 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 33 | def test_add_network(self, m_exec): 34 | self.switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 35 | m_exec.assert_called_with(self.switch, ['vlan 33', 'commit']) 36 | 37 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 38 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 39 | def test_del_network(self, mock_exec): 40 | self.switch.del_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 41 | mock_exec.assert_called_with(self.switch, ['undo vlan 33', 'commit']) 42 | 43 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 44 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 45 | def test_plug_port_to_network(self, mock_exec): 46 | self.switch.plug_port_to_network(3333, 33) 47 | mock_exec.assert_called_with( 48 | self.switch, 49 | ['interface 3333', 50 | 'port link-type access', 51 | 'port default vlan 33', 52 | 'commit']) 53 | 54 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 55 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 56 | def test_plug_port_has_default_vlan(self, m_sctd): 57 | switch = self._make_switch_device({'ngs_port_default_vlan': '20'}) 58 | switch.plug_port_to_network(2222, 22) 59 | m_sctd.assert_called_with( 60 | switch, 61 | ['interface 2222', 62 | 'undo port default vlan 20', 63 | 'commit', 64 | 'interface 2222', 65 | 'port link-type access', 66 | 'port default vlan 22', 67 | 'commit']) 68 | 69 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 70 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 71 | def test_delete_port(self, mock_exec): 72 | self.switch.delete_port(3333, 33) 73 | mock_exec.assert_called_with( 74 | self.switch, 75 | ['interface 3333', 76 | 'undo port default vlan 33', 77 | 'commit']) 78 | 79 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 80 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 81 | def test_delete_port_has_default_vlan(self, mock_exec): 82 | switch = self._make_switch_device({'ngs_port_default_vlan': '20'}) 83 | switch.delete_port(2222, 22) 84 | mock_exec.assert_called_with( 85 | switch, 86 | ['interface 2222', 87 | 'undo port default vlan 22', 88 | 'commit', 89 | 'vlan 20', 90 | 'commit', 91 | 'interface 2222', 92 | 'port link-type access', 93 | 'port default vlan 20', 94 | 'commit']) 95 | 96 | def test__format_commands(self): 97 | cmd_set = self.switch._format_commands( 98 | huawei_vrpv8.Huawei.ADD_NETWORK, 99 | segmentation_id=22, 100 | network_id=22) 101 | self.assertEqual(cmd_set, ['vlan 22', 'commit']) 102 | 103 | cmd_set = self.switch._format_commands( 104 | huawei_vrpv8.Huawei.DELETE_NETWORK, 105 | segmentation_id=22) 106 | self.assertEqual(cmd_set, ['undo vlan 22', 'commit']) 107 | 108 | cmd_set = self.switch._format_commands( 109 | huawei_vrpv8.Huawei.PLUG_PORT_TO_NETWORK, 110 | port=3333, 111 | segmentation_id=33) 112 | self.assertEqual(cmd_set, 113 | ['interface 3333', 114 | 'port link-type access', 115 | 'port default vlan 33', 116 | 'commit']) 117 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_nokia_srl.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from networking_generic_switch.devices.netmiko_devices import nokia 18 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 19 | 20 | 21 | class TestNetmikoNokiaSRL(test_netmiko_base.NetmikoSwitchTestBase): 22 | 23 | def _make_switch_device(self): 24 | device_cfg = {'device_type': 'netmiko_nokia_srl'} 25 | return nokia.NokiaSRL(device_cfg) 26 | 27 | def test_constants(self): 28 | self.assertIsNone(self.switch.SAVE_CONFIGURATION) 29 | 30 | @mock.patch('networking_generic_switch.devices.netmiko_devices.nokia.' 31 | 'NokiaSRL.send_commands_to_device', autospec=True) 32 | def test_add_network(self, m_exec): 33 | self.switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 34 | m_exec.assert_called_with( 35 | self.switch, 36 | ['set tunnel-interface vxlan0 vxlan-interface 33 type bridged', 37 | 'set tunnel-interface vxlan0 vxlan-interface 33 ingress vni 33', 38 | 'set tunnel-interface vxlan0 vxlan-interface 33 egress ' 39 | 'source-ip use-system-ipv4-address', 40 | 'set network-instance mac-vrf-33 type mac-vrf', 41 | 'set network-instance mac-vrf-33 description ' 42 | 'OS-Network-ID-0ae071f55be943e480eae41fefe85b21', 43 | 'set network-instance mac-vrf-33 vxlan-interface vxlan0.33', 44 | 'set network-instance mac-vrf-33 protocols bgp-evpn ' 45 | 'bgp-instance 1 vxlan-interface vxlan0.33', 46 | 'set network-instance mac-vrf-33 protocols bgp-evpn ' 47 | 'bgp-instance 1 evi 33', 48 | 'set network-instance mac-vrf-33 protocols bgp-evpn ' 49 | 'bgp-instance 1 ecmp 8', 50 | 'set network-instance mac-vrf-33 protocols bgp-vpn ' 51 | 'bgp-instance 1 route-target export-rt target:1:33', 52 | 'set network-instance mac-vrf-33 protocols bgp-vpn ' 53 | 'bgp-instance 1 route-target import-rt target:1:33']) 54 | 55 | @mock.patch('networking_generic_switch.devices.netmiko_devices.nokia.' 56 | 'NokiaSRL.send_commands_to_device', autospec=True) 57 | def test_del_network(self, mock_exec): 58 | self.switch.del_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 59 | mock_exec.assert_called_with( 60 | self.switch, 61 | ['delete network-instance mac-vrf-33', 62 | 'delete tunnel-interface vxlan0 vxlan-interface 33']) 63 | 64 | @mock.patch('networking_generic_switch.devices.netmiko_devices.nokia.' 65 | 'NokiaSRL.send_commands_to_device', autospec=True) 66 | def test_plug_port_to_network(self, mock_exec): 67 | self.switch.plug_port_to_network(3333, 33) 68 | mock_exec.assert_called_with( 69 | self.switch, 70 | ['set interface 3333 subinterface 33 type bridged', 71 | 'set network-instance mac-vrf-33 interface 3333.33']) 72 | 73 | @mock.patch('networking_generic_switch.devices.netmiko_devices.nokia.' 74 | 'NokiaSRL.send_commands_to_device', autospec=True) 75 | def test_delete_port(self, mock_exec): 76 | self.switch.delete_port(3333, 33) 77 | mock_exec.assert_called_with( 78 | self.switch, 79 | ['delete network-instance mac-vrf-33 interface 3333.33', 80 | 'delete interface 3333 subinterface 33']) 81 | 82 | def test__format_commands(self): 83 | cmd_set = self.switch._format_commands( 84 | nokia.NokiaSRL.ADD_NETWORK, 85 | segmentation_id=22, 86 | network_id=22, 87 | network_name='vlan-22') 88 | add_exp = [ 89 | 'set tunnel-interface vxlan0 vxlan-interface 22 type bridged', 90 | 'set tunnel-interface vxlan0 vxlan-interface 22 ingress vni 22', 91 | 'set tunnel-interface vxlan0 vxlan-interface 22 egress source-ip ' 92 | 'use-system-ipv4-address', 93 | 'set network-instance mac-vrf-22 type mac-vrf', 94 | 'set network-instance mac-vrf-22 description ' 95 | 'OS-Network-ID-vlan-22', 96 | 'set network-instance mac-vrf-22 vxlan-interface vxlan0.22', 97 | 'set network-instance mac-vrf-22 protocols bgp-evpn ' 98 | 'bgp-instance 1 vxlan-interface vxlan0.22', 99 | 'set network-instance mac-vrf-22 protocols bgp-evpn ' 100 | 'bgp-instance 1 evi 22', 101 | 'set network-instance mac-vrf-22 protocols bgp-evpn ' 102 | 'bgp-instance 1 ecmp 8', 103 | 'set network-instance mac-vrf-22 protocols bgp-vpn ' 104 | 'bgp-instance 1 route-target export-rt target:1:22', 105 | 'set network-instance mac-vrf-22 protocols bgp-vpn ' 106 | 'bgp-instance 1 route-target import-rt target:1:22'] 107 | self.assertEqual(cmd_set, add_exp) 108 | 109 | cmd_set = self.switch._format_commands( 110 | nokia.NokiaSRL.DELETE_NETWORK, 111 | segmentation_id=22) 112 | del_net_exp = ['delete network-instance mac-vrf-22', 113 | 'delete tunnel-interface vxlan0 vxlan-interface 22'] 114 | self.assertEqual(cmd_set, del_net_exp) 115 | 116 | cmd_set = self.switch._format_commands( 117 | nokia.NokiaSRL.PLUG_PORT_TO_NETWORK, 118 | port=3333, 119 | segmentation_id=33) 120 | plug_exp = ['set interface 3333 subinterface 33 type bridged', 121 | 'set network-instance mac-vrf-33 interface 3333.33'] 122 | self.assertEqual(plug_exp, cmd_set) 123 | 124 | cmd_set = self.switch._format_commands( 125 | nokia.NokiaSRL.DELETE_PORT, 126 | port=3333, 127 | segmentation_id=33) 128 | del_port_exp = ['delete network-instance mac-vrf-33 interface 3333.33', 129 | 'delete interface 3333 subinterface 33'] 130 | self.assertEqual(del_port_exp, cmd_set) 131 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_pluribus.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 EscherCloud. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from networking_generic_switch.devices.netmiko_devices import pluribus 18 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 19 | 20 | 21 | class TestNetmikoPluribus(test_netmiko_base.NetmikoSwitchTestBase): 22 | 23 | def _make_switch_device(self): 24 | device_cfg = {'device_type': 'netmiko_pluribus'} 25 | return pluribus.Pluribus(device_cfg) 26 | 27 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 28 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 29 | def test_add_network(self, m_exec): 30 | self.switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 31 | m_exec.assert_called_with( 32 | self.switch, 33 | ['vlan-create id 33 scope fabric ports none description\ 34 | 0ae071f55be943e480eae41fefe85b21 auto-vxlan']) 35 | 36 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 37 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 38 | def test_del_network(self, mock_exec): 39 | self.switch.del_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 40 | mock_exec.assert_called_with(self.switch, ['vlan-delete id 33']) 41 | 42 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 43 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 44 | def test_plug_port_to_network(self, mock_exec): 45 | self.switch.plug_port_to_network(3333, 33) 46 | mock_exec.assert_called_with( 47 | self.switch, 48 | ['vlan-port-remove vlan-range all ports 3333', 49 | 'port-vlan-add port 3333 untagged-vlan 33', ]) 50 | 51 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 52 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 53 | def test_delete_port(self, mock_exec): 54 | self.switch.delete_port(3333, 33) 55 | mock_exec.assert_called_with( 56 | self.switch, 57 | ['vlan-port-remove vlan-range all ports 3333']) 58 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_smc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Nscale. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from networking_generic_switch.devices.netmiko_devices import smc 18 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 19 | 20 | 21 | class TestNetmikoSupermicroSmis(test_netmiko_base.NetmikoSwitchTestBase): 22 | 23 | def _make_switch_device(self): 24 | device_cfg = {'device_type': 'netmiko_supermicro_smis'} 25 | return smc.SupermicroSmis(device_cfg) 26 | 27 | def test_constants(self): 28 | self.assertIsNone(self.switch.SAVE_CONFIGURATION) 29 | 30 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 31 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 32 | def test_add_network(self, m_exec): 33 | self.switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 34 | m_exec.assert_any_call( 35 | self.switch, 36 | ['vlan 33', 'name 0ae071f55be943e480eae41fefe85b21']) 37 | 38 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 39 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 40 | def test_del_network(self, mock_exec): 41 | self.switch.del_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 42 | mock_exec.assert_called_with(self.switch, ['no vlan 33']) 43 | 44 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 45 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 46 | def test_plug_port_to_network(self, mock_exec): 47 | self.switch.plug_port_to_network(3333, 33) 48 | mock_exec.assert_called_with( 49 | self.switch, 50 | ['interface 3333', 'switchport mode access', 51 | 'switchport access vlan 33']) 52 | 53 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 54 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 55 | def test_delete_port(self, mock_exec): 56 | self.switch.delete_port(3333, 33) 57 | mock_exec.assert_called_with( 58 | self.switch, 59 | ['interface 3333', 'no switchport access vlan 33', 60 | 'no switchport mode trunk', 'switchport trunk allowed vlan none']) 61 | 62 | def test__format_commands(self): 63 | cmd_set = self.switch._format_commands( 64 | smc.SupermicroSmis.ADD_NETWORK, 65 | segmentation_id=22, 66 | network_id=22, 67 | network_name='vlan-22') 68 | self.assertEqual(cmd_set, ['vlan 22', 'name vlan-22']) 69 | 70 | cmd_set = self.switch._format_commands( 71 | smc.SupermicroSmis.DELETE_NETWORK, 72 | segmentation_id=22) 73 | self.assertEqual(cmd_set, ['no vlan 22']) 74 | 75 | cmd_set = self.switch._format_commands( 76 | smc.SupermicroSmis.PLUG_PORT_TO_NETWORK, 77 | port=3333, 78 | segmentation_id=33) 79 | plug_exp = ['interface 3333', 'switchport mode access', 80 | 'switchport access vlan 33'] 81 | self.assertEqual(plug_exp, cmd_set) 82 | 83 | cmd_set = self.switch._format_commands( 84 | smc.SupermicroSmis.DELETE_PORT, 85 | port=3333, 86 | segmentation_id=33) 87 | del_exp = ['interface 3333', 88 | 'no switchport access vlan 33', 89 | 'no switchport mode trunk', 90 | 'switchport trunk allowed vlan none'] 91 | self.assertEqual(del_exp, cmd_set) 92 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/netmiko/test_sonic.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 James Denton 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from networking_generic_switch.devices.netmiko_devices import sonic 18 | from networking_generic_switch import exceptions as exc 19 | from networking_generic_switch.tests.unit.netmiko import test_netmiko_base 20 | 21 | 22 | class TestNetmikoSonic(test_netmiko_base.NetmikoSwitchTestBase): 23 | 24 | def _make_switch_device(self, extra_cfg={}): 25 | device_cfg = { 26 | 'device_type': 'netmiko_sonic', 27 | 'ngs_port_default_vlan': '123', 28 | 'ngs_disable_inactive_ports': 'True', 29 | } 30 | device_cfg.update(extra_cfg) 31 | return sonic.Sonic(device_cfg) 32 | 33 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 34 | 'NetmikoSwitch.send_commands_to_device', 35 | return_value="", autospec=True) 36 | def test_add_network(self, mock_exec): 37 | self.switch.add_network(3333, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 38 | mock_exec.assert_called_with( 39 | self.switch, 40 | ['config vlan add 3333']) 41 | 42 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 43 | 'NetmikoSwitch.send_commands_to_device', 44 | return_value="", autospec=True) 45 | def test_delete_network(self, mock_exec): 46 | self.switch.del_network(3333, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') 47 | mock_exec.assert_called_with( 48 | self.switch, 49 | ['config vlan del 3333']) 50 | 51 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 52 | 'NetmikoSwitch.send_commands_to_device', 53 | return_value="", autospec=True) 54 | def test_plug_port_to_network(self, mock_exec): 55 | self.switch.plug_port_to_network(3333, 33) 56 | mock_exec.assert_called_with( 57 | self.switch, 58 | ['config vlan member del 123 3333', 59 | 'config vlan member add -u 33 3333']) 60 | 61 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 62 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 63 | def test_plug_port_to_network_fails(self, mock_exec): 64 | mock_exec.return_value = ( 65 | 'Error: No such command "test".\n\nasdf' 66 | ) 67 | self.assertRaises(exc.GenericSwitchNetmikoConfigError, 68 | self.switch.plug_port_to_network, 3333, 33) 69 | 70 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 71 | 'NetmikoSwitch.send_commands_to_device', autospec=True) 72 | def test_plug_port_to_network_fails_bad_port(self, mock_exec): 73 | mock_exec.return_value = ( 74 | 'Error: Interface name is invalid!!' 75 | '\n\nasdf' 76 | ) 77 | self.assertRaises(exc.GenericSwitchNetmikoConfigError, 78 | self.switch.plug_port_to_network, 3333, 33) 79 | 80 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 81 | 'NetmikoSwitch.send_commands_to_device', 82 | return_value="", autospec=True) 83 | def test_plug_port_simple(self, mock_exec): 84 | switch = self._make_switch_device({ 85 | 'ngs_disable_inactive_ports': 'false', 86 | 'ngs_port_default_vlan': '', 87 | }) 88 | switch.plug_port_to_network(3333, 33) 89 | mock_exec.assert_called_with(switch, 90 | ['config vlan member add -u 33 3333']) 91 | 92 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 93 | 'NetmikoSwitch.send_commands_to_device', 94 | return_value="", autospec=True) 95 | def test_delete_port(self, mock_exec): 96 | self.switch.delete_port(3333, 33) 97 | mock_exec.assert_called_with( 98 | self.switch, 99 | ['config vlan member del 33 3333', 100 | 'config vlan add 123', 101 | 'config vlan member add -u 123 3333']) 102 | 103 | @mock.patch('networking_generic_switch.devices.netmiko_devices.' 104 | 'NetmikoSwitch.send_commands_to_device', 105 | return_value="", autospec=True) 106 | def test_delete_port_simple(self, mock_exec): 107 | switch = self._make_switch_device({ 108 | 'ngs_disable_inactive_ports': 'false', 109 | 'ngs_port_default_vlan': '', 110 | }) 111 | switch.delete_port(3333, 33) 112 | mock_exec.assert_called_with(switch, 113 | ['config vlan member del 33 3333']) 114 | 115 | def test_save(self): 116 | mock_connect = mock.MagicMock() 117 | mock_connect.save_config.side_effect = NotImplementedError 118 | self.switch.save_configuration(mock_connect) 119 | mock_connect.send_command.assert_called_with('config save -y') 120 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/test_config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import os 16 | import shutil 17 | import tempfile 18 | 19 | import fixtures 20 | from oslo_config import fixture as config_fixture 21 | 22 | from networking_generic_switch import config 23 | 24 | 25 | fake_config = """ 26 | [genericswitch:foo] 27 | device_type = foo_device 28 | spam = eggs 29 | """ 30 | 31 | fake_config_bar = """ 32 | [genericswitch:bar] 33 | device_type = bar_device 34 | ham = vikings 35 | """ 36 | 37 | fake_config_baz = """ 38 | [genericswitch:baz] 39 | device_type = baz_device 40 | truffle = brandy 41 | """ 42 | 43 | 44 | class TestConfig(fixtures.TestWithFixtures): 45 | def setUp(self): 46 | super(TestConfig, self).setUp() 47 | 48 | config_file_foo = tempfile.NamedTemporaryFile( 49 | suffix=".conf", prefix="ngs-", delete=False).name 50 | self.addCleanup(os.remove, config_file_foo) 51 | 52 | config_dir = tempfile.mkdtemp('-ngs', 'bar-') 53 | self.addCleanup(shutil.rmtree, config_dir) 54 | 55 | config_file_bar = os.path.join(config_dir, 'bar.conf') 56 | config_file_baz = os.path.join(config_dir, 'baz.conf') 57 | 58 | with open(config_file_foo, 'w') as f: 59 | f.write(fake_config) 60 | with open(config_file_bar, 'w') as f: 61 | f.write(fake_config_bar) 62 | with open(config_file_baz, 'w') as f: 63 | f.write(fake_config_baz) 64 | 65 | self.cfg = self.useFixture(config_fixture.Config()) 66 | self.cfg.conf(args=[f"--config-file={config_file_foo}", 67 | f"--config-dir={config_dir}"]) 68 | 69 | def test_get_devices(self): 70 | device_list = config.get_devices() 71 | self.assertEqual(set(device_list), set(['foo', 'bar', 'baz'])) 72 | self.assertEqual({"device_type": "foo_device", "spam": "eggs"}, 73 | device_list['foo']) 74 | self.assertEqual({"device_type": "bar_device", "ham": "vikings"}, 75 | device_list['bar']) 76 | self.assertEqual({"device_type": "baz_device", "truffle": "brandy"}, 77 | device_list['baz']) 78 | -------------------------------------------------------------------------------- /networking_generic_switch/tests/unit/test_locking.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | import fixtures 18 | from oslo_config import fixture as config_fixture 19 | import tenacity 20 | from tooz import coordination 21 | 22 | from networking_generic_switch import locking as ngs_lock 23 | 24 | 25 | class PoolLockTest(fixtures.TestWithFixtures): 26 | 27 | def setUp(self): 28 | super(PoolLockTest, self).setUp() 29 | self.cfg = self.useFixture(config_fixture.Config()) 30 | 31 | def test_lock_init(self): 32 | coord = mock.Mock() 33 | lock = ngs_lock.PoolLock(coord, locks_pool_size=3, locks_prefix='spam', 34 | timeout=120) 35 | 36 | self.assertEqual(coord, lock.coordinator) 37 | self.assertEqual(['spam0', 'spam1', 'spam2'], list(lock.lock_names)) 38 | self.assertEqual(120, lock.timeout) 39 | 40 | def test_lock_contextmanager_no_coordinator(self): 41 | lock = ngs_lock.PoolLock(None) 42 | with lock as lk: 43 | self.assertFalse(lk.lock) 44 | 45 | @mock.patch.object(ngs_lock.tenacity, 'stop_after_delay', 46 | return_value=tenacity.stop_after_delay(0.1), 47 | autospec=True) 48 | @mock.patch.object(ngs_lock.tenacity, 'wait_random', 49 | return_value=tenacity.wait_fixed(0.01), 50 | autospec=True) 51 | def test_lock_contextmanager_with_coordinator(self, wait_mock, stop_mock): 52 | coord = mock.Mock() 53 | lock_mock = mock.Mock() 54 | coord.get_lock.return_value = lock_mock 55 | lock_mock.acquire.side_effect = [False, False, False, True] 56 | 57 | with ngs_lock.PoolLock(coord, locks_pool_size=2, timeout=1) as lk: 58 | self.assertEqual(coord, lk.coordinator) 59 | self.assertEqual(1, lk.timeout) 60 | self.assertEqual(4, lock_mock.acquire.call_count) 61 | self.assertEqual(lock_mock, lk.lock) 62 | 63 | lock_mock.release.assert_called_once_with() 64 | stop_mock.assert_called_once_with(1) 65 | 66 | @mock.patch.object(ngs_lock.tenacity, 'stop_after_delay', 67 | return_value=tenacity.stop_after_delay(0.1), 68 | autospec=True) 69 | @mock.patch.object(ngs_lock.tenacity, 'wait_random', 70 | return_value=tenacity.wait_fixed(0.01), 71 | autospec=True) 72 | @mock.patch.object(ngs_lock.LOG, 'error', autospec=True) 73 | def test_lock_contextmanager_fail(self, log_mock, wait_mock, stop_mock): 74 | coord = mock.Mock() 75 | lock_mock = mock.Mock() 76 | coord.get_lock.return_value = lock_mock 77 | lock_mock.acquire.side_effect = coordination.LockAcquireFailed('SPAM!') 78 | 79 | def test_call(): 80 | with ngs_lock.PoolLock(coord, locks_pool_size=2, timeout=1): 81 | pass 82 | 83 | self.assertRaises(coordination.LockAcquireFailed, test_call) 84 | log_mock.assert_called_once_with(mock.ANY, exc_info=True) 85 | lock_mock.release.assert_not_called() 86 | stop_mock.assert_called_once_with(1) 87 | -------------------------------------------------------------------------------- /networking_generic_switch/trunk_driver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 StackHPC Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from neutron.objects.ports import Port 16 | from neutron.services.trunk.drivers import base as trunk_base 17 | from neutron_lib.api.definitions import portbindings 18 | from neutron_lib.callbacks import events 19 | from neutron_lib.callbacks import registry 20 | from neutron_lib.callbacks import resources 21 | from neutron_lib import context as n_context 22 | from neutron_lib.db import api as db_api 23 | from neutron_lib.plugins import directory 24 | from neutron_lib.services.trunk import constants as trunk_consts 25 | from oslo_config import cfg 26 | from oslo_log import log as logging 27 | 28 | LOG = logging.getLogger(__name__) 29 | 30 | MECH_DRIVER_NAME = 'genericswitch' 31 | 32 | SUPPORTED_INTERFACES = ( 33 | portbindings.VIF_TYPE_OTHER, 34 | portbindings.VIF_TYPE_VHOST_USER, 35 | ) 36 | 37 | SUPPORTED_SEGMENTATION_TYPES = ( 38 | trunk_consts.SEGMENTATION_TYPE_VLAN, 39 | ) 40 | 41 | 42 | class GenericSwitchTrunkDriver(trunk_base.DriverBase): 43 | @property 44 | def is_loaded(self): 45 | try: 46 | return (MECH_DRIVER_NAME in 47 | cfg.CONF.ml2.mechanism_drivers) 48 | except cfg.NoSuchOptError: 49 | return False 50 | 51 | @registry.receives(resources.TRUNK_PLUGIN, [events.AFTER_INIT]) 52 | def register(self, resource, event, trigger, payload=None): 53 | super(GenericSwitchTrunkDriver, self).register( 54 | resource, event, trigger, payload=payload) 55 | self._handler = GenericSwitchTrunkHandler(self.plugin_driver) 56 | registry.subscribe( 57 | self._handler.subports_added, 58 | resources.SUBPORTS, 59 | events.AFTER_CREATE) 60 | registry.subscribe( 61 | self._handler.subports_deleted, 62 | resources.SUBPORTS, 63 | events.AFTER_DELETE) 64 | 65 | @classmethod 66 | def create(cls, plugin_driver): 67 | cls.plugin_driver = plugin_driver 68 | return cls(MECH_DRIVER_NAME, 69 | SUPPORTED_INTERFACES, 70 | SUPPORTED_SEGMENTATION_TYPES, 71 | None, 72 | can_trunk_bound_port=True) 73 | 74 | 75 | class GenericSwitchTrunkHandler(object): 76 | def __init__(self, plugin_driver): 77 | self.plugin_driver = plugin_driver 78 | self.core_plugin = directory.get_plugin() 79 | 80 | def subports_added(self, resource, event, trunk_plugin, payload): 81 | trunk = payload.states[0] 82 | subports = payload.metadata['subports'] 83 | LOG.debug("GenericSwitch: subports added %s to trunk %s", 84 | subports, trunk) 85 | context = n_context.get_admin_context() 86 | with db_api.CONTEXT_READER.using(context): 87 | parent_port = Port.get_object(context, id=trunk.port_id) 88 | 89 | parent_port_obj = self.core_plugin._make_port_dict(parent_port) 90 | 91 | self.plugin_driver.subports_added( 92 | context, 93 | parent_port_obj, 94 | subports) 95 | 96 | def subports_deleted(self, resource, event, trunk_plugin, payload): 97 | trunk = payload.states[0] 98 | subports = payload.metadata['subports'] 99 | LOG.debug("GenericSwitch: subports deleted %s from trunk %s", 100 | subports, trunk) 101 | context = n_context.get_admin_context() 102 | with db_api.CONTEXT_READER.using(context): 103 | parent_port = Port.get_object(context, id=trunk.port_id) 104 | 105 | parent_port_obj = self.core_plugin._make_port_dict(parent_port) 106 | self.plugin_driver.subports_deleted( 107 | context, 108 | parent_port_obj, 109 | subports) 110 | -------------------------------------------------------------------------------- /networking_generic_switch/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from neutron_lib.api.definitions import portbindings 16 | 17 | 18 | def is_802_3ad(binding_profile): 19 | """Return whether a port binding profile is using 802.3ad link aggregation. 20 | 21 | :param binding_profile: The port binding_profile to check 22 | :returns: Whether the port is a port group using 802.3ad link 23 | aggregation. 24 | """ 25 | binding_profile = binding_profile or {} 26 | local_group_information = binding_profile.get( 27 | 'local_group_information') 28 | if not local_group_information: 29 | return False 30 | return local_group_information.get('bond_mode') in ['4', '802.3ad'] 31 | 32 | 33 | def is_port_supported(port): 34 | """Return whether a port is supported by this driver. 35 | 36 | Ports supported by this driver have a VNIC type of 'baremetal'. 37 | 38 | :param port: The port to check 39 | :returns: Whether the port is supported by the NGS driver 40 | """ 41 | vnic_type = port[portbindings.VNIC_TYPE] 42 | return vnic_type == portbindings.VNIC_BAREMETAL 43 | 44 | 45 | def is_port_bound(port): 46 | """Return whether a port is bound by this driver. 47 | 48 | Ports bound by this driver have their VIF type set to 'other'. 49 | 50 | :param port: The port to check 51 | :returns: Whether the port is bound by the NGS driver 52 | """ 53 | if not is_port_supported(port): 54 | return False 55 | 56 | vif_type = port[portbindings.VIF_TYPE] 57 | return vif_type == portbindings.VIF_TYPE_OTHER 58 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["pbr>=6.0.0", "setuptools>=64.0.0"] 3 | build-backend = "pbr.build" 4 | -------------------------------------------------------------------------------- /releasenotes/notes/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/releasenotes/notes/.placeholder -------------------------------------------------------------------------------- /releasenotes/notes/add-aruba-support-463a90b0b150b9af.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new device driver, ``netmiko_aruba_os``, for managing ArubaOS-CX 5 | switch devices. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/add-cisco-nx-os-support-8046a33107e2a670.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Add support for Cisco Nexus devices (NX-OS). 5 | -------------------------------------------------------------------------------- /releasenotes/notes/add-cumulus-nclu-support-ddcffa604c3e1b18.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new device driver, ``netmiko_cumulus``, for managing cumulus 5 | based switch devices via NCLU. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/add-cumulus-nvue-support-2207d67edc12e866.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new device driver, ``netmiko_cumulus_nvue``, for managing NVIDIA 5 | Cumulus based switch devices via NVUE. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/add-dellos10-support-c6426372f960ded4.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new device driver, ``netmiko_dell_os10``, for managing Dell OS10 5 | based switch devices. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/add-initial-note-ae1c9f9709c2e66f.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | This is release ``1.0.0`` of the ``networking-generic-switch`` project. The 4 | project includes an ML2 mechanism driver for the Networking service that 5 | provides integration with the Bare Metal service. 6 | 7 | The main use case for this project is multi-tenant network isolation for 8 | bare metal servers using VLANs. The driver manages the VLAN membership of 9 | network switch edge ports to which bare metal servers are attached. 10 | 11 | Various network switches from different vendors are supported, including 12 | Arista, Brocade, Cisco, Dell, Huawei, and others. 13 | -------------------------------------------------------------------------------- /releasenotes/notes/add-mellanox-mlnx-os-switch-support-a4bf0661cd27fec7.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new device driver, ``netmiko_mellanox_mlnxos``, for managing Mellanox 5 | MLNX-OS based switch devices 6 | -------------------------------------------------------------------------------- /releasenotes/notes/add-ngs_save_configuration-180c2145f08e54d2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Add new device setting ``ngs_save_configuration``, allowing to disable 5 | saving configuration on the device after each change. This can speed up 6 | the overall process significantly, but changes will be lost if the device 7 | reboots. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/add-ngs_ssh_disabled_algorithms-dfe3e805f480ce90.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Add new device setting ``ngs_ssh_disabled_algorithms``. This allows to 5 | selectively disable SSH algorithms of various types, which may help to 6 | speed up SSH connection (faster key exchange algorithm) or to workaround 7 | buggy SSH implementations found on some devices. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/add-sonic-os-switch-support-73fcaf3acdc8c1d0.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new device driver, ``netmiko_sonic``, for managing switches 5 | running the SONiC open source network operating system. 6 | 7 | -------------------------------------------------------------------------------- /releasenotes/notes/add-supermicro-support-8cc6c6b2265e474b.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new device driver, ``netmiko_supermicro_smis``, for managing Supermicro 5 | based switch devices. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/add-switchmode-option-to-dell-powerconnect-87718a84430444ef.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new config option, ``ngs_switchport_mode`` to the Dell 5 | PowerConnect driver. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/add-vlan-and-port-allowlist-0b1c29967210fbfb.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Add support for `ngs_allowed_vlans` and `ngs_allowed_ports` 5 | configuration options to allow defining specific VLANs and ports 6 | allowed on a switch. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/add_alias_for_hpe_comware-0eb9a016f0c992df.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | issues: 3 | - | 4 | Netmiko has not changed the device name for hp_comware to hpe_comware 5 | yet. See 6 | 7 | https://github.com/ktbyers/netmiko/blob/develop/netmiko/ssh_dispatcher.py 8 | 9 | Therefore, the device will fail to load as netmiko will only recognize 10 | "hp_comware". We'll need to alias "hpe_comware" with "hp_comware" till 11 | netmiko recognizes HPE. 12 | -------------------------------------------------------------------------------- /releasenotes/notes/add_netmiko_hpe_comware_device-c39be5d96943c6fd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Add HPE device type to enable communication with the HPE 5900 series switches 5 | that use comware5 netconf. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/add_ngs_default_vlan-ab09e0f4fd7ce897.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - Fixes an issue where a switch interface will revert to VLAN 1, trunk mode, 4 | or other various configurations when a port is unbound. See 5 | https://bugs.launchpad.net/networking-generic-switch/+bug/1752480. 6 | features: 7 | - Adds a new configuration option ``[genericswitch:] 8 | ngs_port_default_vlan``, which is optional. If ``ngs_port_default_vlan`` is 9 | set to a VLAN ID, the switch's interfaces will be restored to that VLAN 10 | when ports are unbound. 11 | -------------------------------------------------------------------------------- /releasenotes/notes/batching-12d9005924fd9d74.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds support for batching of requests using etcd as a task queue. 5 | -------------------------------------------------------------------------------- /releasenotes/notes/config-dir-d5f59b536c110841.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Switch configuration groups were only found in files specified by 5 | `--config-file` arguments. Configuration files in `--config-dir` 6 | directories are now also included. -------------------------------------------------------------------------------- /releasenotes/notes/cumulus-802.3ad-da9bffe131995f98.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds support for 802.3ad port groups on NVIDIA Cumulus devices. 5 | -------------------------------------------------------------------------------- /releasenotes/notes/delete_network_postcommit-more-defensive-19929702ba7f19fb.yaml: -------------------------------------------------------------------------------- 1 | fixes: 2 | - | 3 | Instead of always assuming we have all provider network metadata, instead 4 | use a getter to avoid erroring in cases where that metadata may not be needed. 5 | -------------------------------------------------------------------------------- /releasenotes/notes/dell-powerconnect-5ce572b9fb2702d3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new device driver, ``netmiko_dell_powerconnect``, for managing Dell 5 | PowerConnect switch devices. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/disable-inactive-ports-bd6c42ceb232aab2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds support for disabling inactive switch interfaces. This is configured 5 | on a per-device basis using the ``ngs_disable_inactive_ports`` flag. See 6 | `Story 2003391 `__ for 7 | details. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/drop-py-2-7-76d7a678dc042bd6.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Python 2.7 support has been dropped. Last release of Networking Generic 5 | Switch to support Python 2.7 is OpenStack Train. The minimum version of 6 | Python now supported by Networking Generic Switch is Python 3.6. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-junos-syntax-27bb18dc737d776b.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixed command syntax of Juniper devices. This makes the driver 5 | incompatible with some very old Junos OS releases that are EOL 6 | and no longer supported by the vendor. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-netmiko-and-ngs-33c79b55ff7e2fd7.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixed the configuration for ngs to use `::1` instead of `localhost`, since 5 | the `/etc/hosts` is using `localhost` as alias for IPv4 and IPv6. 6 | - | 7 | Fixed timeout in netmiko by setting `cmd_verify` to False, we don't need to 8 | verify command echo for each command in in `config_set` 9 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-netmiko-info-leakage-423c4c59b924c06f.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixes a security vulnerability where sensitive information (IP addresses, 5 | usernames, hostname and file paths) were leaked in error messages when a 6 | connection to a network device fails. Error messages are now properly 7 | sanitized to redact these sensitive details while preserving meaningful 8 | information for troubleshooting. 9 | -------------------------------------------------------------------------------- /releasenotes/notes/fixes-netmiko-regression-binding-port-groups-af6978a199a381b1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixes a regression when binding 802.3ad port groups on netmiko devices 5 | other than cumulus. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/juniper-92d75d3086cf78a2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new driver, ``netmiko_juniper``, for Juniper Junos OS devices. 5 | 6 | The private configuration mode is used in order to provide a level of 7 | isolation between sessions, and to ensure that uncommitted changes are not 8 | left on the switch following a failure to commit the configuration. 9 | 10 | Configuration errors are handled by ensuring that the commit operation is 11 | successful. 12 | 13 | A retry mechanism is used to handle temporary failures due to multiple 14 | sessions attempting to lock the Junos OS configuration database 15 | concurrently. The retry mechanism is configured via the configuration 16 | options ``ngs_commit_interval`` and ``ngs_commit_timeout``. 17 | -------------------------------------------------------------------------------- /releasenotes/notes/junos-retry-warnings-f2b004fe99d7770d.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixes an issue with the ``netmiko_juniper`` driver, which could 5 | unnecessarily fail operations if concurrent configuration of a switch is 6 | performed. See `story 2006220 7 | `__ for details. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/log-unknown-ngs-options-8a385406055ccc98.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | To improve logging, the device name is now passed to the device driver 5 | class. If you implemented an out-of-tree device driver inheriting from 6 | ``GenericSwitchDevice`` or ``NetmikoSwitch``, your code needs to be adapted 7 | to accept this new argument to the ``__init__`` method. It is recommended 8 | to use the ``*args, **kwargs`` pattern to accept and pass all unhandled 9 | arguments to the base class. 10 | fixes: 11 | - | 12 | Ignore unknown options starting with ``ngs_`` instead of crashing. 13 | -------------------------------------------------------------------------------- /releasenotes/notes/manage-vlans-c75e4c2e9b9b3403.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | New per switch `ngs_manage_vlans` option. It defaults to True, but can 5 | be set to False so that no VLANs are added or removed on that switch. 6 | This is useful when not all ports on the switch are managed by Neutron. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/net-add-del-failure-f4ea1118bc1f9d28.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixes an issue when creating or deleting a network, a failure to apply this 5 | change to any device would not cause the operation to fail. This could 6 | leave the network in an inconsistent state. See story 2006222 7 | `__ for details. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/netmiko-session-logging-9834fcdb8d6e5bb3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Added support for Netmiko session logging. Session logging is disabled by 5 | default, to enable session logging set configuration option 6 | ``[ngs]/session_log_file``. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/network-name-format-075f5757d599ac92.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds support for configuring the format of the name assigned to VLANs in 5 | device configuration via the ``ngs_network_name_format`` device 6 | configuration option. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/ngs-stress-78f9e993e62e2e36.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | other: 3 | - | 4 | Adds a stress testing script, ``ngs-stress.py``, in the 5 | ``tools/ngs-stress`` directory of the source code repository. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/nokia-srl-support-52ea2a445f4b24d4.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds support for Nokia SRL devices. 5 | -------------------------------------------------------------------------------- /releasenotes/notes/normalize-ngs-macaddr-1ff0b6be7a53087a.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixes an issue that n-g-s is not working when ``ngs_mac_address`` and 5 | ``switch_id`` represent the same address but not case sensitive identical. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/remove-del_network-transitional-2f5742f7cafa2276.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Removes support for device drivers that accept only a single argument for 5 | their ``del_network`` method. All device drivers must now accept a 6 | segmentation ID and a network ID in their ``del_network`` method. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/remove-py38-18af38f2ee386abc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Support for Python 3.8 has been removed. Now the minimum python version 5 | supported is 3.9 . 6 | -------------------------------------------------------------------------------- /releasenotes/notes/ruijie-netmiko-driver-14e521fc36ede897.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds a new device driver, ``netmiko_ruijie``, for managing Ruijie switch devices. 5 | -------------------------------------------------------------------------------- /releasenotes/notes/support-multiple-links-in-port-group-59a11c2c2da73065.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | NGS now attempts to configure the respective switchports of all links 5 | of a port group. As a result, bonding modes not requiring special switch 6 | configurations (e.g. active-passive, ALB, TLB) should be supported. MLAG 7 | may be supported using pre-defined port-channel interfaces. See 8 | `NGS Bug 1759000 `__ 9 | for details. 10 | -------------------------------------------------------------------------------- /releasenotes/notes/vlan-aware-vms-3923cc17254829e9.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Add support of VLAN aware instances for the following drivers 5 | * OVS (as reference implementation) 6 | * AristaEos 7 | * CiscoIos 8 | * DellOS 10 9 | * Cumulus NVUE 10 | -------------------------------------------------------------------------------- /releasenotes/source/2023.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2023.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/2023.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/2023.2.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2023.2 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2023.2 7 | -------------------------------------------------------------------------------- /releasenotes/source/2024.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2024.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2024.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/2024.2.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2024.2 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2024.2 7 | -------------------------------------------------------------------------------- /releasenotes/source/2025.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2025.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2025.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/_static/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/releasenotes/source/_static/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/_templates/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/releasenotes/source/_templates/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/index.rst: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Networking Generic Switch Release Notes 3 | ======================================= 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | unreleased 9 | 2025.1 10 | 2024.2 11 | 2024.1 12 | 2023.2 13 | 2023.1 14 | zed 15 | yoga 16 | xena 17 | wallaby 18 | victoria 19 | ussuri 20 | train 21 | stein 22 | rocky 23 | queens 24 | -------------------------------------------------------------------------------- /releasenotes/source/queens.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Queens Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/queens 7 | -------------------------------------------------------------------------------- /releasenotes/source/rocky.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Rocky Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/rocky 7 | -------------------------------------------------------------------------------- /releasenotes/source/stein.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Stein Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/stein 7 | -------------------------------------------------------------------------------- /releasenotes/source/train.rst: -------------------------------------------------------------------------------- 1 | ========================================== 2 | Train Series (2.0.0 - 2.1.x) Release Notes 3 | ========================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/train 7 | -------------------------------------------------------------------------------- /releasenotes/source/unreleased.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Current Series Release Notes 3 | ============================ 4 | 5 | .. release-notes:: 6 | -------------------------------------------------------------------------------- /releasenotes/source/ussuri.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Ussuri Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/ussuri 7 | -------------------------------------------------------------------------------- /releasenotes/source/victoria.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Victoria Series Release Notes 3 | ============================= 4 | 5 | .. release-notes:: 6 | :branch: stable/victoria 7 | -------------------------------------------------------------------------------- /releasenotes/source/wallaby.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Wallaby Series Release Notes 3 | ============================ 4 | 5 | .. release-notes:: 6 | :branch: stable/wallaby 7 | -------------------------------------------------------------------------------- /releasenotes/source/xena.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Xena Series Release Notes 3 | ========================= 4 | 5 | .. release-notes:: 6 | :branch: stable/xena 7 | -------------------------------------------------------------------------------- /releasenotes/source/yoga.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Yoga Series Release Notes 3 | ========================= 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/yoga 7 | -------------------------------------------------------------------------------- /releasenotes/source/zed.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Zed Series Release Notes 3 | ======================== 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/zed 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Requirements lower bounds listed here are our best effort to keep them up to 2 | # date but we do not test them so no guarantee of having them all correct. If 3 | # you find any incorrect lower bounds, let us know or propose a fix. 4 | etcd3gw>=2.1.0 # Apache-2.0 5 | oslo.service[threading]>=4.2.0 # Apache-2.0 6 | stevedore>=1.20.0 # Apache-2.0 7 | netmiko>=4.1.1 # MIT 8 | neutron>=13.0.0.0b1 # Apache-2.0 9 | neutron-lib>=1.18.0 # Apache-2.0 10 | oslo.config>=5.2.0 # Apache-2.0 11 | oslo.i18n>=3.15.3 # Apache-2.0 12 | oslo.log>=3.36.0 # Apache-2.0 13 | oslo.utils>=3.40.2 # Apache-2.0 14 | tenacity>=6.0.0 # Apache-2.0 15 | tooz>=2.5.1 # Apache-2.0 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = networking-generic-switch 3 | author = Mirantis 4 | author_email = mos-ironic@mirantis.com 5 | home_page = https://github.com/openstack/networking-generic-switch 6 | summary = Generic Switch ML2 Neutron Driver 7 | python_requires = >=3.9 8 | classifier = 9 | Environment :: OpenStack 10 | Intended Audience :: System Administrators 11 | Intended Audience :: Information Technology 12 | License :: OSI Approved :: Apache Software License 13 | Operating System :: OS Independent 14 | Programming Language :: Python 15 | Programming Language :: Python :: Implementation :: CPython 16 | Programming Language :: Python :: 3 :: Only 17 | Programming Language :: Python :: 3 18 | Programming Language :: Python :: 3.9 19 | Programming Language :: Python :: 3.10 20 | Programming Language :: Python :: 3.11 21 | Programming Language :: Python :: 3.12 22 | 23 | description_file = 24 | README.rst 25 | 26 | [files] 27 | packages = 28 | networking_generic_switch 29 | 30 | [entry_points] 31 | neutron.ml2.mechanism_drivers = 32 | genericswitch = networking_generic_switch.generic_switch_mech:GenericSwitchDriver 33 | generic_switch.devices = 34 | netmiko_ovs_linux = networking_generic_switch.devices.netmiko_devices.ovs:OvsLinux 35 | netmiko_cisco_ios = networking_generic_switch.devices.netmiko_devices.cisco:CiscoIos 36 | netmiko_cisco_nxos = networking_generic_switch.devices.netmiko_devices.cisco:CiscoNxOS 37 | netmiko_cisco_s300= networking_generic_switch.devices.netmiko_devices.cisco300:Cisco300 38 | netmiko_huawei = networking_generic_switch.devices.netmiko_devices.huawei:Huawei 39 | netmiko_huawei_vrpv8 = networking_generic_switch.devices.netmiko_devices.huawei_vrpv8:Huawei 40 | netmiko_arista_eos = networking_generic_switch.devices.netmiko_devices.arista:AristaEos 41 | netmiko_dell_os10 = networking_generic_switch.devices.netmiko_devices.dell:DellOS10 42 | netmiko_dell_force10 = networking_generic_switch.devices.netmiko_devices.dell:DellNos 43 | netmiko_dell_powerconnect = networking_generic_switch.devices.netmiko_devices.dell:DellPowerConnect 44 | netmiko_brocade_fastiron = networking_generic_switch.devices.netmiko_devices.brocade:BrocadeFastIron 45 | netmiko_ruijie = networking_generic_switch.devices.netmiko_devices.ruijie:Ruijie 46 | netmiko_hpe_comware = networking_generic_switch.devices.netmiko_devices.hpe:HpeComware 47 | netmiko_hp_comware = networking_generic_switch.devices.netmiko_devices.hpe:HpeComware 48 | netmiko_juniper = networking_generic_switch.devices.netmiko_devices.juniper:Juniper 49 | netmiko_mellanox_mlnxos = networking_generic_switch.devices.netmiko_devices.mellanox_mlnxos:MellanoxMlnxOS 50 | netmiko_cumulus = networking_generic_switch.devices.netmiko_devices.cumulus:Cumulus 51 | netmiko_cumulus_nvue = networking_generic_switch.devices.netmiko_devices.cumulus:CumulusNVUE 52 | netmiko_sonic = networking_generic_switch.devices.netmiko_devices.sonic:Sonic 53 | netmiko_supermicro_smis = networking_generic_switch.devices.netmiko_devices.smc:SupermicroSmis 54 | netmiko_nokia_srl = networking_generic_switch.devices.netmiko_devices.nokia:NokiaSRL 55 | netmiko_pluribus = networking_generic_switch.devices.netmiko_devices.pluribus:Pluribus 56 | netmiko_aruba_os = networking_generic_switch.devices.netmiko_devices.aruba:ArubaOSCX 57 | netmiko_fake = networking_generic_switch.devices.netmiko_devices.fake:Fake 58 | tempest.test_plugins = 59 | ngs_tests = tempest_plugin.plugin:NGSTempestPlugin 60 | 61 | [codespell] 62 | quiet-level = 4 63 | # Words to ignore: 64 | # cna: Intel CNA card 65 | # assertIn: Python's unittest method 66 | ignore-words-list = cna,assertIn 67 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import setuptools 17 | 18 | setuptools.setup( 19 | setup_requires=['pbr>=6.0.0'], 20 | pbr=True) 21 | -------------------------------------------------------------------------------- /tempest_plugin/README.rst: -------------------------------------------------------------------------------- 1 | ================================================ 2 | Tempest Integration of Networking Generic Switch 3 | ================================================ 4 | 5 | This directory contains Tempest tests for networking-generic-switch project. 6 | 7 | -------------------------------------------------------------------------------- /tempest_plugin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/tempest_plugin/__init__.py -------------------------------------------------------------------------------- /tempest_plugin/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | 17 | from oslo_config import cfg 18 | 19 | service_option = cfg.BoolOpt('ngs', 20 | default=False, 21 | help='Whether or not Networking Generic Switch' 22 | 'is expected to be available') 23 | 24 | ngs_group = cfg.OptGroup(name='ngs', 25 | title='Networking Generic Switch', 26 | help='Options group for Networking Generic Switch') 27 | 28 | NGSGroup = [ 29 | cfg.StrOpt('device_type', 30 | default='ovs_linux', 31 | help='Type of the switch.'), 32 | cfg.StrOpt('bridge_name', 33 | default='genericswitch', 34 | help='Bridge name to use.'), 35 | cfg.StrOpt('port_name', 36 | default='gs_port_01', 37 | help='Port name to use.'), 38 | cfg.IntOpt('port_dlm_concurrency', 39 | default=0, 40 | min=0, 41 | help='Concurrency to run the DLM tests with. ' 42 | 'With default values DLM tests are skipped.'), 43 | cfg.StrOpt('network_name', 44 | default='private', 45 | help='Test network name to use.') 46 | ] 47 | -------------------------------------------------------------------------------- /tempest_plugin/plugin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | 17 | import os 18 | 19 | from tempest import config 20 | from tempest.test_discover import plugins 21 | from tempest_plugin import config as project_config 22 | 23 | 24 | class NGSTempestPlugin(plugins.TempestPlugin): 25 | def load_tests(self): 26 | base_path = os.path.split(os.path.dirname( 27 | os.path.abspath(__file__)))[0] 28 | test_dir = "tempest_plugin/tests" 29 | full_test_dir = os.path.join(base_path, test_dir) 30 | return full_test_dir, base_path 31 | 32 | def register_opts(self, conf): 33 | config.register_opt_group(conf, project_config.ngs_group, 34 | project_config.NGSGroup) 35 | conf.register_opt(project_config.service_option, 36 | group='service_available') 37 | 38 | def get_opt_lists(self): 39 | return [(project_config.ngs_group.name, project_config.NGSGroup), 40 | ('service_available', [project_config.service_option])] 41 | -------------------------------------------------------------------------------- /tempest_plugin/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/tempest_plugin/tests/__init__.py -------------------------------------------------------------------------------- /tempest_plugin/tests/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/tempest_plugin/tests/common/__init__.py -------------------------------------------------------------------------------- /tempest_plugin/tests/common/ovs_lib.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | # I'd be happy to reuse ovs_lib from neutron.agent.common 17 | # but Tempest all_plugin run tries to import the module 18 | # and has an issue with importing CLI options 19 | # Current approach is to re-implement a small subset of ovsctl commands 20 | 21 | import json 22 | 23 | from tempest.lib.cli import base 24 | 25 | 26 | def get_port_tag_dict(port_name): 27 | ovsdb_json = json.loads( 28 | base.execute("sudo", "ovsdb-client dump Port name tag -f json")) 29 | 30 | for data in ovsdb_json['data']: 31 | name, tag = data 32 | if name == port_name: 33 | return tag 34 | -------------------------------------------------------------------------------- /tempest_plugin/tests/scenario/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/networking-generic-switch/a2c59ad14d392aafa44c8cc8db7a01766d7f5320/tempest_plugin/tests/scenario/__init__.py -------------------------------------------------------------------------------- /tempest_plugin/tests/scenario/test_ngs_basic_ops.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import futurist 16 | import futurist.waiters 17 | import netifaces 18 | from tempest.api.network import base as net_base 19 | from tempest.common import utils 20 | from tempest import config 21 | from tempest.lib import decorators 22 | from tempest.lib import exceptions 23 | from tempest_plugin.tests.common import ovs_lib 24 | 25 | CONF = config.CONF 26 | 27 | 28 | class NGSBasicOpsBase(net_base.BaseAdminNetworkTest): 29 | 30 | """This smoke test tests the ovs_linux driver. 31 | 32 | It follows this basic set of operations: 33 | * Clear Resources 34 | ** Delete Bridge 35 | ** Delete Port 36 | * Add new Bridge in OVS 37 | * Add new Port in OVS 38 | * Remember and clear the port Tag 39 | * Create and Update Neutron port 40 | * Get new Tag from OVS 41 | * Assert that Tag created via Neutron is equal Tag in OVS 42 | """ 43 | 44 | @classmethod 45 | def skip_checks(cls): 46 | super(NGSBasicOpsBase, cls).skip_checks() 47 | if not CONF.service_available.ngs: 48 | raise cls.skipException('Networking Generic Switch is required.') 49 | 50 | def get_local_port_mac(self, bridge_name): 51 | mac_address = netifaces.ifaddresses( 52 | bridge_name)[netifaces.AF_LINK][0].get('addr') 53 | return mac_address 54 | 55 | def cleanup_port(self, port_id): 56 | """Remove Neutron port and skip NotFound exceptions.""" 57 | try: 58 | self.admin_ports_client.delete_port(port_id) 59 | except exceptions.NotFound: 60 | pass 61 | 62 | def create_neutron_port(self, llc=None, port_name=None): 63 | port_name = port_name or CONF.ngs.port_name 64 | net_id = self.admin_networks_client.list_networks( 65 | name=CONF.ngs.network_name 66 | )['networks'][0]['id'] 67 | port = self.admin_ports_client.create_port( 68 | network_id=net_id, name=port_name)['port'] 69 | self.addCleanup(self.cleanup_port, port['id']) 70 | 71 | host = self.admin_agents_client.list_agents( 72 | agent_type='Open vSwitch agent' 73 | )['agents'][0]['host'] 74 | 75 | if llc is None: 76 | llc = [{'switch_info': CONF.ngs.bridge_name, 77 | 'switch_id': self.get_local_port_mac(CONF.ngs.bridge_name), 78 | 'port_id': port_name}] 79 | 80 | update_args = { 81 | 'device_owner': 'baremetal:none', 82 | 'device_id': 'fake-instance-uuid', 83 | 'admin_state_up': True, 84 | 'binding:vnic_type': 'baremetal', 85 | 'binding:host_id': host, 86 | 'binding:profile': { 87 | 'local_link_information': llc 88 | } 89 | } 90 | self.admin_ports_client.update_port( 91 | port['id'], 92 | **update_args 93 | ) 94 | 95 | return port 96 | 97 | def ovs_get_tag(self, port_name=None): 98 | port_name = port_name or CONF.ngs.port_name 99 | try: 100 | tag = int(ovs_lib.get_port_tag_dict(port_name)) 101 | except (ValueError, TypeError): 102 | tag = None 103 | return tag 104 | 105 | def _test_ngs_basic_ops(self, llc=None, port_name=None): 106 | port = self.create_neutron_port(llc=llc, port_name=port_name) 107 | net_tag = (self.admin_networks_client.list_networks( 108 | name=CONF.ngs.network_name) 109 | ['networks'][0]['provider:segmentation_id']) 110 | ovs_tag = self.ovs_get_tag(port_name=port_name) 111 | self.assertEqual(net_tag, ovs_tag) 112 | 113 | # Ensure that tag is removed when port is deleted 114 | self.admin_ports_client.delete_port(port['id']) 115 | ovs_tag = self.ovs_get_tag(port_name=port_name) 116 | self.assertIsNone(ovs_tag) 117 | 118 | 119 | class NGSBasicOps(NGSBasicOpsBase): 120 | @decorators.idempotent_id('59cb81a5-3fd5-4ad3-8c4a-c0b27435cb9c') 121 | @utils.services('network') 122 | def test_ngs_basic_ops(self): 123 | self._test_ngs_basic_ops() 124 | 125 | @decorators.idempotent_id('282a513d-cc01-486c-aa12-1c45f7b6e5a8') 126 | @utils.services('network') 127 | def test_ngs_basic_ops_switch_id(self): 128 | llc = [{'switch_id': self.get_local_port_mac(CONF.ngs.bridge_name), 129 | 'port_id': CONF.ngs.port_name}] 130 | self._test_ngs_basic_ops(llc=llc) 131 | 132 | 133 | class NGSBasicDLMOps(NGSBasicOpsBase): 134 | 135 | @classmethod 136 | def skip_checks(cls): 137 | super(NGSBasicDLMOps, cls).skip_checks() 138 | if not CONF.ngs.port_dlm_concurrency: 139 | raise cls.skipException("DLM is not configured for n-g-s") 140 | 141 | def test_ngs_basic_dlm_ops(self): 142 | pool = futurist.ThreadPoolExecutor() 143 | self.addCleanup(pool.shutdown) 144 | fts = [] 145 | for i in range(CONF.ngs.port_dlm_concurrency): 146 | fts.append( 147 | pool.submit( 148 | self._test_ngs_basic_ops, 149 | port_name='{base}_{ind}'.format( 150 | base=CONF.ngs.port_name, ind=i))) 151 | 152 | executed = futurist.waiters.wait_for_all(fts) 153 | self.assertFalse(executed.not_done) 154 | # TODO(pas-ha) improve test error reporting here 155 | for ft in executed.done: 156 | self.assertIsNone(ft.exception()) 157 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # Unit tests requirements 2 | coverage>=4.0 # Apache-2.0 3 | fixtures>=3.0.0 # Apache-2.0/BSD 4 | stestr>=2.0.0 # Apache-2.0 5 | 6 | # Tempest plugin requirements 7 | futurist>=1.2.0 # Apache-2.0 8 | -------------------------------------------------------------------------------- /tools/flake8wrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # A simple wrapper around flake8 which makes it possible 4 | # to ask it to only verify files changed in the current 5 | # git HEAD patch. 6 | # 7 | # Intended to be invoked via tox: 8 | # 9 | # tox -epep8 -- -HEAD 10 | # 11 | 12 | if test "x$1" = "x-HEAD" ; then 13 | shift 14 | files=$(git diff --name-only HEAD~1 | tr '\n' ' ') 15 | echo "Running flake8 on ${files}" 16 | diff -u --from-file /dev/null ${files} | flake8 --diff "$@" 17 | else 18 | echo "Running flake8 on all files" 19 | exec flake8 "$@" 20 | fi 21 | -------------------------------------------------------------------------------- /tools/ngs-stress/README.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | Networking-generic-switch Stress Test 3 | ===================================== 4 | 5 | Stress test for the OpenStack Neutron networking-generic-switch (genericswitch) 6 | ML2 mechanism driver. 7 | 8 | This script can stress a switch using the genericswitch driver. It does not 9 | require an OpenStack or Neutron installation, and can operate in isolation. 10 | There are two modes of operation: 11 | 12 | network 13 | Create and delete a number of networks in parallel. 14 | port 15 | Create and delete a number of ports in parallel. 16 | 17 | It is possible to use an existing genericswitch configuration file containing 18 | switch configuration. 19 | 20 | Installation 21 | ============ 22 | 23 | To install dependencies in a virtualenv:: 24 | 25 | python3 -m venv venv 26 | source venv/bin/activate 27 | pip install -U pip 28 | pip install -c https://releases.openstack.org/constraints/upper/ networking-generic-switch 29 | 30 | If you want to use etcd for coordination, install the ``etcd3gw`` package:: 31 | 32 | pip install -c https://releases.openstack.org/constraints/upper/ etcd3gw 33 | 34 | The Bitnami ``Etcd`` container can be used in a standalone mode:: 35 | 36 | docker run --detach -it -e ALLOW_NONE_AUTHENTICATION=yes --name Etcd --net=host bitnami/etcd 37 | 38 | Configuration 39 | ============= 40 | 41 | A configuration file is required to provide details of switch devices, as well 42 | as any other NGS config options necessary. For example, to use the Fake device 43 | driver for testing: 44 | 45 | .. code-block:: ini 46 | 47 | [genericswitch:fake] 48 | device_type = netmiko_fake 49 | 50 | Other drivers will typically require further configuration. 51 | 52 | If you want to use etcd for coordination, add the following: 53 | 54 | .. code-block:: ini 55 | 56 | [ngs_coordination] 57 | backend_url = etcd3+http://localhost:2379?api_version=v3 58 | 59 | Usage 60 | ===== 61 | 62 | To run the stress test in network mode:: 63 | 64 | venv/bin/python /path/to/ngs/tools/ngs-stress/ngs_stress.py \ 65 | --config-file /path/to/ngs-stress.conf \ 66 | --mode network \ 67 | --switch \ 68 | --vlan-range : 69 | 70 | To run the stress test in port mode:: 71 | 72 | venv/bin/python /path/to/ngs/tools/ngs-stress/ngs_stress.py \ 73 | --config-file /path/to/ngs-stress.conf \ 74 | --mode port \ 75 | --switch \ 76 | --vlan-range : \ 77 | --ports 78 | 79 | Other arguments are available, see ``--help``. 80 | -------------------------------------------------------------------------------- /tools/ngs-stress/ngs_stress.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 StackHPC Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import contextlib 16 | import queue 17 | import sys 18 | import threading 19 | import uuid 20 | 21 | from neutron_lib.utils import net 22 | from oslo_config import cfg 23 | import oslo_log.log as logging 24 | 25 | import networking_generic_switch.generic_switch_mech as generic_switch 26 | 27 | CONF = cfg.CONF 28 | LOG = logging.getLogger(__name__) 29 | 30 | 31 | OPTS = [ 32 | cfg.HostAddressOpt('host', default=net.get_hostname(), 33 | help="Hostname to be used by NGS"), 34 | cfg.StrOpt('mode', choices=['network', 'port'], required=True, 35 | help='Mode of operation / resource type to exercise'), 36 | cfg.StrOpt('ports', 37 | help='Comma-separated list of ports to create in port mode.'), 38 | cfg.StrOpt('switch', required=True, 39 | help='Name of switch to connect to'), 40 | cfg.StrOpt('vlan-range', required=True, 41 | help='Colon-separated range of vlan IDs to create in network ' 42 | 'mode. In port mode the first will be used'), 43 | cfg.BoolOpt('create-net', default=True, 44 | help='Whether to create and delete a network in port mode'), 45 | cfg.StrOpt('net-id', required=False, 46 | help='Network UUID when create-net is false in port mode'), 47 | cfg.IntOpt('iterations', default=1, 48 | help='Number of test iterations'), 49 | ] 50 | 51 | 52 | class ErrorQueueingThread(threading.Thread): 53 | """Thread subclass which pushes a raised exception onto a queue.""" 54 | 55 | def __init__(self, target=None, eq=None, *args, **kwargs): 56 | def new_target(*t_args, **t_kwargs): 57 | with self.exceptions_queued(eq): 58 | return target(*t_args, **t_kwargs) 59 | super(ErrorQueueingThread, self).__init__(*args, target=new_target, 60 | **kwargs) 61 | 62 | @contextlib.contextmanager 63 | def exceptions_queued(self, eq): 64 | try: 65 | yield 66 | except Exception: 67 | eq.put(sys.exc_info()) 68 | raise 69 | 70 | 71 | def _log_excs_and_reraise(eq, reraise=True): 72 | """Log all exceptions in a Queue and reraise one.""" 73 | while not eq.empty(): 74 | e = eq.get() 75 | LOG.error("Exception seen during test", exc_info=e) 76 | if reraise and eq.empty(): 77 | raise e[0] 78 | 79 | 80 | def _run_threads(ts): 81 | """Start a list of threads then wait for them to complete.""" 82 | for t in ts: 83 | t.start() 84 | for t in ts: 85 | t.join() 86 | 87 | 88 | def _gen_net_id(): 89 | """Dell Force10 switches can't handle net names beginning with a letter.""" 90 | while True: 91 | net_id = str(uuid.uuid4()) 92 | try: 93 | int(net_id[0]) 94 | except ValueError: 95 | return net_id 96 | 97 | 98 | def _create_net(switch, vlan, net_id): 99 | LOG.info("Creating VLAN %d", vlan) 100 | switch.add_network(vlan, net_id) 101 | 102 | 103 | def _delete_net(switch, vlan, net_id): 104 | LOG.info("Deleting VLAN %d", vlan) 105 | switch.del_network(vlan, net_id) 106 | 107 | 108 | def _create_delete_net(switch, vlan, net_id): 109 | """Create and delete a VLAN.""" 110 | _create_net(switch, vlan, net_id) 111 | _delete_net(switch, vlan, net_id) 112 | 113 | 114 | def _create_delete_nets(switch, vlans): 115 | """Create and delete VLANs in parallel.""" 116 | ts = [] 117 | eq = queue.Queue() 118 | for vlan in vlans: 119 | args = (switch, vlan, _gen_net_id()) 120 | t = ErrorQueueingThread(target=_create_delete_net, args=args, 121 | name='vlan-%d' % vlan, eq=eq) 122 | ts.append(t) 123 | _run_threads(ts) 124 | _log_excs_and_reraise(eq) 125 | 126 | 127 | def _add_remove_port(switch, port_id, vlan): 128 | """Add and remove a port to/from a VLAN.""" 129 | LOG.info("Adding port %s to VLAN %d", port_id, vlan) 130 | switch.plug_port_to_network(port_id, vlan) 131 | LOG.info("Removing port %s from VLAN %d", port_id, vlan) 132 | switch.delete_port(port_id, vlan) 133 | 134 | 135 | def _add_remove_ports(switch, ports, vlan): 136 | """Add and remove ports to/from a VLAN in parallel.""" 137 | ts = [] 138 | eq = queue.Queue() 139 | if CONF.create_net: 140 | net_id = _gen_net_id() 141 | _create_net(switch, vlan, net_id) 142 | else: 143 | net_id = CONF.net_id 144 | for port_id in ports: 145 | args = (switch, port_id, vlan) 146 | t = ErrorQueueingThread(target=_add_remove_port, args=args, 147 | name='port-%s' % port_id, eq=eq) 148 | ts.append(t) 149 | _run_threads(ts) 150 | if CONF.create_net: 151 | _delete_net(switch, vlan, net_id) 152 | _log_excs_and_reraise(eq, reraise=False) 153 | 154 | 155 | def _init(): 156 | logging.register_options(CONF) 157 | CONF.register_cli_opts(OPTS) 158 | CONF(sys.argv[1:]) 159 | logging.setup(CONF, 'ngs_stress') 160 | LOG.info("Starting NGS stress test") 161 | 162 | 163 | def main(): 164 | _init() 165 | gs = generic_switch.GenericSwitchDriver() 166 | gs.initialize() 167 | switch = gs.switches[CONF.switch] 168 | vlans = range(*map(int, CONF.vlan_range.split(':'))) 169 | if CONF.mode == 'network': 170 | for _ in range(CONF.iterations): 171 | _create_delete_nets(switch, vlans) 172 | else: 173 | vlan = vlans[0] 174 | ports = CONF.ports.split(',') 175 | for _ in range(CONF.iterations): 176 | _add_remove_ports(switch, ports, vlan) 177 | LOG.info("NGS stress test complete") 178 | 179 | 180 | if __name__ == "__main__": 181 | main() 182 | -------------------------------------------------------------------------------- /tools/run_bashate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | find "$@" -not \( -type d -name .?\* -prune \) \ 16 | -type f \ 17 | -not -name \*.swp \ 18 | -not -name \*~ \ 19 | -not -name \*.xml \ 20 | -not -name \*.template \ 21 | -not -name \*.py \ 22 | \( \ 23 | -name \*.sh -or \ 24 | -wholename \*/devstack/lib/\* -or \ 25 | -wholename \*/tools/\* \ 26 | \) \ 27 | -print0 | xargs -0 bashate -v -iE006 -eE005,E042 28 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 4.4.0 3 | envlist = py3,pep8 4 | ignore_basepython_conflict=true 5 | 6 | [testenv] 7 | usedevelop = True 8 | setenv = VIRTUAL_ENV={envdir} 9 | PYTHONDONTWRITEBYTECODE = 1 10 | PYTHONWARNINGS=default::DeprecationWarning 11 | LANGUAGE=en_US 12 | LC_ALL=en_US.UTF-8 13 | TESTS_DIR=./networking_generic_switch/tests/unit/ 14 | deps = 15 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 16 | -r{toxinidir}/requirements.txt 17 | -r{toxinidir}/test-requirements.txt 18 | passenv = 19 | http_proxy 20 | HTTP_PROXY 21 | https_proxy 22 | HTTPS_PROXY 23 | no_proxy 24 | NO_PROXY 25 | 26 | commands = 27 | stestr run {posargs} 28 | 29 | [testenv:pep8] 30 | deps = 31 | hacking~=6.1.0 # Apache-2.0 32 | flake8-import-order~=0.18.0# LGPLv3 33 | bashate~=2.1.0 # Apache-2.0 34 | pycodestyle>=2.0.0,<3.0.0 # MIT 35 | doc8~=1.1.0 # Apache-2.0 36 | allowlist_externals = bash 37 | {toxinidir}/tools/run_bashate.sh 38 | commands = 39 | bash tools/flake8wrap.sh {posargs} 40 | # Run bashate during pep8 runs to ensure violations are caught by 41 | # the check and gate queues. 42 | bash {toxinidir}/tools/run_bashate.sh {toxinidir}/devstack 43 | doc8 README.rst CONTRIBUTING.rst doc/source --ignore D001 44 | 45 | [testenv:docs] 46 | setenv = PYTHONHASHSEED=0 47 | sitepackages = False 48 | deps = 49 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 50 | -r{toxinidir}/requirements.txt 51 | -r{toxinidir}/doc/requirements.txt 52 | commands = 53 | sphinx-build -W -b html doc/source doc/build/html 54 | 55 | [testenv:pdf-docs] 56 | allowlist_externals = make 57 | setenv = PYTHONHASHSEED=0 58 | sitepackages = False 59 | deps = {[testenv:docs]deps} 60 | commands = 61 | sphinx-build -b latex doc/source doc/build/pdf 62 | make -C doc/build/pdf 63 | 64 | [testenv:releasenotes] 65 | deps = 66 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 67 | -r{toxinidir}/doc/requirements.txt 68 | commands = 69 | sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html 70 | 71 | [testenv:venv] 72 | setenv = PYTHONHASHSEED=0 73 | deps = 74 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 75 | -r{toxinidir}/test-requirements.txt 76 | -r{toxinidir}/doc/requirements.txt 77 | commands = {posargs} 78 | 79 | [flake8] 80 | exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build 81 | max-complexity=17 82 | # [W503] Line break occurred before a binary operator. Conflicts with W504. 83 | ignore = W503 84 | import-order-style = pep8 85 | application-import-names = networking_generic_switch 86 | # [H106] Don't put vim configuration in source files. 87 | # [H203] Use assertIs(Not)None to check for None. 88 | # [H210] Require 'autospec', 'spec', or 'spec_set' in mock.patch/mock.patch.object calls 89 | # [H904] Delay string interpolations at logging calls. 90 | enable-extensions=H106,H203,H210,H904 91 | 92 | [testenv:cover] 93 | commands = 94 | coverage erase 95 | coverage run --branch --include "networking_generic_switch*" -m unittest discover networking_generic_switch.tests.unit 96 | coverage report -m --fail-under 70 97 | 98 | [hacking] 99 | import_exceptions = networking_generic_switch._i18n 100 | 101 | [testenv:codespell] 102 | description = 103 | Run codespell to check spelling 104 | deps = codespell 105 | # note(JayF): {posargs} lets us run `tox -ecodespell -- -w` to get codespell 106 | # to correct spelling issues in our code it's aware of. 107 | commands = 108 | codespell {posargs} 109 | 110 | -------------------------------------------------------------------------------- /zuul.d/networking-generic-switch-jobs.yaml: -------------------------------------------------------------------------------- 1 | - job: 2 | name: networking-generic-switch-tempest-dlm 3 | parent: devstack-tempest 4 | irrelevant-files: 5 | - ^.*\.rst$ 6 | - ^doc/.*$ 7 | - ^networking_generic_switch/tests/.*$ 8 | - ^releasenotes/.*$ 9 | - ^setup.cfg$ 10 | - ^test-requirements.txt$ 11 | - ^tools/.*$ 12 | - ^tox.ini$ 13 | timeout: 4800 14 | required-projects: 15 | - openstack/networking-generic-switch 16 | vars: 17 | tox_envlist: py3 18 | tempest_test_regex: ngs 19 | devstack_plugins: 20 | networking-generic-switch: https://opendev.org/openstack/networking-generic-switch 21 | devstack_localrc: 22 | ENABLE_TENANT_VLANS: True 23 | GENERIC_SWITCH_USER_MAX_SESSIONS: 2 24 | OVS_PHYSICAL_BRIDGE: brbm 25 | PHYSICAL_NETWORK: mynetwork 26 | Q_AGENT: openvswitch 27 | Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch 28 | Q_ML2_TENANT_NETWORK_TYPE: vlan 29 | TENANT_VLAN_RANGE: 100:150 30 | devstack_services: 31 | c-api: False 32 | c-bak: False 33 | c-sch: False 34 | c-vol: False 35 | cinder: False 36 | etcd3: True 37 | g-api: False 38 | generic_switch: True 39 | glace: False 40 | key: True 41 | mysql: True 42 | n-api-meta: False 43 | n-api: False 44 | n-cond: False 45 | n-cpu: False 46 | n-novnc: False 47 | n-sch: False 48 | nova: False 49 | ovn-controller: False 50 | ovn-northd: False 51 | placement-api: False 52 | q-agt: True 53 | q-dhcp: True 54 | q-l3: True 55 | q-ovn-metadata-agent: False 56 | q-svc: True 57 | rabbit: True 58 | s-account: False 59 | s-container: False 60 | s-object: False 61 | s-proxy: False 62 | swift: False 63 | tempest: True 64 | 65 | - job: 66 | name: networking-generic-switch-tox-codespell 67 | parent: openstack-tox 68 | timeout: 7200 69 | vars: 70 | tox_envlist: codespell -------------------------------------------------------------------------------- /zuul.d/project.yaml: -------------------------------------------------------------------------------- 1 | - project: 2 | templates: 3 | - check-requirements 4 | - openstack-python3-jobs-neutron 5 | - publish-openstack-docs-pti 6 | - release-notes-jobs-python3 7 | check: 8 | jobs: 9 | - networking-generic-switch-tempest-dlm 10 | - ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode: 11 | irrelevant-files: 12 | - ^.*\.rst$ 13 | - ^doc/.*$ 14 | - ^networking_generic_switch/tests/.*$ 15 | - ^releasenotes/.*$ 16 | - ^(test-|)requirements.txt$ 17 | - ^tools/.*$ 18 | - ^tox.ini$ 19 | - ^setup.cfg$ 20 | - networking-generic-switch-tox-codespell: 21 | voting: false 22 | gate: 23 | jobs: 24 | - networking-generic-switch-tempest-dlm 25 | - ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode: 26 | irrelevant-files: 27 | - ^.*\.rst$ 28 | - ^doc/.*$ 29 | - ^networking_generic_switch/tests/.*$ 30 | - ^releasenotes/.*$ 31 | - ^(test-|)requirements.txt$ 32 | - ^tools/.*$ 33 | - ^tox.ini$ 34 | - ^setup.cfg$ 35 | --------------------------------------------------------------------------------