blueprint mellanox-quantum-plugin

Implements Mellanox Quantum plugin.
This plugin implements Quantum v2 APIs with support for Mellanox embedded
switch functionality as part of the VPI (Ethernet/InfiniBand) HCA.

Change-Id: I22907dfec5b6cb8f6ad8c3b6e390abc4f8e0ac10
This commit is contained in:
Irena Berezovsky 2013-03-11 12:10:15 +02:00
parent 8581676fea
commit 94492767e0
23 changed files with 2134 additions and 0 deletions

25
bin/quantum-mlnx-agent Normal file
View File

@ -0,0 +1,25 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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 os
import sys
sys.path.insert(0, os.getcwd())
from quantum.plugins.mlnx.agent.eswitch_quantum_agent import main
main()

View File

@ -0,0 +1,59 @@
[MLNX]
# (StrOpt) Type of network to allocate for tenant networks. The
# default value is 'vlan' You MUST configure network_vlan_ranges below
# in order for tenant networks to provide connectivity between hosts.
# Set to 'none' to disable creation of tenant networks.
#
# Default: tenant_network_type = vlan
# Example: tenant_network_type = vlan
# (ListOpt) Comma-separated list of
# <physical_network>[:<vlan_min>:<vlan_max>] tuples enumerating ranges
# of VLAN IDs on named physical networks that are available for
# allocation. All physical networks listed are available for flat and
# VLAN provider network creation. Specified ranges of VLAN IDs are
# available for tenant network allocation if tenant_network_type is
# 'vlan'. If empty, only local networks may be created.
#
# Default: network_vlan_ranges =
# Example: network_vlan_ranges = default:1:100
[DATABASE]
# This line MUST be changed to actually run the plugin.
# Example:
# sql_connection = mysql://root:nova@127.0.0.1:3306/quantum_linux_bridge
# Replace 127.0.0.1 above with the IP address of the database used by the
# main quantum server. (Leave it as is if the database runs on this host.)
sql_connection = sqlite://
# Database reconnection retry times - in event connectivity is lost
# set to -1 implies an infinite retry count
# sql_max_retries = 10
# Database reconnection interval in seconds - in event connectivity is lost
reconnect_interval = 2
[ESWITCH]
# (ListOpt) Comma-separated list of
# <physical_network>:<physical_interface> tuples mapping physical
# network names to the agent's node-specific physical network
# interfaces to be used for flat and VLAN networks. All physical
# networks listed in network_vlan_ranges on the server should have
# mappings to appropriate interfaces on each agent.
#
# Default: physical_interface_mappings =
# Example: physical_interface_mappings = default:eth2
# (StrOpt) Type of Network Interface to allocate for VM:
# direct or hosdev according to libvirt terminology
# Default: vnic_type = direct
# (StrOpt) Eswitch daemon end point connection url
# Default: daemon_endpoint = 'tcp://127.0.0.1:5001'
# The number of milliseconds the agent will wait for
# response on request to daemon
# Default: request_timeout = 3000
[AGENT]
# Agent's polling interval in seconds
polling_interval = 2

View File

@ -0,0 +1,8 @@
Mellanox Quantum Plugin
This plugin implements Quantum v2 APIs with support for
Mellanox embedded switch functionality as part of the
VPI (Ethernet/InfiniBand) HCA.
For more details on the plugin, please refer to the following link:
https://wiki.openstack.org/wiki/Mellanox-Quantum

