vmware-nsx/neutron/plugins/cisco/models/virt_phy_sw_v2.py
Dane LeBlanc d9bd9328b2 Improve unit test coverage for Cisco plugin base code
Closes-Bug: #1190621

This fix improves test code coverage for the file:
neutron/plugins/cisco/network_plugin.py
from 34% to 98%.

Some of the changes made to realize this code coverage improvements
are the following:
- Core API methods (create_network, update_network, etc.) have been
  removed from the core plugin (network_plugin.py). These methods
  are currently unused. Before this change, we relied on the underlying
  model layer to inform the core plugin whether or not the model layer
  was capable of handling these core API calls itself via a 'MANAGE_STATE'
  attribute. However, there is only one existing model layer
  implementation (i.e. for Nexus plugin; the N1KV plugin does not use
  network_plugin.py), and that model layer supports the core API calls.
  Given that it is unlikely that another model layer for the Cisco Nexus
  plugin will ever be developed (esp. with the availability of the ML2
  plugin), so it makes more sense to delete this untested code.
- Exception raising for non-existent credentials has been removed from
  get_credential_details and rename_credential methods since
  exceptions are already raised for this condition in lower-level
  credential database code.
- The schedule_host, associate_port, and detach_port methods are
  deleted because these essentially do nothing useful (log a debug
  message and then return None), since these methods look for
  the same-named methods in the model layer, but these methods are
  not defined in the model layer.
- The helper functions _invoke_device_plugins and _func_name are
  deleted because they are no longer used.
- In unit test code for the Cisco QoS and credentials database, calls
  are now made indirectly through the core plugin (network_plugin.py)
  extension API methods, as a quick-and-easy way to provide code
  coverage for these core plugin methods.

Change-Id: I0c0d9e2b251a7c0355d83e495912302c5cc4d032
2013-11-27 17:24:35 -05:00

