# 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 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.items(), key=switch_dict.get) return switch_ip[0] except cisco_exc.NexusPortBindingNotFound: pass LOG.debug(_("No round robin or zero weights, using first switch")) # Return the first switch in the config for switch_ip, attr in nexus_switches: return switch_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}