vmware-nsx/neutron/plugins/cisco/nexus/cisco_nexus_plugin_v2.py
Dane LeBlanc 041703c004 Round-robin SVI switch selection fails on Cisco Nexus plugin
Fixes bug 1250969

This fix addresses improper behavior with the Cisco Nexus plugin's
selection of Nexus switch on which to place a Switch Virtual Interface
(SVI) when round-robin switch placement is enabled.

The expected behavior when round-robin SVI switch selection is configured
via the cisco_plugins.ini file, i.e.:
    [cisco]
    nexus_l3_enable = True
    svi_round_robin = True
is that when a virtual router interface is created, the Nexus plugin should
select the Nexus switch with the least number of SVI interfaces configured
for creating the new SVI. The current selection is based on the first
entry in a dictionary, and is therefore indeterminate.

Similarly, this fix also addresses incorrect behavior when round-robin
selection is disabled. In this case, the desired behavior is that
the plugin should select the first switch which appears in the Nexus
switch configuration. Instead, the current selection is also based on the
first entry in a dictionary, and is likewise indeterminate.

Change-Id: I548c1efaa8b54695246251b6823ab59e077836fe
2013-11-14 12:19:54 -05:00

366 lines
15 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Cisco Systems, Inc.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Sumit Naiksatam, Cisco Systems, Inc.
# @author: Edgar Magana, Cisco Systems, Inc.
# @author: Arvind Somya, Cisco Systems, Inc. (asomya@cisco.com)
#
"""
PlugIn for Nexus OS driver
"""
import logging
from neutron.common import exceptions as exc
from neutron.openstack.common import excutils
from neutron.openstack.common import importutils
from neutron.plugins.cisco.common import cisco_constants as const
from neutron.plugins.cisco.common import cisco_exceptions as cisco_exc
from neutron.plugins.cisco.common import config as conf
from neutron.plugins.cisco.db import network_db_v2 as cdb
from neutron.plugins.cisco.db import nexus_db_v2 as nxos_db
from neutron.plugins.cisco.l2device_plugin_base import L2DevicePluginBase
LOG = logging.getLogger(__name__)
class NexusPlugin(L2DevicePluginBase):
"""Nexus PlugIn Main Class."""
_networks = {}
def __init__(self):
"""Extract configuration parameters from the configuration file."""
self._client = importutils.import_object(conf.CISCO.nexus_driver)
LOG.debug(_("Loaded driver %s"), conf.CISCO.nexus_driver)
self._nexus_switches = conf.get_device_dictionary()
def get_all_networks(self, tenant_id):
"""Get all networks.
Returns a dictionary containing all <network_uuid, network_name> for
the specified tenant.
"""
LOG.debug(_("NexusPlugin:get_all_networks() called"))
return self._networks.values()
def create_network(self, network, attachment):
"""Create or update a network when an attachment is changed.
This method is not invoked at the usual plugin create_network() time.
Instead, it is invoked on create/update port.
:param network: Network on which the port operation is happening
:param attachment: Details about the owner of the port
Create a VLAN in the appropriate switch/port, and configure the
appropriate interfaces for this VLAN.
"""
LOG.debug(_("NexusPlugin:create_network() called"))
# Grab the switch IPs and ports for this host
host_connections = []
host = attachment['host_name']
for switch_type, switch_ip, attr in self._nexus_switches:
if str(attr) == str(host):
port = self._nexus_switches[switch_type, switch_ip, attr]
# Get ether type for port, assume an ethernet type
# if none specified.
if ':' in port:
etype, port_id = port.split(':')
else:
etype, port_id = 'ethernet', port
host_connections.append((switch_ip, etype, port_id))
if not host_connections:
raise cisco_exc.NexusComputeHostNotConfigured(host=host)
vlan_id = network[const.NET_VLAN_ID]
vlan_name = network[const.NET_VLAN_NAME]
auto_create = True
auto_trunk = True
if cdb.is_provider_vlan(vlan_id):
vlan_name = ''.join([conf.CISCO.provider_vlan_name_prefix,
str(vlan_id)])
auto_create = conf.CISCO.provider_vlan_auto_create
auto_trunk = conf.CISCO.provider_vlan_auto_trunk
# Check if this network is already in the DB
for switch_ip, etype, port_id in host_connections:
vlan_created = False
vlan_trunked = False
eport_id = '%s:%s' % (etype, port_id)
# Check for switch vlan bindings
try:
# This vlan has already been created on this switch
# via another operation, like SVI bindings.
nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
vlan_created = True
auto_create = False
except cisco_exc.NexusPortBindingNotFound:
# No changes, proceed as normal
pass
try:
nxos_db.get_port_vlan_switch_binding(eport_id, vlan_id,
switch_ip)
except cisco_exc.NexusPortBindingNotFound:
if auto_create and auto_trunk:
# Create vlan and trunk vlan on the port
LOG.debug(_("Nexus: create & trunk vlan %s"), vlan_name)
self._client.create_and_trunk_vlan(
switch_ip, vlan_id, vlan_name, etype, port_id)
vlan_created = True
vlan_trunked = True
elif auto_create:
# Create vlan but do not trunk it on the port
LOG.debug(_("Nexus: create vlan %s"), vlan_name)
self._client.create_vlan(switch_ip, vlan_id, vlan_name)
vlan_created = True
elif auto_trunk:
# Only trunk vlan on the port
LOG.debug(_("Nexus: trunk vlan %s"), vlan_name)
self._client.enable_vlan_on_trunk_int(
switch_ip, vlan_id, etype, port_id)
vlan_trunked = True
try:
instance = attachment[const.INSTANCE_ID]
nxos_db.add_nexusport_binding(eport_id, str(vlan_id),
switch_ip, instance)
except Exception:
with excutils.save_and_reraise_exception():
# Add binding failed, roll back any vlan creation/enabling
if vlan_created and vlan_trunked:
LOG.debug(_("Nexus: delete & untrunk vlan %s"),
vlan_name)
self._client.delete_and_untrunk_vlan(switch_ip,
vlan_id,
etype, port_id)
elif vlan_created:
LOG.debug(_("Nexus: delete vlan %s"), vlan_name)
self._client.delete_vlan(switch_ip, vlan_id)
elif vlan_trunked:
LOG.debug(_("Nexus: untrunk vlan %s"), vlan_name)
self._client.disable_vlan_on_trunk_int(switch_ip,
vlan_id,
etype,
port_id)
net_id = network[const.NET_ID]
new_net_dict = {const.NET_ID: net_id,
const.NET_NAME: network[const.NET_NAME],
const.NET_PORTS: {},
const.NET_VLAN_NAME: vlan_name,
const.NET_VLAN_ID: vlan_id}
self._networks[net_id] = new_net_dict
return new_net_dict
def add_router_interface(self, vlan_name, vlan_id, subnet_id,
gateway_ip, router_id):
"""Create VLAN SVI on the Nexus switch."""
# Find a switch to create the SVI on
switch_ip = self._find_switch_for_svi()
if not switch_ip:
raise cisco_exc.NoNexusSviSwitch()
# Check if this vlan exists on the switch already
try:
nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
except cisco_exc.NexusPortBindingNotFound:
# Create vlan and trunk vlan on the port
self._client.create_and_trunk_vlan(
switch_ip, vlan_id, vlan_name, etype=None, nexus_port=None)
# Check if a router interface has already been created
try:
nxos_db.get_nexusvm_bindings(vlan_id, router_id)
raise cisco_exc.SubnetInterfacePresent(subnet_id=subnet_id,
router_id=router_id)
except cisco_exc.NexusPortBindingNotFound:
self._client.create_vlan_svi(switch_ip, vlan_id, gateway_ip)
nxos_db.add_nexusport_binding('router', str(vlan_id),
switch_ip, router_id)
return True
def remove_router_interface(self, vlan_id, router_id):
"""Remove VLAN SVI from the Nexus Switch."""
# Grab switch_ip from database
switch_ip = nxos_db.get_nexusvm_bindings(vlan_id,
router_id)[0].switch_ip
# Delete the SVI interface from the switch
self._client.delete_vlan_svi(switch_ip, vlan_id)
# Invoke delete_port to delete this row
# And delete vlan if required
return self.delete_port(router_id, vlan_id)
def _find_switch_for_svi(self):
"""Get a switch to create the SVI on."""
LOG.debug(_("Grabbing a switch to create SVI"))
nexus_switches = self._client.nexus_switches
if conf.CISCO.svi_round_robin:
LOG.debug(_("Using round robin to create SVI"))
switch_dict = dict(
(switch_ip, 0) for switch_ip, _ in nexus_switches)
try:
bindings = nxos_db.get_nexussvi_bindings()
# Build a switch dictionary with weights
for binding in bindings:
switch_ip = binding.switch_ip
if switch_ip not in switch_dict:
switch_dict[switch_ip] = 1
else:
switch_dict[switch_ip] += 1
# Search for the lowest value in the dict
if switch_dict:
switch_ip = min(switch_dict, key=switch_dict.get)
return switch_ip
except cisco_exc.NexusPortBindingNotFound:
pass
LOG.debug(_("No round robin or zero weights, using first switch"))
# Return the first switch in the config
return conf.first_device_ip
def delete_network(self, tenant_id, net_id, **kwargs):
"""Delete network.
Deletes the VLAN in all switches, and removes the VLAN configuration
from the relevant interfaces.
"""
LOG.debug(_("NexusPlugin:delete_network() called"))
def update_network(self, tenant_id, net_id, **kwargs):
"""Update the properties of a particular Virtual Network."""
LOG.debug(_("NexusPlugin:update_network() called"))
def get_all_ports(self, tenant_id, net_id, **kwargs):
"""Get all ports.
This is probably not applicable to the Nexus plugin.
Delete if not required.
"""
LOG.debug(_("NexusPlugin:get_all_ports() called"))
def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs):
"""Create port.
This is probably not applicable to the Nexus plugin.
Delete if not required.
"""
LOG.debug(_("NexusPlugin:create_port() called"))
def delete_port(self, device_id, vlan_id):
"""Delete port.
Delete port bindings from the database and scan whether the network
is still required on the interfaces trunked.
"""
LOG.debug(_("NexusPlugin:delete_port() called"))
# Delete DB row(s) for this port
try:
rows = nxos_db.get_nexusvm_bindings(vlan_id, device_id)
except cisco_exc.NexusPortBindingNotFound:
return
auto_delete = True
auto_untrunk = True
if cdb.is_provider_vlan(vlan_id):
auto_delete = conf.CISCO.provider_vlan_auto_create
auto_untrunk = conf.CISCO.provider_vlan_auto_trunk
LOG.debug(_("delete_network(): provider vlan %s"), vlan_id)
instance_id = False
for row in rows:
instance_id = row['instance_id']
switch_ip = row.switch_ip
etype, nexus_port = '', ''
if row['port_id'] == 'router':
etype, nexus_port = 'vlan', row['port_id']
auto_untrunk = False
else:
etype, nexus_port = row['port_id'].split(':')
nxos_db.remove_nexusport_binding(row.port_id, row.vlan_id,
row.switch_ip,
row.instance_id)
# Check for any other bindings with the same vlan_id and switch_ip
try:
nxos_db.get_nexusvlan_binding(row.vlan_id, row.switch_ip)
except cisco_exc.NexusPortBindingNotFound:
try:
# Delete this vlan from this switch
if nexus_port and auto_untrunk:
self._client.disable_vlan_on_trunk_int(
switch_ip, row.vlan_id, etype, nexus_port)
if auto_delete:
self._client.delete_vlan(switch_ip, row.vlan_id)
except Exception:
# The delete vlan operation on the Nexus failed,
# so this delete_port request has failed. For
# consistency, roll back the Nexus database to what
# it was before this request.
with excutils.save_and_reraise_exception():
nxos_db.add_nexusport_binding(row.port_id,
row.vlan_id,
row.switch_ip,
row.instance_id)
return instance_id
def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs):
"""Update port.
This is probably not applicable to the Nexus plugin.
Delete if not required.
"""
LOG.debug(_("NexusPlugin:update_port() called"))
def get_port_details(self, tenant_id, net_id, port_id, **kwargs):
"""Get port details.
This is probably not applicable to the Nexus plugin.
Delete if not required.
"""
LOG.debug(_("NexusPlugin:get_port_details() called"))
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id,
**kwargs):
"""Plug interfaces.
This is probably not applicable to the Nexus plugin.
Delete if not required.
"""
LOG.debug(_("NexusPlugin:plug_interface() called"))
def unplug_interface(self, tenant_id, net_id, port_id, **kwargs):
"""Unplug interface.
This is probably not applicable to the Nexus plugin.
Delete if not required.
"""
LOG.debug(_("NexusPlugin:unplug_interface() called"))
def _get_network(self, tenant_id, network_id, context, base_plugin_ref):
"""Get the Network ID."""
network = base_plugin_ref._get_network(context, network_id)
if not network:
raise exc.NetworkNotFound(net_id=network_id)
return {const.NET_ID: network_id, const.NET_NAME: network.name,
const.NET_PORTS: network.ports}