From c3b2c2a9a22bac5cf17813c589444d3abebaa23b Mon Sep 17 00:00:00 2001 From: Wojciech Dec Date: Tue, 16 Aug 2016 19:27:01 +0200 Subject: Adding Mitaka networking-old module with the ODL topology based port binding resolution mechanism from https://review.openstack.org/333186 Change-Id: I10d400aac9bb639c146527f0f93e6925cb74d9de Signed-off-by: Wojciech Dec --- .../networking_odl/ml2/pseudo_agentdb_binding.py | 263 +++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 networking-odl/networking_odl/ml2/pseudo_agentdb_binding.py (limited to 'networking-odl/networking_odl/ml2/pseudo_agentdb_binding.py') diff --git a/networking-odl/networking_odl/ml2/pseudo_agentdb_binding.py b/networking-odl/networking_odl/ml2/pseudo_agentdb_binding.py new file mode 100644 index 0000000..d24bd55 --- /dev/null +++ b/networking-odl/networking_odl/ml2/pseudo_agentdb_binding.py @@ -0,0 +1,263 @@ +# Copyright (c) 2016 OpenStack Foundation +# All Rights Reserved. +# +# 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 logging +from neutron_lib import constants as nl_const +from requests import exceptions +import six.moves.urllib.parse as urlparse +from string import Template + +from oslo_config import cfg +from oslo_log import log +from oslo_serialization import jsonutils + +from neutron import context +from neutron.extensions import portbindings +from neutron import manager +from neutron.plugins.ml2 import driver_api + +from networking_odl._i18n import _LE, _LI, _LW +from networking_odl.common import client as odl_client +from networking_odl.journal import maintenance as mt +from networking_odl.ml2 import port_binding + +cfg.CONF.import_group('ml2_odl', 'networking_odl.common.config') +LOG = log.getLogger(__name__) + + +class PseudoAgentDBBindingController(port_binding.PortBindingController): + """Switch agnostic Port binding controller for OpenDayLight.""" + + AGENTDB_BINARY = 'neutron-odlagent-portbinding' + L2_TYPE = "ODL L2" + + # TODO(mzmalick): binary, topic and resource_versions to be provided + # by ODL, Pending ODL NB patches. + agentdb_row = { + 'binary': AGENTDB_BINARY, + 'host': '', + 'topic': nl_const.L2_AGENT_TOPIC, + 'configurations': {}, + 'resource_versions': '', + 'agent_type': L2_TYPE, + 'start_flag': True} + # We are not running host agents, so above start_flag is redundant + + def __init__(self, hostconf_uri=None, db_plugin=None): + """Initialization.""" + LOG.debug("Initializing ODL Port Binding Controller") + + if not hostconf_uri: + # extract host/port from ODL URL and append hostconf_uri path + hostconf_uri = self._make_hostconf_uri( + cfg.CONF.ml2_odl.url, cfg.CONF.ml2_odl.odl_hostconf_uri) + + LOG.debug("ODLPORTBINDING hostconfigs URI: %s", hostconf_uri) + + # TODO(mzmalick): disable port-binding for ODL lightweight testing + self.odl_rest_client = odl_client.OpenDaylightRestClient.create_client( + url=hostconf_uri) + + # Neutron DB plugin instance + self.agents_db = db_plugin + + # Start polling ODL restconf using maintenance thread. + # default: 30s (should be <= agent keep-alive poll interval) + self._start_maintenance_thread(cfg.CONF.ml2_odl.restconf_poll_interval) + + def _make_hostconf_uri(self, odl_url=None, path=''): + """Make ODL hostconfigs URI with host/port extraced from ODL_URL.""" + # NOTE(yamahata): for unit test. + odl_url = odl_url or 'http://localhost:8080/' + + # extract ODL_IP and ODL_PORT from ODL_ENDPOINT and append path + # urlsplit and urlunparse don't throw exceptions + purl = urlparse.urlsplit(odl_url) + return urlparse.urlunparse((purl.scheme, purl.netloc, + path, '', '', '')) + # + # TODO(mzmalick): + # 1. implement websockets for ODL hostconfig events + # + + def _start_maintenance_thread(self, poll_interval): + self._mainth = mt.MaintenanceThread() + self._mainth.maintenance_interval = poll_interval + self._mainth.register_operation(self._get_and_update_hostconfigs) + self._mainth.start() + + def _rest_get_hostconfigs(self): + try: + response = self.odl_rest_client.get() + response.raise_for_status() + hostconfigs = response.json()['hostconfigs']['hostconfig'] + except exceptions.ConnectionError: + LOG.error(_LE("Cannot connect to the Opendaylight Controller"), + exc_info=True) + return None + except KeyError: + LOG.error(_LE("got invalid hostconfigs"), + exc_info=True) + return None + except Exception: + LOG.warning(_LW("REST/GET odl hostconfig failed, "), + exc_info=True) + return None + else: + if LOG.isEnabledFor(logging.DEBUG): + _hconfig_str = jsonutils.dumps( + response, sort_keys=True, indent=4, separators=(',', ': ')) + LOG.debug("ODLPORTBINDING hostconfigs:\n%s", _hconfig_str) + + return hostconfigs + + def _get_and_update_hostconfigs(self, session=None): + LOG.info(_LI("REST/GET hostconfigs from ODL")) + + hostconfigs = self._rest_get_hostconfigs() + + if not hostconfigs: + LOG.warning(_LW("ODL hostconfigs REST/GET failed, " + "will retry on next poll")) + return # retry on next poll + + self._update_agents_db(hostconfigs=hostconfigs) + + def _get_neutron_db_plugin(self): + if (not self.agents_db) and manager.NeutronManager.has_instance(): + self.agents_db = manager.NeutronManager.get_plugin() + return self.agents_db + + def _update_agents_db(self, hostconfigs): + LOG.debug("ODLPORTBINDING Updating agents DB with ODL hostconfigs") + + agents_db = self._get_neutron_db_plugin() + + if not agents_db: # if ML2 is still initializing + LOG.warning(_LW("ML2 still initializing, Will retry agentdb" + " update on next poll")) + return # Retry on next poll + + for host_config in hostconfigs: + try: + self.agentdb_row['host'] = host_config['host-id'] + self.agentdb_row['agent_type'] = host_config['host-type'] + self.agentdb_row['configurations'] = host_config['config'] + + agents_db.create_or_update_agent( + context.get_admin_context(), self.agentdb_row) + except Exception: + LOG.exception(_LE("Unable to update agentdb.")) + continue # try next hostcofig + + def _substitute_hconfig_tmpl(self, port_context, hconfig): + # TODO(mzmalick): Explore options for inlines string splicing of + # port-id to 14 bytes as required by vhostuser types + subs_ids = { + # $IDENTIFER string substitution in hostconfigs JSON string + 'PORT_ID': port_context.current['id'][:14] + } + + # Substitute identifiers and Convert JSON string to dict + hconfig_conf_json = Template(hconfig['configurations']) + substituted_str = hconfig_conf_json.safe_substitute(subs_ids) + hconfig['configurations'] = jsonutils.loads(substituted_str) + + return hconfig + + def bind_port(self, port_context): + """bind port using ODL host configuration.""" + # Get all ODL hostconfigs for this host and type + agentdb = port_context.host_agents(self.L2_TYPE) + + if not agentdb: + LOG.warning(_LW("No valid hostconfigs in agentsdb for host %s"), + port_context.host) + return + + for raw_hconfig in agentdb: + # do any $identifier substitution + hconfig = self._substitute_hconfig_tmpl(port_context, raw_hconfig) + + # Found ODL hostconfig for this host in agentdb + LOG.debug("ODLPORTBINDING bind port with hostconfig: %s", hconfig) + + if self._hconfig_bind_port(port_context, hconfig): + break # Port binding suceeded! + else: # Port binding failed! + LOG.warning(_LW("Failed to bind Port %(pid)s for host " + "%(host)s on network %(network)s."), { + 'pid': port_context.current['id'], + 'host': port_context.host, + 'network': port_context.network.current['id']}) + else: # No hostconfig found for host in agentdb. + LOG.warning(_LW("No ODL hostconfigs for host %s found in agentdb"), + port_context.host) + + def _hconfig_bind_port(self, port_context, hconfig): + """bind port after validating odl host configuration.""" + valid_segment = None + + for segment in port_context.segments_to_bind: + if self._is_valid_segment(segment, hconfig['configurations']): + valid_segment = segment + break + else: + LOG.debug("No valid segments found!") + return False + + confs = hconfig['configurations']['supported_vnic_types'] + + # nova provides vnic_type in port_context to neutron. + # neutron provides supported vif_type for binding based on vnic_type + # in this case ODL hostconfigs has the vif_type to bind for vnic_type + vnic_type = port_context.current.get(portbindings.VNIC_TYPE) + + if vnic_type != portbindings.VNIC_NORMAL: + LOG.error(_LE("Binding failed: unsupported VNIC %s"), vnic_type) + return False + + for conf in confs: + if conf["vnic_type"] == vnic_type: + vif_type = conf.get('vif_type', portbindings.VIF_TYPE_OVS) + LOG.debug("Binding vnic:'%s' to vif:'%s'", vnic_type, vif_type) + break + else: + vif_type = portbindings.VIF_TYPE_OVS # default: OVS + LOG.warning(_LW("No supported vif type found for host %s!, " + "defaulting to OVS"), port_context.host) + + vif_details = conf.get('vif_details', {}) + + if not vif_details: # empty vif_details could be trouble, warn. + LOG.warning(_LW("hostconfig:vif_details was empty!")) + + LOG.debug("Bind port %(port)s on network %(network)s with valid " + "segment %(segment)s and VIF type %(vif_type)r " + "VIF details %(vif_details)r.", + {'port': port_context.current['id'], + 'network': port_context.network.current['id'], + 'segment': valid_segment, 'vif_type': vif_type, + 'vif_details': vif_details}) + + port_context.set_binding(valid_segment[driver_api.ID], vif_type, + vif_details, + status=nl_const.PORT_STATUS_ACTIVE) + return True + + def _is_valid_segment(self, segment, conf): + """Verify a segment is supported by ODL.""" + network_type = segment[driver_api.NETWORK_TYPE] + return network_type in conf['allowed_network_types'] -- cgit 1.2.3-korg