View File

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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 Mellanox Technologies, Ltd
#
# 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,405 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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 socket
import sys
import time
import eventlet
from oslo.config import cfg
from quantum.agent import rpc as agent_rpc
from quantum.common import config as logging_config
from quantum.common import constants as q_constants
from quantum.common import topics
from quantum.common import utils as q_utils
from quantum import context
from quantum.openstack.common import log as logging
from quantum.openstack.common import loopingcall
from quantum.openstack.common.rpc import dispatcher
from quantum.plugins.mlnx.agent import utils
from quantum.plugins.mlnx.common import config # noqa
from quantum.plugins.mlnx.common import constants
from quantum.plugins.mlnx.common import exceptions
LOG = logging.getLogger(__name__)
class EswitchManager(object):
def __init__(self, interface_mappings, endpoint, timeout):
self.utils = utils.EswitchUtils(endpoint, timeout)
self.interface_mappings = interface_mappings
self.network_map = {}
self.utils.define_fabric_mappings(interface_mappings)
def get_port_id_by_mac(self, port_mac):
for network_id, data in self.network_map.iteritems():
for port in data['ports']:
if port['port_mac'] == port_mac:
return port['port_id']
err_msg = _("Agent cache inconsistency - port id "
"is not stored for %s") % port_mac
LOG.error(err_msg)
raise exceptions.MlnxException(err_msg)
def get_vnics_mac(self):
return set(self.utils.get_attached_vnics().keys())
def vnic_port_exists(self, port_mac):
return port_mac in self.utils.get_attached_vnics()
def remove_network(self, network_id):
if network_id in self.network_map:
del self.network_map[network_id]
else:
LOG.debug(_("Network %s not defined on Agent."), network_id)
def port_down(self, network_id, physical_network, port_mac):
"""Sets port to down.
Check internal network map for port data.
If port exists set port to Down
"""
for network_id, data in self.network_map.iteritems():
for port in data['ports']:
if port['port_mac'] == port_mac:
self.utils.port_down(physical_network, port_mac)
return
LOG.info(_('Network %s is not available on this agent'), network_id)
def port_up(self, network_id, network_type,
physical_network, seg_id, port_id, port_mac):
"""Sets port to up.
Update internal network map with port data.
-Check if vnic defined
- configure eswitch vport
- set port to Up
"""
LOG.debug(_("Connecting port %s"), port_id)
if network_id not in self.network_map:
self.provision_network(port_id, port_mac,
network_id, network_type,
physical_network, seg_id)
net_map = self.network_map[network_id]
net_map['ports'].append({'port_id': port_id, 'port_mac': port_mac})
if network_type == constants.TYPE_VLAN:
LOG.info(_('Binding VLAN ID %(seg_id)s'
'to eSwitch for vNIC mac_address %(mac)s'),
{'seg_id': seg_id,
'mac': port_mac})
self.utils.set_port_vlan_id(physical_network,
seg_id,
port_mac)
self.utils.port_up(physical_network, port_mac)
elif network_type == constants.TYPE_IB:
LOG.debug(_('Network Type IB currently not supported'))
else:
LOG.error(_('Unsupported network type %s'), network_type)
def port_release(self, port_mac):
"""Clear port configuration from eSwitch."""
for network_id, net_data in self.network_map.iteritems():
for port in net_data['ports']:
if port['port_mac'] == port_mac:
self.utils.port_release(net_data['physical_network'],
port['port_mac'])
return
LOG.info(_('Port_mac %s is not available on this agent'), port_mac)
def provision_network(self, port_id, port_mac,
network_id, network_type,
physical_network, segmentation_id):
LOG.info(_("Provisioning network %s"), network_id)
if network_type == constants.TYPE_VLAN:
LOG.debug(_("creating VLAN Network"))
elif network_type == constants.TYPE_IB:
LOG.debug(_("currently IB network provisioning is not supported"))
else:
LOG.error(_("Unknown network type %(network_type) "
"for network %(network_id)"),
{'network_type': network_type,
'network_id': network_id})
return
data = {
'physical_network': physical_network,
'network_type': network_type,
'ports': [],
'vlan_id': segmentation_id}
self.network_map[network_id] = data
class MlnxEswitchRpcCallbacks():
# Set RPC API version to 1.0 by default.
RPC_API_VERSION = '1.0'
def __init__(self, context, eswitch):
self.context = context
self.eswitch = eswitch
def network_delete(self, context, **kwargs):
LOG.debug(_("network_delete received"))
network_id = kwargs.get('network_id')
if not network_id:
LOG.warning(_("Invalid Network ID, cannot remove Network"))
else:
LOG.debug(_("Delete network %s"), network_id)
self.eswitch.remove_network(network_id)
def port_update(self, context, **kwargs):
LOG.debug(_("port_update received"))
port = kwargs.get('port')
vlan_id = kwargs.get('vlan_id')
physical_network = kwargs.get('physical_network')
net_type = kwargs.get('network_type')
net_id = port['network_id']
if self.eswitch.vnic_port_exists(port['mac_address']):
if port['admin_state_up']:
self.eswitch.port_up(net_id,
net_type,
physical_network,
vlan_id,
port['id'],
port['mac_address'])
else:
self.eswitch.port_down(net_id,
physical_network,
port['mac_address'])
else:
LOG.debug(_("No port %s defined on agent."), port['id'])
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 dispatcher.RpcDispatcher([self])
class MlnxEswitchQuantumAgent(object):
# Set RPC API version to 1.0 by default.
RPC_API_VERSION = '1.0'
def __init__(self, interface_mapping):
self._polling_interval = cfg.CONF.AGENT.polling_interval
self._setup_eswitches(interface_mapping)
self.agent_state = {
'binary': 'quantum-mlnx-agent',
'host': cfg.CONF.host,
'topic': q_constants.L2_AGENT_TOPIC,
'configurations': interface_mapping,
'agent_type': 'eSwitch agent',
'start_flag': True}
self._setup_rpc()
def _setup_eswitches(self, interface_mapping):
daemon = cfg.CONF.ESWITCH.daemon_endpoint
timeout = cfg.CONF.ESWITCH.request_timeout
self.eswitch = EswitchManager(interface_mapping, daemon, timeout)
def _report_state(self):
try:
devices = len(self.eswitch.get_vnics_mac())
self.agent_state['configurations']['devices'] = devices
self.state_rpc.report_state(self.context,
self.agent_state)
self.agent_state.pop('start_flag', None)
except Exception:
LOG.exception(_("Failed reporting state!"))
def _setup_rpc(self):
self.agent_id = 'mlnx-agent.%s' % socket.gethostname()
self.topic = topics.AGENT
self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN)
self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
# RPC network init
self.context = context.get_admin_context_without_session()
# Handle updates from service
self.callbacks = MlnxEswitchRpcCallbacks(self.context, self.eswitch)
self.dispatcher = self.callbacks.create_rpc_dispatcher()
# Define the listening consumers for the agent
consumers = [[topics.PORT, topics.UPDATE],
[topics.NETWORK, topics.DELETE]]
self.connection = agent_rpc.create_consumers(self.dispatcher,
self.topic,
consumers)
report_interval = cfg.CONF.AGENT.report_interval
if report_interval:
heartbeat = loopingcall.LoopingCall(self._report_state)
heartbeat.start(interval=report_interval)
def update_ports(self, registered_ports):
ports = self.eswitch.get_vnics_mac()
if ports == registered_ports:
return
added = ports - registered_ports
removed = registered_ports - ports
return {'current': ports,
'added': added,
'removed': removed}
def process_network_ports(self, port_info):
resync_a = False
resync_b = False
if 'added' in port_info:
LOG.debug(_("ports added!"))
resync_a = self.treat_devices_added(port_info['added'])
if 'removed' in port_info:
LOG.debug(_("ports removed!"))
resync_b = self.treat_devices_removed(port_info['removed'])
# If one of the above opertaions fails => resync with plugin
return (resync_a | resync_b)
def treat_vif_port(self, port_id, port_mac,
network_id, network_type,
physical_network, segmentation_id,
admin_state_up):
if self.eswitch.vnic_port_exists(port_mac):
if admin_state_up:
self.eswitch.port_up(network_id,
network_type,
physical_network,
segmentation_id,
port_id,
port_mac)
else:
self.eswitch.port_down(network_id, physical_network, port_mac)
else:
LOG.debug(_("No port %s defined on agent."), port_id)
def treat_devices_added(self, devices):
resync = False
for device in devices:
LOG.info(_("Adding port with mac %s"), device)
try:
dev_details = self.plugin_rpc.get_device_details(
self.context,
device,
self.agent_id)
except Exception as e:
LOG.debug(_("Unable to get device dev_details for device "
"with mac_address %(device)s: due to %(exc)s"),
{'device': device, 'exc': e})
resync = True
continue
if 'port_id' in dev_details:
LOG.info(_("Port %s updated"), device)
LOG.debug(_("Device details %s"), str(dev_details))
self.treat_vif_port(dev_details['port_id'],
dev_details['port_mac'],
dev_details['network_id'],
dev_details['network_type'],
dev_details['physical_network'],
dev_details['vlan_id'],
dev_details['admin_state_up'])
else:
LOG.debug(_("Device with mac_address %s not defined "
"on Quantum Plugin"), device)
return resync
def treat_devices_removed(self, devices):
resync = False
for device in devices:
LOG.info(_("Removing device with mac_address %s"), device)
try:
port_id = self.eswitch.get_port_id_by_mac(device)
dev_details = self.plugin_rpc.update_device_down(self.context,
port_id,
self.agent_id)
except Exception as e:
LOG.debug(_("Removing port failed for device %(device)s "
"due to %(exc)s"), {'device': device, 'exc': e})
resync = True
continue
if dev_details['exists']:
LOG.info(_("Port %s updated."), device)
self.eswitch.port_release(device)
else:
LOG.debug(_("Device %s not defined on plugin"), device)
return resync
def daemon_loop(self):
sync = True
ports = set()
LOG.info(_("eSwitch Agent Started!"))
while True:
try:
start = time.time()
if sync:
LOG.info(_("Agent out of sync with plugin!"))
ports.clear()
sync = False
port_info = self.update_ports(ports)
# notify plugin about port deltas
if port_info:
LOG.debug(_("Agent loop has new devices!"))
# If treat devices fails - must resync with plugin
sync = self.process_network_ports(port_info)
ports = port_info['current']
except Exception:
LOG.exception(_("Error in agent event loop"))
sync = True
# sleep till end of polling interval
elapsed = (time.time() - start)
if (elapsed < self._polling_interval):
time.sleep(self._polling_interval - elapsed)
else:
LOG.debug(_("Loop iteration exceeded interval "
"(%(polling_interval)s vs. %(elapsed)s)"),
{'polling_interval': self._polling_interval,
'elapsed': elapsed})
def main():
eventlet.monkey_patch()
cfg.CONF(project='quantum')
logging_config.setup_logging(cfg.CONF)
try:
interface_mappings = q_utils.parse_mappings(
cfg.CONF.ESWITCH.physical_interface_mappings)
except ValueError as e:
LOG.error(_("Parsing physical_interface_mappings failed: %s."
" Agent terminated!"), e)
sys.exit(1)
LOG.info(_("Interface mappings: %s"), interface_mappings)
try:
agent = MlnxEswitchQuantumAgent(interface_mappings)
except Exception as e:
LOG.error(_("Failed on Agent initialisation : %s."
" Agent terminated!"), e)
sys.exit(1)
# Start everything.
LOG.info(_("Agent initialised successfully, now running... "))
agent.daemon_loop()
sys.exit(0)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,136 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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 zmq
from quantum.openstack.common import jsonutils
from quantum.openstack.common import log as logging
from quantum.plugins.mlnx.common import exceptions
LOG = logging.getLogger(__name__)
class EswitchUtils(object):
def __init__(self, daemon_endpoint, timeout):
self.__conn = None
self.daemon = daemon_endpoint
self.timeout = timeout
@property
def _conn(self):
if self.__conn is None:
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.setsockopt(zmq.LINGER, 0)
socket.connect(self.daemon)
self.__conn = socket
self.poller = zmq.Poller()
self.poller.register(self._conn, zmq.POLLIN)
return self.__conn
def send_msg(self, msg):
self._conn.send(msg)
socks = dict(self.poller.poll(self.timeout))
if socks.get(self._conn) == zmq.POLLIN:
recv_msg = self._conn.recv()
response = self.parse_response_msg(recv_msg)
return response
else:
self._conn.setsockopt(zmq.LINGER, 0)
self._conn.close()
self.poller.unregister(self._conn)
self.__conn = None
raise exceptions.MlnxException(_("eSwitchD: Request timeout"))
def parse_response_msg(self, recv_msg):
msg = jsonutils.loads(recv_msg)
if msg['status'] == 'OK':
if 'response' in msg:
return msg.get('response')
return
elif msg['status'] == 'FAIL':
msg_dict = dict(action=msg['action'], reason=msg['reason'])
error_msg = _("Action %(action)s failed: %(reason)s") % msg_dict
else:
error_msg = _("Unknown operation status %s") % msg['status']
LOG.error(error_msg)
raise exceptions.MlnxException(error_msg)
def get_attached_vnics(self):
LOG.debug(_("get_attached_vnics"))
msg = jsonutils.dumps({'action': 'get_vnics', 'fabric': '*'})
vnics = self.send_msg(msg)
return vnics
def set_port_vlan_id(self, physical_network,
segmentation_id, port_mac):
LOG.debug(_("Set Vlan %(segmentation_id)s on Port %(port_mac)s "
"on Fabric %(physical_network)s"),
{'port_mac': port_mac,
'segmentation_id': segmentation_id,
'physical_network': physical_network})
msg = jsonutils.dumps({'action': 'set_vlan',
'fabric': physical_network,
'port_mac': port_mac,
'vlan': segmentation_id})
self.send_msg(msg)
def define_fabric_mappings(self, interface_mapping):
for fabric, phy_interface in interface_mapping.iteritems():
LOG.debug(_("Define Fabric %(fabric)s on interface %(ifc)s"),
{'fabric': fabric,
'ifc': phy_interface})
msg = jsonutils.dumps({'action': 'define_fabric_mapping',
'fabric': fabric,
'interface': phy_interface})
self.send_msg(msg)
def port_up(self, fabric, port_mac):
LOG.debug(_("Port Up for %(port_mac)s on fabric %(fabric)s"),
{'port_mac': port_mac, 'fabric': fabric})
msg = jsonutils.dumps({'action': 'port_up',
'fabric': fabric,
'ref_by': 'mac_address',
'mac': 'port_mac'})
self.send_msg(msg)
def port_down(self, fabric, port_mac):
LOG.debug(_("Port Down for %(port_mac)s on fabric %(fabric)s"),
{'port_mac': port_mac, 'fabric': fabric})
msg = jsonutils.dumps({'action': 'port_down',
'fabric': fabric,
'ref_by': 'mac_address',
'mac': port_mac})
self.send_msg(msg)
def port_release(self, fabric, port_mac):
LOG.debug(_("Port Release for %(port_mac)s on fabric %(fabric)s"),
{'port_mac': port_mac, 'fabric': fabric})
msg = jsonutils.dumps({'action': 'port_release',
'fabric': fabric,
'ref_by': 'mac_address',
'mac': port_mac})
self.send_msg(msg)
def get_eswitch_ports(self, fabric):
# TODO(irena) - to implement for next phase
return {}
def get_eswitch_id(self, fabric):
# TODO(irena) - to implement for next phase
return ""

