Merge "Adds Brocade Plugin implementation"
This commit is contained in:
commit
6f627015b6
48
etc/quantum/plugins/brocade/brocade.ini
Normal file
48
etc/quantum/plugins/brocade/brocade.ini
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
[SWITCH]
|
||||||
|
# username = <mgmt admin username>
|
||||||
|
# password = <mgmt admin password>
|
||||||
|
# address = <switch mgmt ip address>
|
||||||
|
# ostype = NOS
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# username = admin
|
||||||
|
# password = password
|
||||||
|
# address = 10.24.84.38
|
||||||
|
# ostype = NOS
|
||||||
|
|
||||||
|
[DATABASE]
|
||||||
|
# sql_connection = sqlite://
|
||||||
|
# Enable the use of eventlet's db_pool for MySQL. The flags sql_min_pool_size,
|
||||||
|
# sql_max_pool_size and sql_idle_timeout are relevant only if this is enabled.
|
||||||
|
# sql_dbpool_enable = False
|
||||||
|
# Minimum number of SQL connections to keep open in a pool
|
||||||
|
# sql_min_pool_size = 1
|
||||||
|
# Maximum number of SQL connections to keep open in a pool
|
||||||
|
# sql_max_pool_size = 5
|
||||||
|
# Timeout in seconds before idle sql connections are reaped
|
||||||
|
# sql_idle_timeout = 3600
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# sql_connection = mysql://root:pass@localhost/brcd_quantum?charset=utf8
|
||||||
|
|
||||||
|
[PHYSICAL_INTERFACE]
|
||||||
|
# physical_interface = <physical network name>
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# physical_interface = physnet1
|
||||||
|
|
||||||
|
[VLANS]
|
||||||
|
# network_vlan_ranges = <physical network name>:nnnn:mmmm
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# network_vlan_ranges = physnet1:1000:2999
|
||||||
|
|
||||||
|
[AGENT]
|
||||||
|
# Example:
|
||||||
|
# root_helper = sudo /usr/local/bin/quantum-rootwrap /etc/quantum/rootwrap.conf
|
||||||
|
|
||||||
|
[LINUX_BRIDGE]
|
||||||
|
# physical_interface_mappings = <physical network name>:<local interface>
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# physical_interface_mappings = physnet1:em1
|
453
quantum/plugins/brocade/QuantumPlugin.py
Normal file
453
quantum/plugins/brocade/QuantumPlugin.py
Normal file
@ -0,0 +1,453 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 Brocade Communications System, 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.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Shiv Haris (sharis@brocade.com)
|
||||||
|
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
||||||
|
#
|
||||||
|
# (Some parts adapted from LinuxBridge Plugin)
|
||||||
|
# TODO (shiv) need support for security groups
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Implentation of Brocade Quantum Plugin.
|
||||||
|
"""
|
||||||
|
from quantum.agent import securitygroups_rpc as sg_rpc
|
||||||
|
from quantum.common import rpc as q_rpc
|
||||||
|
from quantum.common import topics
|
||||||
|
from quantum.common import utils
|
||||||
|
from quantum.db import api as db
|
||||||
|
from quantum.db import db_base_plugin_v2
|
||||||
|
from quantum.db import dhcp_rpc_base
|
||||||
|
from quantum.db import l3_db
|
||||||
|
from quantum.db import l3_rpc_base
|
||||||
|
from quantum.db import securitygroups_rpc_base as sg_db_rpc
|
||||||
|
from quantum.extensions import portbindings
|
||||||
|
from quantum.extensions import securitygroup as ext_sg
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
from quantum.openstack.common import context
|
||||||
|
from quantum.openstack.common import importutils
|
||||||
|
from quantum.openstack.common import log as logging
|
||||||
|
from quantum.openstack.common import rpc
|
||||||
|
from quantum.openstack.common.rpc import proxy
|
||||||
|
from quantum.plugins.brocade.db import models as brocade_db
|
||||||
|
from quantum.plugins.brocade import vlanbm as vbm
|
||||||
|
from quantum import policy
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
PLUGIN_VERSION = 0.88
|
||||||
|
AGENT_OWNER_PREFIX = "network:"
|
||||||
|
NOS_DRIVER = 'quantum.plugins.brocade.nos.nosdriver.NOSdriver'
|
||||||
|
|
||||||
|
SWITCH_OPTS = [cfg.StrOpt('address', default=''),
|
||||||
|
cfg.StrOpt('username', default=''),
|
||||||
|
cfg.StrOpt('password', default=''),
|
||||||
|
cfg.StrOpt('ostype', default='NOS')
|
||||||
|
]
|
||||||
|
|
||||||
|
PHYSICAL_INTERFACE_OPTS = [cfg.StrOpt('physical_interface', default='eth0')
|
||||||
|
]
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(SWITCH_OPTS, "SWITCH")
|
||||||
|
cfg.CONF.register_opts(PHYSICAL_INTERFACE_OPTS, "PHYSICAL_INTERFACE")
|
||||||
|
|
||||||
|
|
||||||
|
class BridgeRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
|
||||||
|
l3_rpc_base.L3RpcCallbackMixin,
|
||||||
|
sg_db_rpc.SecurityGroupServerRpcCallbackMixin):
|
||||||
|
"""Agent callback."""
|
||||||
|
|
||||||
|
RPC_API_VERSION = '1.1'
|
||||||
|
# Device names start with "tap"
|
||||||
|
# history
|
||||||
|
# 1.1 Support Security Group RPC
|
||||||
|
TAP_PREFIX_LEN = 3
|
||||||
|
|
||||||
|
def create_rpc_dispatcher(self):
|
||||||
|
'''Get the rpc dispatcher for this manager.
|
||||||
|
|
||||||
|
If a manager would like to set an rpc API version, or support more than
|
||||||
|
one class as the target of rpc messages, override this method.
|
||||||
|
'''
|
||||||
|
return q_rpc.PluginRpcDispatcher([self])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_port_from_device(cls, device):
|
||||||
|
"""Get port from the brocade specific db."""
|
||||||
|
|
||||||
|
# TODO(shh) context is not being passed as
|
||||||
|
# an argument to this function;
|
||||||
|
#
|
||||||
|
# need to be fixed in:
|
||||||
|
# file: quantum/db/securtygroups_rpc_base.py
|
||||||
|
# function: securitygroup_rules_for_devices()
|
||||||
|
# which needs to pass context to us
|
||||||
|
|
||||||
|
# Doing what other plugins are doing
|
||||||
|
session = db.get_session()
|
||||||
|
port = brocade_db.get_port_from_device(
|
||||||
|
session, device[cls.TAP_PREFIX_LEN:])
|
||||||
|
|
||||||
|
# TODO(shiv): need to extend the db model to include device owners
|
||||||
|
# make it appears that the device owner is of type network
|
||||||
|
if port:
|
||||||
|
port['device'] = device
|
||||||
|
port['device_owner'] = AGENT_OWNER_PREFIX
|
||||||
|
port['binding:vif_type'] = 'bridge'
|
||||||
|
return port
|
||||||
|
|
||||||
|
def get_device_details(self, rpc_context, **kwargs):
|
||||||
|
"""Agent requests device details."""
|
||||||
|
|
||||||
|
agent_id = kwargs.get('agent_id')
|
||||||
|
device = kwargs.get('device')
|
||||||
|
LOG.debug(_("Device %(device)s details requested from %(agent_id)s"),
|
||||||
|
locals())
|
||||||
|
port = brocade_db.get_port(rpc_context, device[self.TAP_PREFIX_LEN:])
|
||||||
|
if port:
|
||||||
|
entry = {'device': device,
|
||||||
|
'vlan_id': port.vlan_id,
|
||||||
|
'network_id': port.network_id,
|
||||||
|
'port_id': port.port_id,
|
||||||
|
'physical_network': port.physical_interface,
|
||||||
|
'admin_state_up': port.admin_state_up
|
||||||
|
}
|
||||||
|
|
||||||
|
else:
|
||||||
|
entry = {'device': device}
|
||||||
|
LOG.debug(_("%s can not be found in database"), device)
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def update_device_down(self, rpc_context, **kwargs):
|
||||||
|
"""Device no longer exists on agent."""
|
||||||
|
|
||||||
|
device = kwargs.get('device')
|
||||||
|
port = self.get_port_from_device(device)
|
||||||
|
if port:
|
||||||
|
entry = {'device': device,
|
||||||
|
'exists': True}
|
||||||
|
# Set port status to DOWN
|
||||||
|
port_id = port['port_id']
|
||||||
|
brocade_db.update_port_state(rpc_context, port_id, False)
|
||||||
|
else:
|
||||||
|
entry = {'device': device,
|
||||||
|
'exists': False}
|
||||||
|
LOG.debug(_("%s can not be found in database"), device)
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
class AgentNotifierApi(proxy.RpcProxy,
|
||||||
|
sg_rpc.SecurityGroupAgentRpcApiMixin):
|
||||||
|
'''Agent side of the linux bridge rpc API.
|
||||||
|
|
||||||
|
API version history:
|
||||||
|
1.0 - Initial version.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
BASE_RPC_API_VERSION = '1.0'
|
||||||
|
|
||||||
|
def __init__(self, topic):
|
||||||
|
super(AgentNotifierApi, self).__init__(
|
||||||
|
topic=topic, default_version=self.BASE_RPC_API_VERSION)
|
||||||
|
self.topic = topic
|
||||||
|
self.topic_network_delete = topics.get_topic_name(topic,
|
||||||
|
topics.NETWORK,
|
||||||
|
topics.DELETE)
|
||||||
|
self.topic_port_update = topics.get_topic_name(topic,
|
||||||
|
topics.PORT,
|
||||||
|
topics.UPDATE)
|
||||||
|
|
||||||
|
def network_delete(self, context, network_id):
|
||||||
|
self.fanout_cast(context,
|
||||||
|
self.make_msg('network_delete',
|
||||||
|
network_id=network_id),
|
||||||
|
topic=self.topic_network_delete)
|
||||||
|
|
||||||
|
def port_update(self, context, port, physical_network, vlan_id):
|
||||||
|
self.fanout_cast(context,
|
||||||
|
self.make_msg('port_update',
|
||||||
|
port=port,
|
||||||
|
physical_network=physical_network,
|
||||||
|
vlan_id=vlan_id),
|
||||||
|
topic=self.topic_port_update)
|
||||||
|
|
||||||
|
|
||||||
|
class BrocadePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||||
|
l3_db.L3_NAT_db_mixin,
|
||||||
|
sg_db_rpc.SecurityGroupServerRpcMixin):
|
||||||
|
"""BrocadePluginV2 is a Quantum plugin.
|
||||||
|
|
||||||
|
Provides L2 Virtual Network functionality using VDX. Upper
|
||||||
|
layer driver class that interfaces to NETCONF layer below.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize Brocade Plugin, specify switch address
|
||||||
|
and db configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.supported_extension_aliases = ["binding", "security-group"]
|
||||||
|
self.binding_view = "extension:port_binding:view"
|
||||||
|
self.binding_set = "extension:port_binding:set"
|
||||||
|
|
||||||
|
self.physical_interface = (cfg.CONF.PHYSICAL_INTERFACE.
|
||||||
|
physical_interface)
|
||||||
|
db.configure_db()
|
||||||
|
self.ctxt = context.get_admin_context()
|
||||||
|
self.ctxt.session = db.get_session()
|
||||||
|
self._vlan_bitmap = vbm.VlanBitmap(self.ctxt)
|
||||||
|
self._setup_rpc()
|
||||||
|
self.brocade_init()
|
||||||
|
|
||||||
|
def brocade_init(self):
|
||||||
|
"""Brocade specific initialization."""
|
||||||
|
|
||||||
|
self._switch = {'address': cfg.CONF.SWITCH.address,
|
||||||
|
'username': cfg.CONF.SWITCH.username,
|
||||||
|
'password': cfg.CONF.SWITCH.password
|
||||||
|
}
|
||||||
|
self._driver = importutils.import_object(NOS_DRIVER)
|
||||||
|
|
||||||
|
def _setup_rpc(self):
|
||||||
|
# RPC support
|
||||||
|
self.topic = topics.PLUGIN
|
||||||
|
self.rpc_context = context.RequestContext('quantum', 'quantum',
|
||||||
|
is_admin=False)
|
||||||
|
self.conn = rpc.create_connection(new=True)
|
||||||
|
self.callbacks = BridgeRpcCallbacks()
|
||||||
|
self.dispatcher = self.callbacks.create_rpc_dispatcher()
|
||||||
|
self.conn.create_consumer(self.topic, self.dispatcher,
|
||||||
|
fanout=False)
|
||||||
|
# Consume from all consumers in a thread
|
||||||
|
self.conn.consume_in_thread()
|
||||||
|
self.notifier = AgentNotifierApi(topics.AGENT)
|
||||||
|
|
||||||
|
def create_network(self, context, network):
|
||||||
|
"""This call to create network translates to creation of
|
||||||
|
port-profile on the physical switch.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
net = super(BrocadePluginV2, self).create_network(context, network)
|
||||||
|
net_uuid = net['id']
|
||||||
|
vlan_id = self._vlan_bitmap.get_next_vlan(None)
|
||||||
|
switch = self._switch
|
||||||
|
try:
|
||||||
|
self._driver.create_network(switch['address'],
|
||||||
|
switch['username'],
|
||||||
|
switch['password'],
|
||||||
|
vlan_id)
|
||||||
|
except Exception as e:
|
||||||
|
# Proper formatting
|
||||||
|
LOG.warning(_("Brocade NOS driver:"))
|
||||||
|
LOG.warning(_("%s"), e)
|
||||||
|
LOG.debug(_("Returning the allocated vlan (%d) to the pool"),
|
||||||
|
vlan_id)
|
||||||
|
self._vlan_bitmap.release_vlan(int(vlan_id))
|
||||||
|
raise Exception("Brocade plugin raised exception, check logs")
|
||||||
|
|
||||||
|
brocade_db.create_network(context, net_uuid, vlan_id)
|
||||||
|
|
||||||
|
LOG.info(_("Allocated vlan (%d) from the pool"), vlan_id)
|
||||||
|
return net
|
||||||
|
|
||||||
|
def delete_network(self, context, net_id):
|
||||||
|
"""This call to delete the network translates to removing
|
||||||
|
the port-profile on the physical switch.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
result = super(BrocadePluginV2, self).delete_network(context,
|
||||||
|
net_id)
|
||||||
|
# we must delete all ports in db first (foreign key constraint)
|
||||||
|
# there is no need to delete port in the driver (its a no-op)
|
||||||
|
# (actually: note there is no such call to the driver)
|
||||||
|
bports = brocade_db.get_ports(context, net_id)
|
||||||
|
for bport in bports:
|
||||||
|
brocade_db.delete_port(context, bport['port_id'])
|
||||||
|
|
||||||
|
# find the vlan for this network
|
||||||
|
net = brocade_db.get_network(context, net_id)
|
||||||
|
vlan_id = net['vlan']
|
||||||
|
|
||||||
|
# Tell hw to do remove PP
|
||||||
|
switch = self._switch
|
||||||
|
try:
|
||||||
|
self._driver.delete_network(switch['address'],
|
||||||
|
switch['username'],
|
||||||
|
switch['password'],
|
||||||
|
net_id)
|
||||||
|
except Exception as e:
|
||||||
|
# Proper formatting
|
||||||
|
LOG.warning(_("Brocade NOS driver:"))
|
||||||
|
LOG.warning(_("%s"), e)
|
||||||
|
raise Exception("Brocade plugin raised exception, check logs")
|
||||||
|
|
||||||
|
# now ok to delete the network
|
||||||
|
brocade_db.delete_network(context, net_id)
|
||||||
|
|
||||||
|
# relinquish vlan in bitmap
|
||||||
|
self._vlan_bitmap.release_vlan(int(vlan_id))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def create_port(self, context, port):
|
||||||
|
"""Create logical port on the switch."""
|
||||||
|
|
||||||
|
tenant_id = port['port']['tenant_id']
|
||||||
|
network_id = port['port']['network_id']
|
||||||
|
admin_state_up = port['port']['admin_state_up']
|
||||||
|
|
||||||
|
physical_interface = self.physical_interface
|
||||||
|
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
bnet = brocade_db.get_network(context, network_id)
|
||||||
|
vlan_id = bnet['vlan']
|
||||||
|
|
||||||
|
quantum_port = super(BrocadePluginV2, self).create_port(context,
|
||||||
|
port)
|
||||||
|
interface_mac = quantum_port['mac_address']
|
||||||
|
port_id = quantum_port['id']
|
||||||
|
|
||||||
|
switch = self._switch
|
||||||
|
|
||||||
|
# convert mac format: xx:xx:xx:xx:xx:xx -> xxxx.xxxx.xxxx
|
||||||
|
mac = self.mac_reformat_62to34(interface_mac)
|
||||||
|
try:
|
||||||
|
self._driver.associate_mac_to_network(switch['address'],
|
||||||
|
switch['username'],
|
||||||
|
switch['password'],
|
||||||
|
vlan_id,
|
||||||
|
mac)
|
||||||
|
except Exception as e:
|
||||||
|
# Proper formatting
|
||||||
|
LOG.warning(_("Brocade NOS driver:"))
|
||||||
|
LOG.warning(_("%s"), e)
|
||||||
|
raise Exception("Brocade plugin raised exception, check logs")
|
||||||
|
|
||||||
|
# save to brocade persistent db
|
||||||
|
brocade_db.create_port(context, port_id, network_id,
|
||||||
|
physical_interface,
|
||||||
|
vlan_id, tenant_id, admin_state_up)
|
||||||
|
|
||||||
|
# apply any extensions
|
||||||
|
return self._extend_port_dict_binding(context, quantum_port)
|
||||||
|
|
||||||
|
def delete_port(self, context, port_id):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
super(BrocadePluginV2, self).delete_port(context, port_id)
|
||||||
|
brocade_db.delete_port(context, port_id)
|
||||||
|
|
||||||
|
def update_port(self, context, port_id, port):
|
||||||
|
original_port = self.get_port(context, port_id)
|
||||||
|
session = context.session
|
||||||
|
port_updated = False
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
# delete the port binding and read it with the new rules
|
||||||
|
if ext_sg.SECURITYGROUPS in port['port']:
|
||||||
|
port['port'][ext_sg.SECURITYGROUPS] = (
|
||||||
|
self._get_security_groups_on_port(context, port))
|
||||||
|
self._delete_port_security_group_bindings(context, port_id)
|
||||||
|
self._process_port_create_security_group(
|
||||||
|
context,
|
||||||
|
port_id,
|
||||||
|
port['port'][ext_sg.SECURITYGROUPS])
|
||||||
|
port_updated = True
|
||||||
|
|
||||||
|
port = super(BrocadePluginV2, self).update_port(
|
||||||
|
context, port_id, port)
|
||||||
|
self._extend_port_dict_security_group(context, port)
|
||||||
|
|
||||||
|
if original_port['admin_state_up'] != port['admin_state_up']:
|
||||||
|
port_updated = True
|
||||||
|
|
||||||
|
if (original_port['fixed_ips'] != port['fixed_ips'] or
|
||||||
|
not utils.compare_elements(
|
||||||
|
original_port.get(ext_sg.SECURITYGROUPS),
|
||||||
|
port.get(ext_sg.SECURITYGROUPS))):
|
||||||
|
self.notifier.security_groups_member_updated(
|
||||||
|
context, port.get(ext_sg.SECURITYGROUPS))
|
||||||
|
|
||||||
|
if port_updated:
|
||||||
|
self._notify_port_updated(context, port)
|
||||||
|
|
||||||
|
return self._extend_port_dict_binding(context, port)
|
||||||
|
|
||||||
|
def get_port(self, context, port_id, fields=None):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
port = super(BrocadePluginV2, self).get_port(
|
||||||
|
context, port_id, fields)
|
||||||
|
self._extend_port_dict_security_group(context, port)
|
||||||
|
self._extend_port_dict_binding(context, port)
|
||||||
|
|
||||||
|
return self._fields(port, fields)
|
||||||
|
|
||||||
|
def get_ports(self, context, filters=None, fields=None):
|
||||||
|
res_ports = []
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
ports = super(BrocadePluginV2, self).get_ports(context,
|
||||||
|
filters,
|
||||||
|
fields)
|
||||||
|
for port in ports:
|
||||||
|
self._extend_port_dict_security_group(context, port)
|
||||||
|
self._extend_port_dict_binding(context, port)
|
||||||
|
res_ports.append(self._fields(port, fields))
|
||||||
|
|
||||||
|
return res_ports
|
||||||
|
|
||||||
|
def _notify_port_updated(self, context, port):
|
||||||
|
port_id = port['id']
|
||||||
|
bport = brocade_db.get_port(context, port_id)
|
||||||
|
self.notifier.port_update(context, port,
|
||||||
|
bport.physical_interface,
|
||||||
|
bport.vlan_id)
|
||||||
|
|
||||||
|
def _extend_port_dict_binding(self, context, port):
|
||||||
|
if self._check_view_auth(context, port, self.binding_view):
|
||||||
|
port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_BRIDGE
|
||||||
|
port['binding:vif_type'] = portbindings.VIF_TYPE_BRIDGE
|
||||||
|
port[portbindings.CAPABILITIES] = {
|
||||||
|
portbindings.CAP_PORT_FILTER:
|
||||||
|
'security-group' in self.supported_extension_aliases}
|
||||||
|
return port
|
||||||
|
|
||||||
|
def _check_view_auth(self, context, resource, action):
|
||||||
|
return policy.check(context, action, resource)
|
||||||
|
|
||||||
|
def get_plugin_version(self):
|
||||||
|
"""Get version number of the plugin."""
|
||||||
|
return PLUGIN_VERSION
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mac_reformat_62to34(interface_mac):
|
||||||
|
"""Transform MAC address format.
|
||||||
|
|
||||||
|
Transforms from 6 groups of 2 hexadecimal numbers delimited by ":"
|
||||||
|
to 3 groups of 4 hexadecimals numbers delimited by ".".
|
||||||
|
|
||||||
|
:param interface_mac: MAC address in the format xx:xx:xx:xx:xx:xx
|
||||||
|
:type interface_mac: string
|
||||||
|
:returns: MAC address in the format xxxx.xxxx.xxxx
|
||||||
|
:rtype: string
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
mac = interface_mac.replace(":", "")
|
||||||
|
mac = mac[0:4] + "." + mac[4:8] + "." + mac[8:12]
|
||||||
|
return mac
|
112
quantum/plugins/brocade/README.md
Normal file
112
quantum/plugins/brocade/README.md
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
Brocade Openstack Quantum Plugin
|
||||||
|
================================
|
||||||
|
|
||||||
|
* up-to-date version of these instructions are located at:
|
||||||
|
http://wiki.openstack.org/brocade-quantum-plugin
|
||||||
|
|
||||||
|
* N.B.: Please see Prerequisites section regarding ncclient (netconf client library)
|
||||||
|
|
||||||
|
* Supports VCS (Virtual Cluster of Switches)
|
||||||
|
|
||||||
|
|
||||||
|
Openstack Brocade Quantum Plugin implements the Quantum v2.0 API.
|
||||||
|
|
||||||
|
This plugin is meant to orchestrate Brocade VCS switches running NOS, examples of these are:
|
||||||
|
|
||||||
|
1. VDX 67xx series of switches
|
||||||
|
2. VDX 87xx series of switches
|
||||||
|
|
||||||
|
Brocade Quantum plugin implements the Quantum v2.0 API. It uses NETCONF at the backend
|
||||||
|
to configure the Brocade switch.
|
||||||
|
|
||||||
|
+------------+ +------------+ +-------------+
|
||||||
|
| | | | | |
|
||||||
|
| | | | | Brocade |
|
||||||
|
| Openstack | v2.0 | Brocade | NETCONF | VCS Switch |
|
||||||
|
| Quantum +--------+ Quantum +----------+ |
|
||||||
|
| | | Plugin | | VDX 67xx |
|
||||||
|
| | | | | VDX 87xx |
|
||||||
|
| | | | | |
|
||||||
|
| | | | | |
|
||||||
|
+------------+ +------------+ +-------------+
|
||||||
|
|
||||||
|
|
||||||
|
Directory Structure
|
||||||
|
===================
|
||||||
|
|
||||||
|
Normally you will have your Openstack directory structure as follows:
|
||||||
|
|
||||||
|
/opt/stack/nova/
|
||||||
|
/opt/stack/horizon/
|
||||||
|
...
|
||||||
|
/opt/stack/quantum/quantum/plugins/
|
||||||
|
|
||||||
|
Within this structure, Brocade plugin resides at:
|
||||||
|
|
||||||
|
/opt/stack/quantum/quantum/plugins/brocade
|
||||||
|
|
||||||
|
|
||||||
|
Prerequsites
|
||||||
|
============
|
||||||
|
|
||||||
|
This plugin requires installation of the python netconf client (ncclient) library:
|
||||||
|
|
||||||
|
ncclient v0.3.1 - Python library for NETCONF clients available at http://github.com/brocade/ncclient
|
||||||
|
|
||||||
|
% git clone https://www.github.com/brocade/ncclient
|
||||||
|
% cd ncclient; sudo python ./setup.py install
|
||||||
|
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
1. Specify to Quantum that you will be using the Brocade Plugin - this is done
|
||||||
|
by setting the parameter core_plugin in Quantum:
|
||||||
|
|
||||||
|
core_plugin = quantum.plugins.brocade.QuantumPlugin.BrocadePluginV2
|
||||||
|
|
||||||
|
2. Physical switch configuration parameters and Brocade specific database configuration is specified in
|
||||||
|
the configuration file specified in the brocade.ini files:
|
||||||
|
|
||||||
|
% cat /etc/quantum/plugins/brocade/brocade.ini
|
||||||
|
[SWITCH]
|
||||||
|
username = admin
|
||||||
|
password = password
|
||||||
|
address = <switch mgmt ip address>
|
||||||
|
ostype = NOS
|
||||||
|
|
||||||
|
[DATABASE]
|
||||||
|
sql_connection = mysql://root:pass@localhost/brocade_quantum?charset=utf8
|
||||||
|
|
||||||
|
(please see list of more configuration parameters in the brocade.ini file)
|
||||||
|
|
||||||
|
Running Setup.py
|
||||||
|
================
|
||||||
|
|
||||||
|
Running setup.py with appropriate permissions will copy the default configuration
|
||||||
|
file to /etc/quantum/plugins/brocade/brocade.ini. This file MUST be edited to
|
||||||
|
suit your setup/environment.
|
||||||
|
|
||||||
|
% cd /opt/stack/quantum/quantum/plugins/brocade
|
||||||
|
% python setup.py
|
||||||
|
|
||||||
|
|
||||||
|
Devstack
|
||||||
|
========
|
||||||
|
|
||||||
|
Please see special notes for devstack at:
|
||||||
|
http://wiki.openstack.org/brocade-quantum-plugin
|
||||||
|
|
||||||
|
In order to use Brocade Quantum Plugin, add the following lines in localrc, if localrc file doe
|
||||||
|
not exist create one:
|
||||||
|
|
||||||
|
ENABLED_SERVICES=g-api,g-reg,key,n-api,n-crt,n-obj,n-cpu,n-net,n-cond,cinder,c-sch,c-api,c-vol,n-sch,n-novnc,n-xvnc,n-cauth,horizon,rabbit,quantum,q-svc,q-agt
|
||||||
|
Q_PLUGIN=brocade
|
||||||
|
|
||||||
|
As part of running devstack/stack.sh, the configuration files is copied as:
|
||||||
|
|
||||||
|
% cp /opt/stack/quantum/etc/quantum/plugins/brocade/brocade.ini /etc/quantum/plugins/brocade/brocade.ini
|
||||||
|
|
||||||
|
(hence it is important to make any changes to the configuration in:
|
||||||
|
/opt/stack/quantum/etc/quantum/plugins/brocade/brocade.ini)
|
||||||
|
|
16
quantum/plugins/brocade/__init__.py
Normal file
16
quantum/plugins/brocade/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 Brocade Communications System, 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.
|
16
quantum/plugins/brocade/db/__init__.py
Normal file
16
quantum/plugins/brocade/db/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 Brocade Communications System, 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.
|
152
quantum/plugins/brocade/db/models.py
Normal file
152
quantum/plugins/brocade/db/models.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 Brocade Communications System, 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.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Shiv Haris (sharis@brocade.com)
|
||||||
|
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Brocade specific database schema/model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from quantum.db import model_base
|
||||||
|
from quantum.db import models_v2
|
||||||
|
|
||||||
|
|
||||||
|
class BrocadeNetwork(model_base.BASEV2, models_v2.HasId):
|
||||||
|
"""Schema for brocade network."""
|
||||||
|
vlan = sa.Column(sa.String(10))
|
||||||
|
|
||||||
|
|
||||||
|
class BrocadePort(model_base.BASEV2):
|
||||||
|
"""Schema for brocade port."""
|
||||||
|
|
||||||
|
port_id = sa.Column(sa.String(36), primary_key=True, default="")
|
||||||
|
network_id = sa.Column(sa.String(36),
|
||||||
|
sa.ForeignKey("brocadenetworks.id"),
|
||||||
|
nullable=False)
|
||||||
|
admin_state_up = sa.Column(sa.Boolean, nullable=False)
|
||||||
|
physical_interface = sa.Column(sa.String(36))
|
||||||
|
vlan_id = sa.Column(sa.String(36))
|
||||||
|
tenant_id = sa.Column(sa.String(36))
|
||||||
|
|
||||||
|
|
||||||
|
def create_network(context, net_id, vlan):
|
||||||
|
"""Create a brocade specific network/port-profiles."""
|
||||||
|
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
net = BrocadeNetwork(id=net_id, vlan=vlan)
|
||||||
|
session.add(net)
|
||||||
|
|
||||||
|
return net
|
||||||
|
|
||||||
|
|
||||||
|
def delete_network(context, net_id):
|
||||||
|
"""Delete a brocade specific network/port-profiles."""
|
||||||
|
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
net = (session.query(BrocadeNetwork).filter_by(id=net_id).first())
|
||||||
|
if net is not None:
|
||||||
|
session.delete(net)
|
||||||
|
|
||||||
|
|
||||||
|
def get_network(context, net_id, fields=None):
|
||||||
|
"""Get brocade specific network, with vlan extension."""
|
||||||
|
|
||||||
|
session = context.session
|
||||||
|
return (session.query(BrocadeNetwork).filter_by(id=net_id).first())
|
||||||
|
|
||||||
|
|
||||||
|
def get_networks(context, filters=None, fields=None):
|
||||||
|
"""Get all brocade specific networks."""
|
||||||
|
|
||||||
|
session = context.session
|
||||||
|
try:
|
||||||
|
nets = session.query(BrocadeNetwork).all()
|
||||||
|
return nets
|
||||||
|
except sa.exc.SQLAlchemyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def create_port(context, port_id, network_id, physical_interface,
|
||||||
|
vlan_id, tenant_id, admin_state_up):
|
||||||
|
"""Create a brocade specific port, has policy like vlan."""
|
||||||
|
|
||||||
|
# port_id is truncated: since the linux-bridge tap device names are
|
||||||
|
# based on truncated port id, this enables port lookups using
|
||||||
|
# tap devices
|
||||||
|
port_id = port_id[0:11]
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
port = BrocadePort(port_id=port_id,
|
||||||
|
network_id=network_id,
|
||||||
|
physical_interface=physical_interface,
|
||||||
|
vlan_id=vlan_id,
|
||||||
|
admin_state_up=admin_state_up,
|
||||||
|
tenant_id=tenant_id)
|
||||||
|
session.add(port)
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
def get_port(context, port_id):
|
||||||
|
"""get a brocade specific port."""
|
||||||
|
|
||||||
|
port_id = port_id[0:11]
|
||||||
|
session = context.session
|
||||||
|
port = (session.query(BrocadePort).filter_by(port_id=port_id).first())
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
def get_ports(context, network_id=None):
|
||||||
|
"""get a brocade specific port."""
|
||||||
|
|
||||||
|
session = context.session
|
||||||
|
ports = (session.query(BrocadePort).filter_by(network_id=network_id).all())
|
||||||
|
return ports
|
||||||
|
|
||||||
|
|
||||||
|
def delete_port(context, port_id):
|
||||||
|
"""delete brocade specific port."""
|
||||||
|
|
||||||
|
port_id = port_id[0:11]
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
port = (session.query(BrocadePort).filter_by(port_id=port_id).first())
|
||||||
|
if port is not None:
|
||||||
|
session.delete(port)
|
||||||
|
|
||||||
|
|
||||||
|
def get_port_from_device(session, port_id):
|
||||||
|
"""get port from the tap device."""
|
||||||
|
|
||||||
|
# device is same as truncated port_id
|
||||||
|
port = (session.query(BrocadePort).filter_by(port_id=port_id).first())
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
def update_port_state(context, port_id, admin_state_up):
|
||||||
|
"""Update port attributes."""
|
||||||
|
|
||||||
|
port_id = port_id[0:11]
|
||||||
|
session = context.session
|
||||||
|
session.query(BrocadePort).filter_by(
|
||||||
|
port_id=port_id).update({'admin_state_up': admin_state_up})
|
16
quantum/plugins/brocade/nos/__init__.py
Normal file
16
quantum/plugins/brocade/nos/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 Brocade Communications 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.
|
118
quantum/plugins/brocade/nos/fake_nosdriver.py
Normal file
118
quantum/plugins/brocade/nos/fake_nosdriver.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 Brocade Communications System, 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.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
||||||
|
# Shiv Haris (sharis@brocade.com)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
FAKE DRIVER, for unit tests purposes
|
||||||
|
Brocade NOS Driver implements NETCONF over SSHv2 for
|
||||||
|
Quantum network life-cycle management
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class NOSdriver():
|
||||||
|
"""NOS NETCONF interface driver for Quantum network.
|
||||||
|
|
||||||
|
Fake: Handles life-cycle management of Quantum network,
|
||||||
|
leverages AMPP on NOS
|
||||||
|
(for use by unit tests, avoids touching any hardware)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def connect(self, host, username, password):
|
||||||
|
"""Connect via SSH and initialize the NETCONF session."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_network(self, host, username, password, net_id):
|
||||||
|
"""Creates a new virtual network."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete_network(self, host, username, password, net_id):
|
||||||
|
"""Deletes a virtual network."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def associate_mac_to_network(self, host, username, password,
|
||||||
|
net_id, mac):
|
||||||
|
"""Associates a MAC address to virtual network."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def dissociate_mac_from_network(self, host, username, password,
|
||||||
|
net_id, mac):
|
||||||
|
"""Dissociates a MAC address from virtual network."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_vlan_interface(self, mgr, vlan_id):
|
||||||
|
"""Configures a VLAN interface."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete_vlan_interface(self, mgr, vlan_id):
|
||||||
|
"""Deletes a VLAN interface."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_port_profiles(self, mgr):
|
||||||
|
"""Retrieves all port profiles."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_port_profile(self, mgr, name):
|
||||||
|
"""Retrieves a port profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_port_profile(self, mgr, name):
|
||||||
|
"""Creates a port profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete_port_profile(self, mgr, name):
|
||||||
|
"""Deletes a port profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def activate_port_profile(self, mgr, name):
|
||||||
|
"""Activates a port profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def deactivate_port_profile(self, mgr, name):
|
||||||
|
"""Deactivates a port profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def associate_mac_to_port_profile(self, mgr, name, mac_address):
|
||||||
|
"""Associates a MAC address to a port profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def dissociate_mac_from_port_profile(self, mgr, name, mac_address):
|
||||||
|
"""Dissociates a MAC address from a port profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_vlan_profile_for_port_profile(self, mgr, name):
|
||||||
|
"""Creates VLAN sub-profile for port profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def configure_l2_mode_for_vlan_profile(self, mgr, name):
|
||||||
|
"""Configures L2 mode for VLAN sub-profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def configure_trunk_mode_for_vlan_profile(self, mgr, name):
|
||||||
|
"""Configures trunk mode for VLAN sub-profile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def configure_allowed_vlans_for_vlan_profile(self, mgr, name, vlan_id):
|
||||||
|
"""Configures allowed VLANs for VLAN sub-profile."""
|
||||||
|
pass
|
204
quantum/plugins/brocade/nos/nctemplates.py
Normal file
204
quantum/plugins/brocade/nos/nctemplates.py
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 Brocade Communications 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.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
||||||
|
# Shiv Haris (sharis@brocade.com)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
NOS NETCONF XML Configuration Command Templates
|
||||||
|
Interface Configuration Commands
|
||||||
|
"""
|
||||||
|
# Create VLAN (vlan_id)
|
||||||
|
CREATE_VLAN_INTERFACE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<interface-vlan xmlns="urn:brocade.com:mgmt:brocade-interface">
|
||||||
|
<interface>
|
||||||
|
<vlan>
|
||||||
|
<name>{vlan_id}</name>
|
||||||
|
</vlan>
|
||||||
|
</interface>
|
||||||
|
</interface-vlan>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Delete VLAN (vlan_id)
|
||||||
|
DELETE_VLAN_INTERFACE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<interface-vlan xmlns="urn:brocade.com:mgmt:brocade-interface">
|
||||||
|
<interface>
|
||||||
|
<vlan operation="delete">
|
||||||
|
<name>{vlan_id}</name>
|
||||||
|
</vlan>
|
||||||
|
</interface>
|
||||||
|
</interface-vlan>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
#
|
||||||
|
# AMPP Life-cycle Management Configuration Commands
|
||||||
|
#
|
||||||
|
|
||||||
|
# Create AMPP port-profile (port_profile_name)
|
||||||
|
CREATE_PORT_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
||||||
|
<name>{name}</name>
|
||||||
|
</port-profile>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create VLAN sub-profile for port-profile (port_profile_name)
|
||||||
|
CREATE_VLAN_PROFILE_FOR_PORT_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
||||||
|
<name>{name}</name>
|
||||||
|
<vlan-profile/>
|
||||||
|
</port-profile>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Configure L2 mode for VLAN sub-profile (port_profile_name)
|
||||||
|
CONFIGURE_L2_MODE_FOR_VLAN_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
||||||
|
<name>{name}</name>
|
||||||
|
<vlan-profile>
|
||||||
|
<switchport/>
|
||||||
|
</vlan-profile>
|
||||||
|
</port-profile>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Configure trunk mode for VLAN sub-profile (port_profile_name)
|
||||||
|
CONFIGURE_TRUNK_MODE_FOR_VLAN_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
||||||
|
<name>{name}</name>
|
||||||
|
<vlan-profile>
|
||||||
|
<switchport>
|
||||||
|
<mode>
|
||||||
|
<vlan-mode>trunk</vlan-mode>
|
||||||
|
</mode>
|
||||||
|
</switchport>
|
||||||
|
</vlan-profile>
|
||||||
|
</port-profile>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Configure allowed VLANs for VLAN sub-profile
|
||||||
|
# (port_profile_name, allowed_vlan, native_vlan)
|
||||||
|
CONFIGURE_ALLOWED_VLANS_FOR_VLAN_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
||||||
|
<name>{name}</name>
|
||||||
|
<vlan-profile>
|
||||||
|
<switchport>
|
||||||
|
<trunk>
|
||||||
|
<allowed>
|
||||||
|
<vlan>
|
||||||
|
<add>{vlan_id}</add>
|
||||||
|
</vlan>
|
||||||
|
</allowed>
|
||||||
|
<native-vlan>{vlan_id}</native-vlan>
|
||||||
|
</trunk>
|
||||||
|
</switchport>
|
||||||
|
</vlan-profile>
|
||||||
|
</port-profile>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Delete port-profile (port_profile_name)
|
||||||
|
DELETE_PORT_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile
|
||||||
|
xmlns="urn:brocade.com:mgmt:brocade-port-profile" operation="delete">
|
||||||
|
<name>{name}</name>
|
||||||
|
</port-profile>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Activate port-profile (port_profile_name)
|
||||||
|
ACTIVATE_PORT_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
||||||
|
<port-profile>
|
||||||
|
<name>{name}</name>
|
||||||
|
<activate/>
|
||||||
|
</port-profile>
|
||||||
|
</port-profile-global>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Deactivate port-profile (port_profile_name)
|
||||||
|
DEACTIVATE_PORT_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
||||||
|
<port-profile>
|
||||||
|
<name>{name}</name>
|
||||||
|
<activate
|
||||||
|
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete" />
|
||||||
|
</port-profile>
|
||||||
|
</port-profile-global>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Associate MAC address to port-profile (port_profile_name, mac_address)
|
||||||
|
ASSOCIATE_MAC_TO_PORT_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
||||||
|
<port-profile>
|
||||||
|
<name>{name}</name>
|
||||||
|
<static>
|
||||||
|
<mac-address>{mac_address}</mac-address>
|
||||||
|
</static>
|
||||||
|
</port-profile>
|
||||||
|
</port-profile-global>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Dissociate MAC address from port-profile (port_profile_name, mac_address)
|
||||||
|
DISSOCIATE_MAC_FROM_PORT_PROFILE = """
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
||||||
|
<port-profile>
|
||||||
|
<name>{name}</name>
|
||||||
|
<static
|
||||||
|
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete">
|
||||||
|
<mac-address>{mac_address}</mac-address>
|
||||||
|
</static>
|
||||||
|
</port-profile>
|
||||||
|
</port-profile-global>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
|
||||||
|
#
|
||||||
|
# Custom RPC Commands
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Constants
|
||||||
|
#
|
||||||
|
|
||||||
|
# Port profile naming convention for Quantum networks
|
||||||
|
OS_PORT_PROFILE_NAME = "openstack-profile-{id}"
|
||||||
|
|
||||||
|
# Port profile filter expressions
|
||||||
|
PORT_PROFILE_XPATH_FILTER = "/port-profile"
|
||||||
|
PORT_PROFILE_NAME_XPATH_FILTER = "/port-profile[name='{name}']"
|
202
quantum/plugins/brocade/nos/nosdriver.py
Normal file
202
quantum/plugins/brocade/nos/nosdriver.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 Brocade Communications System, 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.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
||||||
|
# Shiv Haris (sharis@brocade.com)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Brocade NOS Driver implements NETCONF over SSHv2 for
|
||||||
|
Quantum network life-cycle management
|
||||||
|
"""
|
||||||
|
from ncclient import manager
|
||||||
|
|
||||||
|
from quantum.openstack.common import log as logging
|
||||||
|
from quantum.plugins.brocade.nos import nctemplates as template
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
SSH_PORT = 22
|
||||||
|
|
||||||
|
|
||||||
|
def nos_unknown_host_cb(host, fingerprint):
|
||||||
|
"""An unknown host callback.
|
||||||
|
|
||||||
|
Returns `True` if it finds the key acceptable,
|
||||||
|
and `False` if not. This default callback for NOS always returns 'True'
|
||||||
|
(i.e. trusts all hosts for now).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class NOSdriver():
|
||||||
|
"""NOS NETCONF interface driver for Quantum network.
|
||||||
|
|
||||||
|
Handles life-cycle management of Quantum network (leverages AMPP on NOS)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def connect(self, host, username, password):
|
||||||
|
"""Connect via SSH and initialize the NETCONF session."""
|
||||||
|
try:
|
||||||
|
mgr = manager.connect(host=host, port=SSH_PORT,
|
||||||
|
username=username, password=password,
|
||||||
|
unknown_host_cb=nos_unknown_host_cb)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.debug(_("Connect failed to switch: %s"), e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
LOG.debug(_("Connect success to host %s:%d"), host, SSH_PORT)
|
||||||
|
return mgr
|
||||||
|
|
||||||
|
def create_network(self, host, username, password, net_id):
|
||||||
|
"""Creates a new virtual network."""
|
||||||
|
|
||||||
|
name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
|
||||||
|
with self.connect(host, username, password) as mgr:
|
||||||
|
self.create_vlan_interface(mgr, net_id)
|
||||||
|
self.create_port_profile(mgr, name)
|
||||||
|
self.create_vlan_profile_for_port_profile(mgr, name)
|
||||||
|
self.configure_l2_mode_for_vlan_profile(mgr, name)
|
||||||
|
self.configure_trunk_mode_for_vlan_profile(mgr, name)
|
||||||
|
self.configure_allowed_vlans_for_vlan_profile(mgr, name, net_id)
|
||||||
|
self.activate_port_profile(mgr, name)
|
||||||
|
|
||||||
|
def delete_network(self, host, username, password, net_id):
|
||||||
|
"""Deletes a virtual network."""
|
||||||
|
|
||||||
|
name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
|
||||||
|
with self.connect(host, username, password) as mgr:
|
||||||
|
self.deactivate_port_profile(mgr, name)
|
||||||
|
self.delete_port_profile(mgr, name)
|
||||||
|
self.delete_vlan_interface(mgr, net_id)
|
||||||
|
|
||||||
|
def associate_mac_to_network(self, host, username, password,
|
||||||
|
net_id, mac):
|
||||||
|
"""Associates a MAC address to virtual network."""
|
||||||
|
|
||||||
|
name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
|
||||||
|
with self.connect(host, username, password) as mgr:
|
||||||
|
self.associate_mac_to_port_profile(mgr, name, mac)
|
||||||
|
|
||||||
|
def dissociate_mac_from_network(self, host, username, password,
|
||||||
|
net_id, mac):
|
||||||
|
"""Dissociates a MAC address from virtual network."""
|
||||||
|
|
||||||
|
name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
|
||||||
|
with self.connect(host, username, password) as mgr:
|
||||||
|
self.dissociate_mac_from_port_profile(mgr, name, mac)
|
||||||
|
|
||||||
|
def create_vlan_interface(self, mgr, vlan_id):
|
||||||
|
"""Configures a VLAN interface."""
|
||||||
|
|
||||||
|
confstr = template.CREATE_VLAN_INTERFACE.format(vlan_id=vlan_id)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def delete_vlan_interface(self, mgr, vlan_id):
|
||||||
|
"""Deletes a VLAN interface."""
|
||||||
|
|
||||||
|
confstr = template.DELETE_VLAN_INTERFACE.format(vlan_id=vlan_id)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def get_port_profiles(self, mgr):
|
||||||
|
"""Retrieves all port profiles."""
|
||||||
|
|
||||||
|
filterstr = template.PORT_PROFILE_XPATH_FILTER
|
||||||
|
response = mgr.get_config(source='running',
|
||||||
|
filter=('xpath', filterstr)).data_xml
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_port_profile(self, mgr, name):
|
||||||
|
"""Retrieves a port profile."""
|
||||||
|
|
||||||
|
filterstr = template.PORT_PROFILE_NAME_XPATH_FILTER.format(name=name)
|
||||||
|
response = mgr.get_config(source='running',
|
||||||
|
filter=('xpath', filterstr)).data_xml
|
||||||
|
return response
|
||||||
|
|
||||||
|
def create_port_profile(self, mgr, name):
|
||||||
|
"""Creates a port profile."""
|
||||||
|
|
||||||
|
confstr = template.CREATE_PORT_PROFILE.format(name=name)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def delete_port_profile(self, mgr, name):
|
||||||
|
"""Deletes a port profile."""
|
||||||
|
|
||||||
|
confstr = template.DELETE_PORT_PROFILE.format(name=name)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def activate_port_profile(self, mgr, name):
|
||||||
|
"""Activates a port profile."""
|
||||||
|
|
||||||
|
confstr = template.ACTIVATE_PORT_PROFILE.format(name=name)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def deactivate_port_profile(self, mgr, name):
|
||||||
|
"""Deactivates a port profile."""
|
||||||
|
|
||||||
|
confstr = template.DEACTIVATE_PORT_PROFILE.format(name=name)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def associate_mac_to_port_profile(self, mgr, name, mac_address):
|
||||||
|
"""Associates a MAC address to a port profile."""
|
||||||
|
|
||||||
|
confstr = template.ASSOCIATE_MAC_TO_PORT_PROFILE.format(
|
||||||
|
name=name, mac_address=mac_address)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def dissociate_mac_from_port_profile(self, mgr, name, mac_address):
|
||||||
|
"""Dissociates a MAC address from a port profile."""
|
||||||
|
|
||||||
|
confstr = template.DISSOCIATE_MAC_FROM_PORT_PROFILE.format(
|
||||||
|
name=name, mac_address=mac_address)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def create_vlan_profile_for_port_profile(self, mgr, name):
|
||||||
|
"""Creates VLAN sub-profile for port profile."""
|
||||||
|
|
||||||
|
confstr = template.CREATE_VLAN_PROFILE_FOR_PORT_PROFILE.format(
|
||||||
|
name=name)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def configure_l2_mode_for_vlan_profile(self, mgr, name):
|
||||||
|
"""Configures L2 mode for VLAN sub-profile."""
|
||||||
|
|
||||||
|
confstr = template.CONFIGURE_L2_MODE_FOR_VLAN_PROFILE.format(
|
||||||
|
name=name)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def configure_trunk_mode_for_vlan_profile(self, mgr, name):
|
||||||
|
"""Configures trunk mode for VLAN sub-profile."""
|
||||||
|
|
||||||
|
confstr = template.CONFIGURE_TRUNK_MODE_FOR_VLAN_PROFILE.format(
|
||||||
|
name=name)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
||||||
|
|
||||||
|
def configure_allowed_vlans_for_vlan_profile(self, mgr, name, vlan_id):
|
||||||
|
"""Configures allowed VLANs for VLAN sub-profile."""
|
||||||
|
|
||||||
|
confstr = template.CONFIGURE_ALLOWED_VLANS_FOR_VLAN_PROFILE.format(
|
||||||
|
name=name, vlan_id=vlan_id)
|
||||||
|
mgr.edit_config(target='running', config=confstr)
|
24
quantum/plugins/brocade/tests/README
Normal file
24
quantum/plugins/brocade/tests/README
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Start the quantum-server with IP address of switch configured in brocade.ini:
|
||||||
|
(for configuration instruction please see README.md in the above directory)
|
||||||
|
|
||||||
|
nostest.py:
|
||||||
|
This tests two things:
|
||||||
|
1. Creates port-profile on the physical switch when a quantum 'network' is created
|
||||||
|
2. Associates the MAC address with the created port-profile
|
||||||
|
|
||||||
|
noscli.py:
|
||||||
|
CLI interface to create/delete/associate MAC/dissociate MAC
|
||||||
|
Commands:
|
||||||
|
% noscli.py create <network>
|
||||||
|
(after running check that PP is created on the switch)
|
||||||
|
|
||||||
|
% noscli.py delete <network>
|
||||||
|
(after running check that PP is deleted from the switch)
|
||||||
|
|
||||||
|
% noscli.py associate <network> <mac>
|
||||||
|
(after running check that MAC is associated with PP)
|
||||||
|
|
||||||
|
% noscli.py dissociate <network> <mac>
|
||||||
|
(after running check that MAC is dissociated from the PP)
|
||||||
|
|
||||||
|
|
93
quantum/plugins/brocade/tests/noscli.py
Normal file
93
quantum/plugins/brocade/tests/noscli.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 Brocade Communications 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.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
||||||
|
# Shiv Haris (sharis@brocade.com)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Brocade NOS Driver CLI
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from quantum.openstack.common import log as logging
|
||||||
|
from quantum.plugins.brocade.nos import nosdriver as nos
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NOSCli(object):
|
||||||
|
|
||||||
|
def __init__(self, host, username, password):
|
||||||
|
self.host = host
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.driver = nos.NOSdriver()
|
||||||
|
|
||||||
|
def execute(self, cmd):
|
||||||
|
numargs = len(args.otherargs)
|
||||||
|
|
||||||
|
if args.cmd == 'create' and numargs == 1:
|
||||||
|
self._create(args.otherargs[0])
|
||||||
|
elif args.cmd == 'delete' and numargs == 1:
|
||||||
|
self._delete(args.otherargs[0])
|
||||||
|
elif args.cmd == 'associate' and numargs == 2:
|
||||||
|
self._associate(args.otherargs[0], args.otherargs[1])
|
||||||
|
elif args.cmd == 'dissociate' and numargs == 2:
|
||||||
|
self._dissociate(args.otherargs[0], args.otherargs[1])
|
||||||
|
else:
|
||||||
|
print usage_desc
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
def _create(self, net_id):
|
||||||
|
self.driver.create_network(self.host, self.username, self.password,
|
||||||
|
net_id)
|
||||||
|
|
||||||
|
def _delete(self, net_id):
|
||||||
|
self.driver.delete_network(self.host, self.username, self.password,
|
||||||
|
net_id)
|
||||||
|
|
||||||
|
def _associate(self, net_id, mac):
|
||||||
|
self.driver.associate_mac_to_network(
|
||||||
|
self.host, self.username, self.password, net_id, mac)
|
||||||
|
|
||||||
|
def _dissociate(self, net_id, mac):
|
||||||
|
self.driver.dissociate_mac_from_network(
|
||||||
|
self.host, self.username, self.password, net_id, mac)
|
||||||
|
|
||||||
|
|
||||||
|
usage_desc = """
|
||||||
|
Command descriptions:
|
||||||
|
|
||||||
|
create <id>
|
||||||
|
delete <id>
|
||||||
|
associate <id> <mac>
|
||||||
|
dissociate <id> <mac>
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='process args',
|
||||||
|
usage=usage_desc, epilog='foo bar help')
|
||||||
|
parser.add_argument('--ip', default='localhost')
|
||||||
|
parser.add_argument('--username', default='admin')
|
||||||
|
parser.add_argument('--password', default='password')
|
||||||
|
parser.add_argument('cmd')
|
||||||
|
parser.add_argument('otherargs', nargs='*')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
noscli = NOSCli(args.ip, args.username, args.password)
|
||||||
|
noscli.execute(args.cmd)
|
48
quantum/plugins/brocade/tests/nostest.py
Normal file
48
quantum/plugins/brocade/tests/nostest.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Copyright (c) 2013 Brocade Communications 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.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
||||||
|
# Shiv Haris (sharis@brocade.com)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Brocade NOS Driver Test
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from quantum.plugins.brocade.nos import nosdriver as nos
|
||||||
|
|
||||||
|
|
||||||
|
def nostest(host, username, password):
|
||||||
|
# Driver
|
||||||
|
driver = nos.NOSdriver()
|
||||||
|
|
||||||
|
# Quantum operations
|
||||||
|
vlan = 1001
|
||||||
|
mac = '0050.56bf.0001'
|
||||||
|
driver.create_network(host, username, password, vlan)
|
||||||
|
driver.associate_mac_to_network(host, username, password, vlan, mac)
|
||||||
|
driver.dissociate_mac_from_network(host, username, password, vlan, mac)
|
||||||
|
driver.delete_network(host, username, password, vlan)
|
||||||
|
|
||||||
|
# AMPP enumeration
|
||||||
|
with driver.connect(host, username, password) as mgr:
|
||||||
|
print driver.get_port_profiles(mgr)
|
||||||
|
print driver.get_port_profile(mgr, 'default')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
nostest(sys.argv[1], sys.argv[2], sys.argv[3])
|
61
quantum/plugins/brocade/vlanbm.py
Normal file
61
quantum/plugins/brocade/vlanbm.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 Brocade Communications System, 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.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Shiv Haris (sharis@brocade.com)
|
||||||
|
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
A Vlan Bitmap class to handle allocation/de-allocation of vlan ids.
|
||||||
|
"""
|
||||||
|
from quantum.plugins.brocade.db import models as brocade_db
|
||||||
|
|
||||||
|
|
||||||
|
MIN_VLAN = 2
|
||||||
|
MAX_VLAN = 4094
|
||||||
|
|
||||||
|
|
||||||
|
class VlanBitmap(object):
|
||||||
|
"""Setup a vlan bitmap for allocation/de-allocation."""
|
||||||
|
|
||||||
|
# Keep track of the vlans that have been allocated/de-allocated
|
||||||
|
# uses a bitmap to do this
|
||||||
|
|
||||||
|
def __init__(self, ctxt):
|
||||||
|
"""initialize the vlan as a set."""
|
||||||
|
self.vlans = set(int(net['vlan'])
|
||||||
|
for net in brocade_db.get_networks(ctxt)
|
||||||
|
if net['vlan']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_next_vlan(self, vlan_id=None):
|
||||||
|
"""try to get a specific vlan if requested
|
||||||
|
or get the next vlan.
|
||||||
|
"""
|
||||||
|
min_vlan_search = vlan_id or MIN_VLAN
|
||||||
|
max_vlan_search = (vlan_id and vlan_id + 1) or MAX_VLAN
|
||||||
|
|
||||||
|
for vlan in xrange(min_vlan_search, max_vlan_search):
|
||||||
|
if vlan not in self.vlans:
|
||||||
|
self.vlans.add(vlan)
|
||||||
|
return vlan
|
||||||
|
|
||||||
|
def release_vlan(self, vlan_id):
|
||||||
|
"""return the vlan to the pool."""
|
||||||
|
if vlan_id in self.vlans:
|
||||||
|
self.vlans.remove(vlan_id)
|
17
quantum/tests/unit/brocade/__init__.py
Normal file
17
quantum/tests/unit/brocade/__init__.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 OpenStack LLC.
|
||||||
|
#
|
||||||
|
# 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.
|
98
quantum/tests/unit/brocade/test_brocade_db.py
Normal file
98
quantum/tests/unit/brocade/test_brocade_db.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 OpenStack, LLC.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Unit test brocade db.
|
||||||
|
"""
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from quantum import context
|
||||||
|
from quantum.plugins.brocade.db import models as brocade_db
|
||||||
|
from quantum.tests.unit import test_db_plugin as test_plugin
|
||||||
|
|
||||||
|
TEST_VLAN = 1000
|
||||||
|
|
||||||
|
|
||||||
|
class TestBrocadeDb(test_plugin.QuantumDbPluginV2TestCase):
|
||||||
|
"""Test brocade db functionality"""
|
||||||
|
|
||||||
|
def test_create_network(self):
|
||||||
|
"""Test brocade specific network db."""
|
||||||
|
|
||||||
|
net_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# Create a network
|
||||||
|
self.context = context.get_admin_context()
|
||||||
|
brocade_db.create_network(self.context, net_id, TEST_VLAN)
|
||||||
|
|
||||||
|
# Get the network and verify
|
||||||
|
net = brocade_db.get_network(self.context, net_id)
|
||||||
|
self.assertEqual(net['id'], net_id)
|
||||||
|
self.assertEqual(int(net['vlan']), TEST_VLAN)
|
||||||
|
|
||||||
|
# Delete the network
|
||||||
|
brocade_db.delete_network(self.context, net['id'])
|
||||||
|
|
||||||
|
def test_create_port(self):
|
||||||
|
"""Test brocade specific port db."""
|
||||||
|
|
||||||
|
net_id = str(uuid.uuid4())
|
||||||
|
port_id = str(uuid.uuid4())
|
||||||
|
# port_id is truncated: since the linux-bridge tap device names are
|
||||||
|
# based on truncated port id, this enables port lookups using
|
||||||
|
# tap devices
|
||||||
|
port_id = port_id[0:11]
|
||||||
|
tenant_id = str(uuid.uuid4())
|
||||||
|
admin_state_up = True
|
||||||
|
|
||||||
|
# Create Port
|
||||||
|
|
||||||
|
# To create a port a network must exists, Create a network
|
||||||
|
self.context = context.get_admin_context()
|
||||||
|
brocade_db.create_network(self.context, net_id, TEST_VLAN)
|
||||||
|
|
||||||
|
physical_interface = "em1"
|
||||||
|
brocade_db.create_port(self.context, port_id, net_id,
|
||||||
|
physical_interface,
|
||||||
|
TEST_VLAN, tenant_id, admin_state_up)
|
||||||
|
|
||||||
|
port = brocade_db.get_port(self.context, port_id)
|
||||||
|
self.assertEqual(port['port_id'], port_id)
|
||||||
|
self.assertEqual(port['network_id'], net_id)
|
||||||
|
self.assertEqual(port['physical_interface'], physical_interface)
|
||||||
|
self.assertEqual(int(port['vlan_id']), TEST_VLAN)
|
||||||
|
self.assertEqual(port['tenant_id'], tenant_id)
|
||||||
|
self.assertEqual(port['admin_state_up'], admin_state_up)
|
||||||
|
|
||||||
|
admin_state_up = True
|
||||||
|
brocade_db.update_port_state(self.context, port_id, admin_state_up)
|
||||||
|
port = brocade_db.get_port(self.context, port_id)
|
||||||
|
self.assertEqual(port['admin_state_up'], admin_state_up)
|
||||||
|
|
||||||
|
admin_state_up = False
|
||||||
|
brocade_db.update_port_state(self.context, port_id, admin_state_up)
|
||||||
|
port = brocade_db.get_port(self.context, port_id)
|
||||||
|
self.assertEqual(port['admin_state_up'], admin_state_up)
|
||||||
|
|
||||||
|
admin_state_up = True
|
||||||
|
brocade_db.update_port_state(self.context, port_id, admin_state_up)
|
||||||
|
port = brocade_db.get_port(self.context, port_id)
|
||||||
|
self.assertEqual(port['admin_state_up'], admin_state_up)
|
||||||
|
|
||||||
|
# Delete Port
|
||||||
|
brocade_db.delete_port(self.context, port_id)
|
74
quantum/tests/unit/brocade/test_brocade_plugin.py
Normal file
74
quantum/tests/unit/brocade/test_brocade_plugin.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Copyright (c) 2012 OpenStack, LLC.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from quantum.extensions import portbindings
|
||||||
|
from quantum.openstack.common import importutils
|
||||||
|
from quantum.plugins.brocade import QuantumPlugin as brocade_plugin
|
||||||
|
from quantum.tests.unit import _test_extension_portbindings as test_bindings
|
||||||
|
from quantum.tests.unit import test_db_plugin as test_plugin
|
||||||
|
|
||||||
|
|
||||||
|
PLUGIN_NAME = ('quantum.plugins.brocade.'
|
||||||
|
'QuantumPlugin.BrocadePluginV2')
|
||||||
|
NOS_DRIVER = ('quantum.plugins.brocade.'
|
||||||
|
'nos.fake_nosdriver.NOSdriver')
|
||||||
|
FAKE_IPADDRESS = '2.2.2.2'
|
||||||
|
FAKE_USERNAME = 'user'
|
||||||
|
FAKE_PASSWORD = 'password'
|
||||||
|
FAKE_PHYSICAL_INTERFACE = 'em1'
|
||||||
|
|
||||||
|
|
||||||
|
class BrocadePluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase):
|
||||||
|
_plugin_name = PLUGIN_NAME
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
|
||||||
|
def mocked_brocade_init(self):
|
||||||
|
|
||||||
|
self._switch = {'address': FAKE_IPADDRESS,
|
||||||
|
'username': FAKE_USERNAME,
|
||||||
|
'password': FAKE_PASSWORD
|
||||||
|
}
|
||||||
|
self._driver = importutils.import_object(NOS_DRIVER)
|
||||||
|
|
||||||
|
with mock.patch.object(brocade_plugin.BrocadePluginV2,
|
||||||
|
'brocade_init', new=mocked_brocade_init):
|
||||||
|
super(BrocadePluginV2TestCase, self).setUp(self._plugin_name)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBrocadeBasicGet(test_plugin.TestBasicGet,
|
||||||
|
BrocadePluginV2TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestBrocadeV2HTTPResponse(test_plugin.TestV2HTTPResponse,
|
||||||
|
BrocadePluginV2TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestBrocadePortsV2(test_plugin.TestPortsV2,
|
||||||
|
BrocadePluginV2TestCase,
|
||||||
|
test_bindings.PortBindingsTestCase):
|
||||||
|
|
||||||
|
VIF_TYPE = portbindings.VIF_TYPE_BRIDGE
|
||||||
|
HAS_PORT_FILTER = True
|
||||||
|
|
||||||
|
|
||||||
|
class TestBrocadeNetworksV2(test_plugin.TestNetworksV2,
|
||||||
|
BrocadePluginV2TestCase):
|
||||||
|
pass
|
74
quantum/tests/unit/brocade/test_brocade_vlan.py
Normal file
74
quantum/tests/unit/brocade/test_brocade_vlan.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 OpenStack, LLC.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test vlans alloc/dealloc.
|
||||||
|
"""
|
||||||
|
import unittest2 as unittest
|
||||||
|
|
||||||
|
from quantum.db import api as db
|
||||||
|
from quantum.openstack.common import context
|
||||||
|
from quantum.plugins.brocade import vlanbm as vlan_bitmap
|
||||||
|
|
||||||
|
|
||||||
|
class TestVlanBitmap(unittest.TestCase):
|
||||||
|
"""exercise Vlan bitmap ."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
db.configure_db()
|
||||||
|
self.context = context.get_admin_context()
|
||||||
|
self.context.session = db.get_session()
|
||||||
|
|
||||||
|
def test_vlan(self):
|
||||||
|
"""test vlan allocation/de-alloc."""
|
||||||
|
|
||||||
|
self.vbm_ = vlan_bitmap.VlanBitmap(self.context)
|
||||||
|
vlan_id = self.vbm_.get_next_vlan(None)
|
||||||
|
|
||||||
|
# First vlan is always 2
|
||||||
|
self.assertEqual(vlan_id, 2)
|
||||||
|
|
||||||
|
# next vlan is always 3
|
||||||
|
vlan_id = self.vbm_.get_next_vlan(None)
|
||||||
|
self.assertEqual(vlan_id, 3)
|
||||||
|
|
||||||
|
# get a specific vlan i.e. 4
|
||||||
|
vlan_id = self.vbm_.get_next_vlan(4)
|
||||||
|
self.assertEqual(vlan_id, 4)
|
||||||
|
|
||||||
|
# get a specific vlan i.e. 5
|
||||||
|
vlan_id = self.vbm_.get_next_vlan(5)
|
||||||
|
self.assertEqual(vlan_id, 5)
|
||||||
|
|
||||||
|
# Skip 6
|
||||||
|
|
||||||
|
# get a specific vlan i.e. 7
|
||||||
|
vlan_id = self.vbm_.get_next_vlan(7)
|
||||||
|
self.assertEqual(vlan_id, 7)
|
||||||
|
|
||||||
|
# get a specific vlan i.e. 1900
|
||||||
|
vlan_id = self.vbm_.get_next_vlan(1900)
|
||||||
|
self.assertEqual(vlan_id, 1900)
|
||||||
|
|
||||||
|
# Release 4 and get next again
|
||||||
|
self.vbm_.release_vlan(4)
|
||||||
|
vlan_id = self.vbm_.get_next_vlan(None)
|
||||||
|
self.assertEqual(vlan_id, 4)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
db.clear_db()
|
3
setup.py
3
setup.py
@ -45,6 +45,7 @@ init_path = 'etc/init.d'
|
|||||||
rootwrap_path = 'etc/quantum/rootwrap.d'
|
rootwrap_path = 'etc/quantum/rootwrap.d'
|
||||||
ovs_plugin_config_path = 'etc/quantum/plugins/openvswitch'
|
ovs_plugin_config_path = 'etc/quantum/plugins/openvswitch'
|
||||||
bigswitch_plugin_config_path = 'etc/quantum/plugins/bigswitch'
|
bigswitch_plugin_config_path = 'etc/quantum/plugins/bigswitch'
|
||||||
|
brocade_plugin_config_path = 'etc/quantum/plugins/brocade'
|
||||||
cisco_plugin_config_path = 'etc/quantum/plugins/cisco'
|
cisco_plugin_config_path = 'etc/quantum/plugins/cisco'
|
||||||
linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
|
linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
|
||||||
nvp_plugin_config_path = 'etc/quantum/plugins/nicira'
|
nvp_plugin_config_path = 'etc/quantum/plugins/nicira'
|
||||||
@ -94,6 +95,8 @@ else:
|
|||||||
'etc/quantum/plugins/cisco/db_conn.ini']),
|
'etc/quantum/plugins/cisco/db_conn.ini']),
|
||||||
(bigswitch_plugin_config_path,
|
(bigswitch_plugin_config_path,
|
||||||
['etc/quantum/plugins/bigswitch/restproxy.ini']),
|
['etc/quantum/plugins/bigswitch/restproxy.ini']),
|
||||||
|
(brocade_plugin_config_path,
|
||||||
|
['etc/quantum/plugins/brocade/brocade.ini']),
|
||||||
(linuxbridge_plugin_config_path,
|
(linuxbridge_plugin_config_path,
|
||||||
['etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini']),
|
['etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini']),
|
||||||
(nvp_plugin_config_path,
|
(nvp_plugin_config_path,
|
||||||
|
Loading…
Reference in New Issue
Block a user