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" 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.