View File

@ -0,0 +1,59 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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.
from quantum.common import topics
from quantum.openstack.common import log as logging
from quantum.openstack.common.rpc import proxy
LOG = logging.getLogger(__name__)
class AgentNotifierApi(proxy.RpcProxy):
"""Agent side of the Embedded Switch 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_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):
LOG.debug(_("Sending delete network message"))
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,
network_type, vlan_id):
LOG.debug(_("Sending update port message"))
self.fanout_cast(context,
self.make_msg('port_update',
port=port,
physical_network=physical_network,
network_type=network_type,
vlan_id=vlan_id),
topic=self.topic_port_update)

View File

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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,63 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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.
from oslo.config import cfg
from quantum.agent.common import config
from quantum.plugins.mlnx.common import constants
DEFAULT_VLAN_RANGES = ['default:1:1000']
DEFAULT_INTERFACE_MAPPINGS = []
vlan_opts = [
cfg.StrOpt('tenant_network_type', default='vlan',
help=_("Network type for tenant networks "
"(local, ib, vlan, or none)")),
cfg.ListOpt('network_vlan_ranges',
default=DEFAULT_VLAN_RANGES,
help=_("List of <physical_network>:<vlan_min>:<vlan_max> "
"or <physical_network>")),
]
eswitch_opts = [
cfg.ListOpt('physical_interface_mappings',
default=DEFAULT_INTERFACE_MAPPINGS,
help=_("List of <physical_network>:<physical_interface>")),
cfg.StrOpt('vnic_type',
default=constants.VIF_TYPE_DIRECT,
help=_("type of VM network interface: direct or hosdev")),
cfg.StrOpt('daemon_endpoint',
default='tcp://127.0.0.1:5001',
help=_('eswitch daemon end point')),
cfg.IntOpt('request_timeout', default=3000,
help=_("The number of milliseconds the agent will wait for "
"response on request to daemon.")),
]
agent_opts = [
cfg.IntOpt('polling_interval', default=2,
help=_("The number of seconds the agent will wait between "
"polling for local device changes.")),
]
cfg.CONF.register_opts(vlan_opts, "MLNX")
cfg.CONF.register_opts(eswitch_opts, "ESWITCH")
cfg.CONF.register_opts(agent_opts, "AGENT")
config.register_agent_state_opts_helper(cfg.CONF)
config.register_root_helper(cfg.CONF)

View File

@ -0,0 +1,33 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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.
LOCAL_VLAN_ID = -2
FLAT_VLAN_ID = -1
VLAN_ID_MIN = 1
VLAN_ID_MAX = 4096
# Values for network_type
TYPE_LOCAL = 'local'
TYPE_FLAT = 'flat'
TYPE_VLAN = 'vlan'
TYPE_IB = 'ib'
TYPE_NONE = 'none'
VIF_TYPE_DIRECT = 'direct'
VIF_TYPE_HOSTDEV = 'hostdev'
VNIC_TYPE = 'vnic_type'

View File

@ -0,0 +1,22 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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.
from quantum.common import exceptions as qexc
class MlnxException(qexc.QuantumException):
message = _("Mlnx Exception: %(err_msg)s")

View File

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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,241 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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.
from sqlalchemy.orm import exc
from quantum.common import exceptions as q_exc
import quantum.db.api as db
from quantum.db import models_v2
from quantum.openstack.common import log as logging
from quantum.plugins.mlnx.common import config # noqa
from quantum.plugins.mlnx.db import mlnx_models_v2
LOG = logging.getLogger(__name__)
def initialize():
db.configure_db()
def _remove_non_allocatable_vlans(session, allocations,
physical_network, vlan_ids):
if physical_network in allocations:
for entry in allocations[physical_network]:
try:
# see if vlan is allocatable
vlan_ids.remove(entry.segmentation_id)
except KeyError:
# it's not allocatable, so check if its allocated
if not entry.allocated:
# it's not, so remove it from table
LOG.debug(_(
"Removing vlan %(seg_id)s on "
"physical network "
"%(net)s from pool"),
{'seg_id': entry.segmentation_id,
'net': physical_network})
session.delete(entry)
del allocations[physical_network]
def _add_missing_allocatable_vlans(session, physical_network, vlan_ids):
for vlan_id in sorted(vlan_ids):
entry = mlnx_models_v2.SegmentationIdAllocation(physical_network,
vlan_id)
session.add(entry)
def _remove_unconfigured_vlans(session, allocations):
for entries in allocations.itervalues():
for entry in entries:
if not entry.allocated:
LOG.debug(_("removing vlan %(seg_id)s on physical "
"network %(net)s from pool"),
{'seg_id': entry.segmentation_id,
'net': entry.physical_network})
session.delete(entry)
def sync_network_states(network_vlan_ranges):
"""Synchronize network_states table with current configured VLAN ranges."""
session = db.get_session()
with session.begin():
# get existing allocations for all physical networks
allocations = dict()
entries = (session.query(mlnx_models_v2.SegmentationIdAllocation).
all())
for entry in entries:
allocations.setdefault(entry.physical_network, set()).add(entry)
# process vlan ranges for each configured physical network
for physical_network, vlan_ranges in network_vlan_ranges.iteritems():
# determine current configured allocatable vlans for this
# physical network
vlan_ids = set()
for vlan_range in vlan_ranges:
vlan_ids |= set(xrange(vlan_range[0], vlan_range[1] + 1))
# remove from table unallocated vlans not currently allocatable
_remove_non_allocatable_vlans(session, allocations,
physical_network, vlan_ids)
# add missing allocatable vlans to table
_add_missing_allocatable_vlans(session, physical_network, vlan_ids)
# remove from table unallocated vlans for any unconfigured physical
# networks
_remove_unconfigured_vlans(session, allocations)
def get_network_state(physical_network, segmentation_id):
"""Get entry of specified network."""
session = db.get_session()
qry = session.query(mlnx_models_v2.SegmentationIdAllocation)
qry = qry.filter_by(physical_network=physical_network,
segmentation_id=segmentation_id)
return qry.first()
def reserve_network(session):
with session.begin(subtransactions=True):
entry = (session.query(mlnx_models_v2.SegmentationIdAllocation).
filter_by(allocated=False).
first())
if not entry:
raise q_exc.NoNetworkAvailable()
LOG.debug(_("Reserving vlan %(seg_id)s on physical network "
"%(net)s from pool"),
{'seg_id': entry.segmentation_id,
'net': entry.physical_network})
entry.allocated = True
return (entry.physical_network, entry.segmentation_id)
def reserve_specific_network(session, physical_network, segmentation_id):
with session.begin(subtransactions=True):
log_args = {'seg_id': segmentation_id, 'phy_net': physical_network}
try:
entry = (session.query(mlnx_models_v2.SegmentationIdAllocation).
filter_by(physical_network=physical_network,
segmentation_id=segmentation_id).
one())
if entry.allocated:
raise q_exc.VlanIdInUse(vlan_id=segmentation_id,
physical_network=physical_network)
LOG.debug(_("Reserving specific vlan %(seg_id)s "
"on physical network %(phy_net)s from pool"),
log_args)
entry.allocated = True
except exc.NoResultFound:
LOG.debug(_("Reserving specific vlan %(seg_id)s on "
"physical network %(phy_net)s outside pool"),
log_args)
entry = mlnx_models_v2.SegmentationIdAllocation(physical_network,
segmentation_id)
entry.allocated = True
session.add(entry)
def release_network(session, physical_network,
segmentation_id, network_vlan_ranges):
with session.begin(subtransactions=True):
log_args = {'seg_id': segmentation_id, 'phy_net': physical_network}
try:
state = (session.query(mlnx_models_v2.SegmentationIdAllocation).
filter_by(physical_network=physical_network,
segmentation_id=segmentation_id).
with_lockmode('update').
one())
state.allocated = False
inside = False
for vlan_range in network_vlan_ranges.get(physical_network, []):
if (segmentation_id >= vlan_range[0] and
segmentation_id <= vlan_range[1]):
inside = True
break
if inside:
LOG.debug(_("Releasing vlan %(seg_id)s "
"on physical network "
"%(phy_net)s to pool"),
log_args)
else:
LOG.debug(_("Releasing vlan %(seg_id)s "
"on physical network "
"%(phy_net)s outside pool"),
log_args)
session.delete(state)
except exc.NoResultFound:
LOG.warning(_("vlan_id %(seg_id)s on physical network "
"%(phy_net)s not found"),
log_args)
def add_network_binding(session, network_id, network_type,
physical_network, vlan_id):
with session.begin(subtransactions=True):
binding = mlnx_models_v2.NetworkBinding(network_id, network_type,
physical_network, vlan_id)
session.add(binding)
def get_network_binding(session, network_id):
qry = session.query(mlnx_models_v2.NetworkBinding)
qry = qry.filter_by(network_id=network_id)
return qry.first()
def add_port_profile_binding(session, port_id, vnic_type):
with session.begin(subtransactions=True):
binding = mlnx_models_v2.PortProfileBinding(port_id, vnic_type)
session.add(binding)
def get_port_profile_binding(session, port_id):
qry = session.query(mlnx_models_v2.PortProfileBinding)
return qry.filter_by(port_id=port_id).first()
def get_port_from_device(device):
"""Get port from database."""
LOG.debug(_("get_port_from_device() called"))
session = db.get_session()
ports = session.query(models_v2.Port).all()
for port in ports:
if port['id'].startswith(device):
return port
def get_port_from_device_mac(device_mac):
"""Get port from database."""
LOG.debug(_("Get_port_from_device_mac() called"))
session = db.get_session()
qry = session.query(models_v2.Port).filter_by(mac_address=device_mac)
return qry.first()
def set_port_status(port_id, status):
"""Set the port status."""
LOG.debug(_("Set_port_status as %s called"), status)
session = db.get_session()
try:
port = session.query(models_v2.Port).filter_by(id=port_id).one()
port['status'] = status
session.merge(port)
session.flush()
except exc.NoResultFound:
raise q_exc.PortNotFound(port_id=port_id)

View File

@ -0,0 +1,86 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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 sqlalchemy as sa
from quantum.db import model_base
class SegmentationIdAllocation(model_base.BASEV2):
"""Represents allocation state of segmentation_id on physical network."""
__tablename__ = 'segmentation_id_allocation'
physical_network = sa.Column(sa.String(64), nullable=False,
primary_key=True)
segmentation_id = sa.Column(sa.Integer, nullable=False, primary_key=True,
autoincrement=False)
allocated = sa.Column(sa.Boolean, nullable=False, default=False)
def __init__(self, physical_network, segmentation_id):
self.physical_network = physical_network
self.segmentation_id = segmentation_id
self.allocated = False
def __repr__(self):
return "<SegmentationIdAllocation(%s,%d,%s)>" % (self.physical_network,
self.segmentation_id,
self.allocated)
class NetworkBinding(model_base.BASEV2):
"""Represents binding of virtual network.
Binds network to physical_network and segmentation_id
"""
__tablename__ = 'mlnx_network_bindings'
network_id = sa.Column(sa.String(36),
sa.ForeignKey('networks.id', ondelete="CASCADE"),
primary_key=True)
network_type = sa.Column(sa.String(32), nullable=False)
physical_network = sa.Column(sa.String(64))
segmentation_id = sa.Column(sa.Integer, nullable=False)
def __init__(self, network_id, network_type, physical_network, vlan_id):
self.network_id = network_id
self.network_type = network_type
self.physical_network = physical_network
self.segmentation_id = vlan_id
def __repr__(self):
return "<NetworkBinding(%s,%s,%s,%d)>" % (self.network_id,
self.network_type,
self.physical_network,
self.segmentation_id)
class PortProfileBinding(model_base.BASEV2):
"""Represents port profile binding to the port on virtual network."""
__tablename__ = 'port_profile'
port_id = sa.Column(sa.String(36),
sa.ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
vnic_type = sa.Column(sa.String(32), nullable=False)
def __init__(self, port_id, vnic_type):
self.port_id = port_id
self.vnic_type = vnic_type
def __repr__(self):
return "<PortProfileBinding(%s,%s,%s,%d)>" % (self.port_id,
self.vnic_type)

View File

@ -0,0 +1,409 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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 sys
from oslo.config import cfg
from quantum.agent import securitygroups_rpc as sg_rpc
from quantum.api.v2 import attributes
from quantum.common import exceptions as q_exc
from quantum.common import topics
from quantum.db import agents_db
from quantum.db import db_base_plugin_v2
from quantum.db import l3_db
from quantum.db import quota_db # noqa
from quantum.db import securitygroups_rpc_base as sg_db_rpc
from quantum.extensions import portbindings
from quantum.extensions import providernet as provider
from quantum.openstack.common import log as logging
from quantum.openstack.common import rpc
from quantum.plugins.common import utils as plugin_utils
from quantum.plugins.mlnx import agent_notify_api
from quantum.plugins.mlnx.common import constants
from quantum.plugins.mlnx.db import mlnx_db_v2 as db
from quantum.plugins.mlnx import rpc_callbacks
from quantum import policy
LOG = logging.getLogger(__name__)
class MellanoxEswitchPlugin(db_base_plugin_v2.QuantumDbPluginV2,
l3_db.L3_NAT_db_mixin,
agents_db.AgentDbMixin,
sg_db_rpc.SecurityGroupServerRpcMixin):
"""Realization of Quantum API on Mellanox HCA embedded switch technology.
Current plugin provides embedded HCA Switch connectivity.
Code is based on the Linux Bridge plugin content to
support consistency with L3 & DHCP Agents.
"""
# This attribute specifies whether the plugin supports or not
# bulk operations. Name mangling is used in order to ensure it
# is qualified by class
__native_bulk_support = True
_supported_extension_aliases = ["provider", "router", "binding",
"agent", "quotas", "security-group"]
@property
def supported_extension_aliases(self):
if not hasattr(self, '_aliases'):
aliases = self._supported_extension_aliases[:]
sg_rpc.disable_security_group_extension_if_noop_driver(aliases)
self._aliases = aliases
return self._aliases
network_view = "extension:provider_network:view"
network_set = "extension:provider_network:set"
binding_view = "extension:port_binding:view"
binding_set = "extension:port_binding:set"
def __init__(self):
"""Start Mellanox Quantum Plugin."""
db.initialize()
self._parse_network_vlan_ranges()
db.sync_network_states(self.network_vlan_ranges)
self._set_tenant_network_type()
self.vnic_type = cfg.CONF.ESWITCH.vnic_type
self._setup_rpc()
LOG.debug(_("Mellanox Embedded Switch Plugin initialisation complete"))
def _setup_rpc(self):
# RPC support
self.topic = topics.PLUGIN
self.conn = rpc.create_connection(new=True)
self.notifier = agent_notify_api.AgentNotifierApi(topics.AGENT)
self.callbacks = rpc_callbacks.MlnxRpcCallbacks()
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()
def _parse_network_vlan_ranges(self):
try:
self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges(
cfg.CONF.MLNX.network_vlan_ranges)
except Exception as ex:
LOG.error(_("%s. Server terminated!"), ex)
sys.exit(1)
LOG.info(_("Network VLAN ranges: %s"), self.network_vlan_ranges)
def _check_view_auth(self, context, resource, action):
return policy.check(context, action, resource)
def _enforce_set_auth(self, context, resource, action):
policy.enforce(context, action, resource)
def _add_network_vlan_range(self, physical_network, vlan_min, vlan_max):
self._add_network(physical_network)
self.network_vlan_ranges[physical_network].append((vlan_min, vlan_max))
def _add_network(self, physical_network):
if physical_network not in self.network_vlan_ranges:
self.network_vlan_ranges[physical_network] = []
def _extend_network_dict_provider(self, context, network):
if self._check_view_auth(context, network, self.network_view):
binding = db.get_network_binding(context.session, network['id'])
network[provider.NETWORK_TYPE] = binding.network_type
if binding.network_type == constants.TYPE_FLAT:
network[provider.PHYSICAL_NETWORK] = binding.physical_network
network[provider.SEGMENTATION_ID] = None
elif binding.network_type == constants.TYPE_LOCAL:
network[provider.PHYSICAL_NETWORK] = None
network[provider.SEGMENTATION_ID] = None
else:
network[provider.PHYSICAL_NETWORK] = binding.physical_network
network[provider.SEGMENTATION_ID] = binding.segmentation_id
def _set_tenant_network_type(self):
self.tenant_network_type = cfg.CONF.MLNX.tenant_network_type
if self.tenant_network_type not in [constants.TYPE_VLAN,
constants.TYPE_IB,
constants.TYPE_LOCAL,
constants.TYPE_NONE]:
LOG.error(_("Invalid tenant_network_type: %s. "
"Service terminated!"),
self.tenant_network_type)
sys.exit(1)
def _process_provider_create(self, context, attrs):
network_type = attrs.get(provider.NETWORK_TYPE)
physical_network = attrs.get(provider.PHYSICAL_NETWORK)
segmentation_id = attrs.get(provider.SEGMENTATION_ID)
network_type_set = attributes.is_attr_set(network_type)
physical_network_set = attributes.is_attr_set(physical_network)
segmentation_id_set = attributes.is_attr_set(segmentation_id)
if not (network_type_set or physical_network_set or
segmentation_id_set):
return (None, None, None)
# Authorize before exposing plugin details to client
self._enforce_set_auth(context, attrs, self.network_set)
if not network_type_set:
msg = _("provider:network_type required")
raise q_exc.InvalidInput(error_message=msg)
elif network_type == constants.TYPE_FLAT:
self._process_flat_net(segmentation_id_set)
segmentation_id = constants.FLAT_VLAN_ID
elif network_type in [constants.TYPE_VLAN, constants.TYPE_IB]:
self._process_vlan_net(segmentation_id, segmentation_id_set)
elif network_type == constants.TYPE_LOCAL:
self._process_local_net(physical_network_set,
segmentation_id_set)
segmentation_id = constants.LOCAL_VLAN_ID
physical_network = None
else:
msg = _("provider:network_type %s not supported") % network_type
raise q_exc.InvalidInput(error_message=msg)
physical_network = self._process_net_type(network_type,
physical_network,
physical_network_set)
return (network_type, physical_network, segmentation_id)
def _process_flat_net(self, segmentation_id_set):
if segmentation_id_set:
msg = _("provider:segmentation_id specified for flat network")
raise q_exc.InvalidInput(error_message=msg)
def _process_vlan_net(self, segmentation_id, segmentation_id_set):
if not segmentation_id_set:
msg = _("provider:segmentation_id required")
raise q_exc.InvalidInput(error_message=msg)
if segmentation_id < 1 or segmentation_id > 4094:
msg = _("provider:segmentation_id out of range "
"(1 through 4094)")
raise q_exc.InvalidInput(error_message=msg)
def _process_local_net(self, physical_network_set, segmentation_id_set):
if physical_network_set:
msg = _("provider:physical_network specified for local "
"network")
raise q_exc.InvalidInput(error_message=msg)
if segmentation_id_set:
msg = _("provider:segmentation_id specified for local "
"network")
raise q_exc.InvalidInput(error_message=msg)
def _process_net_type(self, network_type,
physical_network,
physical_network_set):
if network_type in [constants.TYPE_VLAN,
constants.TYPE_IB,
constants.TYPE_FLAT]:
if physical_network_set:
if physical_network not in self.network_vlan_ranges:
msg = _("unknown provider:physical_network "
"%s") % physical_network
raise q_exc.InvalidInput(error_message=msg)
elif 'default' in self.network_vlan_ranges:
physical_network = 'default'
else:
msg = _("provider:physical_network required")
raise q_exc.InvalidInput(error_message=msg)
return physical_network
def _check_provider_update(self, context, attrs):
network_type = attrs.get(provider.NETWORK_TYPE)
physical_network = attrs.get(provider.PHYSICAL_NETWORK)
segmentation_id = attrs.get(provider.SEGMENTATION_ID)
network_type_set = attributes.is_attr_set(network_type)
physical_network_set = attributes.is_attr_set(physical_network)
segmentation_id_set = attributes.is_attr_set(segmentation_id)
if not (network_type_set or physical_network_set or
segmentation_id_set):
return
# Authorize before exposing plugin details to client
self._enforce_set_auth(context, attrs, self.network_set)
msg = _("Plugin does not support updating provider attributes")
raise q_exc.InvalidInput(error_message=msg)
def _process_port_binding_create(self, context, attrs):
binding_profile = attrs.get(portbindings.PROFILE)
binding_profile_set = attributes.is_attr_set(binding_profile)
if not binding_profile_set:
return self.vnic_type
if constants.VNIC_TYPE in binding_profile:
req_vnic_type = binding_profile[constants.VNIC_TYPE]
if req_vnic_type in (constants.VIF_TYPE_DIRECT,
constants.VIF_TYPE_HOSTDEV):
return req_vnic_type
else:
msg = _("invalid vnic_type on port_create")
else:
msg = _("vnic_type is not defined in port profile")
raise q_exc.InvalidInput(error_message=msg)
def create_network(self, context, network):
(network_type, physical_network,
vlan_id) = self._process_provider_create(context,
network['network'])
session = context.session
with session.begin(subtransactions=True):
if not network_type:
# tenant network
network_type = self.tenant_network_type
if network_type == constants.TYPE_NONE:
raise q_exc.TenantNetworksDisabled()
elif network_type in [constants.TYPE_VLAN, constants.TYPE_IB]:
physical_network, vlan_id = db.reserve_network(session)
else: # TYPE_LOCAL
vlan_id = constants.LOCAL_VLAN_ID
else:
# provider network
if network_type in [constants.TYPE_VLAN,
constants.TYPE_IB,
constants.TYPE_FLAT]:
db.reserve_specific_network(session,
physical_network,
vlan_id)
net = super(MellanoxEswitchPlugin, self).create_network(context,
network)
db.add_network_binding(session, net['id'],
network_type,
physical_network,
vlan_id)
self._process_l3_create(context, network['network'], net['id'])
self._extend_network_dict_provider(context, net)
self._extend_network_dict_l3(context, net)
# note - exception will rollback entire transaction
LOG.debug(_("Created network: %s"), net['id'])
return net
def update_network(self, context, net_id, network):
self._check_provider_update(context, network['network'])
session = context.session
with session.begin(subtransactions=True):
net = super(MellanoxEswitchPlugin, self).update_network(context,
net_id,
network)
self._process_l3_update(context, network['network'], net_id)
self._extend_network_dict_provider(context, net)
self._extend_network_dict_l3(context, net)
return net
def delete_network(self, context, net_id):
LOG.debug(_("delete network"))
session = context.session
with session.begin(subtransactions=True):
binding = db.get_network_binding(session, net_id)
super(MellanoxEswitchPlugin, self).delete_network(context,
net_id)
if binding.segmentation_id != constants.LOCAL_VLAN_ID:
db.release_network(session, binding.physical_network,
binding.segmentation_id,
self.network_vlan_ranges)
# the network_binding record is deleted via cascade from
# the network record, so explicit removal is not necessary
self.notifier.network_delete(context, net_id)
def get_network(self, context, net_id, fields=None):
session = context.session
with session.begin(subtransactions=True):
net = super(MellanoxEswitchPlugin, self).get_network(context,
net_id,
None)
self._extend_network_dict_provider(context, net)
self._extend_network_dict_l3(context, net)
return self._fields(net, fields)
def get_networks(self, context, filters=None, fields=None):
session = context.session
with session.begin(subtransactions=True):
nets = super(MellanoxEswitchPlugin, self).get_networks(context,
filters,
None)
for net in nets:
self._extend_network_dict_provider(context, net)
self._extend_network_dict_l3(context, net)
# TODO(rkukura): Filter on extended provider attributes.
nets = self._filter_nets_l3(context, nets, filters)
return [self._fields(net, fields) for net in nets]
def _extend_port_dict_binding(self, context, port):
if self._check_view_auth(context, port, self.binding_view):
port_binding = db.get_port_profile_binding(context.session,
port['id'])
if port_binding:
port[portbindings.VIF_TYPE] = port_binding.vnic_type
port[portbindings.CAPABILITIES] = {
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}
binding = db.get_network_binding(context.session,
port['network_id'])
fabric = binding.physical_network
port[portbindings.PROFILE] = {'physical_network': fabric}
return port
def create_port(self, context, port):
LOG.debug(_("create_port with %s"), port)
vnic_type = self._process_port_binding_create(context, port['port'])
port = super(MellanoxEswitchPlugin, self).create_port(context, port)
db.add_port_profile_binding(context.session, port['id'], vnic_type)
return self._extend_port_dict_binding(context, port)
def get_port(self, context, id, fields=None):
port = super(MellanoxEswitchPlugin, self).get_port(context, id, fields)
return self._fields(self._extend_port_dict_binding(context, port),
fields)
def get_ports(self, context, filters=None, fields=None):
ports = super(MellanoxEswitchPlugin, self).get_ports(
context, filters, fields)
return [self._fields(self._extend_port_dict_binding(context, port),
fields) for port in ports]
def update_port(self, context, port_id, port):
original_port = super(MellanoxEswitchPlugin, self).get_port(context,
port_id)
session = context.session
with session.begin(subtransactions=True):
port = super(MellanoxEswitchPlugin, self).update_port(context,
port_id,
port)
if original_port['admin_state_up'] != port['admin_state_up']:
binding = db.get_network_binding(context.session,
port['network_id'])
self.notifier.port_update(context, port,
binding.physical_network,
binding.network_type,
binding.segmentation_id)
return self._extend_port_dict_binding(context, port)
def delete_port(self, context, port_id, l3_port_check=True):
# if needed, check to see if this is a port owned by
# and l3-router. If so, we should prevent deletion.
if l3_port_check:
self.prevent_l3_port_deletion(context, port_id)
session = context.session
with session.begin(subtransactions=True):
self.disassociate_floatingips(context, port_id)
return super(MellanoxEswitchPlugin, self).delete_port(context,
port_id)

View File

@ -0,0 +1,123 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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.
from quantum.common import constants as q_const
from quantum.common import rpc as q_rpc
from quantum.db import agents_db
from quantum.db import api as db_api
from quantum.db import dhcp_rpc_base
from quantum.db import l3_rpc_base
from quantum.db import securitygroups_rpc_base as sg_db_rpc
from quantum.openstack.common import log as logging
from quantum.plugins.mlnx.db import mlnx_db_v2 as db
LOG = logging.getLogger(__name__)
class MlnxRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
l3_rpc_base.L3RpcCallbackMixin,
sg_db_rpc.SecurityGroupServerRpcCallbackMixin):
# History
# 1.1 Support Security Group RPC
RPC_API_VERSION = '1.1'
#to be compatible with Linux Bridge Agent on Network Node
TAP_PREFIX_LEN = 3
def __init__(self):
pass
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,
agents_db.AgentExtRpcCallback()])
@classmethod
def get_port_from_device(cls, device):
"""Get port according to device.
To maintain compatibility with Linux Bridge L2 Agent for DHCP/L3
services get device either by linux bridge plugin
device name convention or by mac address
"""
port = db.get_port_from_device(device[cls.TAP_PREFIX_LEN:])
if port:
port['device'] = device
else:
port = db.get_port_from_device_mac(device)
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 %s details requested from %s", device, agent_id)
port = self.get_port_from_device(device)
if port:
binding = db.get_network_binding(db_api.get_session(),
port['network_id'])
entry = {'device': device,
'physical_network': binding.physical_network,
'network_type': binding.network_type,
'vlan_id': binding.segmentation_id,
'network_id': port['network_id'],
'port_mac': port['mac_address'],
'port_id': port['id'],
'admin_state_up': port['admin_state_up']}
# Set the port status to UP
db.set_port_status(port['id'], q_const.PORT_STATUS_ACTIVE)
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."""
agent_id = kwargs.get('agent_id')
device = kwargs.get('device')
LOG.debug(_("Device %(device)s no longer exists on %(agent_id)s"),
{'device': device, 'agent_id': agent_id})
port = db.get_port_from_device(device)
if port:
entry = {'device': device,
'exists': True}
# Set port status to DOWN
db.set_port_status(port['id'], q_const.PORT_STATUS_DOWN)
else:
entry = {'device': device,
'exists': False}
LOG.debug(_("%s can not be found in database"), device)
return entry
def update_device_up(self, rpc_context, **kwargs):
"""Device is up on agent."""
agent_id = kwargs.get('agent_id')
device = kwargs.get('device')
LOG.debug(_("Device %(device)s up %(agent_id)s"),
{'device': device, 'agent_id': agent_id})
port = self.get_port_from_device(device)
if port:
if port['status'] != q_const.PORT_STATUS_ACTIVE:
# Set port status to ACTIVE
db.set_port_status(port['id'], q_const.PORT_STATUS_ACTIVE)
else:
LOG.debug(_("%s can not be found in database"), device)