542 lines
22 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: Rohit Agarwalla, Cisco Systems, Inc.
#
import inspect
import logging
import sys
from neutron.api.v2 import attributes
from neutron.db import api as db_api
from neutron.extensions import portbindings
from neutron.extensions import providernet as provider
from neutron import neutron_plugin_base_v2
from neutron.openstack.common import importutils
from neutron.plugins.cisco.common import cisco_constants as const
from neutron.plugins.cisco.common import cisco_credentials_v2 as cred
from neutron.plugins.cisco.common import cisco_exceptions as cexc
from neutron.plugins.cisco.common import config as conf
from neutron.plugins.cisco.db import network_db_v2 as cdb
from neutron.plugins.openvswitch import ovs_db_v2 as odb
LOG = logging.getLogger(__name__)
class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
"""Virtual Physical Switch Model.
This implementation works with OVS and Nexus plugin for the
following topology:
One or more servers to a nexus switch.
"""
__native_bulk_support = True
supported_extension_aliases = ["provider", "binding"]
_plugins = {}
_methods_to_delegate = ['create_network_bulk',
'get_network', 'get_networks',
'create_port_bulk',
'get_port', 'get_ports',
'create_subnet', 'create_subnet_bulk',
'delete_subnet', 'update_subnet',
'get_subnet', 'get_subnets',
'create_or_update_agent', 'report_state']
def __init__(self):
"""Initialize the segmentation manager.
Checks which device plugins are configured, and load the inventories
those device plugins for which the inventory is configured.
"""
conf.CiscoConfigOptions()
for key in conf.CISCO_PLUGINS.keys():
plugin_obj = conf.CISCO_PLUGINS[key]
if plugin_obj is not None:
self._plugins[key] = importutils.import_object(plugin_obj)
LOG.debug(_("Loaded device plugin %s"),
conf.CISCO_PLUGINS[key])
if ((const.VSWITCH_PLUGIN in self._plugins) and
hasattr(self._plugins[const.VSWITCH_PLUGIN],
"supported_extension_aliases")):
self.supported_extension_aliases.extend(
self._plugins[const.VSWITCH_PLUGIN].
supported_extension_aliases)
# At this point, all the database models should have been loaded. It's
# possible that configure_db() may have been called by one of the
# plugins loaded in above. Otherwise, this call is to make sure that
# the database is initialized
db_api.configure_db()
# Initialize credential store after database initialization
cred.Store.initialize()
LOG.debug(_("%(module)s.%(name)s init done"),
{'module': __name__,
'name': self.__class__.__name__})
# Check whether we have a valid Nexus driver loaded
self.is_nexus_plugin = False
nexus_driver = conf.CISCO.nexus_driver
if nexus_driver.endswith('CiscoNEXUSDriver'):
self.is_nexus_plugin = True
def __getattribute__(self, name):
"""Delegate calls to OVS sub-plugin.
This delegates the calls to the methods implemented only by the OVS
sub-plugin. Note: Currently, bulking is handled by the caller
(PluginV2), and this model class expects to receive only non-bulking
calls. If, however, a bulking call is made, this will method will
delegate the call to the OVS plugin.
"""
super_getattribute = super(VirtualPhysicalSwitchModelV2,
self).__getattribute__
methods = super_getattribute('_methods_to_delegate')
if name in methods:
plugin = super_getattribute('_plugins')[const.VSWITCH_PLUGIN]
return getattr(plugin, name)
try:
return super_getattribute(name)
except AttributeError:
plugin = super_getattribute('_plugins')[const.VSWITCH_PLUGIN]
return getattr(plugin, name)
def _func_name(self, offset=0):
"""Get the name of the calling function."""
frame_record = inspect.stack()[1 + offset]
func_name = frame_record[3]
return func_name
def _invoke_plugin_per_device(self, plugin_key, function_name,
args, **kwargs):
"""Invoke plugin per device.
Invokes a device plugin's relevant functions (based on the
plugin implementation) for completing this operation.
"""
if plugin_key not in self._plugins:
LOG.info(_("No %s Plugin loaded"), plugin_key)
LOG.info(_("%(plugin_key)s: %(function_name)s with args %(args)s "
"ignored"),
{'plugin_key': plugin_key, 'function_name': function_name,
'args': args})
return
return [self._invoke_plugin(plugin_key, function_name, args, kwargs)]
def _invoke_plugin(self, plugin_key, function_name, args, kwargs):
"""Invoke plugin.
Invokes the relevant function on a device plugin's
implementation for completing this operation.
"""
func = getattr(self._plugins[plugin_key], function_name)
func_args_len = int(inspect.getargspec(func).args.__len__()) - 1
if args.__len__() > func_args_len:
func_args = args[:func_args_len]
extra_args = args[func_args_len:]
for dict_arg in extra_args:
for k, v in dict_arg.iteritems():
kwargs[k] = v
return func(*func_args, **kwargs)
else:
return func(*args, **kwargs)
def _get_segmentation_id(self, network_id):
binding_seg_id = odb.get_network_binding(None, network_id)
if not binding_seg_id:
raise cexc.NetworkSegmentIDNotFound(net_id=network_id)
return binding_seg_id.segmentation_id
def _get_provider_vlan_id(self, network):
if (all(attributes.is_attr_set(network.get(attr))
for attr in (provider.NETWORK_TYPE,
provider.PHYSICAL_NETWORK,
provider.SEGMENTATION_ID))
and
network[provider.NETWORK_TYPE] == const.NETWORK_TYPE_VLAN):
return network[provider.SEGMENTATION_ID]
def create_network(self, context, network):
"""Create network.
Perform this operation in the context of the configured device
plugins.
"""
LOG.debug(_("create_network() called"))
provider_vlan_id = self._get_provider_vlan_id(network[const.NETWORK])
args = [context, network]
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),
args)
# The vswitch plugin did all the verification. If it's a provider
# vlan network, save it for the nexus plugin to use later.
if provider_vlan_id:
network_id = ovs_output[0][const.NET_ID]
cdb.add_provider_network(network_id,
const.NETWORK_TYPE_VLAN,
provider_vlan_id)
LOG.debug(_("Provider network added to DB: %(network_id)s, "
"%(vlan_id)s"),
{'network_id': network_id, 'vlan_id': provider_vlan_id})
return ovs_output[0]
def update_network(self, context, id, network):
"""Update network.
Perform this operation in the context of the configured device
plugins.
Note that the Nexus sub-plugin does not need to be notified
(and the Nexus switch does not need to be [re]configured)
for an update network operation because the Nexus sub-plugin
is agnostic of all network-level attributes except the
segmentation ID. Furthermore, updating of the segmentation ID
is not supported by the OVS plugin since it is considered a
provider attribute, so it is not supported by this method.
"""
LOG.debug(_("update_network() called"))
# We can only support updating of provider attributes if all the
# configured sub-plugins support it. Currently we have no method
# in place for checking whether a sub-plugin supports it,
# so assume not.
provider._raise_if_updates_provider_attributes(network['network'])
args = [context, id, network]
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),
args)
return ovs_output[0]
def delete_network(self, context, id):
"""Delete network.
Perform this operation in the context of the configured device
plugins.
"""
args = [context, id]
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),
args)
if cdb.remove_provider_network(id):
LOG.debug(_("Provider network removed from DB: %s"), id)
return ovs_output[0]
def get_network(self, context, id, fields=None):
"""For this model this method will be delegated to vswitch plugin."""
pass
def get_networks(self, context, filters=None, fields=None):
"""For this model this method will be delegated to vswitch plugin."""
pass
def _invoke_nexus_for_net_create(self, context, tenant_id, net_id,
instance_id, host_id):
if not self.is_nexus_plugin:
return False
network = self.get_network(context, net_id)
vlan_id = self._get_segmentation_id(net_id)
vlan_name = conf.CISCO.vlan_name_prefix + str(vlan_id)
network[const.NET_VLAN_ID] = vlan_id
network[const.NET_VLAN_NAME] = vlan_name
attachment = {
const.TENANT_ID: tenant_id,
const.INSTANCE_ID: instance_id,
const.HOST_NAME: host_id,
}
self._invoke_plugin_per_device(
const.NEXUS_PLUGIN,
'create_network',
[network, attachment])
def _check_valid_port_device_owner(self, port):
"""Check the port for valid device_owner.
Don't call the nexus plugin for router and dhcp
port owners.
"""
return port['device_owner'].startswith('compute')
def _get_port_host_id_from_bindings(self, port):
"""Get host_id from portbindings."""
host_id = None
if (portbindings.HOST_ID in port and
attributes.is_attr_set(port[portbindings.HOST_ID])):
host_id = port[portbindings.HOST_ID]
return host_id
def create_port(self, context, port):
"""Create port.
Perform this operation in the context of the configured device
plugins.
"""
LOG.debug(_("create_port() called"))
args = [context, port]
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),
args)
instance_id = port['port']['device_id']
# Only call nexus plugin if there's a valid instance_id, host_id
# and device_owner
try:
host_id = self._get_port_host_id_from_bindings(port['port'])
if (instance_id and host_id and
self._check_valid_port_device_owner(port['port'])):
net_id = port['port']['network_id']
tenant_id = port['port']['tenant_id']
self._invoke_nexus_for_net_create(
context, tenant_id, net_id, instance_id, host_id)
except Exception:
# Create network on the Nexus plugin has failed, so we need
# to rollback the port creation on the VSwitch plugin.
exc_info = sys.exc_info()
try:
id = ovs_output[0]['id']
args = [context, id]
ovs_output = self._invoke_plugin_per_device(
const.VSWITCH_PLUGIN,
'delete_port',
args)
finally:
# Re-raise the original exception
raise exc_info[0], exc_info[1], exc_info[2]
return ovs_output[0]
def get_port(self, context, id, fields=None):
"""For this model this method will be delegated to vswitch plugin."""
pass
def get_ports(self, context, filters=None, fields=None):
"""For this model this method will be delegated to vswitch plugin."""
pass
def _check_nexus_net_create_needed(self, new_port, old_port):
"""Check if nexus plugin should be invoked for net_create.
In the following cases, the plugin should be invoked:
-- a port is attached to a VM instance. The old host id is None
-- VM migration. The old host id has a valid value
When the plugin needs to be invoked, return the old_host_id,
and a list of calling arguments.
Otherwise, return '' for old host id and an empty list
"""
old_device_id = old_port['device_id']
new_device_id = new_port.get('device_id')
new_host_id = self._get_port_host_id_from_bindings(new_port)
tenant_id = old_port['tenant_id']
net_id = old_port['network_id']
old_host_id = self._get_port_host_id_from_bindings(old_port)
LOG.debug(_("tenant_id: %(tid)s, net_id: %(nid)s, "
"old_device_id: %(odi)s, new_device_id: %(ndi)s, "
"old_host_id: %(ohi)s, new_host_id: %(nhi)s, "
"old_device_owner: %(odo)s, new_device_owner: %(ndo)s"),
{'tid': tenant_id, 'nid': net_id,
'odi': old_device_id, 'ndi': new_device_id,
'ohi': old_host_id, 'nhi': new_host_id,
'odo': old_port.get('device_owner'),
'ndo': new_port.get('device_owner')})
# A port is attached to an instance
if (new_device_id and not old_device_id and new_host_id and
self._check_valid_port_device_owner(new_port)):
return '', [tenant_id, net_id, new_device_id, new_host_id]
# An instance is being migrated
if (old_device_id and old_host_id and new_host_id != old_host_id and
self._check_valid_port_device_owner(old_port)):
return old_host_id, [tenant_id, net_id, old_device_id, new_host_id]
# no need to invoke the plugin
return '', []
def update_port(self, context, id, port):
"""Update port.
Perform this operation in the context of the configured device
plugins.
"""
LOG.debug(_("update_port() called"))
old_port = self.get_port(context, id)
args = [context, id, port]
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),
args)
try:
# Check if the nexus plugin needs to be invoked
old_host_id, create_args = self._check_nexus_net_create_needed(
port['port'], old_port)
# In the case of migration, invoke it to remove
# the previous port binding
if old_host_id:
vlan_id = self._get_segmentation_id(old_port['network_id'])
delete_args = [old_port['device_id'], vlan_id]
self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
"delete_port",
delete_args)
# Invoke the Nexus plugin to create a net and/or new port binding
if create_args:
self._invoke_nexus_for_net_create(context, *create_args)
return ovs_output[0]
except Exception:
exc_info = sys.exc_info()
LOG.error(_("Unable to update port '%s' on Nexus switch"),
old_port['name'], exc_info=exc_info)
try:
# Roll back vSwitch plugin to original port attributes.
args = [context, id, {'port': old_port}]
ovs_output = self._invoke_plugin_per_device(
const.VSWITCH_PLUGIN,
self._func_name(),
args)
finally:
# Re-raise the original exception
raise exc_info[0], exc_info[1], exc_info[2]
def delete_port(self, context, id, l3_port_check=True):
"""Delete port.
Perform this operation in the context of the configured device
plugins.
"""
LOG.debug(_("delete_port() called"))
port = self.get_port(context, id)
host_id = self._get_port_host_id_from_bindings(port)
if (self.is_nexus_plugin and host_id and
self._check_valid_port_device_owner(port)):
vlan_id = self._get_segmentation_id(port['network_id'])
n_args = [port['device_id'], vlan_id]
self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
self._func_name(),
n_args)
try:
args = [context, id]
ovs_output = self._invoke_plugin_per_device(
const.VSWITCH_PLUGIN, self._func_name(),
args, l3_port_check=l3_port_check)
except Exception:
exc_info = sys.exc_info()
# Roll back the delete port on the Nexus plugin
try:
tenant_id = port['tenant_id']
net_id = port['network_id']
instance_id = port['device_id']
host_id = port[portbindings.HOST_ID]
self._invoke_nexus_for_net_create(context, tenant_id, net_id,
instance_id, host_id)
finally:
# Raise the original exception.
raise exc_info[0], exc_info[1], exc_info[2]
return ovs_output[0]
def add_router_interface(self, context, router_id, interface_info):
"""Add a router interface on a subnet.
Only invoke the Nexus plugin to create SVI if L3 support on
the Nexus switches is enabled and a Nexus plugin is loaded,
otherwise send it to the vswitch plugin
"""
if (conf.CISCO.nexus_l3_enable and self.is_nexus_plugin):
LOG.debug(_("L3 enabled on Nexus plugin, create SVI on switch"))
if 'subnet_id' not in interface_info:
raise cexc.SubnetNotSpecified()
if 'port_id' in interface_info:
raise cexc.PortIdForNexusSvi()
subnet = self.get_subnet(context, interface_info['subnet_id'])
gateway_ip = subnet['gateway_ip']
# Get gateway IP address and netmask
cidr = subnet['cidr']
netmask = cidr.split('/', 1)[1]
gateway_ip = gateway_ip + '/' + netmask
network_id = subnet['network_id']
vlan_id = self._get_segmentation_id(network_id)
vlan_name = conf.CISCO.vlan_name_prefix + str(vlan_id)
n_args = [vlan_name, vlan_id, subnet['id'], gateway_ip, router_id]
return self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
self._func_name(),
n_args)
else:
LOG.debug(_("L3 disabled or not Nexus plugin, send to vswitch"))
n_args = [context, router_id, interface_info]
return self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),
n_args)
def remove_router_interface(self, context, router_id, interface_info):
"""Remove a router interface.
Only invoke the Nexus plugin to delete SVI if L3 support on
the Nexus switches is enabled and a Nexus plugin is loaded,
otherwise send it to the vswitch plugin
"""
if (conf.CISCO.nexus_l3_enable and self.is_nexus_plugin):
LOG.debug(_("L3 enabled on Nexus plugin, delete SVI from switch"))
subnet = self.get_subnet(context, interface_info['subnet_id'])
network_id = subnet['network_id']
vlan_id = self._get_segmentation_id(network_id)
n_args = [vlan_id, router_id]
return self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
self._func_name(),
n_args)
else:
LOG.debug(_("L3 disabled or not Nexus plugin, send to vswitch"))
n_args = [context, router_id, interface_info]
return self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),
n_args)
def create_subnet(self, context, subnet):
"""For this model this method will be delegated to vswitch plugin."""
pass
def update_subnet(self, context, id, subnet):
"""For this model this method will be delegated to vswitch plugin."""
pass
def get_subnet(self, context, id, fields=None):
"""For this model this method will be delegated to vswitch plugin."""
pass
def delete_subnet(self, context, id, kwargs):
"""For this model this method will be delegated to vswitch plugin."""
pass
def get_subnets(self, context, filters=None, fields=None):
"""For this model this method will be delegated to vswitch plugin."""
pass