vmware-nsx/neutron/plugins/cisco/nexus/cisco_nexus_plugin_v2.py
Dane LeBlanc 02d36fd5c3 Improve unit test coverage for Cisco plugin nexus code
Closes-Bug: #1190622

This fix improves the unit test coverage for the Cisco Nexus plugin's
cisco_nexus_network_driver_v2.py and cisco_nexus_plugin_v2.py
source files from:
cisco_nexus_network_driver_v2: 72%
cisco_nexus_plugin_v2:         79%
To:
cisco_nexus_network_driver_v2: 99%
cisco_nexus_plugin_v2:         94%

Much of what the remaining "uncovered" code (coverage tool
reports as partially covered) can be attributed to the
coverage tool not being aware that execution does not
continue at the end of a save_and_reraise_exception()
context block (i.e. the exception will be reraised, but
the coverage tool isn't aware of this).

This fix and coverage results are dependent on the fix for #1246080.

Change-Id: Ie0e11fc0a12502739fb39bb4b30deb04dd31b7b0
2013-11-25 17:24:17 -05:00

348 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.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 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.
Not applicable to Nexus plugin. Defined here to satisfy abstract
method requirements.
"""
LOG.debug(_("NexusPlugin:delete_network() called")) # pragma no cover
def update_network(self, tenant_id, net_id, **kwargs):
"""Update the properties of a particular Virtual Network.
Not applicable to Nexus plugin. Defined here to satisfy abstract
method requirements.
"""
LOG.debug(_("NexusPlugin:update_network() called")) # pragma no cover
def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs):
"""Create port.
Not applicable to Nexus plugin. Defined here to satisfy abstract
method requirements.
"""
LOG.debug(_("NexusPlugin:create_port() called")) # pragma no cover
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 whether there are any remaining instances using this
# vlan on this Nexus port.
try:
nxos_db.get_port_vlan_switch_binding(row.port_id,
row.vlan_id,
row.switch_ip)
except cisco_exc.NexusPortBindingNotFound:
try:
if nexus_port and auto_untrunk:
# Untrunk the vlan from this Nexus interface
self._client.disable_vlan_on_trunk_int(
switch_ip, row.vlan_id, etype, nexus_port)
# Check whether there are any remaining instances
# using this vlan on the Nexus switch.
if auto_delete:
try:
nxos_db.get_nexusvlan_binding(row.vlan_id,
row.switch_ip)
except cisco_exc.NexusPortBindingNotFound:
# Delete this vlan from this switch
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.
Not applicable to Nexus plugin. Defined here to satisfy abstract
method requirements.
"""
LOG.debug(_("NexusPlugin:update_port() called")) # pragma no cover
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id,
**kwargs):
"""Plug interfaces.
Not applicable to Nexus plugin. Defined here to satisfy abstract
method requirements.
"""
LOG.debug(_("NexusPlugin:plug_interface() called")) # pragma no cover
def unplug_interface(self, tenant_id, net_id, port_id, **kwargs):
"""Unplug interface.
Not applicable to Nexus plugin. Defined here to satisfy abstract
method requirements.
"""
LOG.debug(_("NexusPlugin:unplug_interface() called")
) # pragma no cover