View File

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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,36 @@
# 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.
from oslo.config import cfg
#NOTE this import loads tests required options
from quantum.plugins.mlnx.common import config # noqa
from quantum.tests import base
class ConfigurationTest(base.BaseTestCase):
def test_defaults(self):
self.assertEqual(2,
cfg.CONF.AGENT.polling_interval)
self.assertEqual('sudo',
cfg.CONF.AGENT.root_helper)
self.assertEqual('vlan',
cfg.CONF.MLNX.tenant_network_type)
self.assertEqual(1,
len(cfg.CONF.MLNX.network_vlan_ranges))
self.assertEqual(0,
len(cfg.CONF.ESWITCH.
physical_interface_mappings))

View File

@ -0,0 +1,180 @@
# 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.
from testtools import matchers
from quantum.common import exceptions as q_exc
from quantum.db import api as db
from quantum.plugins.mlnx.db import mlnx_db_v2 as mlnx_db
from quantum.tests import base
from quantum.tests.unit import test_db_plugin as test_plugin
PHYS_NET = 'physnet1'
PHYS_NET_2 = 'physnet2'
NET_TYPE = 'vlan'
VLAN_MIN = 10
VLAN_MAX = 19
VLAN_RANGES = {PHYS_NET: [(VLAN_MIN, VLAN_MAX)]}
UPDATED_VLAN_RANGES = {PHYS_NET: [(VLAN_MIN + 5, VLAN_MAX + 5)],
PHYS_NET_2: [(VLAN_MIN + 20, VLAN_MAX + 20)]}
TEST_NETWORK_ID = 'abcdefghijklmnopqrstuvwxyz'
class SegmentationIdAllocationTest(base.BaseTestCase):
def setUp(self):
super(SegmentationIdAllocationTest, self).setUp()
mlnx_db.initialize()
mlnx_db.sync_network_states(VLAN_RANGES)
self.session = db.get_session()
self.addCleanup(db.clear_db)
def test_sync_segmentationIdAllocation(self):
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET,
VLAN_MIN - 1))
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MIN).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MIN + 1).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MAX - 1).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MAX).allocated)
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET,
VLAN_MAX + 1))
mlnx_db.sync_network_states(UPDATED_VLAN_RANGES)
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET,
VLAN_MIN + 5 - 1))
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MIN + 5).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MIN + 5 + 1).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MAX + 5 - 1).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MAX + 5).allocated)
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET,
VLAN_MAX + 5 + 1))
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET_2,
VLAN_MIN + 20 - 1))
self.assertFalse(mlnx_db.get_network_state(PHYS_NET_2,
VLAN_MIN + 20).allocated)
self.assertFalse(
mlnx_db.get_network_state(PHYS_NET_2,
VLAN_MIN + 20 + 1).allocated)
self.assertFalse(
mlnx_db.get_network_state(PHYS_NET_2,
VLAN_MAX + 20 - 1).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET_2,
VLAN_MAX + 20).allocated)
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET_2,
VLAN_MAX + 20 + 1))
mlnx_db.sync_network_states(VLAN_RANGES)
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET,
VLAN_MIN - 1))
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MIN).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MIN + 1).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MAX - 1).allocated)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
VLAN_MAX).allocated)
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET,
VLAN_MAX + 1))
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET_2,
VLAN_MIN + 20))
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET_2,
VLAN_MAX + 20))
def test_segmentationId_pool(self):
vlan_ids = set()
for x in xrange(VLAN_MIN, VLAN_MAX + 1):
physical_network, vlan_id = mlnx_db.reserve_network(self.session)
self.assertEqual(physical_network, PHYS_NET)
self.assertThat(vlan_id, matchers.GreaterThan(VLAN_MIN - 1))
self.assertThat(vlan_id, matchers.LessThan(VLAN_MAX + 1))
vlan_ids.add(vlan_id)
self.assertRaises(q_exc.NoNetworkAvailable,
mlnx_db.reserve_network,
self.session)
for vlan_id in vlan_ids:
mlnx_db.release_network(self.session, PHYS_NET,
vlan_id, VLAN_RANGES)
def test_specific_segmentationId_inside_pool(self):
vlan_id = VLAN_MIN + 5
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
vlan_id).allocated)
mlnx_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
self.assertTrue(mlnx_db.get_network_state(PHYS_NET,
vlan_id).allocated)
self.assertRaises(q_exc.VlanIdInUse,
mlnx_db.reserve_specific_network,
self.session,
PHYS_NET,
vlan_id)
mlnx_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
self.assertFalse(mlnx_db.get_network_state(PHYS_NET,
vlan_id).allocated)
def test_specific_segmentationId_outside_pool(self):
vlan_id = VLAN_MAX + 5
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET, vlan_id))
mlnx_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
self.assertTrue(mlnx_db.get_network_state(PHYS_NET,
vlan_id).allocated)
self.assertRaises(q_exc.VlanIdInUse,
mlnx_db.reserve_specific_network,
self.session,
PHYS_NET,
vlan_id)
mlnx_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
self.assertIsNone(mlnx_db.get_network_state(PHYS_NET, vlan_id))
class NetworkBindingsTest(test_plugin.QuantumDbPluginV2TestCase):
def setUp(self):
super(NetworkBindingsTest, self).setUp()
mlnx_db.initialize()
self.session = db.get_session()
def test_add_network_binding(self):
with self.network() as network:
TEST_NETWORK_ID = network['network']['id']
self.assertIsNone(mlnx_db.get_network_binding(self.session,
TEST_NETWORK_ID))
mlnx_db.add_network_binding(self.session,
TEST_NETWORK_ID,
NET_TYPE,
PHYS_NET,
1234)
binding = mlnx_db.get_network_binding(self.session,
TEST_NETWORK_ID)
self.assertIsNotNone(binding)
self.assertEqual(binding.network_id, TEST_NETWORK_ID)
self.assertEqual(binding.network_type, NET_TYPE)
self.assertEqual(binding.physical_network, PHYS_NET)
self.assertEqual(binding.segmentation_id, 1234)

