From: Moshe Levi Date: Sun, 11 Jan 2015 13:25:59 +0000 (+0200) Subject: Thin MLNX ML2 mechanism driver and agent X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=65f5da12df74722946716f356fd3e7a787e2239d;p=openstack-build%2Fneutron-build.git Thin MLNX ML2 mechanism driver and agent This commit thins the in-tree MLNX ML2 MechanismDriver and Agent. A matching change to the stackforge/networking-mlnx project has the backend logic there. Partial-Implements: blueprint core-vendor-decomposition Closes-Bug: 1414902 Change-Id: I22e6ff37ea289f58ca5a7f49d65f634f72730402 --- diff --git a/neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py b/neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py index b1d0f1ba8..c754c5890 100644 --- a/neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py +++ b/neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py @@ -14,9 +14,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from networking_mlnx.plugins.ml2.drivers.mlnx import constants from oslo.config import cfg -from neutron.common import constants +from neutron.common import constants as n_const from neutron.extensions import portbindings from neutron.openstack.common import log from neutron.plugins.common import constants as p_constants @@ -45,7 +46,7 @@ class MlnxMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): # several MDs are capable to bing bind port on chosen host, the # first listed MD will bind the port for VNIC_NORMAL. super(MlnxMechanismDriver, self).__init__( - constants.AGENT_TYPE_MLNX, + n_const.AGENT_TYPE_MLNX, cfg.CONF.ESWITCH.vnic_type, {portbindings.CAP_PORT_FILTER: False}, portbindings.VNIC_TYPES) @@ -59,18 +60,12 @@ class MlnxMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): def try_to_bind_segment_for_agent(self, context, segment, agent): if self.check_segment_for_agent(segment, agent): - vif_type = self._get_vif_type( - context.current[portbindings.VNIC_TYPE]) - if segment[api.NETWORK_TYPE] in ['flat', 'vlan']: + vif_type = constants.VNIC_TO_VIF_MAPPING.get( + context.current[portbindings.VNIC_TYPE], self.vif_type) + if (segment[api.NETWORK_TYPE] in + (p_constants.TYPE_FLAT, p_constants.TYPE_VLAN)): self.vif_details['physical_network'] = segment[ 'physical_network'] context.set_binding(segment[api.ID], vif_type, self.vif_details) - - def _get_vif_type(self, requested_vnic_type): - if requested_vnic_type == portbindings.VNIC_MACVTAP: - return portbindings.VIF_TYPE_MLNX_DIRECT - elif requested_vnic_type == portbindings.VNIC_DIRECT: - return portbindings.VIF_TYPE_MLNX_HOSTDEV - return self.vif_type diff --git a/neutron/plugins/ml2/drivers/mlnx/requirements.txt b/neutron/plugins/ml2/drivers/mlnx/requirements.txt new file mode 100644 index 000000000..0165a4cb7 --- /dev/null +++ b/neutron/plugins/ml2/drivers/mlnx/requirements.txt @@ -0,0 +1,6 @@ +# The order of packages is significant, because pip processes them in the +# order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +networking_mlnx diff --git a/neutron/plugins/mlnx/common/config.py b/neutron/plugins/mlnx/agent/config.py similarity index 97% rename from neutron/plugins/mlnx/common/config.py rename to neutron/plugins/mlnx/agent/config.py index caed6db8d..1776ae376 100644 --- a/neutron/plugins/mlnx/common/config.py +++ b/neutron/plugins/mlnx/agent/config.py @@ -13,10 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from networking_mlnx.plugins.mlnx.agent import constants from oslo.config import cfg from neutron.agent.common import config -from neutron.plugins.mlnx.common import constants DEFAULT_INTERFACE_MAPPINGS = [] diff --git a/neutron/plugins/mlnx/agent/eswitch_neutron_agent.py b/neutron/plugins/mlnx/agent/eswitch_neutron_agent.py index 7c8e981f1..05b689db6 100644 --- a/neutron/plugins/mlnx/agent/eswitch_neutron_agent.py +++ b/neutron/plugins/mlnx/agent/eswitch_neutron_agent.py @@ -14,389 +14,26 @@ # limitations under the License. -import socket import sys -import time - -import eventlet -eventlet.monkey_patch() +from networking_mlnx.plugins.mlnx.agent import mlnx_eswitch_neutron_agent from oslo.config import cfg -from oslo import messaging -from neutron.agent import rpc as agent_rpc -from neutron.agent import securitygroups_rpc as sg_rpc +from neutron.i18n import _LE, _LI from neutron.common import config as common_config -from neutron.common import constants as q_constants -from neutron.common import topics -from neutron.common import utils as q_utils -from neutron import context -from neutron.i18n import _LE, _LI, _LW +from neutron.common import utils from neutron.openstack.common import log as logging -from neutron.openstack.common import loopingcall -from neutron.plugins.common import constants as p_const -from neutron.plugins.mlnx.agent import utils -from neutron.plugins.mlnx.common import config # noqa -from neutron.plugins.mlnx.common import exceptions +from neutron.plugins.mlnx.agent import config # noqa 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'] - LOG.error(_LE("Agent cache inconsistency - port id " - "is not stored for %s"), port_mac) - raise exceptions.MlnxException(err_msg=("Agent cache inconsistency, " - "check logs")) - - 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(_LI('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 == p_const.TYPE_VLAN: - LOG.info(_LI('Binding Segmentation 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) - else: - LOG.error(_LE('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(_LI('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(_LI("Provisioning network %s"), network_id) - if network_type == p_const.TYPE_VLAN: - LOG.debug("Creating VLAN Network") - else: - LOG.error(_LE("Unknown network type %(network_type)s " - "for network %(network_id)s"), - {'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(sg_rpc.SecurityGroupAgentRpcCallbackMixin): - - # Set RPC API version to 1.0 by default. - # history - # 1.1 Support Security Group RPC - target = messaging.Target(version='1.1') - - def __init__(self, context, agent, sg_agent): - super(MlnxEswitchRpcCallbacks, self).__init__() - self.context = context - self.agent = agent - self.eswitch = agent.eswitch - self.sg_agent = sg_agent - - def network_delete(self, context, **kwargs): - LOG.debug("network_delete received") - network_id = kwargs.get('network_id') - if not network_id: - LOG.warning(_LW("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): - port = kwargs.get('port') - self.agent.add_port_update(port['mac_address']) - LOG.debug("port_update message processed for port with mac %s", - port['mac_address']) - - -class MlnxEswitchNeutronAgent(object): - - def __init__(self, interface_mapping, root_helper): - self._polling_interval = cfg.CONF.AGENT.polling_interval - self._setup_eswitches(interface_mapping) - configurations = {'interface_mappings': interface_mapping} - self.agent_state = { - 'binary': 'neutron-mlnx-agent', - 'host': cfg.CONF.host, - 'topic': q_constants.L2_AGENT_TOPIC, - 'configurations': configurations, - 'agent_type': q_constants.AGENT_TYPE_MLNX, - 'start_flag': True} - # Stores port update notifications for processing in main rpc loop - self.updated_ports = set() - self.context = context.get_admin_context_without_session() - self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN) - self.sg_plugin_rpc = sg_rpc.SecurityGroupServerRpcApi(topics.PLUGIN) - self.sg_agent = sg_rpc.SecurityGroupAgentRpc(self.context, - self.sg_plugin_rpc, root_helper) - 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.get('configurations')['devices'] = devices - self.state_rpc.report_state(self.context, - self.agent_state) - self.agent_state.pop('start_flag', None) - except Exception: - LOG.exception(_LE("Failed reporting state!")) - - def _setup_rpc(self): - self.agent_id = 'mlnx-agent.%s' % socket.gethostname() - LOG.info(_LI("RPC agent_id: %s"), self.agent_id) - - self.topic = topics.AGENT - self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN) - # RPC network init - # Handle updates from service - self.endpoints = [MlnxEswitchRpcCallbacks(self.context, self, - self.sg_agent)] - # Define the listening consumers for the agent - consumers = [[topics.PORT, topics.UPDATE], - [topics.NETWORK, topics.DELETE], - [topics.SECURITY_GROUP, topics.UPDATE]] - self.connection = agent_rpc.create_consumers(self.endpoints, - self.topic, - consumers) - - report_interval = cfg.CONF.AGENT.report_interval - if report_interval: - heartbeat = loopingcall.FixedIntervalLoopingCall( - self._report_state) - heartbeat.start(interval=report_interval) - - def add_port_update(self, port): - self.updated_ports.add(port) - - def scan_ports(self, previous, sync): - cur_ports = self.eswitch.get_vnics_mac() - port_info = {'current': cur_ports} - updated_ports = self.updated_ports - self.updated_ports = set() - if sync: - # Either it's the first iteration or previous iteration had - # problems. - port_info['added'] = cur_ports - port_info['removed'] = ((previous['removed'] | previous['current']) - - cur_ports) - port_info['updated'] = ((previous['updated'] | updated_ports) - & cur_ports) - else: - # Shouldn't process updates for not existing ports - port_info['added'] = cur_ports - previous['current'] - port_info['removed'] = previous['current'] - cur_ports - port_info['updated'] = updated_ports & cur_ports - return port_info - - def process_network_ports(self, port_info): - resync_a = False - resync_b = False - device_added_updated = port_info['added'] | port_info['updated'] - - if device_added_updated: - resync_a = self.treat_devices_added_or_updated( - device_added_updated) - if port_info['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_or_updated(self, devices): - try: - devs_details_list = self.plugin_rpc.get_devices_details_list( - self.context, - devices, - self.agent_id) - except Exception as e: - LOG.debug("Unable to get device details for devices " - "with MAC address %(devices)s: due to %(exc)s", - {'devices': devices, 'exc': e}) - # resync is needed - return True - - for dev_details in devs_details_list: - device = dev_details['device'] - LOG.info(_LI("Adding or updating port with mac %s"), device) - - if 'port_id' in dev_details: - LOG.info(_LI("Port %s updated"), device) - LOG.debug("Device details %s", str(dev_details)) - self.treat_vif_port(dev_details['port_id'], - dev_details['device'], - dev_details['network_id'], - dev_details['network_type'], - dev_details['physical_network'], - dev_details['segmentation_id'], - dev_details['admin_state_up']) - if dev_details.get('admin_state_up'): - LOG.debug("Setting status for %s to UP", device) - self.plugin_rpc.update_device_up( - self.context, device, self.agent_id) - else: - LOG.debug("Setting status for %s to DOWN", device) - self.plugin_rpc.update_device_down( - self.context, device, self.agent_id) - else: - LOG.debug("Device with mac_address %s not defined " - "on Neutron Plugin", device) - return False - - def treat_devices_removed(self, devices): - resync = False - for device in devices: - LOG.info(_LI("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, - cfg.CONF.host) - 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(_LI("Port %s updated."), device) - else: - LOG.debug("Device %s not defined on plugin", device) - self.eswitch.port_release(device) - return resync - - def _port_info_has_changes(self, port_info): - return (port_info['added'] or - port_info['removed'] or - port_info['updated']) - - def daemon_loop(self): - LOG.info(_LI("eSwitch Agent Started!")) - sync = True - port_info = {'current': set(), - 'added': set(), - 'removed': set(), - 'updated': set()} - while True: - start = time.time() - try: - port_info = self.scan_ports(previous=port_info, sync=sync) - except exceptions.RequestTimeout: - LOG.exception(_LE("Request timeout in agent event loop " - "eSwitchD is not responding - exiting...")) - raise SystemExit(1) - if sync: - LOG.info(_LI("Agent out of sync with plugin!")) - sync = False - if self._port_info_has_changes(port_info): - LOG.debug("Starting to process devices in:%s", port_info) - try: - sync = self.process_network_ports(port_info) - except Exception: - LOG.exception(_LE("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(): common_config.init(sys.argv[1:]) common_config.setup_logging() try: - interface_mappings = q_utils.parse_mappings( + interface_mappings = utils.parse_mappings( cfg.CONF.ESWITCH.physical_interface_mappings) except ValueError as e: LOG.error(_LE("Parsing physical_interface_mappings failed: %s. " @@ -406,7 +43,8 @@ def main(): root_helper = cfg.CONF.AGENT.root_helper try: - agent = MlnxEswitchNeutronAgent(interface_mappings, root_helper) + agent = mlnx_eswitch_neutron_agent.MlnxEswitchNeutronAgent( + interface_mappings, root_helper) except Exception as e: LOG.error(_LE("Failed on Agent initialisation : %s. " "Agent terminated!"), e) @@ -414,7 +52,7 @@ def main(): # Start everything. LOG.info(_LI("Agent initialised successfully, now running... ")) - agent.daemon_loop() + agent.run() sys.exit(0) diff --git a/neutron/plugins/mlnx/agent/utils.py b/neutron/plugins/mlnx/agent/utils.py deleted file mode 100644 index 5cf056eb0..000000000 --- a/neutron/plugins/mlnx/agent/utils.py +++ /dev/null @@ -1,143 +0,0 @@ -# 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.serialization import jsonutils -from oslo.utils import importutils - -from neutron.i18n import _LE -from neutron.openstack.common import log as logging -from neutron.plugins.mlnx.common import comm_utils -from neutron.plugins.mlnx.common import exceptions - -zmq = importutils.try_import('eventlet.green.zmq') - -LOG = logging.getLogger(__name__) - - -class EswitchUtils(object): - def __init__(self, daemon_endpoint, timeout): - if not zmq: - LOG.error(_LE("Failed to import eventlet.green.zmq. " - "Won't connect to eSwitchD - exiting...")) - raise SystemExit(1) - 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 - - @comm_utils.RetryDecorator(exceptions.RequestTimeout) - 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.RequestTimeout() - - 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 = _LE("Action %(action)s failed: %(reason)s") % msg_dict - else: - error_msg = _LE("Unknown operation status %s") % msg['status'] - LOG.error(error_msg) - raise exceptions.OperationFailed(err_msg=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 "" diff --git a/neutron/plugins/mlnx/common/__init__.py b/neutron/plugins/mlnx/common/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/neutron/plugins/mlnx/common/comm_utils.py b/neutron/plugins/mlnx/common/comm_utils.py deleted file mode 100644 index d0cbb2b12..000000000 --- a/neutron/plugins/mlnx/common/comm_utils.py +++ /dev/null @@ -1,63 +0,0 @@ -# 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 time - -from oslo.config import cfg - -from neutron.openstack.common import log as logging -from neutron.plugins.mlnx.common import config # noqa - -LOG = logging.getLogger(__name__) - - -class RetryDecorator(object): - """Retry decorator reruns a method 'retries' times if an exception occurs. - - Decorator for retrying a method if exceptionToCheck exception occurs - If method raises exception, retries 'retries' times with increasing - back off period between calls with 'interval' multiplier - - :param exceptionToCheck: the exception to check - :param interval: initial delay between retries in seconds - :param retries: number of times to try before giving up - :raises: exceptionToCheck - """ - - def __init__(self, exceptionToCheck, - interval=cfg.CONF.ESWITCH.request_timeout / 1000, - retries=cfg.CONF.ESWITCH.retries, - backoff_rate=cfg.CONF.ESWITCH.backoff_rate): - self.exc = exceptionToCheck - self.interval = interval - self.retries = retries - self.backoff_rate = backoff_rate - - def __call__(self, original_func): - def decorated(*args, **kwargs): - sleep_interval = self.interval - num_of_iter = self.retries - while num_of_iter > 0: - try: - return original_func(*args, **kwargs) - except self.exc: - LOG.debug("Request timeout - call again after " - "%s seconds", sleep_interval) - time.sleep(sleep_interval) - num_of_iter -= 1 - sleep_interval *= self.backoff_rate - - return original_func(*args, **kwargs) - return decorated diff --git a/neutron/plugins/mlnx/common/constants.py b/neutron/plugins/mlnx/common/constants.py deleted file mode 100644 index 4ca82d759..000000000 --- a/neutron/plugins/mlnx/common/constants.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 - -# Values for physical network_type -TYPE_IB = 'ib' -TYPE_ETH = 'eth' - -VIF_TYPE_DIRECT = 'mlnx_direct' -VIF_TYPE_HOSTDEV = 'hostdev' - -VNIC_TYPE = 'vnic_type' diff --git a/neutron/plugins/mlnx/common/exceptions.py b/neutron/plugins/mlnx/common/exceptions.py deleted file mode 100644 index 457a1006f..000000000 --- a/neutron/plugins/mlnx/common/exceptions.py +++ /dev/null @@ -1,28 +0,0 @@ -# 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 neutron.common import exceptions as qexc - - -class MlnxException(qexc.NeutronException): - message = _("Mlnx Exception: %(err_msg)s") - - -class RequestTimeout(qexc.NeutronException): - message = _("Request Timeout: no response from eSwitchD") - - -class OperationFailed(qexc.NeutronException): - message = _("Operation Failed: %(err_msg)s") diff --git a/neutron/tests/unit/ml2/drivers/test_mech_mlnx.py b/neutron/tests/unit/ml2/drivers/test_mech_mlnx.py index 1192ab489..6ccbe8658 100644 --- a/neutron/tests/unit/ml2/drivers/test_mech_mlnx.py +++ b/neutron/tests/unit/ml2/drivers/test_mech_mlnx.py @@ -12,14 +12,25 @@ # 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 - +import mock from neutron.common import constants from neutron.extensions import portbindings from neutron.plugins.ml2 import driver_api as api -from neutron.plugins.ml2.drivers.mlnx import mech_mlnx from neutron.tests.unit.ml2 import _test_mech_agent as base +m_const_mock = mock.Mock() + +with mock.patch.dict(sys.modules, + {'networking_mlnx': mock.Mock(), + 'networking_mlnx.plugins': mock.Mock(), + 'networking_mlnx.plugins.ml2': mock.Mock(), + 'networking_mlnx.plugins.ml2.drivers': mock.Mock(), + 'networking_mlnx.plugins.ml2.drivers.mlnx': + m_const_mock}): + from neutron.plugins.ml2.drivers.mlnx import mech_mlnx + class MlnxMechanismBaseTestCase(base.AgentMechanismBaseTestCase): VIF_TYPE = portbindings.VIF_TYPE_MLNX_DIRECT @@ -49,6 +60,8 @@ class MlnxMechanismBaseTestCase(base.AgentMechanismBaseTestCase): super(MlnxMechanismBaseTestCase, self).setUp() self.driver = mech_mlnx.MlnxMechanismDriver() self.driver.initialize() + m_const_mock.constants.VNIC_TO_VIF_MAPPING.get.return_value = ( + self.driver.vif_type) class MlnxMechanismGenericTestCase(MlnxMechanismBaseTestCase, @@ -68,6 +81,7 @@ class MlnxMechanismFlatTestCase(MlnxMechanismBaseTestCase, class MlnxMechanismVnicTypeTestCase(MlnxMechanismBaseTestCase, base.AgentMechanismVlanTestCase): + def _check_vif_type_for_vnic_type(self, vnic_type, expected_vif_type): context = base.FakePortContext(self.AGENT_TYPE, @@ -78,10 +92,15 @@ class MlnxMechanismVnicTypeTestCase(MlnxMechanismBaseTestCase, self.assertEqual(expected_vif_type, context._bound_vif_type) def test_vnic_type_direct(self): + m_const_mock.constants.VNIC_TO_VIF_MAPPING.get.return_value = ( + portbindings.VIF_TYPE_MLNX_HOSTDEV) self._check_vif_type_for_vnic_type(portbindings.VNIC_DIRECT, portbindings.VIF_TYPE_MLNX_HOSTDEV) def test_vnic_type_macvtap(self): + m_const_mock.constants.VNIC_TO_VIF_MAPPING.get.return_value = ( + portbindings.VIF_TYPE_MLNX_DIRECT) + self._check_vif_type_for_vnic_type(portbindings.VNIC_MACVTAP, portbindings.VIF_TYPE_MLNX_DIRECT) diff --git a/neutron/tests/unit/mlnx/__init__.py b/neutron/tests/unit/mlnx/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/neutron/tests/unit/mlnx/test_mlnx_comm_utils.py b/neutron/tests/unit/mlnx/test_mlnx_comm_utils.py deleted file mode 100644 index 49f2eacee..000000000 --- a/neutron/tests/unit/mlnx/test_mlnx_comm_utils.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import mock -from oslo.config import cfg - -from neutron.plugins.mlnx.common import comm_utils -from neutron.plugins.mlnx.common import config # noqa -from neutron.plugins.mlnx.common import exceptions -from neutron.tests import base - - -class WrongException(Exception): - pass - - -class TestRetryDecorator(base.BaseTestCase): - def setUp(self): - super(TestRetryDecorator, self).setUp() - self.sleep_fn_p = mock.patch("time.sleep") - self.sleep_fn = self.sleep_fn_p.start() - - def test_no_retry_required(self): - self.counter = 0 - - @comm_utils.RetryDecorator(exceptions.RequestTimeout, interval=2, - retries=3, backoff_rate=2) - def succeeds(): - self.counter += 1 - return 'success' - - ret = succeeds() - self.assertFalse(self.sleep_fn.called) - self.assertEqual(ret, 'success') - self.assertEqual(self.counter, 1) - - def test_retry_zero_times(self): - self.counter = 0 - interval = 2 - backoff_rate = 2 - retries = 0 - - @comm_utils.RetryDecorator(exceptions.RequestTimeout, interval, - retries, backoff_rate) - def always_fails(): - self.counter += 1 - raise exceptions.RequestTimeout() - - self.assertRaises(exceptions.RequestTimeout, always_fails) - self.assertEqual(self.counter, 1) - self.assertFalse(self.sleep_fn.called) - - def test_retries_once(self): - self.counter = 0 - interval = 2 - backoff_rate = 2 - retries = 3 - - @comm_utils.RetryDecorator(exceptions.RequestTimeout, interval, - retries, backoff_rate) - def fails_once(): - self.counter += 1 - if self.counter < 2: - raise exceptions.RequestTimeout() - else: - return 'success' - - ret = fails_once() - self.assertEqual(ret, 'success') - self.assertEqual(self.counter, 2) - self.assertEqual(self.sleep_fn.call_count, 1) - self.sleep_fn.assert_called_with(interval) - - def test_limit_is_reached(self): - self.counter = 0 - retries = 3 - interval = 2 - backoff_rate = 4 - - @comm_utils.RetryDecorator(exceptions.RequestTimeout, interval, - retries, backoff_rate) - def always_fails(): - self.counter += 1 - raise exceptions.RequestTimeout() - - self.assertRaises(exceptions.RequestTimeout, always_fails) - self.assertEqual(self.counter, retries + 1) - self.assertEqual(self.sleep_fn.call_count, retries) - - expected_sleep_fn_arg = [] - for i in range(retries): - expected_sleep_fn_arg.append(interval) - interval *= backoff_rate - - self.sleep_fn.assert_has_calls(map(mock.call, expected_sleep_fn_arg)) - - def test_limit_is_reached_with_conf(self): - self.counter = 0 - - @comm_utils.RetryDecorator(exceptions.RequestTimeout) - def always_fails(): - self.counter += 1 - raise exceptions.RequestTimeout() - - retry = cfg.CONF.ESWITCH.retries - interval = cfg.CONF.ESWITCH.request_timeout / 1000 - delay_rate = cfg.CONF.ESWITCH.backoff_rate - - expected_sleep_fn_arg = [] - for i in range(retry): - expected_sleep_fn_arg.append(interval) - interval *= delay_rate - - self.assertRaises(exceptions.RequestTimeout, always_fails) - self.assertEqual(self.counter, retry + 1) - self.assertEqual(self.sleep_fn.call_count, retry) - self.sleep_fn.assert_has_calls(map(mock.call, expected_sleep_fn_arg)) - - def test_wrong_exception_no_retry(self): - - @comm_utils.RetryDecorator(exceptions.RequestTimeout) - def raise_unexpected_error(): - raise WrongException("wrong exception") - - self.assertRaises(WrongException, raise_unexpected_error) - self.assertFalse(self.sleep_fn.called) diff --git a/neutron/tests/unit/mlnx/test_mlnx_neutron_agent.py b/neutron/tests/unit/mlnx/test_mlnx_neutron_agent.py deleted file mode 100644 index e149af231..000000000 --- a/neutron/tests/unit/mlnx/test_mlnx_neutron_agent.py +++ /dev/null @@ -1,234 +0,0 @@ -# Copyright 2014 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 contextlib - -import mock -from oslo.config import cfg -import testtools - -from neutron.plugins.mlnx.agent import eswitch_neutron_agent -from neutron.plugins.mlnx.agent import utils -from neutron.plugins.mlnx.common import exceptions -from neutron.tests import base - - -class TestEswichManager(base.BaseTestCase): - - def setUp(self): - super(TestEswichManager, self).setUp() - - class MockEswitchUtils(object): - def __init__(self, endpoint, timeout): - pass - - mock.patch('neutron.plugins.mlnx.agent.utils.EswitchManager', - new=MockEswitchUtils) - - with mock.patch.object(utils, 'zmq'): - self.manager = eswitch_neutron_agent.EswitchManager({}, None, None) - - def test_get_not_exist_port_id(self): - with testtools.ExpectedException(exceptions.MlnxException): - self.manager.get_port_id_by_mac('no-such-mac') - - -class TestMlnxEswitchRpcCallbacks(base.BaseTestCase): - - def setUp(self): - super(TestMlnxEswitchRpcCallbacks, self).setUp() - agent = mock.Mock() - self.rpc_callbacks = eswitch_neutron_agent.MlnxEswitchRpcCallbacks( - 'context', - agent, - agent - ) - - def test_port_update(self): - port = {'mac_address': '10:20:30:40:50:60'} - add_port_update = self.rpc_callbacks.agent.add_port_update - self.rpc_callbacks.port_update('context', port=port) - add_port_update.assert_called_once_with(port['mac_address']) - - -class TestEswitchAgent(base.BaseTestCase): - - def setUp(self): - super(TestEswitchAgent, self).setUp() - cfg.CONF.set_default('firewall_driver', - 'neutron.agent.firewall.NoopFirewallDriver', - group='SECURITYGROUP') - - class MockFixedIntervalLoopingCall(object): - def __init__(self, f): - self.f = f - - def start(self, interval=0): - self.f() - - mock.patch('neutron.openstack.common.loopingcall.' - 'FixedIntervalLoopingCall', - new=MockFixedIntervalLoopingCall) - - with mock.patch.object(utils, 'zmq'): - self.agent = eswitch_neutron_agent.MlnxEswitchNeutronAgent({}, {}) - self.agent.plugin_rpc = mock.Mock() - self.agent.context = mock.Mock() - self.agent.agent_id = mock.Mock() - self.agent.eswitch = mock.Mock() - self.agent.eswitch.get_vnics_mac.return_value = [] - - def test_treat_devices_added_returns_true_for_missing_device(self): - attrs = {'get_devices_details_list.side_effect': Exception()} - self.agent.plugin_rpc.configure_mock(**attrs) - with contextlib.nested( - mock.patch('neutron.plugins.mlnx.agent.eswitch_neutron_agent.' - 'EswitchManager.get_vnics_mac', - return_value=[])): - self.assertTrue(self.agent.treat_devices_added_or_updated([{}])) - - def _mock_treat_devices_added_updated(self, details, func_name): - """Mock treat devices added. - - :param details: the details to return for the device - :param func_name: the function that should be called - :returns: whether the named function was called - """ - with contextlib.nested( - mock.patch('neutron.plugins.mlnx.agent.eswitch_neutron_agent.' - 'EswitchManager.get_vnics_mac', - return_value=[]), - mock.patch.object(self.agent.plugin_rpc, - 'get_devices_details_list', - return_value=[details]), - mock.patch.object(self.agent.plugin_rpc, 'update_device_up'), - mock.patch.object(self.agent, func_name) - ) as (vnics_fn, get_dev_fn, upd_dev_up, func): - self.assertFalse(self.agent.treat_devices_added_or_updated([{}])) - return (func.called, upd_dev_up.called) - - def test_treat_devices_added_updates_known_port(self): - details = mock.MagicMock() - details.__contains__.side_effect = lambda x: True - func, dev_up = self._mock_treat_devices_added_updated(details, - 'treat_vif_port') - self.assertTrue(func) - self.assertTrue(dev_up) - - def test_treat_devices_added_updates_known_port_admin_down(self): - details = {'port_id': '1234567890', - 'device': '01:02:03:04:05:06', - 'network_id': '123456789', - 'network_type': 'vlan', - 'physical_network': 'default', - 'segmentation_id': 2, - 'admin_state_up': False} - func, dev_up = self._mock_treat_devices_added_updated(details, - 'treat_vif_port') - self.assertTrue(func) - self.assertFalse(dev_up) - - def test_treat_devices_removed_returns_true_for_missing_device(self): - with mock.patch.object(self.agent.plugin_rpc, 'update_device_down', - side_effect=Exception()): - self.assertTrue(self.agent.treat_devices_removed([{}])) - - def test_treat_devices_removed_releases_port(self): - details = dict(exists=False) - with mock.patch.object(self.agent.plugin_rpc, 'update_device_down', - return_value=details): - with mock.patch.object(self.agent.eswitch, - 'port_release') as port_release: - self.assertFalse(self.agent.treat_devices_removed([{}])) - self.assertTrue(port_release.called) - - def _test_process_network_ports(self, port_info): - with contextlib.nested( - mock.patch.object(self.agent, 'treat_devices_added_or_updated', - return_value=False), - mock.patch.object(self.agent, 'treat_devices_removed', - return_value=False) - ) as (device_added_updated, device_removed): - self.assertFalse(self.agent.process_network_ports(port_info)) - device_added_updated.assert_called_once_with( - port_info['added'] | port_info['updated']) - device_removed.assert_called_once_with(port_info['removed']) - - def test_process_network_ports(self): - self._test_process_network_ports( - {'current': set(['10:20:30:40:50:60']), - 'updated': set(), - 'added': set(['11:21:31:41:51:61']), - 'removed': set(['13:23:33:43:53:63'])}) - - def test_process_network_ports_with_updated_ports(self): - self._test_process_network_ports( - {'current': set(['10:20:30:40:50:60']), - 'updated': set(['12:22:32:42:52:62']), - 'added': set(['11:21:31:41:51:61']), - 'removed': set(['13:23:33:43:53:63'])}) - - def test_add_port_update(self): - mac_addr = '10:20:30:40:50:60' - self.agent.add_port_update(mac_addr) - self.assertEqual(set([mac_addr]), self.agent.updated_ports) - - def _mock_scan_ports(self, vif_port_set, previous, - updated_ports, sync=False): - self.agent.updated_ports = updated_ports - with mock.patch.object(self.agent.eswitch, 'get_vnics_mac', - return_value=vif_port_set): - return self.agent.scan_ports(previous, sync) - - def test_scan_ports_return_current_for_unchanged_ports(self): - vif_port_set = set([1, 2]) - previous = dict(current=set([1, 2]), added=set(), - removed=set(), updated=set()) - expected = dict(current=vif_port_set, added=set(), - removed=set(), updated=set()) - actual = self._mock_scan_ports(vif_port_set, - previous, set()) - self.assertEqual(expected, actual) - - def test_scan_ports_return_port_changes(self): - vif_port_set = set([1, 3]) - previous = dict(current=set([1, 2]), added=set(), - removed=set(), updated=set()) - expected = dict(current=vif_port_set, added=set([3]), - removed=set([2]), updated=set()) - actual = self._mock_scan_ports(vif_port_set, - previous, set()) - self.assertEqual(expected, actual) - - def test_scan_ports_with_updated_ports(self): - vif_port_set = set([1, 3, 4]) - previous = dict(current=set([1, 2, 4]), added=set(), - removed=set(), updated=set()) - expected = dict(current=vif_port_set, added=set([3]), - removed=set([2]), updated=set([4])) - actual = self._mock_scan_ports(vif_port_set, - previous, set([4])) - self.assertEqual(expected, actual) - - def test_scan_ports_with_unknown_updated_ports(self): - vif_port_set = set([1, 3, 4]) - previous = dict(current=set([1, 2, 4]), added=set(), - removed=set(), updated=set()) - expected = dict(current=vif_port_set, added=set([3]), - removed=set([2]), updated=set([4])) - actual = self._mock_scan_ports(vif_port_set, - previous, - updated_ports=set([4, 5])) - self.assertEqual(expected, actual)