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
--- /dev/null
+# 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()
--- /dev/null
+[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
--- /dev/null
+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
\ No newline at end of file
--- /dev/null
+# 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.
--- /dev/null
+# 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.
--- /dev/null
+# 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()
--- /dev/null
+# 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 ""
--- /dev/null
+# 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)
--- /dev/null
+# 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.
--- /dev/null
+# 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)
--- /dev/null
+# 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'
--- /dev/null
+# 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")
--- /dev/null
+# 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.
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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.
--- /dev/null
+# 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))
--- /dev/null
+# 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)
--- /dev/null
+# 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
--- /dev/null
+# 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')
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
['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 = [
'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 = [