View File

@ -0,0 +1,62 @@
# 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.
from quantum import context
from quantum.manager import QuantumManager
from quantum.plugins.mlnx.common import constants
from quantum.tests.unit import test_db_plugin as test_plugin
PLUGIN_NAME = ('quantum.plugins.mlnx.mlnx_plugin.MellanoxEswitchPlugin')
class MlnxPluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase):
_plugin_name = PLUGIN_NAME
def setUp(self):
super(MlnxPluginV2TestCase, self).setUp(self._plugin_name)
class TestMlnxBasicGet(test_plugin.TestBasicGet, MlnxPluginV2TestCase):
pass
class TestMlnxV2HTTPResponse(test_plugin.TestV2HTTPResponse,
MlnxPluginV2TestCase):
pass
class TestMlnxPortsV2(test_plugin.TestPortsV2,
MlnxPluginV2TestCase):
VIF_TYPE = constants.VIF_TYPE_DIRECT
HAS_PORT_FILTER = False
def test_port_vif_details(self):
plugin = QuantumManager.get_plugin()
with self.port(name='name') as port:
port_id = port['port']['id']
self.assertEqual(port['port']['binding:vif_type'],
self.VIF_TYPE)
# By default user is admin - now test non admin user
ctx = context.Context(user_id=None,
tenant_id=self._tenant_id,
is_admin=False,
read_deleted="no")
non_admin_port = plugin.get_port(ctx, port_id)
self.assertIn('status', non_admin_port)
self.assertNotIn('binding:vif_type', non_admin_port)
class TestMlnxNetworksV2(test_plugin.TestNetworksV2, MlnxPluginV2TestCase):
pass

