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:
parent
8581676fea
commit
94492767e0
25
bin/quantum-mlnx-agent
Normal file
25
bin/quantum-mlnx-agent
Normal 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()
|
59
etc/quantum/plugins/mlnx/mlnx_conf.ini
Normal file
59
etc/quantum/plugins/mlnx/mlnx_conf.ini
Normal 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
|
8
quantum/plugins/mlnx/README
Normal file
8
quantum/plugins/mlnx/README
Normal 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
|
16
quantum/plugins/mlnx/__init__.py
Normal file
16
quantum/plugins/mlnx/__init__.py
Normal 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.
|
16
quantum/plugins/mlnx/agent/__init__.py
Normal file
16
quantum/plugins/mlnx/agent/__init__.py
Normal 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.
|
405
quantum/plugins/mlnx/agent/eswitch_quantum_agent.py
Normal file
405
quantum/plugins/mlnx/agent/eswitch_quantum_agent.py
Normal 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()
|
136
quantum/plugins/mlnx/agent/utils.py
Normal file
136
quantum/plugins/mlnx/agent/utils.py
Normal 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 ""
|
59
quantum/plugins/mlnx/agent_notify_api.py
Normal file
59
quantum/plugins/mlnx/agent_notify_api.py
Normal 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)
|
16
quantum/plugins/mlnx/common/__init__.py
Normal file
16
quantum/plugins/mlnx/common/__init__.py
Normal 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.
|
63
quantum/plugins/mlnx/common/config.py
Normal file
63
quantum/plugins/mlnx/common/config.py
Normal 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)
|
33
quantum/plugins/mlnx/common/constants.py
Normal file
33
quantum/plugins/mlnx/common/constants.py
Normal 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'
|
22
quantum/plugins/mlnx/common/exceptions.py
Normal file
22
quantum/plugins/mlnx/common/exceptions.py
Normal 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")
|
16
quantum/plugins/mlnx/db/__init__.py
Normal file
16
quantum/plugins/mlnx/db/__init__.py
Normal 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.
|
241
quantum/plugins/mlnx/db/mlnx_db_v2.py
Normal file
241
quantum/plugins/mlnx/db/mlnx_db_v2.py
Normal 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)
|
86
quantum/plugins/mlnx/db/mlnx_models_v2.py
Normal file
86
quantum/plugins/mlnx/db/mlnx_models_v2.py
Normal 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)
|
409
quantum/plugins/mlnx/mlnx_plugin.py
Normal file
409
quantum/plugins/mlnx/mlnx_plugin.py
Normal 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)
|
123
quantum/plugins/mlnx/rpc_callbacks.py
Normal file
123
quantum/plugins/mlnx/rpc_callbacks.py
Normal 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)
|
16
quantum/tests/unit/mlnx/__init__.py
Normal file
16
quantum/tests/unit/mlnx/__init__.py
Normal 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.
|
36
quantum/tests/unit/mlnx/test_defaults.py
Normal file
36
quantum/tests/unit/mlnx/test_defaults.py
Normal 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))
|
180
quantum/tests/unit/mlnx/test_mlnx_db.py
Normal file
180
quantum/tests/unit/mlnx/test_mlnx_db.py
Normal 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)
|
62
quantum/tests/unit/mlnx/test_mlnx_plugin.py
Normal file
62
quantum/tests/unit/mlnx/test_mlnx_plugin.py
Normal 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
|
102
quantum/tests/unit/mlnx/test_rpcapi.py
Normal file
102
quantum/tests/unit/mlnx/test_rpcapi.py
Normal 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')
|
5
setup.py
5
setup.py
@ -55,6 +55,7 @@ nec_plugin_config_path = 'etc/quantum/plugins/nec'
|
|||||||
hyperv_plugin_config_path = 'etc/quantum/plugins/hyperv'
|
hyperv_plugin_config_path = 'etc/quantum/plugins/hyperv'
|
||||||
plumgrid_plugin_config_path = 'etc/quantum/plugins/plumgrid'
|
plumgrid_plugin_config_path = 'etc/quantum/plugins/plumgrid'
|
||||||
midonet_plugin_config_path = 'etc/quantum/plugins/midonet'
|
midonet_plugin_config_path = 'etc/quantum/plugins/midonet'
|
||||||
|
mlnx_plugin_config_path = 'etc/quantum/plugins/mlnx'
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
# Windows doesn't have an "/etc" directory equivalent
|
# Windows doesn't have an "/etc" directory equivalent
|
||||||
@ -111,6 +112,8 @@ else:
|
|||||||
['etc/quantum/plugins/plumgrid/plumgrid.ini']),
|
['etc/quantum/plugins/plumgrid/plumgrid.ini']),
|
||||||
(midonet_plugin_config_path,
|
(midonet_plugin_config_path,
|
||||||
['etc/quantum/plugins/midonet/midonet.ini']),
|
['etc/quantum/plugins/midonet/midonet.ini']),
|
||||||
|
(mlnx_plugin_config_path,
|
||||||
|
['etc/quantum/plugins/mlnx/mlnx_conf.ini']),
|
||||||
]
|
]
|
||||||
|
|
||||||
ConsoleScripts = [
|
ConsoleScripts = [
|
||||||
@ -139,6 +142,8 @@ else:
|
|||||||
'quantum.plugins.services.agent_loadbalancer.agent:main'),
|
'quantum.plugins.services.agent_loadbalancer.agent:main'),
|
||||||
('quantum-check-nvp-config = '
|
('quantum-check-nvp-config = '
|
||||||
'quantum.plugins.nicira.check_nvp_config:main'),
|
'quantum.plugins.nicira.check_nvp_config:main'),
|
||||||
|
('quantum-mlnx-agent ='
|
||||||
|
'quantum.plugins.mlnx.agent.eswitch_quantum_agent:main'),
|
||||||
]
|
]
|
||||||
|
|
||||||
ProjectScripts = [
|
ProjectScripts = [
|
||||||
|
Loading…
Reference in New Issue
Block a user