Merge "Adds Brocade Plugin implementation"

This commit is contained in:
Jenkins 2013-02-14 23:03:18 +00:00 committed by Gerrit Code Review
commit 6f627015b6
19 changed files with 1829 additions and 0 deletions

View 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

View 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

View 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)

View 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.

View 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.

View 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})

View 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.

View 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

View 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}']"

View 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)

View 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)

View 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)

View 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])

View 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)

View 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.

View 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)

View 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

View 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()

View File

@ -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,