View File

@ -0,0 +1,102 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Mellanox Technologies, Ltd
#
# 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 Tests for Mellanox RPC (major reuse of linuxbridge rpc unit tests)
"""
import stubout
from quantum.agent import rpc as agent_rpc
from quantum.common import topics
from quantum.openstack.common import context
from quantum.openstack.common import rpc
from quantum.plugins.mlnx import agent_notify_api
from quantum.tests import base
class rpcApiTestCase(base.BaseTestCase):
def _test_mlnx_api(self, rpcapi, topic, method, rpc_method, **kwargs):
ctxt = context.RequestContext('fake_user', 'fake_project')
expected_retval = 'foo' if method == 'call' else None
expected_msg = rpcapi.make_msg(method, **kwargs)
expected_msg['version'] = rpcapi.BASE_RPC_API_VERSION
if rpc_method == 'cast' and method == 'run_instance':
kwargs['call'] = False
self.fake_args = None
self.fake_kwargs = None
def _fake_rpc_method(*args, **kwargs):
self.fake_args = args
self.fake_kwargs = kwargs
if expected_retval:
return expected_retval
self.stubs = stubout.StubOutForTesting()
self.stubs.Set(rpc, rpc_method, _fake_rpc_method)
retval = getattr(rpcapi, method)(ctxt, **kwargs)
self.assertEqual(retval, expected_retval)
expected_args = [ctxt, topic, expected_msg]
for arg, expected_arg in zip(self.fake_args, expected_args):
self.assertEqual(arg, expected_arg)
def test_delete_network(self):
rpcapi = agent_notify_api.AgentNotifierApi(topics.AGENT)
self._test_mlnx_api(rpcapi,
topics.get_topic_name(topics.AGENT,
topics.NETWORK,
topics.DELETE),
'network_delete', rpc_method='fanout_cast',
network_id='fake_request_spec')
def test_port_update(self):
rpcapi = agent_notify_api.AgentNotifierApi(topics.AGENT)
self._test_mlnx_api(rpcapi,
topics.get_topic_name(topics.AGENT,
topics.PORT,
topics.UPDATE),
'port_update', rpc_method='fanout_cast',
port='fake_port',
network_type='vlan',
physical_network='fake_net',
vlan_id='fake_vlan_id')
def test_device_details(self):
rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
self._test_mlnx_api(rpcapi, topics.PLUGIN,
'get_device_details', rpc_method='call',
device='fake_device',
agent_id='fake_agent_id')
def test_update_device_down(self):
rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
self._test_mlnx_api(rpcapi, topics.PLUGIN,
'update_device_down', rpc_method='call',
device='fake_device',
agent_id='fake_agent_id')
def test_update_device_up(self):
rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
self._test_mlnx_api(rpcapi, topics.PLUGIN,
'update_device_up', rpc_method='call',
device='fake_device',
agent_id='fake_agent_id')

View File

@ -55,6 +55,7 @@ nec_plugin_config_path = 'etc/quantum/plugins/nec'
hyperv_plugin_config_path = 'etc/quantum/plugins/hyperv'
plumgrid_plugin_config_path = 'etc/quantum/plugins/plumgrid'
midonet_plugin_config_path = 'etc/quantum/plugins/midonet'
mlnx_plugin_config_path = 'etc/quantum/plugins/mlnx'
if sys.platform == 'win32':
# Windows doesn't have an "/etc" directory equivalent
@ -111,6 +112,8 @@ else:
['etc/quantum/plugins/plumgrid/plumgrid.ini']),
(midonet_plugin_config_path,
['etc/quantum/plugins/midonet/midonet.ini']),
(mlnx_plugin_config_path,
['etc/quantum/plugins/mlnx/mlnx_conf.ini']),
]
ConsoleScripts = [
@ -139,6 +142,8 @@ else:
'quantum.plugins.services.agent_loadbalancer.agent:main'),
('quantum-check-nvp-config = '
'quantum.plugins.nicira.check_nvp_config:main'),
('quantum-mlnx-agent ='
'quantum.plugins.mlnx.agent.eswitch_quantum_agent:main'),
]
ProjectScripts = [