3c164b9988
Port of the quantum/plugin/cisco/nexus plugin to run under the Modular Layer 2 (ML2) infrastructure as defined in https://blueprints.launchpad.net/quantum/+spec/ml2-mechanism-drivers Implements blueprint ml2-md-cisco-nexus Change-Id: Ifdd03bec554a08266de859387f1901858a3be4a1
222 lines
9.2 KiB
Python
222 lines
9.2 KiB
Python
# Copyright 2013 OpenStack Foundation
|
|
# 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.
|
|
|
|
"""
|
|
ML2 Mechanism Driver for Cisco Nexus platforms.
|
|
"""
|
|
|
|
from novaclient.v1_1 import client as nova_client
|
|
from oslo.config import cfg
|
|
|
|
from neutron.openstack.common import excutils
|
|
from neutron.openstack.common import log as logging
|
|
from neutron.plugins.ml2 import driver_api as api
|
|
from neutron.plugins.ml2.drivers.cisco import config as conf
|
|
from neutron.plugins.ml2.drivers.cisco import credentials_v2 as cred
|
|
from neutron.plugins.ml2.drivers.cisco import exceptions as excep
|
|
from neutron.plugins.ml2.drivers.cisco import nexus_db_v2 as nxos_db
|
|
from neutron.plugins.ml2.drivers.cisco import nexus_network_driver
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class CiscoNexusMechanismDriver(api.MechanismDriver):
|
|
|
|
"""Cisco Nexus ML2 Mechanism Driver."""
|
|
|
|
def initialize(self):
|
|
# Create ML2 device dictionary from ml2_conf.ini entries.
|
|
conf.ML2MechCiscoConfig()
|
|
|
|
# Extract configuration parameters from the configuration file.
|
|
self._nexus_switches = conf.ML2MechCiscoConfig.nexus_dict
|
|
LOG.debug(_("nexus_switches found = %s"), self._nexus_switches)
|
|
|
|
self.credentials = {}
|
|
self.driver = nexus_network_driver.CiscoNexusDriver()
|
|
|
|
# Initialize credential store after database initialization
|
|
cred.Store.initialize()
|
|
|
|
def _get_vlanid(self, port_context):
|
|
"""Return the VLAN ID (segmentation ID) for this network."""
|
|
# NB: Currently only a single physical network is supported.
|
|
network_context = port_context.network
|
|
network_segments = network_context.network_segments
|
|
return network_segments[0]['segmentation_id']
|
|
|
|
def _get_credential(self, nexus_ip):
|
|
"""Return credential information for a given Nexus IP address.
|
|
|
|
If credential doesn't exist then also add to local dictionary.
|
|
"""
|
|
if nexus_ip not in self.credentials:
|
|
_nexus_username = cred.Store.get_username(nexus_ip)
|
|
_nexus_password = cred.Store.get_password(nexus_ip)
|
|
self.credentials[nexus_ip] = {
|
|
'username': _nexus_username,
|
|
'password': _nexus_password
|
|
}
|
|
return self.credentials[nexus_ip]
|
|
|
|
def _manage_port(self, vlan_name, vlan_id, host, instance):
|
|
"""Called during create and update port events.
|
|
|
|
Create a VLAN in the appropriate switch/port and configure the
|
|
appropriate interfaces for this VLAN.
|
|
"""
|
|
|
|
# Grab the switch IP and port for this host
|
|
for switch_ip, attr in self._nexus_switches:
|
|
if str(attr) == str(host):
|
|
port_id = self._nexus_switches[switch_ip, attr]
|
|
break
|
|
else:
|
|
raise excep.NexusComputeHostNotConfigured(host=host)
|
|
|
|
# Check if this network is already in the DB
|
|
vlan_created = False
|
|
vlan_trunked = False
|
|
|
|
try:
|
|
nxos_db.get_port_vlan_switch_binding(port_id, vlan_id, switch_ip)
|
|
except excep.NexusPortBindingNotFound:
|
|
# Check for vlan/switch binding
|
|
try:
|
|
nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
|
|
except excep.NexusPortBindingNotFound:
|
|
# Create vlan and trunk vlan on the port
|
|
LOG.debug(_("Nexus: create & trunk vlan %s"), vlan_name)
|
|
self.driver.create_and_trunk_vlan(switch_ip, vlan_id,
|
|
vlan_name, port_id)
|
|
vlan_created = True
|
|
vlan_trunked = True
|
|
else:
|
|
# Only trunk vlan on the port
|
|
LOG.debug(_("Nexus: trunk vlan %s"), vlan_name)
|
|
self.driver.enable_vlan_on_trunk_int(switch_ip, vlan_id,
|
|
port_id)
|
|
vlan_trunked = True
|
|
|
|
try:
|
|
nxos_db.add_nexusport_binding(port_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.driver.delete_and_untrunk_vlan(switch_ip, vlan_id,
|
|
port_id)
|
|
elif vlan_created:
|
|
LOG.debug(_("Nexus: delete vlan %s"), vlan_name)
|
|
self.driver.delete_vlan(switch_ip, vlan_id)
|
|
elif vlan_trunked:
|
|
LOG.debug(_("Nexus: untrunk vlan %s"), vlan_name)
|
|
self.driver.disable_vlan_on_trunk_int(switch_ip, vlan_id,
|
|
port_id)
|
|
|
|
# TODO(rcurran) Temporary access to host_id. When available use
|
|
# port-binding to access host name.
|
|
def _get_instance_host(self, instance_id):
|
|
keystone_conf = cfg.CONF.keystone_authtoken
|
|
keystone_auth_url = '%s://%s:%s/v2.0/' % (keystone_conf.auth_protocol,
|
|
keystone_conf.auth_host,
|
|
keystone_conf.auth_port)
|
|
nc = nova_client.Client(keystone_conf.admin_user,
|
|
keystone_conf.admin_password,
|
|
keystone_conf.admin_tenant_name,
|
|
keystone_auth_url,
|
|
no_cache=True)
|
|
serv = nc.servers.get(instance_id)
|
|
host = serv.__getattr__('OS-EXT-SRV-ATTR:host')
|
|
|
|
return host
|
|
|
|
def _invoke_nexus_on_port_event(self, context, instance_id):
|
|
"""Prepare variables for call to nexus switch."""
|
|
vlan_id = self._get_vlanid(context)
|
|
host = self._get_instance_host(instance_id)
|
|
|
|
# Trunk segmentation id for only this host
|
|
vlan_name = cfg.CONF.ml2_cisco.vlan_name_prefix + str(vlan_id)
|
|
self._manage_port(vlan_name, vlan_id, host, instance_id)
|
|
|
|
def create_port_postcommit(self, context):
|
|
"""Create port post-database commit event."""
|
|
port = context.current
|
|
instance_id = port['device_id']
|
|
device_owner = port['device_owner']
|
|
|
|
if instance_id and device_owner != 'network:dhcp':
|
|
self._invoke_nexus_on_port_event(context, instance_id)
|
|
|
|
def update_port_postcommit(self, context):
|
|
"""Update port post-database commit event."""
|
|
port = context.current
|
|
old_port = context.original
|
|
old_device = old_port['device_id']
|
|
instance_id = port['device_id'] if 'device_id' in port else ""
|
|
|
|
# Check if there's a new device_id
|
|
if instance_id and not old_device:
|
|
self._invoke_nexus_on_port_event(context, instance_id)
|
|
|
|
def delete_port_precommit(self, context):
|
|
"""Delete port pre-database commit event.
|
|
|
|
Delete port bindings from the database and scan whether the network
|
|
is still required on the interfaces trunked.
|
|
"""
|
|
port = context.current
|
|
device_id = port['device_id']
|
|
vlan_id = self._get_vlanid(context)
|
|
|
|
# Delete DB row for this port
|
|
try:
|
|
row = nxos_db.get_nexusvm_binding(vlan_id, device_id)
|
|
except excep.NexusPortBindingNotFound:
|
|
return
|
|
|
|
switch_ip = row.switch_ip
|
|
nexus_port = None
|
|
if row.port_id != 'router':
|
|
nexus_port = row.port_id
|
|
|
|
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 excep.NexusPortBindingNotFound:
|
|
try:
|
|
# Delete this vlan from this switch
|
|
if nexus_port:
|
|
self.driver.disable_vlan_on_trunk_int(switch_ip,
|
|
row.vlan_id,
|
|
nexus_port)
|
|
self.driver.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)
|