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'
|
||||
plumgrid_plugin_config_path = 'etc/quantum/plugins/plumgrid'
|
||||
midonet_plugin_config_path = 'etc/quantum/plugins/midonet'
|
||||
mlnx_plugin_config_path = 'etc/quantum/plugins/mlnx'
|
||||
|
||||
if sys.platform == 'win32':
|
||||
# Windows doesn't have an "/etc" directory equivalent
|
||||
@ -111,6 +112,8 @@ else:
|
||||
['etc/quantum/plugins/plumgrid/plumgrid.ini']),
|
||||
(midonet_plugin_config_path,
|
||||
['etc/quantum/plugins/midonet/midonet.ini']),
|
||||
(mlnx_plugin_config_path,
|
||||
['etc/quantum/plugins/mlnx/mlnx_conf.ini']),
|
||||
]
|
||||
|
||||
ConsoleScripts = [
|
||||
@ -139,6 +142,8 @@ else:
|
||||
'quantum.plugins.services.agent_loadbalancer.agent:main'),
|
||||
('quantum-check-nvp-config = '
|
||||
'quantum.plugins.nicira.check_nvp_config:main'),
|
||||
('quantum-mlnx-agent ='
|
||||
'quantum.plugins.mlnx.agent.eswitch_quantum_agent:main'),
|
||||
]
|
||||
|
||||
ProjectScripts = [
|
||||
|
Loading…
Reference in New Issue
Block a user