From 1c61066a473bf2ce470f92f7d29661c93ad291b9 Mon Sep 17 00:00:00 2001 From: Julia Kreger Date: Wed, 21 May 2025 08:47:43 -0700 Subject: [PATCH 1/2] ci: workaround neutron's move to uwsgi only When neutron changed to using uwsgi only, it automatically excluded configuration parameters needed to supply configuration to launch ML2 plugins in the standard style of devstack, because the pattern only allowed for auto-configuration which only loads two files, or from a files in a single directory pattern, or a list of files supplied by environment variable. Ideally, we'd be able to restart neutron as well. We can't due to when things happen upstream; the post-config phase is not always late enough since neutron does some configuration there, the extra phase is too late as Ironic will be setting up nodes there. For now, we're relying on a race condition that we reliably win. Change-Id: I5cfd1fc247043ede9b71252ee681ce2ca413ede6 Signed-off-by: Jay Faulkner --- devstack/plugin.sh | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 24b25e97..85bd6bbf 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -164,8 +164,13 @@ function configure_generic_switch { done fi fi + # NOTE(TheJulia): This is not respected presently with uwsgi launched + # neutron as it auto-identifies it's configuration files. neutron_server_config_add $GENERIC_SWITCH_INI_FILE + if [ -f /etc/neutron/neutron-api-uwsgi.ini ]; then + iniset -sudo /etc/neutron/neutron-api-uwsgi.ini uwsgi env OS_NEUTRON_CONFIG_FILES='/etc/neutron/neutron.conf;/etc/neutron/plugins/ml2/ml2_conf.ini;/etc/neutron/plugins/ml2/ml2_conf_genericswitch.ini' + fi } function add_generic_switch_to_ml2_config { @@ -241,6 +246,26 @@ function ngs_configure_tempest { fi } +function ngs_restart_neutron { + echo_summary "NGS doing required neutron restart. Stopping neutron." + # NOTE(JayF) In practice restarting OVN causes problems, I'm not sure why. + # This avoids the restart. + local existing_skip_stop_ovn + SKIP_STOP_OVN=True + # We are changing the base config, and need ot restart the neutron services + stop_neutron + # NOTE(JayF): Neutron services are initialized in a particular order, this appears to + # match that order as currently defined in stack.sh (2025-05-22). + # TODO(JayF): Introduce a function in upstream devstack that documents this order so + # ironic won't break anytime initialization steps are rearranged. + echo_summary "NGS starting neutron service" + start_neutron_service_and_check + echo_summary "NGS started neutron service, now launch neutron agents" + start_neutron + echo_summary "NGS required neutron restart completed." + SKIP_STOP_OVN=False +} + # check for service enabled if is_service_enabled generic_switch; then @@ -250,7 +275,7 @@ if is_service_enabled generic_switch; then install_generic_switch elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then - # Configure after the other layer 1 and 2 services have been configured + # Configure after the other layer 1 and 2 services have been started echo_summary "Configuring Generic_switch ML2" # Source ml2 plugin, set default config @@ -262,7 +287,22 @@ if is_service_enabled generic_switch; then Q_PLUGIN_CLASS="ml2" fi + # TODO(JayF): This currently relies on winning a race, as many of the + # files modified by this method are created during this + # phase. In practice it works, but moving forward we likely + # need a supported-by-devstack/neutron-upstream method to + # ensure this is done at the right moment. configure_generic_switch + + if is_service_enabled neutron; then + # TODO(JayF): Similarly, we'd like to restart neutron to ensure + # our config changes have taken effect; we can't do + # that reliably here because it may not be fully + # configured, and extra phase is too late. + echo_summary "Skipping ngs_restart_neutron" + #ngs_restart_neutron + fi + elif [[ "$1" == "stack" && "$2" == "test-config" ]]; then if is_service_enabled tempest; then echo_summary "Configuring Tempest NGS" From 1b58a37bc735893023002c4898caf8e8e1cbf9cc Mon Sep 17 00:00:00 2001 From: mumesan Date: Tue, 27 May 2025 14:20:28 +0100 Subject: [PATCH 2/2] Improve NGS documentation. Some minor nitpicks and changes Change-Id: I593a0c44b4ad3638875bc6959a3aea8feebdba0e --- doc/source/dev/dev-quickstart.rst | 121 ++++++++------- networking_generic_switch/devices/__init__.py | 8 +- .../devices/netmiko_devices/cumulus.py | 141 +++++++++--------- .../devices/netmiko_devices/smc.py | 4 +- .../devices/netmiko_devices/sonic.py | 54 +++---- 5 files changed, 173 insertions(+), 155 deletions(-) diff --git a/doc/source/dev/dev-quickstart.rst b/doc/source/dev/dev-quickstart.rst index 368a4f50..bdd4ef47 100644 --- a/doc/source/dev/dev-quickstart.rst +++ b/doc/source/dev/dev-quickstart.rst @@ -34,33 +34,36 @@ Switch to the stack user and clone DevStack:: git clone https://github.com/openstack-dev/devstack.git devstack Create devstack/local.conf with minimal settings required to enable -Networking-generic-switch. Here is and example of local.conf:: - - [[local|localrc]] - # Set credentials - ADMIN_PASSWORD=secrete - DATABASE_PASSWORD=secrete - RABBIT_PASSWORD=secrete - SERVICE_PASSWORD=secrete - SERVICE_TOKEN=secrete - - # Enable minimal required services - ENABLED_SERVICES="dstat,mysql,rabbit,key,q-svc,q-agt,q-dhcp" - - # Enable networking-generic-switch plugin - enable_plugin networking-generic-switch https://review.openstack.org/openstack/networking-generic-switch - - # Configure Neutron - OVS_PHYSICAL_BRIDGE=brbm - PHYSICAL_NETWORK=mynetwork - Q_PLUGIN=ml2 - ENABLE_TENANT_VLANS=True - Q_ML2_TENANT_NETWORK_TYPE=vlan - TENANT_VLAN_RANGE=100:150 - - # Configure logging - LOGFILE=$HOME/devstack.log - LOGDIR=$HOME/logs +Networking-generic-switch. Here is an example of local.conf: + + + .. code-block:: ini + + [[local|localrc]] + # Set credentials + ADMIN_PASSWORD=secrete + DATABASE_PASSWORD=secrete + RABBIT_PASSWORD=secrete + SERVICE_PASSWORD=secrete + SERVICE_TOKEN=secrete + + # Enable minimal required services + ENABLED_SERVICES="dstat,mysql,rabbit,key,q-svc,q-agt,q-dhcp" + + # Enable networking-generic-switch plugin + enable_plugin networking-generic-switch https://review.openstack.org/openstack/networking-generic-switch + + # Configure Neutron + OVS_PHYSICAL_BRIDGE=brbm + PHYSICAL_NETWORK=mynetwork + Q_PLUGIN=ml2 + ENABLE_TENANT_VLANS=True + Q_ML2_TENANT_NETWORK_TYPE=vlan + TENANT_VLAN_RANGE=100:150 + + # Configure logging + LOGFILE=$HOME/devstack.log + LOGDIR=$HOME/logs Run stack.sh:: @@ -86,45 +89,53 @@ Test with real hardware Add information about hardware switch to Networking-generic-switch config ``/etc/neutron/plugins/ml2/ml2_conf_genericswitch.ini`` and -restart Neutron server:: +restart Neutron server: + + .. code-block:: ini - [genericswitch:cisco_switch_1] - device_type = netmiko_cisco_ios - ip = 1.2.3.4 - username = cisco - password = cisco - secret = enable_password + [genericswitch:cisco_switch_1] + device_type = netmiko_cisco_ios + ip = 1.2.3.4 + username = cisco + password = cisco + secret = enable_password Get current configuration of the port on the switch, for example for -Cisco IOS device:: +Cisco IOS device: + + .. code-block:: ini - sh running-config int gig 0/12 - Building configuration... + sh running-config int gig 0/12 + Building configuration... - Current configuration : 283 bytes - ! - interface GigabitEthernet0/12 - switchport mode access - end + Current configuration : 283 bytes + ! + interface GigabitEthernet0/12 + switchport mode access + end Run exercise.py to create/update Neutron port. It will print VLAN id to be -assigned:: +assigned: - $ neutron net-create test - $ 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 - 126 + .. code-block:: ini + + $ neutron net-create test + $ 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 + 126 Verify that VLAN has been changed on the switch port, for example for -Cisco IOS device:: +Cisco IOS device: + + .. code-block:: ini - sh running-config int gig 0/12 - Building configuration... + sh running-config int gig 0/12 + Building configuration... - Current configuration : 311 bytes - ! - interface GigabitEthernet0/12 - switchport access vlan 126 - switchport mode access - end + Current configuration : 311 bytes + ! + interface GigabitEthernet0/12 + switchport access vlan 126 + switchport mode access + end diff --git a/networking_generic_switch/devices/__init__.py b/networking_generic_switch/devices/__init__.py index 0b6f2cfb..68209cdf 100644 --- a/networking_generic_switch/devices/__init__.py +++ b/networking_generic_switch/devices/__init__.py @@ -235,7 +235,7 @@ def plug_port_to_network(self, port_id, segmentation_id, trunk_details=None): """Plug port into network. - :param port_id: Then name of the switch interface + :param port_id: The name of the switch interface :param segmentation_id: VLAN identifier of the network used as access or native VLAN for port. @@ -247,7 +247,7 @@ def plug_port_to_network(self, port_id, segmentation_id, def delete_port(self, port_id, segmentation_id, trunk_details=None): """Delete port from specific network. - :param port_id: Then name of the switch interface + :param port_id: The name of the switch interface :param segmentation_id: VLAN identifier of the network used as access or native VLAN for port. @@ -259,7 +259,7 @@ def plug_bond_to_network(self, bond_id, segmentation_id, trunk_details=None): """Plug bond port into network. - :param port_id: Then name of the switch interface + :param port_id: The name of the switch interface :param segmentation_id: VLAN identifier of the network used as access or native VLAN for port. @@ -275,7 +275,7 @@ def unplug_bond_from_network(self, bond_id, segmentation_id, trunk_details=None): """Unplug bond port from network. - :param port_id: Then name of the switch interface + :param port_id: The name of the switch interface :param segmentation_id: VLAN identifier of the network used as access or native VLAN for port. diff --git a/networking_generic_switch/devices/netmiko_devices/cumulus.py b/networking_generic_switch/devices/netmiko_devices/cumulus.py index ce965d2a..895d02df 100644 --- a/networking_generic_switch/devices/netmiko_devices/cumulus.py +++ b/networking_generic_switch/devices/netmiko_devices/cumulus.py @@ -22,71 +22,73 @@ class Cumulus(netmiko_devices.NetmikoSwitch): Note for this switch you want config like this, where secret is the password needed for sudo su: - [genericswitch:] - device_type = netmiko_cumulus - ip = - username = - password = - secret = - ngs_physical_networks = physnet1 - ngs_max_connections = 1 - ngs_port_default_vlan = 123 - ngs_disable_inactive_ports = False + .. code-block:: ini + + [genericswitch:] + device_type = netmiko_cumulus + ip = + username = + password = + secret = + ngs_physical_networks = physnet1 + ngs_max_connections = 1 + ngs_port_default_vlan = 123 + ngs_disable_inactive_ports = False """ NETMIKO_DEVICE_TYPE = "linux" - ADD_NETWORK = [ + ADD_NETWORK = ( 'net add vlan {segmentation_id}', - ] + ) - DELETE_NETWORK = [ + DELETE_NETWORK = ( 'net del vlan {segmentation_id}', - ] + ) - PLUG_PORT_TO_NETWORK = [ + PLUG_PORT_TO_NETWORK = ( 'net add interface {port} bridge access {segmentation_id}', - ] + ) - DELETE_PORT = [ + DELETE_PORT = ( 'net del interface {port} bridge access {segmentation_id}', - ] + ) - PLUG_BOND_TO_NETWORK = [ + PLUG_BOND_TO_NETWORK = ( 'net add bond {bond} bridge access {segmentation_id}', - ] + ) - UNPLUG_BOND_FROM_NETWORK = [ + UNPLUG_BOND_FROM_NETWORK = ( 'net del bond {bond} bridge access {segmentation_id}', - ] + ) - ENABLE_PORT = [ + ENABLE_PORT = ( 'net del interface {port} link down', - ] + ) - DISABLE_PORT = [ + DISABLE_PORT = ( 'net add interface {port} link down', - ] + ) - ENABLE_BOND = [ + ENABLE_BOND = ( 'net del bond {bond} link down', - ] + ) - DISABLE_BOND = [ + DISABLE_BOND = ( 'net add bond {bond} link down', - ] + ) - SAVE_CONFIGURATION = [ + SAVE_CONFIGURATION = ( 'net commit', - ] + ) - ERROR_MSG_PATTERNS = [ + ERROR_MSG_PATTERNS = ( # Its tempting to add this error message, but as only one # bridge-access is allowed, we ignore that error for now: # re.compile(r'configuration does not have "bridge-access') re.compile(r'ERROR: Command not found.'), re.compile(r'command not found'), re.compile(r'is not a physical interface on this switch'), - ] + ) class CumulusNVUE(netmiko_devices.NetmikoSwitch): @@ -95,44 +97,47 @@ class CumulusNVUE(netmiko_devices.NetmikoSwitch): Note for this switch you want config like this, where secret is the password needed for sudo su: - [genericswitch:] - device_type = netmiko_cumulus_nvue - ip = - username = - password = - secret = - ngs_physical_networks = physnet1 - ngs_max_connections = 1 - ngs_port_default_vlan = 123 - ngs_disable_inactive_ports = False + .. code-block:: ini + + [genericswitch:] + device_type = netmiko_cumulus_nvue + ip = + username = + password = + secret = + ngs_physical_networks = physnet1 + ngs_max_connections = 1 + ngs_port_default_vlan = 123 + ngs_disable_inactive_ports = False + """ NETMIKO_DEVICE_TYPE = "linux" - ADD_NETWORK = [ + ADD_NETWORK = ( 'nv set bridge domain br_default vlan {segmentation_id}', - ] + ) - DELETE_NETWORK = [ + DELETE_NETWORK = ( 'nv unset bridge domain br_default vlan {segmentation_id}', - ] + ) - PLUG_PORT_TO_NETWORK = [ + PLUG_PORT_TO_NETWORK = ( 'nv unset interface {port} bridge domain br_default untagged', 'nv set interface {port} bridge domain br_default access ' '{segmentation_id}', - ] + ) - ADD_NETWORK_TO_TRUNK = [ + ADD_NETWORK_TO_TRUNK = ( 'nv unset interface {port} bridge domain br_default access', 'nv set interface {port} bridge domain br_default vlan ' '{segmentation_id}', - ] + ) - ADD_NETWORK_TO_BOND_TRUNK = [ + ADD_NETWORK_TO_BOND_TRUNK = ( 'nv unset interface {bond} bridge domain br_default access', 'nv set interface {bond} bridge domain br_default vlan ' '{segmentation_id}', - ] + ) REMOVE_NETWORK_FROM_TRUNK = ( 'nv unset interface {port} bridge domain br_default vlan ' @@ -144,21 +149,21 @@ class CumulusNVUE(netmiko_devices.NetmikoSwitch): '{segmentation_id}', ) - SET_NATIVE_VLAN = [ + SET_NATIVE_VLAN = ( 'nv unset interface {port} bridge domain br_default access', 'nv set interface {port} bridge domain br_default untagged ' '{segmentation_id}', 'nv set interface {port} bridge domain br_default vlan ' '{segmentation_id}', - ] + ) - SET_NATIVE_VLAN_BOND = [ + SET_NATIVE_VLAN_BOND = ( 'nv unset interface {bond} bridge domain br_default access', 'nv set interface {bond} bridge domain br_default untagged ' '{segmentation_id}', 'nv set interface {bond} bridge domain br_default vlan ' '{segmentation_id}', - ] + ) DELETE_NATIVE_VLAN = ( 'nv unset interface {port} bridge domain br_default untagged ' @@ -174,25 +179,25 @@ class CumulusNVUE(netmiko_devices.NetmikoSwitch): '{segmentation_id}', ) - DELETE_PORT = [ + DELETE_PORT = ( 'nv unset interface {port} bridge domain br_default access', 'nv unset interface {port} bridge domain br_default untagged', 'nv unset interface {port} bridge domain br_default vlan', - ] + ) - ENABLE_PORT = [ + ENABLE_PORT = ( 'nv set interface {port} link state up', - ] + ) - DISABLE_PORT = [ + DISABLE_PORT = ( 'nv set interface {port} link state down', - ] + ) - SAVE_CONFIGURATION = [ + SAVE_CONFIGURATION = ( 'nv config save', - ] + ) - ERROR_MSG_PATTERNS = [ + ERROR_MSG_PATTERNS = ( # Its tempting to add this error message, but as only one # bridge-access is allowed, we ignore that error for now: # re.compile(r'configuration does not have "bridge-access') @@ -204,7 +209,7 @@ class CumulusNVUE(netmiko_devices.NetmikoSwitch): re.compile(r'Error: Invalid parameter'), re.compile(r'Unable to restart services'), re.compile(r'Failure during apply'), - ] + ) def send_config_set(self, net_connect, cmd_set): """Send a set of configuration lines to the device. diff --git a/networking_generic_switch/devices/netmiko_devices/smc.py b/networking_generic_switch/devices/netmiko_devices/smc.py index 12365082..e0174a7d 100644 --- a/networking_generic_switch/devices/netmiko_devices/smc.py +++ b/networking_generic_switch/devices/netmiko_devices/smc.py @@ -16,8 +16,8 @@ class SupermicroSmis(netmiko_devices.NetmikoSwitch): - """A class to represent a Supermicro SMIS switch""" - """ + """A class to represent a Supermicro SMIS switch + Inherits from: -------------- netmiko_devices.NetmikoSwitch diff --git a/networking_generic_switch/devices/netmiko_devices/sonic.py b/networking_generic_switch/devices/netmiko_devices/sonic.py index 7a78c80a..96f02565 100644 --- a/networking_generic_switch/devices/netmiko_devices/sonic.py +++ b/networking_generic_switch/devices/netmiko_devices/sonic.py @@ -22,53 +22,55 @@ class Sonic(netmiko_devices.NetmikoSwitch): Note for this switch you want config like this, where secret is the password needed for sudo su: - [genericswitch:] - device_type = netmiko_sonic - ip = - username = - password = - secret = - ngs_physical_networks = physnet1 - ngs_max_connections = 1 - ngs_port_default_vlan = 123 - ngs_disable_inactive_ports = False + .. code-block:: ini + + [genericswitch:] + device_type = netmiko_sonic + ip = + username = + password = + secret = + ngs_physical_networks = physnet1 + ngs_max_connections = 1 + ngs_port_default_vlan = 123 + ngs_disable_inactive_ports = False """ NETMIKO_DEVICE_TYPE = "linux" - ADD_NETWORK = [ + ADD_NETWORK = ( 'config vlan add {segmentation_id}', - ] + ) - DELETE_NETWORK = [ + DELETE_NETWORK = ( 'config vlan del {segmentation_id}', - ] + ) - PLUG_PORT_TO_NETWORK = [ + PLUG_PORT_TO_NETWORK = ( 'config vlan member add -u {segmentation_id} {port}', - ] + ) - DELETE_PORT = [ + DELETE_PORT = ( 'config vlan member del {segmentation_id} {port}', - ] + ) - ADD_NETWORK_TO_TRUNK = [ + ADD_NETWORK_TO_TRUNK = ( 'config vlan member add {segmentation_id} {port}', - ] + ) - REMOVE_NETWORK_FROM_TRUNK = [ + REMOVE_NETWORK_FROM_TRUNK = ( 'config vlan member del {segmentation_id} {port}', - ] + ) - SAVE_CONFIGURATION = [ + SAVE_CONFIGURATION = ( 'config save -y', - ] + ) - ERROR_MSG_PATTERNS = [ + ERROR_MSG_PATTERNS = ( re.compile(r'VLAN[0-9]+ doesn\'t exist'), re.compile(r'Invalid Vlan Id , Valid Range : 1 to 4094'), re.compile(r'Interface name is invalid!!'), re.compile(r'No such command'), - ] + ) def send_config_set(self, net_connect, cmd_set): """Send a set of configuration lines to the device.