diff options
author | Wojciech Dec <wdec@cisco.com> | 2016-08-17 13:14:23 +0200 |
---|---|---|
committer | Wojciech Dec <wdec@cisco.com> | 2016-08-17 13:14:23 +0200 |
commit | 4979a23b8b2c0094ced98cf05eebb692d6609937 (patch) | |
tree | c49ceeb5b127fdb0e10c0f5ac0516be96cbd31a9 /networking-odl/networking_odl | |
parent | c3b2c2a9a22bac5cf17813c589444d3abebaa23b (diff) |
Correcting networking-odl to mitaka/stable + app topology patch
Change-Id: Iddcd8dda2d49fcdd8e0f37a1d052a6fa8a24b035
Signed-off-by: Wojciech Dec <wdec@cisco.com>
Diffstat (limited to 'networking-odl/networking_odl')
51 files changed, 771 insertions, 2184 deletions
diff --git a/networking-odl/networking_odl/cmd/__init__.py b/networking-odl/networking_odl/cmd/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/networking-odl/networking_odl/cmd/__init__.py +++ /dev/null diff --git a/networking-odl/networking_odl/cmd/set_ovs_hostconfigs.py b/networking-odl/networking_odl/cmd/set_ovs_hostconfigs.py deleted file mode 100644 index 8b8b1d3..0000000 --- a/networking-odl/networking_odl/cmd/set_ovs_hostconfigs.py +++ /dev/null @@ -1,123 +0,0 @@ -# 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. - -from oslo_config import cfg -from oslo_log import log -from oslo_serialization import jsonutils - -from neutron._i18n import _ -from neutron._i18n import _LE -from neutron._i18n import _LI -from neutron.agent.common import utils -from neutron.common import config - -LOG = log.getLogger(__name__) - - -class SetOvsHostconfigs(object): - - # Refer below for ovs ext-id strings - # https://review.openstack.org/#/c/309630/ - extid_str = 'external_ids:{}={}' - odl_os_hconf_str = 'odl_os_hostconfig_config_{}' - odl_os_hostid_str = 'odl_os_hostconfig_hostid' - odl_os_hosttype_str = 'odl_os_hostconfig_hosttype' - - # TODO(mzmalick): use neutron.agent.ovsdb instead of subprocess.Popen - ovs_cmd_get_uuid = ['ovs-vsctl', 'get', 'Open_vSwitch', '.', '_uuid'] - ovs_cmd_set_extid = ['ovs-vsctl', 'set', 'Open_vSwitch', '', ''] - - UUID = 3 - EXTID = 4 - - def __init__(self): - self.ovs_uuid = self.get_ovs_uuid() - - def ovs_exec_cmd(self, cmd): - LOG.info(_LI("SET-HOSTCONFIGS: Executing cmd: %s"), ' '.join(cmd)) - return utils.execute(cmd, return_stderr=True, run_as_root=True) - - def get_ovs_uuid(self): - return self.ovs_exec_cmd(self.ovs_cmd_get_uuid)[0].strip() - - def set_extid_hostname(self, hname): - self.ovs_cmd_set_extid[self.UUID] = self.ovs_uuid - self.ovs_cmd_set_extid[self.EXTID] = self.extid_str.format( - self.odl_os_hostid_str, hname) - return self.ovs_exec_cmd(self.ovs_cmd_set_extid) - - def set_extid_hosttype(self, htype): - self.ovs_cmd_set_extid[self.UUID] = self.ovs_uuid - self.ovs_cmd_set_extid[self.EXTID] = self.extid_str.format( - self.odl_os_hosttype_str, htype) - return self.ovs_exec_cmd(self.ovs_cmd_set_extid) - - def set_extid_hostconfig(self, htype, hconfig): - ext_htype = self.odl_os_hconf_str.format( - htype.lower().replace(' ', '_')) - self.ovs_cmd_set_extid[self.UUID] = self.ovs_uuid - self.ovs_cmd_set_extid[self.EXTID] = self.extid_str.format( - ext_htype, jsonutils.dumps(hconfig)) - return self.ovs_exec_cmd(self.ovs_cmd_set_extid) - - def set_ovs_extid_hostconfigs(self, conf): - if not conf.ovs_hostconfigs: - LOG.error(_LE("ovs_hostconfigs argument needed!")) - return - - json_str = cfg.CONF.ovs_hostconfigs - json_str.replace("\'", "\"") - LOG.debug("SET-HOSTCONFIGS: JSON String %s", json_str) - - self.set_extid_hostname(cfg.CONF.host) - htype_config = jsonutils.loads(json_str) - - for htype in htype_config.keys(): - self.set_extid_hostconfig(htype, htype_config[htype]) - - -def setup_conf(): - """setup cmdline options.""" - cli_opts = [ - cfg.StrOpt('ovs_hostconfigs', help=_( - "OVS hostconfiguration for OpenDaylight " - "as a JSON string")) - ] - - conf = cfg.CONF - conf.register_cli_opts(cli_opts) - conf.import_opt('host', 'neutron.common.config') - conf() - return conf - - -def main(): - - conf = setup_conf() - config.setup_logging() - SetOvsHostconfigs().set_ovs_extid_hostconfigs(conf) - -# -# command line example (run without line breaks): -# -# set_ovs_hostconfigs.py --ovs_hostconfigs='{"ODL L2": { -# "supported_vnic_types":[{"vnic_type":"normal", "vif_type":"ovs", -# "vif_details":{}}], "allowed_network_types":["local","vlan", -# "vxlan","gre"], "bridge_mappings":{"physnet1":"br-ex"}}, -# "ODL L3": {}}' --debug -# - -if __name__ == '__main__': - main() diff --git a/networking-odl/networking_odl/cmd/test_setup_hostconfig.sh b/networking-odl/networking_odl/cmd/test_setup_hostconfig.sh deleted file mode 100755 index 1651d0e..0000000 --- a/networking-odl/networking_odl/cmd/test_setup_hostconfig.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -python set_ovs_hostconfigs.py --debug --ovs_hostconfigs='{"ODL L2": {"supported_vnic_types":[{"vnic_type":"normal", "vif_type":"ovs", "vif_details":{}}], "allowed_network_types":["local","vlan", "vxlan","gre"], "bridge_mappings":{"physnet1":"br-ex"}}, "ODL L3": {"some_details": "dummy_details"}}' diff --git a/networking-odl/networking_odl/common/cache.py b/networking-odl/networking_odl/common/cache.py index 6c44cc3..8b5287e 100644 --- a/networking-odl/networking_odl/common/cache.py +++ b/networking-odl/networking_odl/common/cache.py @@ -45,9 +45,6 @@ class CacheEntry(collections.namedtuple('CacheEntry', ['timeout', 'values'])): def __eq__(self, other): return self is other - def __ne__(self, other): - return not self.__eq__(other) - class Cache(object): '''Generic mapping class used to cache mapping diff --git a/networking-odl/networking_odl/common/callback.py b/networking-odl/networking_odl/common/callback.py index d9d168b..fe09037 100644 --- a/networking-odl/networking_odl/common/callback.py +++ b/networking-odl/networking_odl/common/callback.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import collections - from oslo_log import log as logging from neutron.callbacks import events @@ -25,49 +23,79 @@ from networking_odl.common import constants as odl_const LOG = logging.getLogger(__name__) -ODLResource = collections.namedtuple('ODLResource', ('singular', 'plural')) -_RESOURCE_MAPPING = { - resources.SECURITY_GROUP: ODLResource(odl_const.ODL_SG, odl_const.ODL_SGS), - resources.SECURITY_GROUP_RULE: ODLResource(odl_const.ODL_SG_RULE, - odl_const.ODL_SG_RULES), -} -_OPERATION_MAPPING = { - events.AFTER_CREATE: odl_const.ODL_CREATE, - events.AFTER_UPDATE: odl_const.ODL_UPDATE, - events.AFTER_DELETE: odl_const.ODL_DELETE, -} - class OdlSecurityGroupsHandler(object): - def __init__(self, odl_driver): - self.odl_driver = odl_driver - self._subscribe() + def __init__(self, odl_client, event_type="AFTER"): + self.odl_client = odl_client + self.subscribe(event_type) - def _subscribe(self): - for event in (events.AFTER_CREATE, events.AFTER_DELETE): - registry.subscribe(self.sg_callback, resources.SECURITY_GROUP, - event) - registry.subscribe(self.sg_callback, resources.SECURITY_GROUP_RULE, - event) + def sg_callback(self, resource, event, trigger, **kwargs): - registry.subscribe(self.sg_callback, resources.SECURITY_GROUP, - events.AFTER_UPDATE) + res_key_mapping = { + odl_const.ODL_SGS: odl_const.ODL_SG, + odl_const.ODL_SG_RULES: odl_const.ODL_SG_RULE, + } + res_name_mapping = { + resources.SECURITY_GROUP: odl_const.ODL_SGS, + resources.SECURITY_GROUP_RULE: odl_const.ODL_SG_RULES, + } + ops_mapping = { + events.AFTER_CREATE: odl_const.ODL_CREATE, + events.AFTER_UPDATE: odl_const.ODL_UPDATE, + events.AFTER_DELETE: odl_const.ODL_DELETE, + events.PRECOMMIT_CREATE: odl_const.ODL_CREATE, + events.PRECOMMIT_UPDATE: odl_const.ODL_UPDATE, + events.PRECOMMIT_DELETE: odl_const.ODL_DELETE, + } - def sg_callback(self, resource, event, trigger, **kwargs): + # Loop up the ODL's counterpart resource label + # e.g. resources.SECURITY_GROUP -> odl_const.ODL_SGS + # Note: 1) url will use dashes instead of underscore; + # 2) when res is a list, append 's' to odl_res_key + # Ref: https://github.com/opendaylight/neutron/blob/master + # /northbound-api/src/main/java/org/opendaylight + # /neutron/northbound/api + # /NeutronSecurityGroupRequest.java#L33 res = kwargs.get(resource) res_id = kwargs.get("%s_id" % resource) - odl_res_type = _RESOURCE_MAPPING[resource] + odl_res_type = res_name_mapping[resource] + odl_res_key = res_key_mapping[odl_res_type] + odl_ops = ops_mapping[event] + odl_res_type_uri = odl_res_type.replace('_', '-') + + if type(res) is list: + odl_res_key += "s" - odl_ops = _OPERATION_MAPPING[event] - odl_res_dict = None if res is None else {odl_res_type.singular: res} + if res is None: + odl_res_dict = None + else: + odl_res_dict = {odl_res_key: res} LOG.debug("Calling sync_from_callback with ODL_OPS (%(odl_ops)s) " "ODL_RES_TYPE (%(odl_res_type)s) RES_ID (%(res_id)s) " - "ODL_RES_DICT (%(odl_res_dict)s) KWARGS (%(kwargs)s)", + "ODL_RES_KEY (%(odl_res_key)s) RES (%(res)s) " + "KWARGS (%(kwargs)s)", {'odl_ops': odl_ops, 'odl_res_type': odl_res_type, - 'res_id': res_id, 'odl_res_dict': odl_res_dict, + 'res_id': res_id, 'odl_res_key': odl_res_key, 'res': res, 'kwargs': kwargs}) - self.odl_driver.sync_from_callback(odl_ops, odl_res_type, - res_id, odl_res_dict) + self.odl_client.sync_from_callback(odl_ops, odl_res_type_uri, res_id, + odl_res_dict) + + def subscribe(self, event_type): + registry.subscribe( + self.sg_callback, resources.SECURITY_GROUP, + getattr(events, "%s_CREATE" % event_type)) + registry.subscribe( + self.sg_callback, resources.SECURITY_GROUP, + getattr(events, "%s_UPDATE" % event_type)) + registry.subscribe( + self.sg_callback, resources.SECURITY_GROUP, + getattr(events, "%s_DELETE" % event_type)) + registry.subscribe( + self.sg_callback, resources.SECURITY_GROUP_RULE, + getattr(events, "%s_CREATE" % event_type)) + registry.subscribe( + self.sg_callback, resources.SECURITY_GROUP_RULE, + getattr(events, "%s_DELETE" % event_type)) diff --git a/networking-odl/networking_odl/common/client.py b/networking-odl/networking_odl/common/client.py index 45349e9..537665d 100644 --- a/networking-odl/networking_odl/common/client.py +++ b/networking-odl/networking_odl/common/client.py @@ -27,9 +27,9 @@ cfg.CONF.import_group('ml2_odl', 'networking_odl.common.config') class OpenDaylightRestClient(object): @classmethod - def create_client(cls, url=None): + def create_client(cls): if cfg.CONF.ml2_odl.enable_lightweight_testing: - LOG.debug("ODL lightweight testing is enabled, " + LOG.debug("ODL lightweight testing is enabled, ", "returning a OpenDaylightLwtClient instance") """Have to import at here, otherwise we create a dependency loop""" @@ -37,7 +37,7 @@ class OpenDaylightRestClient(object): cls = lwt.OpenDaylightLwtClient return cls( - url or cfg.CONF.ml2_odl.url, + cfg.CONF.ml2_odl.url, cfg.CONF.ml2_odl.username, cfg.CONF.ml2_odl.password, cfg.CONF.ml2_odl.timeout) diff --git a/networking-odl/networking_odl/common/config.py b/networking-odl/networking_odl/common/config.py index c921242..0e38e2f 100644 --- a/networking-odl/networking_odl/common/config.py +++ b/networking-odl/networking_odl/common/config.py @@ -34,34 +34,12 @@ odl_opts = [ cfg.IntOpt('retry_count', default=5, help=_("(V2 driver) Number of times to retry a row " "before failing.")), - cfg.IntOpt('maintenance_interval', default=300, - help=_("(V2 driver) Journal maintenance operations interval " - "in seconds.")), - cfg.IntOpt('completed_rows_retention', default=600, - help=_("(V2 driver) Time to keep completed rows in seconds." - "Completed rows retention will be checked every " - "maintenance_interval by the cleanup thread." - "To disable completed rows deletion " - "value should be -1")), cfg.BoolOpt('enable_lightweight_testing', default=False, help=_('Test without real ODL.')), cfg.StrOpt('port_binding_controller', default='network-topology', - help=_('Name of the controller to be used for port binding.')), - cfg.IntOpt('processing_timeout', default='100', - help=_("(V2 driver) Time in seconds to wait before a " - "processing row is marked back to pending.")), - cfg.StrOpt('odl_hostconf_uri', - help=_("Path for ODL host configuration REST interface"), - default="/restconf/operational/neutron:neutron/hostconfigs"), - cfg.IntOpt('restconf_poll_interval', default=30, - help=_("Poll interval in seconds for getting ODL hostconfig")), - + help=_('Name of the controller to be used for port binding.')) ] cfg.CONF.register_opts(odl_opts, "ml2_odl") - - -def list_opts(): - return [('ml2_odl', odl_opts)] diff --git a/networking-odl/networking_odl/common/constants.py b/networking-odl/networking_odl/common/constants.py index 50c0117..9fed790 100644 --- a/networking-odl/networking_odl/common/constants.py +++ b/networking-odl/networking_odl/common/constants.py @@ -24,10 +24,8 @@ ODL_SGS = 'security_groups' ODL_SG_RULE = 'security_group_rule' ODL_SG_RULES = 'security_group_rules' ODL_ROUTER = 'router' -ODL_ROUTERS = 'routers' ODL_ROUTER_INTF = 'router_interface' ODL_FLOATINGIP = 'floatingip' -ODL_FLOATINGIPS = 'floatingips' ODL_LOADBALANCER = 'loadbalancer' ODL_LOADBALANCERS = 'loadbalancers' diff --git a/networking-odl/networking_odl/common/exceptions.py b/networking-odl/networking_odl/common/exceptions.py index f174c10..59956b1 100644 --- a/networking-odl/networking_odl/common/exceptions.py +++ b/networking-odl/networking_odl/common/exceptions.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -from neutron_lib import exceptions as exc +from neutron.common import exceptions as exc class OpendaylightAuthError(exc.NeutronException): diff --git a/networking-odl/networking_odl/common/filters.py b/networking-odl/networking_odl/common/filters.py index fb42a0e..340fa4d 100644 --- a/networking-odl/networking_odl/common/filters.py +++ b/networking-odl/networking_odl/common/filters.py @@ -12,85 +12,158 @@ # 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 abc +import six + from networking_odl.common import constants as odl_const from networking_odl.common import utils as odl_utils -def _filter_unmapped_null(resource_dict, unmapped_keys): - # NOTE(yamahata): bug work around - # https://bugs.eclipse.org/bugs/show_bug.cgi?id=475475 - # Null-value for an unmapped element causes next mapped - # collection to contain a null value - # JSON: { "unmappedField": null, "mappedCollection": [ "a" ] } - # - # Java Object: - # class Root { - # Collection<String> mappedCollection = new ArrayList<String>; - # } - # - # Result: - # Field B contains one element; null - # - # TODO(yamahata): update along side with neutron and ODL - # add when neutron adds more extensions - # delete when ODL neutron northbound supports it - # TODO(yamahata): do same thing for other resources - keys_to_del = [key for key in unmapped_keys - if resource_dict.get(key) is None] - if keys_to_del: - odl_utils.try_del(resource_dict, keys_to_del) - - -_NETWORK_UNMAPPED_KEYS = ['qos_policy_id'] -_PORT_UNMAPPED_KEYS = ['binding:profile', 'dns_name', - 'port_security_enabled', 'qos_policy_id'] - - -def _filter_network_create(network): - odl_utils.try_del(network, ['status', 'subnets']) - _filter_unmapped_null(network, _NETWORK_UNMAPPED_KEYS) - - -def _filter_network_update(network): - odl_utils.try_del(network, ['id', 'status', 'subnets', 'tenant_id']) - _filter_unmapped_null(network, _NETWORK_UNMAPPED_KEYS) - - -def _filter_subnet_update(subnet): - odl_utils.try_del(subnet, ['id', 'network_id', 'ip_version', 'cidr', - 'allocation_pools', 'tenant_id']) - - -def _filter_port_create(port): - """Filter out port attributes not required for a create.""" - odl_utils.try_del(port, ['status']) - _filter_unmapped_null(port, _PORT_UNMAPPED_KEYS) - - -def _filter_port_update(port): - """Filter out port attributes for an update operation.""" - odl_utils.try_del(port, ['network_id', 'id', 'status', 'mac_address', - 'tenant_id', 'fixed_ips']) - _filter_unmapped_null(port, _PORT_UNMAPPED_KEYS) - - -def _filter_router_update(router): - """Filter out attributes for an update operation.""" - odl_utils.try_del(router, ['id', 'tenant_id', 'status']) - - -_FILTER_MAP = { - (odl_const.ODL_NETWORK, odl_const.ODL_CREATE): _filter_network_create, - (odl_const.ODL_NETWORK, odl_const.ODL_UPDATE): _filter_network_update, - (odl_const.ODL_SUBNET, odl_const.ODL_UPDATE): _filter_subnet_update, - (odl_const.ODL_PORT, odl_const.ODL_CREATE): _filter_port_create, - (odl_const.ODL_PORT, odl_const.ODL_UPDATE): _filter_port_update, - (odl_const.ODL_ROUTER, odl_const.ODL_UPDATE): _filter_router_update, +@six.add_metaclass(abc.ABCMeta) +class ResourceFilterBase(object): + @staticmethod + @abc.abstractmethod + def filter_create_attributes(resource): + pass + + @staticmethod + @abc.abstractmethod + def filter_update_attributes(resource): + pass + + +class NetworkFilter(ResourceFilterBase): + @staticmethod + def filter_create_attributes(network): + """Filter out network attributes not required for a create.""" + odl_utils.try_del(network, ['status', 'subnets']) + + @staticmethod + def filter_update_attributes(network): + """Filter out network attributes for an update operation.""" + odl_utils.try_del(network, ['id', 'status', 'subnets', 'tenant_id']) + + +class SubnetFilter(ResourceFilterBase): + @staticmethod + def filter_create_attributes(subnet): + """Filter out subnet attributes not required for a create.""" + pass + + @staticmethod + def filter_update_attributes(subnet): + """Filter out subnet attributes for an update operation.""" + odl_utils.try_del(subnet, ['id', 'network_id', 'ip_version', 'cidr', + 'allocation_pools', 'tenant_id']) + + +class PortFilter(ResourceFilterBase): + @staticmethod + def _filter_unmapped_null(port): + # NOTE(yamahata): bug work around + # https://bugs.eclipse.org/bugs/show_bug.cgi?id=475475 + # Null-value for an unmapped element causes next mapped + # collection to contain a null value + # JSON: { "unmappedField": null, "mappedCollection": [ "a" ] } + # + # Java Object: + # class Root { + # Collection<String> mappedCollection = new ArrayList<String>; + # } + # + # Result: + # Field B contains one element; null + # + # TODO(yamahata): update along side with neutron and ODL + # add when neutron adds more extensions + # delete when ODL neutron northbound supports it + # TODO(yamahata): do same thing for other resources + unmapped_keys = ['dns_name', 'port_security_enabled', + 'binding:profile'] + keys_to_del = [key for key in unmapped_keys if port.get(key) is None] + if keys_to_del: + odl_utils.try_del(port, keys_to_del) + + @classmethod + def filter_create_attributes(cls, port): + """Filter out port attributes not required for a create.""" + cls._filter_unmapped_null(port) + odl_utils.try_del(port, ['status']) + + @classmethod + def filter_update_attributes(cls, port): + """Filter out port attributes for an update operation.""" + cls._filter_unmapped_null(port) + odl_utils.try_del(port, ['network_id', 'id', 'status', 'mac_address', + 'tenant_id', 'fixed_ips']) + + +class SecurityGroupFilter(ResourceFilterBase): + @staticmethod + def filter_create_attributes(sg): + """Filter out security-group attributes not required for a create.""" + pass + + @staticmethod + def filter_update_attributes(sg): + """Filter out security-group attributes for an update operation.""" + pass + + +class SecurityGroupRuleFilter(ResourceFilterBase): + @staticmethod + def filter_create_attributes(sg_rule): + """Filter out sg-rule attributes not required for a create.""" + pass + + @staticmethod + def filter_update_attributes(sg_rule): + """Filter out sg-rule attributes for an update operation.""" + pass + + +class RouterFilter(ResourceFilterBase): + @staticmethod + def filter_create_attributes(router): + """Filter out attributes not required for a create.""" + pass + + @staticmethod + def filter_update_attributes(router): + """Filter out attributes for an update operation.""" + odl_utils.try_del(router, ['id', 'tenant_id', 'status']) + + +class FloatingIPFilter(ResourceFilterBase): + @staticmethod + def filter_create_attributes(floatingip): + """Filter out attributes not required for a create.""" + pass + + @staticmethod + def filter_update_attributes(floatingip): + """Filter out attributes for an update operation.""" + pass + + +class RouterIntfFilter(ResourceFilterBase): + @staticmethod + def filter_add_attributes(routerintf): + """Filter out attributes not required for a create.""" + pass + + @staticmethod + def filter_remove_attributes(routerintf): + """Filter out attributes for an update operation.""" + pass + +FILTER_MAP = { + odl_const.ODL_NETWORK: NetworkFilter, + odl_const.ODL_SUBNET: SubnetFilter, + odl_const.ODL_PORT: PortFilter, + odl_const.ODL_ROUTER: RouterFilter, + odl_const.ODL_ROUTER_INTF: RouterIntfFilter, + odl_const.ODL_FLOATINGIP: FloatingIPFilter, + odl_const.ODL_SG: SecurityGroupFilter, + odl_const.ODL_SG_RULE: SecurityGroupRuleFilter, } - - -def filter_for_odl(object_type, operation, data): - """Filter out the attributed before sending the data to ODL""" - filter_key = (object_type, operation) - if filter_key in _FILTER_MAP: - _FILTER_MAP[filter_key](data) diff --git a/networking-odl/networking_odl/common/lightweight_testing.py b/networking-odl/networking_odl/common/lightweight_testing.py index 3d0cf2e..3f9c2bc 100644 --- a/networking-odl/networking_odl/common/lightweight_testing.py +++ b/networking-odl/networking_odl/common/lightweight_testing.py @@ -20,7 +20,6 @@ import six from oslo_log import log as logging from oslo_serialization import jsonutils -from networking_odl._i18n import _ from networking_odl.common import client from networking_odl.common import constants as odl_const @@ -69,7 +68,7 @@ class OpenDaylightLwtClient(client.OpenDaylightRestClient): """No ID in URL, elements in resource_list must have ID""" if resource_list is None: - raise ValueError(_("resource_list can not be None")) + raise ValueError("resource_list can not be None") for resource in resource_list: if resource['id'] in resource_dict: @@ -88,7 +87,7 @@ class OpenDaylightLwtClient(client.OpenDaylightRestClient): resource_id = cls._get_resource_id(urlpath) if resource_list is None: - raise ValueError(_("resource_list can not be None")) + raise ValueError("resource_list can not be None") if resource_id and len(resource_list) != 1: LOG.debug("Updating %s with multiple resources", urlpath) diff --git a/networking-odl/networking_odl/db/db.py b/networking-odl/networking_odl/db/db.py index 31f4ce2..a8e7ade 100644 --- a/networking-odl/networking_odl/db/db.py +++ b/networking-odl/networking_odl/db/db.py @@ -12,8 +12,6 @@ # 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 datetime - from sqlalchemy import asc from sqlalchemy import func from sqlalchemy import or_ @@ -163,72 +161,3 @@ def create_pending_row(session, object_type, object_uuid, # Keep session flush for unit tests. NOOP for L2/L3 events since calls are # made inside database session transaction with subtransactions=True. session.flush() - - -@db_api.retry_db_errors -def delete_pending_rows(session, operations_to_delete): - with session.begin(): - session.query(models.OpendaylightJournal).filter( - models.OpendaylightJournal.operation.in_(operations_to_delete), - models.OpendaylightJournal.state == odl_const.PENDING).delete( - synchronize_session=False) - session.expire_all() - - -@db_api.retry_db_errors -def _update_maintenance_state(session, expected_state, state): - with session.begin(): - row = session.query(models.OpendaylightMaintenance).filter_by( - state=expected_state).with_for_update().one_or_none() - if row is None: - return False - - row.state = state - return True - - -def lock_maintenance(session): - return _update_maintenance_state(session, odl_const.PENDING, - odl_const.PROCESSING) - - -def unlock_maintenance(session): - return _update_maintenance_state(session, odl_const.PROCESSING, - odl_const.PENDING) - - -def update_maintenance_operation(session, operation=None): - """Update the current maintenance operation details. - - The function assumes the lock is held, so it mustn't be run outside of a - locked context. - """ - op_text = None - if operation: - op_text = operation.__name__ - - with session.begin(): - row = session.query(models.OpendaylightMaintenance).one_or_none() - row.processing_operation = op_text - - -def delete_rows_by_state_and_time(session, state, time_delta): - with session.begin(): - now = session.execute(func.now()).scalar() - session.query(models.OpendaylightJournal).filter( - models.OpendaylightJournal.state == state, - models.OpendaylightJournal.last_retried < now - time_delta).delete( - synchronize_session=False) - session.expire_all() - - -def reset_processing_rows(session, max_timedelta): - with session.begin(): - now = session.execute(func.now()).scalar() - max_timedelta = datetime.timedelta(seconds=max_timedelta) - rows = session.query(models.OpendaylightJournal).filter( - models.OpendaylightJournal.last_retried < now - max_timedelta, - models.OpendaylightJournal.state == odl_const.PROCESSING, - ).update({'state': odl_const.PENDING}) - - return rows diff --git a/networking-odl/networking_odl/db/migration/alembic_migrations/versions/EXPAND_HEAD b/networking-odl/networking_odl/db/migration/alembic_migrations/versions/EXPAND_HEAD index 34912ba..95ad199 100644 --- a/networking-odl/networking_odl/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/networking-odl/networking_odl/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -703dbf02afde +37e242787ae5 diff --git a/networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/contract/383acb0d38a0_initial_contract.py b/networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/contract/383acb0d38a0_initial_contract.py index 43959c0..5a81be5 100644 --- a/networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/contract/383acb0d38a0_initial_contract.py +++ b/networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/contract/383acb0d38a0_initial_contract.py @@ -19,7 +19,6 @@ Create Date: 2015-09-03 22:27:49.306394 """ -from neutron.db import migration from neutron.db.migration import cli @@ -28,9 +27,6 @@ revision = '383acb0d38a0' down_revision = 'b89a299e19f9' branch_labels = (cli.CONTRACT_BRANCH,) -# milestone identifier, used by neutron-db-manage -neutron_milestone = [migration.MITAKA] - def upgrade(): pass diff --git a/networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/expand/37e242787ae5_opendaylight_neutron_mechanism_driver_.py b/networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/expand/37e242787ae5_opendaylight_neutron_mechanism_driver_.py index 71d8273..b78993d 100644 --- a/networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/expand/37e242787ae5_opendaylight_neutron_mechanism_driver_.py +++ b/networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/expand/37e242787ae5_opendaylight_neutron_mechanism_driver_.py @@ -20,17 +20,10 @@ Revises: 247501328046 Create Date: 2015-10-30 22:09:27.221767 """ -from neutron.db import migration - - # revision identifiers, used by Alembic. revision = '37e242787ae5' down_revision = '247501328046' -# milestone identifier, used by neutron-db-manage -neutron_milestone = [migration.MITAKA] - - from alembic import op import sqlalchemy as sa @@ -44,8 +37,7 @@ def upgrade(): sa.Column('operation', sa.String(36), nullable=False), sa.Column('data', sa.PickleType, nullable=True), sa.Column('state', - sa.Enum('pending', 'processing', 'failed', 'completed', - name='state'), + sa.Enum('pending', 'processing', 'failed', 'completed'), nullable=False, default='pending'), sa.Column('retry_count', sa.Integer, default=0), sa.Column('created_at', sa.DateTime, default=sa.func.now()), diff --git a/networking-odl/networking_odl/db/migration/alembic_migrations/versions/newton/expand/703dbf02afde_add_journal_maintenance_table.py b/networking-odl/networking_odl/db/migration/alembic_migrations/versions/newton/expand/703dbf02afde_add_journal_maintenance_table.py deleted file mode 100644 index bbe0c46..0000000 --- a/networking-odl/networking_odl/db/migration/alembic_migrations/versions/newton/expand/703dbf02afde_add_journal_maintenance_table.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2016 Red Hat Inc. -# -# 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. -# - -"""Add journal maintenance table - -Revision ID: 703dbf02afde -Revises: 37e242787ae5 -Create Date: 2016-04-12 10:49:31.802663 - -""" - -# revision identifiers, used by Alembic. -revision = '703dbf02afde' -down_revision = '37e242787ae5' - -from alembic import op -from oslo_utils import uuidutils -import sqlalchemy as sa - -from networking_odl.common import constants as odl_const - - -def upgrade(): - maint_table = op.create_table( - 'opendaylight_maintenance', - sa.Column('id', sa.String(36), primary_key=True), - sa.Column('state', sa.Enum(odl_const.PENDING, odl_const.PROCESSING, - name='state'), - nullable=False), - sa.Column('processing_operation', sa.String(70)), - sa.Column('lock_updated', sa.TIMESTAMP, nullable=False, - server_default=sa.func.now(), - onupdate=sa.func.now()) - ) - - # Insert the only row here that is used to synchronize the lock between - # different Neutron processes. - op.bulk_insert(maint_table, - [{'id': uuidutils.generate_uuid(), - 'state': odl_const.PENDING}]) diff --git a/networking-odl/networking_odl/db/models.py b/networking-odl/networking_odl/db/models.py index 0416ed1..94c3ef0 100644 --- a/networking-odl/networking_odl/db/models.py +++ b/networking-odl/networking_odl/db/models.py @@ -34,14 +34,3 @@ class OpendaylightJournal(model_base.BASEV2, HasId): created_at = sa.Column(sa.DateTime, server_default=sa.func.now()) last_retried = sa.Column(sa.TIMESTAMP, server_default=sa.func.now(), onupdate=sa.func.now()) - - -class OpendaylightMaintenance(model_base.BASEV2, HasId): - __tablename__ = 'opendaylight_maintenance' - - state = sa.Column(sa.Enum(odl_const.PENDING, odl_const.PROCESSING), - nullable=False) - processing_operation = sa.Column(sa.String(70)) - lock_updated = sa.Column(sa.TIMESTAMP, nullable=False, - server_default=sa.func.now(), - onupdate=sa.func.now()) diff --git a/networking-odl/networking_odl/journal/cleanup.py b/networking-odl/networking_odl/journal/cleanup.py deleted file mode 100644 index 994fb82..0000000 --- a/networking-odl/networking_odl/journal/cleanup.py +++ /dev/null @@ -1,46 +0,0 @@ -# -# Copyright (C) 2016 Red Hat, Inc. -# -# 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 datetime import timedelta - -from oslo_config import cfg -from oslo_log import log as logging - -from networking_odl._i18n import _LI -from networking_odl.common import constants as odl_const -from networking_odl.db import db - -LOG = logging.getLogger(__name__) - - -class JournalCleanup(object): - """Journal maintenance operation for deleting completed rows.""" - def __init__(self): - self._rows_retention = cfg.CONF.ml2_odl.completed_rows_retention - self._processing_timeout = cfg.CONF.ml2_odl.processing_timeout - - def delete_completed_rows(self, session): - if self._rows_retention is not -1: - LOG.debug("Deleting completed rows") - db.delete_rows_by_state_and_time( - session, odl_const.COMPLETED, - timedelta(seconds=self._rows_retention)) - - def cleanup_processing_rows(self, session): - row_count = db.reset_processing_rows(session, self._processing_timeout) - if row_count: - LOG.info(_LI("Reset %(num)s orphaned rows back to pending"), - {"num": row_count}) diff --git a/networking-odl/networking_odl/journal/dependency_validations.py b/networking-odl/networking_odl/journal/dependency_validations.py index a6f5f96..07c657c 100644 --- a/networking-odl/networking_odl/journal/dependency_validations.py +++ b/networking-odl/networking_odl/journal/dependency_validations.py @@ -235,7 +235,7 @@ def validate_security_group_rule_operation(session, row): """ return True -_VALIDATION_MAP = { +VALIDATION_MAP = { odl_const.ODL_NETWORK: validate_network_operation, odl_const.ODL_SUBNET: validate_subnet_operation, odl_const.ODL_PORT: validate_port_operation, @@ -245,23 +245,3 @@ _VALIDATION_MAP = { odl_const.ODL_SG: validate_security_group_operation, odl_const.ODL_SG_RULE: validate_security_group_rule_operation, } - - -def validate(session, row): - """Validate resource dependency in journaled operations. - - :param session: db session - :param row: entry in journal entry to be validated - """ - return _VALIDATION_MAP[row.object_type](session, row) - - -def register_validator(object_type, validator): - """Register validator function for given resource. - - :param object_type: neutron resource type - :param validator: function to be registered which validates resource - dependencies - """ - assert object_type not in _VALIDATION_MAP - _VALIDATION_MAP[object_type] = validator diff --git a/networking-odl/networking_odl/journal/full_sync.py b/networking-odl/networking_odl/journal/full_sync.py deleted file mode 100644 index dad7215..0000000 --- a/networking-odl/networking_odl/journal/full_sync.py +++ /dev/null @@ -1,114 +0,0 @@ -# -# Copyright (C) 2016 Red Hat, Inc. -# -# 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 requests - -from neutron import context as neutron_context -from neutron import manager -from neutron.plugins.common import constants -from neutron_lib import constants as l3_constants - -from networking_odl.common import client -from networking_odl.common import constants as odl_const -from networking_odl.db import db - -# Define which pending operation types should be deleted -_CANARY_NETWORK_ID = "bd8db3a8-2b30-4083-a8b3-b3fd46401142" -_CANARY_TENANT_ID = "bd8db3a8-2b30-4083-a8b3-b3fd46401142" -_CANARY_NETWORK_DATA = {'id': _CANARY_NETWORK_ID, - 'tenant_id': _CANARY_TENANT_ID, - 'name': 'Sync Canary Network', - 'admin_state_up': False} -_OPS_TO_DELETE_ON_SYNC = (odl_const.ODL_CREATE, odl_const.ODL_UPDATE) -_L2_RESOURCES_TO_SYNC = [(odl_const.ODL_SG, odl_const.ODL_SGS), - (odl_const.ODL_SG_RULE, odl_const.ODL_SG_RULES), - (odl_const.ODL_NETWORK, odl_const.ODL_NETWORKS), - (odl_const.ODL_SUBNET, odl_const.ODL_SUBNETS), - (odl_const.ODL_PORT, odl_const.ODL_PORTS)] -_L3_RESOURCES_TO_SYNC = [(odl_const.ODL_ROUTER, odl_const.ODL_ROUTERS), - (odl_const.ODL_FLOATINGIP, odl_const.ODL_FLOATINGIPS)] -_CLIENT = client.OpenDaylightRestClient.create_client() - - -def full_sync(session): - if not _full_sync_needed(session): - return - - db.delete_pending_rows(session, _OPS_TO_DELETE_ON_SYNC) - - dbcontext = neutron_context.get_admin_context() - plugin = manager.NeutronManager.get_plugin() - for resource_type, collection_name in _L2_RESOURCES_TO_SYNC: - _sync_resources(session, plugin, dbcontext, resource_type, - collection_name) - - l3plugin = manager.NeutronManager.get_service_plugins().get( - constants.L3_ROUTER_NAT) - for resource_type, collection_name in _L3_RESOURCES_TO_SYNC: - _sync_resources(session, l3plugin, dbcontext, resource_type, - collection_name) - _sync_router_ports(session, plugin, dbcontext) - - db.create_pending_row(session, odl_const.ODL_NETWORK, _CANARY_NETWORK_ID, - odl_const.ODL_CREATE, _CANARY_NETWORK_DATA) - - -def _full_sync_needed(session): - return (_canary_network_missing_on_odl() and - _canary_network_not_in_journal(session)) - - -def _canary_network_missing_on_odl(): - # Try to reach the ODL server, sometimes it might be up & responding to - # HTTP calls but inoperative.. - response = _CLIENT.get(odl_const.ODL_NETWORKS) - response.raise_for_status() - - response = _CLIENT.get(odl_const.ODL_NETWORKS + "/" + _CANARY_NETWORK_ID) - if response.status_code == requests.codes.not_found: - return True - - # In case there was an error raise it up because we don't know how to deal - # with it.. - response.raise_for_status() - return False - - -def _canary_network_not_in_journal(session): - return not db.check_for_pending_or_processing_ops(session, - _CANARY_NETWORK_ID, - odl_const.ODL_CREATE) - - -def _sync_resources(session, plugin, dbcontext, object_type, collection_name): - obj_getter = getattr(plugin, 'get_%s' % collection_name) - resources = obj_getter(dbcontext) - - for resource in resources: - db.create_pending_row(session, object_type, resource['id'], - odl_const.ODL_CREATE, resource) - - -def _sync_router_ports(session, plugin, dbcontext): - filters = {'device_owner': [l3_constants.DEVICE_OWNER_ROUTER_INTF]} - router_ports = plugin.get_ports(dbcontext, filters=filters) - for port in router_ports: - resource = {'subnet_id': port['fixed_ips'][0]['subnet_id'], - 'port_id': port['id'], - 'id': port['device_id'], - 'tenant_id': port['tenant_id']} - db.create_pending_row(session, odl_const.ODL_ROUTER_INTF, port['id'], - odl_const.ODL_ADD, resource) diff --git a/networking-odl/networking_odl/journal/journal.py b/networking-odl/networking_odl/journal/journal.py index ca0d2c2..26295b3 100644 --- a/networking-odl/networking_odl/journal/journal.py +++ b/networking-odl/networking_odl/journal/journal.py @@ -21,9 +21,7 @@ from requests import exceptions from oslo_config import cfg from oslo_log import log as logging -from neutron import context as neutron_context from neutron.db import api as neutron_db_api -from neutron import manager from networking_odl.common import client from networking_odl.common import constants as odl_const @@ -44,51 +42,6 @@ def call_thread_on_end(func): return new_func -def _enrich_port(db_session, context, object_type, operation, data): - """Enrich the port with additional information needed by ODL""" - if context: - plugin = context._plugin - dbcontext = context._plugin_context - else: - dbcontext = neutron_context.get_admin_context() - plugin = manager.NeutronManager.get_plugin() - - groups = [plugin.get_security_group(dbcontext, sg) - for sg in data['security_groups']] - new_data = copy.deepcopy(data) - new_data['security_groups'] = groups - - # NOTE(yamahata): work around for port creation for router - # tenant_id=''(empty string) is passed when port is created - # by l3 plugin internally for router. - # On the other hand, ODL doesn't accept empty string for tenant_id. - # In that case, deduce tenant_id from network_id for now. - # Right fix: modify Neutron so that don't allow empty string - # for tenant_id even for port for internal use. - # TODO(yamahata): eliminate this work around when neutron side - # is fixed - # assert port['tenant_id'] != '' - if ('tenant_id' not in new_data or new_data['tenant_id'] == ''): - if context: - tenant_id = context._network_context._network['tenant_id'] - else: - network = plugin.get_network(dbcontext, new_data['network_id']) - tenant_id = network['tenant_id'] - new_data['tenant_id'] = tenant_id - - return new_data - - -def record(db_session, object_type, object_uuid, operation, data, - context=None): - if (object_type == odl_const.ODL_PORT and - operation in (odl_const.ODL_CREATE, odl_const.ODL_UPDATE)): - data = _enrich_port(db_session, context, object_type, operation, data) - - db.create_pending_row(db_session, object_type, object_uuid, operation, - data) - - class OpendaylightJournalThread(object): """Thread worker for the Opendaylight Journal Database.""" def __init__(self): @@ -123,28 +76,40 @@ class OpendaylightJournalThread(object): self._timer.start() def _json_data(self, row): - data = copy.deepcopy(row.data) - filters.filter_for_odl(row.object_type, row.operation, data) + filter_cls = filters.FILTER_MAP[row.object_type] url_object = row.object_type.replace('_', '-') if row.operation == odl_const.ODL_CREATE: method = 'post' + attr_filter = filter_cls.filter_create_attributes + data = copy.deepcopy(row.data) urlpath = url_object + 's' + attr_filter(data) to_send = {row.object_type: data} elif row.operation == odl_const.ODL_UPDATE: method = 'put' + attr_filter = filter_cls.filter_update_attributes + data = copy.deepcopy(row.data) urlpath = url_object + 's/' + row.object_uuid + attr_filter(data) to_send = {row.object_type: data} elif row.operation == odl_const.ODL_DELETE: method = 'delete' + data = None urlpath = url_object + 's/' + row.object_uuid to_send = None elif row.operation == odl_const.ODL_ADD: method = 'put' + attr_filter = filter_cls.filter_add_attributes + data = copy.deepcopy(row.data) + attr_filter(data) urlpath = 'routers/' + data['id'] + '/add_router_interface' to_send = data elif row.operation == odl_const.ODL_REMOVE: method = 'put' + attr_filter = filter_cls.filter_remove_attributes + data = copy.deepcopy(row.data) + attr_filter(data) urlpath = 'routers/' + data['id'] + '/remove_router_interface' to_send = data @@ -177,7 +142,9 @@ class OpendaylightJournalThread(object): break # Validate the operation - valid = dependency_validations.validate(session, row) + validate_func = (dependency_validations. + VALIDATION_MAP[row.object_type]) + valid = validate_func(session, row) if not valid: LOG.info(_LI("%(operation)s %(type)s %(uuid)s is not a " "valid operation yet, skipping for now"), diff --git a/networking-odl/networking_odl/journal/maintenance.py b/networking-odl/networking_odl/journal/maintenance.py deleted file mode 100644 index 7fb82a0..0000000 --- a/networking-odl/networking_odl/journal/maintenance.py +++ /dev/null @@ -1,73 +0,0 @@ -# -# Copyright (C) 2016 Red Hat, Inc. -# -# 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.db import api as neutron_db_api -from oslo_config import cfg -from oslo_log import log as logging -from oslo_service import loopingcall - -from networking_odl._i18n import _LI, _LE -from networking_odl.db import db - - -LOG = logging.getLogger(__name__) - - -class MaintenanceThread(object): - def __init__(self): - self.timer = loopingcall.FixedIntervalLoopingCall(self.execute_ops) - self.maintenance_interval = cfg.CONF.ml2_odl.maintenance_interval - self.maintenance_ops = [] - - def start(self): - self.timer.start(self.maintenance_interval, stop_on_exception=False) - - def _execute_op(self, operation, session): - op_details = operation.__name__ - if operation.__doc__: - op_details += " (%s)" % operation.func_doc - - try: - LOG.info(_LI("Starting maintenance operation %s."), op_details) - db.update_maintenance_operation(session, operation=operation) - operation(session=session) - LOG.info(_LI("Finished maintenance operation %s."), op_details) - except Exception: - LOG.exception(_LE("Failed during maintenance operation %s."), - op_details) - - def execute_ops(self): - LOG.info(_LI("Starting journal maintenance run.")) - session = neutron_db_api.get_session() - if not db.lock_maintenance(session): - LOG.info(_LI("Maintenance already running, aborting.")) - return - - try: - for operation in self.maintenance_ops: - self._execute_op(operation, session) - finally: - db.update_maintenance_operation(session, operation=None) - db.unlock_maintenance(session) - LOG.info(_LI("Finished journal maintenance run.")) - - def register_operation(self, f): - """Register a function to be run by the maintenance thread. - - :param f: Function to call when the thread runs. The function will - receive a DB session to use for DB operations. - """ - self.maintenance_ops.append(f) diff --git a/networking-odl/networking_odl/l3/l3_odl.py b/networking-odl/networking_odl/l3/l3_odl.py index e06e335..36d8779 100644 --- a/networking-odl/networking_odl/l3/l3_odl.py +++ b/networking-odl/networking_odl/l3/l3_odl.py @@ -19,6 +19,7 @@ from oslo_log import log as logging from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api from neutron.api.rpc.handlers import l3_rpc +from neutron.common import constants as q_const from neutron.common import rpc as n_rpc from neutron.common import topics from neutron.db import extraroute_db @@ -26,7 +27,6 @@ from neutron.db import l3_agentschedulers_db from neutron.db import l3_dvr_db from neutron.db import l3_gwmode_db from neutron.plugins.common import constants -from neutron_lib import constants as q_const from networking_odl.common import client as odl_client from networking_odl.common import utils as odl_utils diff --git a/networking-odl/networking_odl/l3/l3_odl_v2.py b/networking-odl/networking_odl/l3/l3_odl_v2.py index 2732ea6..da5829b 100644..100755 --- a/networking-odl/networking_odl/l3/l3_odl_v2.py +++ b/networking-odl/networking_odl/l3/l3_odl_v2.py @@ -16,6 +16,7 @@ from oslo_log import log as logging +from neutron.common import constants as q_const from neutron.db import api as db_api from neutron.db import common_db_mixin from neutron.db import extraroute_db @@ -23,7 +24,6 @@ from neutron.db import l3_agentschedulers_db from neutron.db import l3_dvr_db from neutron.db import l3_gwmode_db from neutron.plugins.common import constants -from neutron_lib import constants as q_const from networking_odl.common import config # noqa from networking_odl.common import constants as odl_const diff --git a/networking-odl/networking_odl/lbaas/driver_v1.py b/networking-odl/networking_odl/lbaas/driver_v1.py index aaf3dcf..0d66f70 100644 --- a/networking-odl/networking_odl/lbaas/driver_v1.py +++ b/networking-odl/networking_odl/lbaas/driver_v1.py @@ -20,13 +20,10 @@ from oslo_log import log as logging from neutron_lbaas.services.loadbalancer.drivers import abstract_driver from networking_odl.common import client as odl_client -from networking_odl.common import constants as odl_const + cfg.CONF.import_group('ml2_odl', 'networking_odl.common.config') LOG = logging.getLogger(__name__) -LBAAS = "lbaas" -POOLS_URL_PATH = LBAAS + '/' + odl_const.ODL_POOLS -HEALTHMONITORS_URL_PATH = LBAAS + '/' + odl_const.ODL_HEALTHMONITORS class OpenDaylightLbaasDriverV1(abstract_driver.LoadBalancerAbstractDriver): @@ -43,82 +40,53 @@ class OpenDaylightLbaasDriverV1(abstract_driver.LoadBalancerAbstractDriver): self.client = odl_client.OpenDaylightRestClient.create_client() def create_vip(self, context, vip): - """Create a vip on the OpenDaylight Controller. - - No code related to vip in the OpenDayLight neutronNorthbound, - so pass this method. - """ + """Create a vip on the OpenDaylight Controller.""" pass def update_vip(self, context, old_vip, vip): - """Update a vip on the OpenDaylight Controller. - - No code related to vip in the OpenDayLight neutronNorthbound, - so pass this method. - """ + """Update a vip on the OpenDaylight Controller.""" pass def delete_vip(self, context, vip): - """Delete a vip on the OpenDaylight Controller. - - No code related to vip in the OpenDayLight neutronNorthbound, - so pass this method. - """ + """Delete a vip on the OpenDaylight Controller.""" pass def create_pool(self, context, pool): """Create a pool on the OpenDaylight Controller.""" - url = POOLS_URL_PATH - self.client.sendjson('post', url, {odl_const.ODL_POOL: pool}) + pass def update_pool(self, context, old_pool, pool): """Update a pool on the OpenDaylight Controller.""" - url = POOLS_URL_PATH + "/" + old_pool['id'] - self.client.sendjson('put', url, {odl_const.ODL_POOL: pool}) + pass def delete_pool(self, context, pool): """Delete a pool on the OpenDaylight Controller.""" - url = POOLS_URL_PATH + "/" + pool['id'] - self.client.sendjson('delete', url, None) + pass def create_member(self, context, member): """Create a pool member on the OpenDaylight Controller.""" - url = ( - POOLS_URL_PATH + '/' + member['pool_id'] + - '/' + odl_const.ODL_MEMBERS) - self.client.sendjson('post', url, {odl_const.ODL_MEMBER: member}) + pass def update_member(self, context, old_member, member): """Update a pool member on the OpenDaylight Controller.""" - url = ( - POOLS_URL_PATH + '/' + member['pool_id'] + - '/' + odl_const.ODL_MEMBERS + "/" + old_member['id']) - self.client.sendjson('put', url, {odl_const.ODL_MEMBER: member}) + pass def delete_member(self, context, member): """Delete a pool member on the OpenDaylight Controller.""" - url = ( - POOLS_URL_PATH + '/' + member['pool_id'] + - '/' + odl_const.ODL_MEMBERS + "/" + member['id']) - self.client.sendjson('delete', url, None) + pass def create_pool_health_monitor(self, context, health_monitor, pool_id): """Create a pool health monitor on the OpenDaylight Controller.""" - url = HEALTHMONITORS_URL_PATH - self.client.sendjson( - 'post', url, {odl_const.ODL_HEALTHMONITOR: health_monitor}) + pass def update_pool_health_monitor(self, context, old_health_monitor, health_monitor, pool_id): """Update a pool health monitor on the OpenDaylight Controller.""" - url = HEALTHMONITORS_URL_PATH + "/" + old_health_monitor['id'] - self.client.sendjson( - 'put', url, {odl_const.ODL_HEALTHMONITOR: health_monitor}) + pass def delete_pool_health_monitor(self, context, health_monitor, pool_id): """Delete a pool health monitor on the OpenDaylight Controller.""" - url = HEALTHMONITORS_URL_PATH + "/" + health_monitor['id'] - self.client.sendjson('delete', url, None) + pass def stats(self, context, pool_id): """Retrieve pool statistics from the OpenDaylight Controller.""" diff --git a/networking-odl/networking_odl/ml2/legacy_port_binding.py b/networking-odl/networking_odl/ml2/legacy_port_binding.py index 7b9b918..18cf95f 100644 --- a/networking-odl/networking_odl/ml2/legacy_port_binding.py +++ b/networking-odl/networking_odl/ml2/legacy_port_binding.py @@ -16,10 +16,10 @@ from oslo_log import log +from neutron.common import constants as n_const from neutron.extensions import portbindings from neutron.plugins.common import constants from neutron.plugins.ml2 import driver_api -from neutron_lib import constants as n_const from networking_odl.ml2 import port_binding @@ -31,18 +31,11 @@ class LegacyPortBindingManager(port_binding.PortBindingController): def __init__(self): self.vif_details = {portbindings.CAP_PORT_FILTER: True} - self.supported_vnic_types = [portbindings.VNIC_NORMAL] def bind_port(self, port_context): """Set binding for all valid segments """ - vnic_type = port_context.current.get(portbindings.VNIC_TYPE, - portbindings.VNIC_NORMAL) - if vnic_type not in self.supported_vnic_types: - LOG.debug("Refusing to bind due to unsupported vnic_type: %s", - vnic_type) - return valid_segment = None for segment in port_context.segments_to_bind: diff --git a/networking-odl/networking_odl/ml2/mech_driver.py b/networking-odl/networking_odl/ml2/mech_driver.py index adde8d9..2d60e7a 100644 --- a/networking-odl/networking_odl/ml2/mech_driver.py +++ b/networking-odl/networking_odl/ml2/mech_driver.py @@ -23,13 +23,13 @@ from oslo_log import log as logging from oslo_utils import excutils import requests +from neutron.common import exceptions as n_exc from neutron.common import utils from neutron import context as neutron_context from neutron.extensions import allowedaddresspairs as addr_pair from neutron.extensions import securitygroup as sg from neutron.plugins.ml2 import driver_api from neutron.plugins.ml2 import driver_context -from neutron_lib import exceptions as n_exc from networking_odl._i18n import _LE from networking_odl.common import callback as odl_call @@ -67,46 +67,17 @@ class ResourceFilterBase(object): def filter_create_attributes_with_plugin(resource, plugin, dbcontext): pass - @staticmethod - def _filter_unmapped_null(resource_dict, unmapped_keys): - # NOTE(yamahata): bug work around - # https://bugs.eclipse.org/bugs/show_bug.cgi?id=475475 - # Null-value for an unmapped element causes next mapped - # collection to contain a null value - # JSON: { "unmappedField": null, "mappedCollection": [ "a" ] } - # - # Java Object: - # class Root { - # Collection<String> mappedCollection = new ArrayList<String>; - # } - # - # Result: - # Field B contains one element; null - # - # TODO(yamahata): update along side with neutron and ODL - # add when neutron adds more extensions - # delete when ODL neutron northbound supports it - # TODO(yamahata): do same thing for other resources - keys_to_del = [key for key in unmapped_keys - if resource_dict.get(key) is None] - if keys_to_del: - odl_utils.try_del(resource_dict, keys_to_del) - class NetworkFilter(ResourceFilterBase): - _UNMAPPED_KEYS = ['qos_policy_id'] - - @classmethod - def filter_create_attributes(cls, network, context): + @staticmethod + def filter_create_attributes(network, context): """Filter out network attributes not required for a create.""" odl_utils.try_del(network, ['status', 'subnets']) - cls._filter_unmapped_null(network, cls._UNMAPPED_KEYS) - @classmethod - def filter_update_attributes(cls, network, context): + @staticmethod + def filter_update_attributes(network, context): """Filter out network attributes for an update operation.""" odl_utils.try_del(network, ['id', 'status', 'subnets', 'tenant_id']) - cls._filter_unmapped_null(network, cls._UNMAPPED_KEYS) @classmethod def filter_create_attributes_with_plugin(cls, network, plugin, dbcontext): @@ -135,9 +106,6 @@ class SubnetFilter(ResourceFilterBase): class PortFilter(ResourceFilterBase): - _UNMAPPED_KEYS = ['binding:profile', 'dns_name', - 'port_security_enabled', 'qos_policy_id'] - @staticmethod def _add_security_groups(port, context): """Populate the 'security_groups' field with entire records.""" @@ -154,12 +122,38 @@ class PortFilter(ResourceFilterBase): network_address = str(netaddr.IPNetwork(ip_address)) address_pair['ip_address'] = network_address + @staticmethod + def _filter_unmapped_null(port): + # NOTE(yamahata): bug work around + # https://bugs.eclipse.org/bugs/show_bug.cgi?id=475475 + # Null-value for an unmapped element causes next mapped + # collection to contain a null value + # JSON: { "unmappedField": null, "mappedCollection": [ "a" ] } + # + # Java Object: + # class Root { + # Collection<String> mappedCollection = new ArrayList<String>; + # } + # + # Result: + # Field B contains one element; null + # + # TODO(yamahata): update along side with neutron and ODL + # add when neutron adds more extensions + # delete when ODL neutron northbound supports it + # TODO(yamahata): do same thing for other resources + unmapped_keys = ['dns_name', 'port_security_enabled', + 'binding:profile'] + keys_to_del = [key for key in unmapped_keys if port.get(key) is None] + if keys_to_del: + odl_utils.try_del(port, keys_to_del) + @classmethod def filter_create_attributes(cls, port, context): """Filter out port attributes not required for a create.""" cls._add_security_groups(port, context) cls._fixup_allowed_ipaddress_pairs(port[addr_pair.ADDRESS_PAIRS]) - cls._filter_unmapped_null(port, cls._UNMAPPED_KEYS) + cls._filter_unmapped_null(port) odl_utils.try_del(port, ['status']) # NOTE(yamahata): work around for port creation for router @@ -181,7 +175,7 @@ class PortFilter(ResourceFilterBase): """Filter out port attributes for an update operation.""" cls._add_security_groups(port, context) cls._fixup_allowed_ipaddress_pairs(port[addr_pair.ADDRESS_PAIRS]) - cls._filter_unmapped_null(port, cls._UNMAPPED_KEYS) + cls._filter_unmapped_null(port) odl_utils.try_del(port, ['network_id', 'id', 'status', 'tenant_id']) @classmethod @@ -363,8 +357,8 @@ class OpenDaylightDriver(object): 'object_id': obj_id}) self.out_of_sync = True - def sync_from_callback(self, operation, res_type, res_id, resource_dict): - object_type = res_type.plural.replace('_', '-') + def sync_from_callback(self, operation, object_type, res_id, + resource_dict): try: if operation == odl_const.ODL_DELETE: self.out_of_sync |= not self.client.try_delete( @@ -380,8 +374,7 @@ class OpenDaylightDriver(object): except Exception: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to perform %(operation)s on " - "%(object_type)s %(res_id)s " - "%(resource_dict)s"), + "%(object_type)s %(res_id)s %(resource_dict)s"), {'operation': operation, 'object_type': object_type, 'res_id': res_id, @@ -419,40 +412,31 @@ class OpenDaylightMechanismDriver(driver_api.MechanismDriver): # Postcommit hooks are used to trigger synchronization. def create_network_postcommit(self, context): - self.odl_drv.synchronize(odl_const.ODL_CREATE, odl_const.ODL_NETWORKS, - context) + self.odl_drv.synchronize('create', odl_const.ODL_NETWORKS, context) def update_network_postcommit(self, context): - self.odl_drv.synchronize(odl_const.ODL_UPDATE, odl_const.ODL_NETWORKS, - context) + self.odl_drv.synchronize('update', odl_const.ODL_NETWORKS, context) def delete_network_postcommit(self, context): - self.odl_drv.synchronize(odl_const.ODL_DELETE, odl_const.ODL_NETWORKS, - context) + self.odl_drv.synchronize('delete', odl_const.ODL_NETWORKS, context) def create_subnet_postcommit(self, context): - self.odl_drv.synchronize(odl_const.ODL_CREATE, odl_const.ODL_SUBNETS, - context) + self.odl_drv.synchronize('create', odl_const.ODL_SUBNETS, context) def update_subnet_postcommit(self, context): - self.odl_drv.synchronize(odl_const.ODL_UPDATE, odl_const.ODL_SUBNETS, - context) + self.odl_drv.synchronize('update', odl_const.ODL_SUBNETS, context) def delete_subnet_postcommit(self, context): - self.odl_drv.synchronize(odl_const.ODL_DELETE, odl_const.ODL_SUBNETS, - context) + self.odl_drv.synchronize('delete', odl_const.ODL_SUBNETS, context) def create_port_postcommit(self, context): - self.odl_drv.synchronize(odl_const.ODL_CREATE, odl_const.ODL_PORTS, - context) + self.odl_drv.synchronize('create', odl_const.ODL_PORTS, context) def update_port_postcommit(self, context): - self.odl_drv.synchronize(odl_const.ODL_UPDATE, odl_const.ODL_PORTS, - context) + self.odl_drv.synchronize('update', odl_const.ODL_PORTS, context) def delete_port_postcommit(self, context): - self.odl_drv.synchronize(odl_const.ODL_DELETE, odl_const.ODL_PORTS, - context) + self.odl_drv.synchronize('delete', odl_const.ODL_PORTS, context) def bind_port(self, context): self.odl_drv.bind_port(context) diff --git a/networking-odl/networking_odl/ml2/mech_driver_v2.py b/networking-odl/networking_odl/ml2/mech_driver_v2.py index dfc8df1..6fc199b 100644 --- a/networking-odl/networking_odl/ml2/mech_driver_v2.py +++ b/networking-odl/networking_odl/ml2/mech_driver_v2.py @@ -12,6 +12,7 @@ # 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 copy from oslo_config import cfg from oslo_log import log as logging @@ -21,11 +22,8 @@ from neutron.plugins.ml2 import driver_api as api from networking_odl.common import callback from networking_odl.common import config as odl_conf -from networking_odl.common import constants as odl_const -from networking_odl.journal import cleanup -from networking_odl.journal import full_sync +from networking_odl.db import db from networking_odl.journal import journal -from networking_odl.journal import maintenance from networking_odl.ml2 import port_binding LOG = logging.getLogger(__name__) @@ -44,66 +42,83 @@ class OpenDaylightMechanismDriver(api.MechanismDriver): self.sg_handler = callback.OdlSecurityGroupsHandler(self) self.journal = journal.OpendaylightJournalThread() self.port_binding_controller = port_binding.PortBindingManager.create() - self._start_maintenance_thread() - - def _start_maintenance_thread(self): - # start the maintenance thread and register all the maintenance - # operations : - # (1) JournalCleanup - Delete completed rows from journal - # (2) CleanupProcessing - Mark orphaned processing rows to pending - # (3) Full sync - Re-sync when detecting an ODL "cold reboot" - cleanup_obj = cleanup.JournalCleanup() - self._maintenance_thread = maintenance.MaintenanceThread() - self._maintenance_thread.register_operation( - cleanup_obj.delete_completed_rows) - self._maintenance_thread.register_operation( - cleanup_obj.cleanup_processing_rows) - self._maintenance_thread.register_operation(full_sync.full_sync) - self._maintenance_thread.start() - - @staticmethod - def _record_in_journal(context, object_type, operation, data=None): - if data is None: - data = context.current - journal.record(context._plugin_context.session, object_type, - context.current['id'], operation, data) def create_network_precommit(self, context): - OpenDaylightMechanismDriver._record_in_journal( - context, odl_const.ODL_NETWORK, odl_const.ODL_CREATE) + db.create_pending_row(context._plugin_context.session, 'network', + context.current['id'], 'create', context.current) def create_subnet_precommit(self, context): - OpenDaylightMechanismDriver._record_in_journal( - context, odl_const.ODL_SUBNET, odl_const.ODL_CREATE) + db.create_pending_row(context._plugin_context.session, 'subnet', + context.current['id'], 'create', context.current) def create_port_precommit(self, context): - OpenDaylightMechanismDriver._record_in_journal( - context, odl_const.ODL_PORT, odl_const.ODL_CREATE) + dbcontext = context._plugin_context + groups = [context._plugin.get_security_group(dbcontext, sg) + for sg in context.current['security_groups']] + new_context = copy.deepcopy(context.current) + new_context['security_groups'] = groups + # NOTE(yamahata): work around for port creation for router + # tenant_id=''(empty string) is passed when port is created + # by l3 plugin internally for router. + # On the other hand, ODL doesn't accept empty string for tenant_id. + # In that case, deduce tenant_id from network_id for now. + # Right fix: modify Neutron so that don't allow empty string + # for tenant_id even for port for internal use. + # TODO(yamahata): eliminate this work around when neutron side + # is fixed + # assert port['tenant_id'] != '' + if ('tenant_id' not in context.current or + context.current['tenant_id'] == ''): + tenant_id = context._network_context._network['tenant_id'] + new_context['tenant_id'] = tenant_id + db.create_pending_row(context._plugin_context.session, 'port', + context.current['id'], 'create', new_context) def update_network_precommit(self, context): - OpenDaylightMechanismDriver._record_in_journal( - context, odl_const.ODL_NETWORK, odl_const.ODL_UPDATE) + db.create_pending_row(context._plugin_context.session, 'network', + context.current['id'], 'update', context.current) def update_subnet_precommit(self, context): - OpenDaylightMechanismDriver._record_in_journal( - context, odl_const.ODL_SUBNET, odl_const.ODL_UPDATE) + db.create_pending_row(context._plugin_context.session, 'subnet', + context.current['id'], 'update', context.current) def update_port_precommit(self, context): - OpenDaylightMechanismDriver._record_in_journal( - context, odl_const.ODL_PORT, odl_const.ODL_UPDATE) + port = context._plugin.get_port(context._plugin_context, + context.current['id']) + dbcontext = context._plugin_context + new_context = copy.deepcopy(context.current) + groups = [context._plugin.get_security_group(dbcontext, sg) + for sg in port['security_groups']] + new_context['security_groups'] = groups + # Add the network_id in for validation + new_context['network_id'] = port['network_id'] + # NOTE(yamahata): work around for port creation for router + # tenant_id=''(empty string) is passed when port is created + # by l3 plugin internally for router. + # On the other hand, ODL doesn't accept empty string for tenant_id. + # In that case, deduce tenant_id from network_id for now. + # Right fix: modify Neutron so that don't allow empty string + # for tenant_id even for port for internal use. + # TODO(yamahata): eliminate this work around when neutron side + # is fixed + # assert port['tenant_id'] != '' + if ('tenant_id' not in context.current or + context.current['tenant_id'] == ''): + port['tenant_id'] = context._network_context._network['tenant_id'] + db.create_pending_row(context._plugin_context.session, 'port', + context.current['id'], 'update', new_context) def delete_network_precommit(self, context): - OpenDaylightMechanismDriver._record_in_journal( - context, odl_const.ODL_NETWORK, odl_const.ODL_DELETE, data=[]) + db.create_pending_row(context._plugin_context.session, 'network', + context.current['id'], 'delete', None) def delete_subnet_precommit(self, context): # Use the journal row's data field to store parent object # uuids. This information is required for validation checking # when deleting parent objects. new_context = [context.current['network_id']] - OpenDaylightMechanismDriver._record_in_journal( - context, odl_const.ODL_SUBNET, odl_const.ODL_DELETE, - data=new_context) + db.create_pending_row(context._plugin_context.session, 'subnet', + context.current['id'], 'delete', new_context) def delete_port_precommit(self, context): # Use the journal row's data field to store parent object @@ -112,19 +127,19 @@ class OpenDaylightMechanismDriver(api.MechanismDriver): new_context = [context.current['network_id']] for subnet in context.current['fixed_ips']: new_context.append(subnet['subnet_id']) - OpenDaylightMechanismDriver._record_in_journal( - context, odl_const.ODL_PORT, odl_const.ODL_DELETE, - data=new_context) + db.create_pending_row(context._plugin_context.session, 'port', + context.current['id'], 'delete', new_context) @journal.call_thread_on_end - def sync_from_callback(self, operation, res_type, res_id, resource_dict): - object_type = res_type.singular + def sync_from_callback(self, operation, res_type_uri, res_id, + resource_dict): + object_type = res_type_uri.replace('-', '_')[:-1] object_uuid = (resource_dict[object_type]['id'] if operation == 'create' else res_id) if resource_dict is not None: resource_dict = resource_dict[object_type] - journal.record(db_api.get_session(), object_type, object_uuid, - operation, resource_dict) + db.create_pending_row(db_api.get_session(), object_type, object_uuid, + operation, resource_dict) def _postcommit(self, context): self.journal.set_sync_event() diff --git a/networking-odl/networking_odl/ml2/network_topology.py b/networking-odl/networking_odl/ml2/network_topology.py index b0bfae1..99137a8 100644 --- a/networking-odl/networking_odl/ml2/network_topology.py +++ b/networking-odl/networking_odl/ml2/network_topology.py @@ -27,7 +27,7 @@ from oslo_serialization import jsonutils from networking_odl.common import cache from networking_odl.common import client from networking_odl.common import utils -from networking_odl._i18n import _, _LI, _LW, _LE +from networking_odl._i18n import _LI, _LW, _LE from networking_odl.ml2 import port_binding @@ -44,7 +44,8 @@ class NetworkTopologyManager(port_binding.PortBindingController): # List of class names of registered implementations of interface # NetworkTopologyParser network_topology_parsers = [ - 'networking_odl.ml2.ovsdb_topology.OvsdbNetworkTopologyParser'] + 'networking_odl.ml2.ovsdb_topology.OvsdbNetworkTopologyParser', + 'networking_odl.ml2.vpp_topology.VppNetworkTopologyParser'] def __init__(self, vif_details=None, client=None): # Details for binding port @@ -65,6 +66,7 @@ class NetworkTopologyManager(port_binding.PortBindingController): """ host_name = port_context.host + LOG.debug('Processing port for host: %s', host_name) elements = list() try: # Append to empty list to add as much elements as possible @@ -85,6 +87,7 @@ class NetworkTopologyManager(port_binding.PortBindingController): {'host_name': host_name}) # Imported here to avoid cyclic module dependencies + # TODO(wdec): Add vpp topology import from networking_odl.ml2 import ovsdb_topology elements = [ovsdb_topology.OvsdbNetworkTopologyElement()] @@ -100,7 +103,8 @@ class NetworkTopologyManager(port_binding.PortBindingController): # it is invalid for at least one element: discard it vif_type_is_valid_for_all = False break - + # TODO(wdec): This needs to deal with not all network elements + # supporting all binding types. if vif_type_is_valid_for_all: # This is the best VIF type valid for all elements LOG.debug( @@ -206,13 +210,14 @@ class NetworkTopologyManager(port_binding.PortBindingController): try: for element in parser.parse_network_topology(network_topology): if not isinstance(element, NetworkTopologyElement): - raise TypeError(_( + raise TypeError( "Yield element doesn't implement interface " - "'NetworkTopologyElement': {!r}").format(element)) + "'NetworkTopologyElement': {!r}".format(element)) # the same element can be known by more host addresses for host_address in element.host_addresses: if host_address in addresses: at_least_one_element_for_asked_addresses = True + LOG.debug("Found cached Host: %s \n", host_address) yield host_address, element except Exception: LOG.exception( @@ -224,8 +229,8 @@ class NetworkTopologyManager(port_binding.PortBindingController): # calling this method again as soon it is requested and avoid # waiting for cache expiration raise ValueError( - _('No such topology element for given host addresses: {}') - .format(', '.join(addresses))) + 'No such topology element for given host addresses: {}'.format( + ', '.join(addresses))) @six.add_metaclass(abc.ABCMeta) @@ -240,9 +245,9 @@ class NetworkTopologyParser(object): module = importlib.import_module(module_name) clss = getattr(module, class_name) if not issubclass(clss, cls): - raise TypeError(_( + raise TypeError( "Class {class_name!r} of module {module_name!r} doesn't " - "implement 'NetworkTopologyParser' interface.").format( + "implement 'NetworkTopologyParser' interface.".format( class_name=class_name, module_name=module_name)) return clss() diff --git a/networking-odl/networking_odl/ml2/ovsdb_topology.py b/networking-odl/networking_odl/ml2/ovsdb_topology.py index f2c8ad8..ed82032 100644 --- a/networking-odl/networking_odl/ml2/ovsdb_topology.py +++ b/networking-odl/networking_odl/ml2/ovsdb_topology.py @@ -21,12 +21,11 @@ from oslo_log import log import six from six.moves.urllib import parse +from neutron.common import constants as n_const from neutron.extensions import portbindings from neutron.plugins.common import constants from neutron.plugins.ml2 import driver_api -from neutron_lib import constants as n_const -from networking_odl._i18n import _ from networking_odl.ml2 import network_topology @@ -171,8 +170,7 @@ class OvsdbNetworkTopologyElement(network_topology.NetworkTopologyElement): status=n_const.PORT_STATUS_ACTIVE) return - raise ValueError( - _('Unable to find any valid segment in given context.')) + raise ValueError('Unable to find any valid segment in given context.') def to_dict(self): data = super(OvsdbNetworkTopologyElement, self).to_dict() diff --git a/networking-odl/networking_odl/ml2/pseudo_agentdb_binding.py b/networking-odl/networking_odl/ml2/pseudo_agentdb_binding.py deleted file mode 100644 index d24bd55..0000000 --- a/networking-odl/networking_odl/ml2/pseudo_agentdb_binding.py +++ /dev/null @@ -1,263 +0,0 @@ -# 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'] diff --git a/networking-odl/networking_odl/ml2/vpp_ml2.tar b/networking-odl/networking_odl/ml2/vpp_ml2.tar Binary files differnew file mode 100644 index 0000000..e181208 --- /dev/null +++ b/networking-odl/networking_odl/ml2/vpp_ml2.tar diff --git a/networking-odl/networking_odl/ml2/vpp_topology.py b/networking-odl/networking_odl/ml2/vpp_topology.py new file mode 100644 index 0000000..c16399d --- /dev/null +++ b/networking-odl/networking_odl/ml2/vpp_topology.py @@ -0,0 +1,194 @@ +# 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 collections +import os + +from oslo_log import log +import six + +from neutron.common import constants as n_const +from neutron.extensions import portbindings +from neutron.plugins.common import constants +from neutron.plugins.ml2 import driver_api + +from networking_odl.ml2 import network_topology + +LOG = log.getLogger(__name__) +HC_VPP_CAPABILITY = 'urn:opendaylight:params:xml:ns:yang:v3po' + + +class VppNetworkTopologyParser(network_topology.NetworkTopologyParser): + def new_element(self, devname): + return VppNetworkTopologyElement(devname=devname) + + def parse_network_topology(self, network_topologies): + LOG.debug("Parsing Topology using VPP Topology Parser") + elements_by_name = collections.OrderedDict() + for topology in network_topologies['network-topology']['topology']: + if topology['topology-id'].startswith('topology-netconf'): + for node in topology['node']: + # expected : + # "node-id": "name", + # "netconf-node-topology:host": "172.21.174.41" + # "netconf-node-topology:available-capabilities": { + # "available-capability" : contains the v3po model + node_name = node['node-id'] + LOG.debug("Examining capabilities for node: %s\n", + node_name) + try: + capabilities = node[ + 'netconf-node-topology:available-capabilities'] + LOG.debug("Node's capabilities: %s\n", + capabilities) + for item in capabilities['available-capability']: + if HC_VPP_CAPABILITY in item: + LOG.debug("Found VPP matching capability for " + "node: %s\n", node_name) + element = elements_by_name.get(node_name) + if element is None: + elements_by_name[node_name] = element = \ + self.new_element(node_name) + + self._update_elmnt_from_json_netconf_topo_node( + node, element, node_name) + except KeyError: + LOG.debug("No netconf available capabilities found for" + ": %s\n", node_name) + + # Can there can be more VPP instances connected beside the same IP + # address? + # Cache will yield more instaces for the same key + for __, element in six.iteritems(elements_by_name): + yield element + + def _update_elmnt_from_json_netconf_topo_node( + self, node, element, node_name): + + # fetch remote IP address + element.remote_ip = node["netconf-node-topology:host"] + # Assume Honeycomb-VPP supports vhost_user + element.support_vhost_user = True + + LOG.debug( + 'Topology element updated:\n' + ' - VPP node name: %(node_name)r\n' + ' - remote_ip: %(remote_ip)r\n' + ' - support_vhost_user: %(support_vhost_user)r', + {'node_name': node_name, + 'remote_ip': element.remote_ip, + 'support_vhost_user': element.support_vhost_user}) + + +class VppNetworkTopologyElement(network_topology.NetworkTopologyElement): + devname = None # Filled in by parser + remote_ip = None # Filled in by parser + has_datapath_type_netdev = False # Placeholder for future capability + support_vhost_user = False # VPP supports it by default actually. + + # location for vhostuser sockets. + # TODO(wdec): This should be configurable in the ML2 config. + vhostuser_socket_dir = '/tmp/' + + # TODO(wdec): And also this should be configurable in ML2... + # prefix for port + port_prefix = 'socket_' + + def __init__(self, **kwargs): + for name, value in six.iteritems(kwargs): + setattr(self, name, value) + + @property + def host_addresses(self): + # For now it support only the remote IP found in connection info + return self.remote_ip, + + @property + def valid_vif_types(self): + return [portbindings.VIF_TYPE_VHOST_USER] + + def bind_port(self, port_context, vif_type, vif_details): + + port_context_id = port_context.current['id'] + network_context_id = port_context.network.current['id'] + + # Bind port to the first valid segment + for segment in port_context.segments_to_bind: + if self._is_valid_segment(segment): + # Guest best VIF type for given host + vif_details = self._get_vif_details( + vif_details=vif_details, port_context_id=port_context_id, + vif_type=vif_type) + LOG.debug( + 'Bind port with valid segment:\n' + '\tport: %(port)r\n' + '\tnetwork: %(network)r\n' + '\tsegment: %(segment)r\n' + '\tVIF type: %(vif_type)r\n' + '\tVIF details: %(vif_details)r', + {'port': port_context_id, + 'network': network_context_id, + 'segment': segment, 'vif_type': vif_type, + 'vif_details': vif_details}) + port_context.set_binding( + segment[driver_api.ID], vif_type, vif_details, + status=n_const.PORT_STATUS_ACTIVE) + return + + raise ValueError('Unable to find any valid segment in given context.') + + def to_dict(self): + data = super(VppNetworkTopologyElement, self).to_dict() + data.update( + {'uuid': self.devname, + 'has_datapath_type_netdev': self.has_datapath_type_netdev, + 'support_vhost_user': self.support_vhost_user, + 'valid_vif_types': self.valid_vif_types}) + if portbindings.VIF_TYPE_VHOST_USER in self.valid_vif_types: + data.update({'port_prefix': self.port_prefix, + 'vhostuser_socket_dir': self.vhostuser_socket_dir}) + return data + + def _is_valid_segment(self, segment): + """Verify a segment is valid for the OpenDaylight MechanismDriver. + + Verify the requested segment is supported by ODL and return True or + False to indicate this to callers. + """ + + network_type = segment[driver_api.NETWORK_TYPE] + return network_type in [constants.TYPE_LOCAL, constants.TYPE_GRE, + constants.TYPE_VXLAN, constants.TYPE_VLAN, + constants.TYPE_FLAT] + + def _get_vif_details(self, vif_details, port_context_id, vif_type): + vif_details = dict(vif_details) + if vif_type == portbindings.VIF_TYPE_VHOST_USER: + socket_path = os.path.join( + self.vhostuser_socket_dir, + (self.port_prefix + port_context_id)) + + vif_details.update({ + portbindings.VHOST_USER_MODE: + portbindings.VHOST_USER_MODE_SERVER, + portbindings.VHOST_USER_SOCKET: socket_path + }) + return vif_details + + def __setattr__(self, name, value): + # raises Attribute error if the class hasn't this attribute + getattr(type(self), name) + super(VppNetworkTopologyElement, self).__setattr__(name, value) diff --git a/networking-odl/networking_odl/tests/unit/common/test_callback.py b/networking-odl/networking_odl/tests/unit/common/test_callback.py index f5e2ee6..b7720b3 100644 --- a/networking-odl/networking_odl/tests/unit/common/test_callback.py +++ b/networking-odl/networking_odl/tests/unit/common/test_callback.py @@ -18,6 +18,7 @@ from networking_odl.common import constants as odl_const from networking_odl.ml2.mech_driver import OpenDaylightDriver import mock +import testscenarios import testtools from neutron.callbacks import events @@ -27,57 +28,110 @@ from neutron.callbacks import resources FAKE_ID = 'fakeid' -class ODLCallbackTestCase(testtools.TestCase): - odl_driver = OpenDaylightDriver() - sgh = callback.OdlSecurityGroupsHandler(odl_driver) +class ODLCallbackTestCase(testscenarios.WithScenarios, testtools.TestCase): + odl_client = OpenDaylightDriver() + scenarios = [ + ('after', { + 'sgh': callback.OdlSecurityGroupsHandler(odl_client, + "AFTER")}), + ('precommit', { + 'sgh': callback.OdlSecurityGroupsHandler(odl_client, + "PRECOMMIT")}), + ] def setUp(self): super(ODLCallbackTestCase, self).setUp() @mock.patch.object(OpenDaylightDriver, 'sync_from_callback') - def _test_callback_for_sg(self, event, op, sg, sg_id, sfc): + def test_callback_sg_create(self, sfc): + context = mock.Mock() + sg = mock.Mock() + default_sg = mock.Mock() + kwargs = { + 'context': context, + 'security_group': sg, + 'security_groups': odl_const.ODL_SGS, + 'is_default': default_sg, + } self.sgh.sg_callback(resources.SECURITY_GROUP, - event, - None, - security_group=sg, - security_group_id=sg_id) + events.AFTER_CREATE, + "trigger", + **kwargs) - expected_dict = ({resources.SECURITY_GROUP: sg} - if sg is not None else None) - sfc.assert_called_with( - op, callback._RESOURCE_MAPPING[resources.SECURITY_GROUP], sg_id, - expected_dict) + sfc.assert_called_with(odl_const.ODL_CREATE, + 'security-groups', + None, {'security_group': sg}) - def test_callback_sg_create(self): - self._test_callback_for_sg(events.AFTER_CREATE, odl_const.ODL_CREATE, - mock.Mock(), None) + @mock.patch.object(OpenDaylightDriver, 'sync_from_callback') + def test_callback_sg_update(self, sfc): + context = mock.Mock() + sg = mock.Mock() + kwargs = { + 'context': context, + 'security_group_id': FAKE_ID, + 'security_group': sg, + 'security_groups': odl_const.ODL_SGS, + } + self.sgh.sg_callback(resources.SECURITY_GROUP, + events.AFTER_UPDATE, + "trigger", + **kwargs) + + sfc.assert_called_with(odl_const.ODL_UPDATE, + 'security-groups', + FAKE_ID, {'security_group': sg}) + + @mock.patch.object(OpenDaylightDriver, 'sync_from_callback') + def test_callback_sg_delete(self, sfc): + context = mock.Mock() + sg = mock.Mock() + kwargs = { + 'context': context, + 'security_group_id': FAKE_ID, + 'security_group': sg, + 'security_groups': odl_const.ODL_SGS, + } + self.sgh.sg_callback(resources.SECURITY_GROUP, + events.AFTER_DELETE, + "trigger", + **kwargs) - def test_callback_sg_update(self): - self._test_callback_for_sg(events.AFTER_UPDATE, odl_const.ODL_UPDATE, - mock.Mock(), FAKE_ID) + sfc.assert_called_with(odl_const.ODL_DELETE, + 'security-groups', + FAKE_ID, {'security_group': sg}) - def test_callback_sg_delete(self): - self._test_callback_for_sg(events.AFTER_DELETE, odl_const.ODL_DELETE, - None, FAKE_ID) + @mock.patch.object(OpenDaylightDriver, 'sync_from_callback') + def test_callback_sg_rules_create(self, sfc): + context = mock.Mock() + security_group_rule = mock.Mock() + kwargs = { + 'context': context, + 'security_group_rule': security_group_rule, + 'security_group_rules': odl_const.ODL_SG_RULES, + } + self.sgh.sg_callback(resources.SECURITY_GROUP_RULE, + events.AFTER_CREATE, + "trigger", + **kwargs) + + sfc.assert_called_with(odl_const.ODL_CREATE, + 'security-group-rules', + None, + {'security_group_rule': security_group_rule}) @mock.patch.object(OpenDaylightDriver, 'sync_from_callback') - def _test_callback_for_sg_rules(self, event, op, sg_rule, sg_rule_id, sfc): + def test_callback_sg_rules_delete(self, sfc): + context = mock.Mock() + kwargs = { + 'context': context, + 'security_group_rule_id': FAKE_ID, + 'security_group_rules': odl_const.ODL_SG_RULES, + } self.sgh.sg_callback(resources.SECURITY_GROUP_RULE, - event, - None, - security_group_rule=sg_rule, - security_group_rule_id=sg_rule_id) - - expected_dict = ({resources.SECURITY_GROUP_RULE: sg_rule} - if sg_rule is not None else None) - sfc.assert_called_with( - op, callback._RESOURCE_MAPPING[resources.SECURITY_GROUP_RULE], - sg_rule_id, expected_dict) - - def test_callback_sg_rules_create(self): - self._test_callback_for_sg_rules( - events.AFTER_CREATE, odl_const.ODL_CREATE, mock.Mock(), None) - - def test_callback_sg_rules_delete(self): - self._test_callback_for_sg_rules( - events.AFTER_DELETE, odl_const.ODL_DELETE, None, FAKE_ID) + events.AFTER_DELETE, + "trigger", + **kwargs) + + sfc.assert_called_with(odl_const.ODL_DELETE, + 'security-group-rules', + FAKE_ID, None) diff --git a/networking-odl/networking_odl/tests/unit/common/test_lightweight_testing.py b/networking-odl/networking_odl/tests/unit/common/test_lightweight_testing.py index ea3b5a8..3fa63fa 100644 --- a/networking-odl/networking_odl/tests/unit/common/test_lightweight_testing.py +++ b/networking-odl/networking_odl/tests/unit/common/test_lightweight_testing.py @@ -152,7 +152,7 @@ class LightweightTestingTestCase(base.DietTestCase): self.assertEqual(lwt.NO_CONTENT, response.status_code) lwt_dict = lwt.OpenDaylightLwtClient.lwt_dict network = lwt_dict['networks'].get('fakeid1') - self.assertIsNone(network) + self.assertEqual(None, network) @mock.patch.dict(lwt.OpenDaylightLwtClient.lwt_dict, {'networks': {'fakeid1': {'id': 'fakeid1', @@ -169,6 +169,6 @@ class LightweightTestingTestCase(base.DietTestCase): self.assertEqual(lwt.NO_CONTENT, response.status_code) lwt_dict = lwt.OpenDaylightLwtClient.lwt_dict network = lwt_dict['networks'].get('fakeid1') - self.assertIsNone(network) + self.assertEqual(None, network) network = lwt_dict['networks'].get('fakeid2') - self.assertIsNone(network) + self.assertEqual(None, network) diff --git a/networking-odl/networking_odl/tests/unit/db/test_db.py b/networking-odl/networking_odl/tests/unit/db/test_db.py index 72749ad..9c03490 100644 --- a/networking-odl/networking_odl/tests/unit/db/test_db.py +++ b/networking-odl/networking_odl/tests/unit/db/test_db.py @@ -41,7 +41,6 @@ class DbTestCase(SqlTestCaseLight, TestCase): def _db_cleanup(self): self.db_session.query(models.OpendaylightJournal).delete() - self.db_session.query(models.OpendaylightMaintenance).delete() def _update_row(self, row): self.db_session.merge(row) @@ -162,33 +161,6 @@ class DbTestCase(SqlTestCaseLight, TestCase): self.assertEqual(2, update_mock.call_count) - def _test_delete_rows_by_state_and_time(self, last_retried, row_retention, - state, expected_rows): - db.create_pending_row(self.db_session, *self.UPDATE_ROW) - - # update state and last retried - row = db.get_all_db_rows(self.db_session)[0] - row.state = state - row.last_retried = row.last_retried - timedelta(seconds=last_retried) - self._update_row(row) - - db.delete_rows_by_state_and_time(self.db_session, - odl_const.COMPLETED, - timedelta(seconds=row_retention)) - - # validate the number of rows in the journal - rows = db.get_all_db_rows(self.db_session) - self.assertEqual(expected_rows, len(rows)) - - def test_delete_completed_rows_no_new_rows(self): - self._test_delete_rows_by_state_and_time(0, 10, odl_const.COMPLETED, 1) - - def test_delete_completed_rows_one_new_row(self): - self._test_delete_rows_by_state_and_time(6, 5, odl_const.COMPLETED, 0) - - def test_delete_completed_rows_wrong_state(self): - self._test_delete_rows_by_state_and_time(10, 8, odl_const.PENDING, 1) - def test_valid_retry_count(self): self._test_retry_count(1, 1, 1, odl_const.PENDING) @@ -206,38 +178,3 @@ class DbTestCase(SqlTestCaseLight, TestCase): def test_update_row_state_to_completed(self): self._test_update_row_state(odl_const.PROCESSING, odl_const.COMPLETED) - - def _test_maintenance_lock_unlock(self, db_func, existing_state, - expected_state, expected_result): - row = models.OpendaylightMaintenance(id='test', - state=existing_state) - self.db_session.add(row) - self.db_session.flush() - - self.assertEqual(expected_result, db_func(self.db_session)) - row = self.db_session.query(models.OpendaylightMaintenance).one() - self.assertEqual(expected_state, row['state']) - - def test_lock_maintenance(self): - self._test_maintenance_lock_unlock(db.lock_maintenance, - odl_const.PENDING, - odl_const.PROCESSING, - True) - - def test_lock_maintenance_fails_when_processing(self): - self._test_maintenance_lock_unlock(db.lock_maintenance, - odl_const.PROCESSING, - odl_const.PROCESSING, - False) - - def test_unlock_maintenance(self): - self._test_maintenance_lock_unlock(db.unlock_maintenance, - odl_const.PROCESSING, - odl_const.PENDING, - True) - - def test_unlock_maintenance_fails_when_pending(self): - self._test_maintenance_lock_unlock(db.unlock_maintenance, - odl_const.PENDING, - odl_const.PENDING, - False) diff --git a/networking-odl/networking_odl/tests/unit/journal/__init__.py b/networking-odl/networking_odl/tests/unit/journal/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/networking-odl/networking_odl/tests/unit/journal/__init__.py +++ /dev/null diff --git a/networking-odl/networking_odl/tests/unit/journal/test_dependency_validations.py b/networking-odl/networking_odl/tests/unit/journal/test_dependency_validations.py deleted file mode 100644 index 39a4b98..0000000 --- a/networking-odl/networking_odl/tests/unit/journal/test_dependency_validations.py +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright (C) 2016 Intel Corp. Isaku Yamahata <isaku.yamahata@gmail com> -# 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 mock - -from neutron.tests import base - -from networking_odl.journal import dependency_validations - - -class DependencyValidationsTestCase(base.DietTestCase): - _RESOURCE_DUMMY = 'test_type' - - def setUp(self): - super(DependencyValidationsTestCase, self).setUp() - mock_validation_map = mock.patch.dict( - dependency_validations._VALIDATION_MAP) - mock_validation_map.start() - self.addCleanup(mock_validation_map.stop) - - def test_register_validator(self): - mock_session = mock.Mock() - mock_validator = mock.Mock(return_value=False) - mock_row = mock.Mock() - mock_row.object_type = self._RESOURCE_DUMMY - dependency_validations.register_validator(self._RESOURCE_DUMMY, - mock_validator) - valid = dependency_validations.validate(mock_session, mock_row) - mock_validator.assert_called_once_with(mock_session, mock_row) - self.assertFalse(valid) diff --git a/networking-odl/networking_odl/tests/unit/journal/test_full_sync.py b/networking-odl/networking_odl/tests/unit/journal/test_full_sync.py deleted file mode 100644 index cedccbd..0000000 --- a/networking-odl/networking_odl/tests/unit/journal/test_full_sync.py +++ /dev/null @@ -1,152 +0,0 @@ -# -# Copyright (C) 2016 Red Hat, Inc. -# -# 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 -import requests - -from neutron.db import api as neutron_db_api -from neutron import manager -from neutron.tests.unit.testlib_api import SqlTestCaseLight - -from networking_odl.common import constants as odl_const -from networking_odl.db import db -from networking_odl.db import models -from networking_odl.journal import full_sync - - -class FullSyncTestCase(SqlTestCaseLight): - def setUp(self): - super(FullSyncTestCase, self).setUp() - self.db_session = neutron_db_api.get_session() - - full_sync._CLIENT = mock.MagicMock() - self.plugin_mock = mock.patch.object(manager.NeutronManager, - 'get_plugin').start() - self.l3_plugin_mock = mock.patch.object(manager.NeutronManager, - 'get_service_plugins').start() - - self.addCleanup(self._db_cleanup) - - def _db_cleanup(self): - self.db_session.query(models.OpendaylightJournal).delete() - - def test_no_full_sync_when_canary_exists(self): - full_sync.full_sync(self.db_session) - self.assertEqual([], db.get_all_db_rows(self.db_session)) - - def _mock_l2_resources(self): - expected_journal = {odl_const.ODL_NETWORK: '1', - odl_const.ODL_SUBNET: '2', - odl_const.ODL_PORT: '3'} - plugin_instance = self.plugin_mock.return_value - plugin_instance.get_networks.return_value = [ - {'id': expected_journal[odl_const.ODL_NETWORK]}] - plugin_instance.get_subnets.return_value = [ - {'id': expected_journal[odl_const.ODL_SUBNET]}] - plugin_instance.get_ports.side_effect = ([ - {'id': expected_journal[odl_const.ODL_PORT]}], []) - return expected_journal - - def _filter_out_canary(self, rows): - return [row for row in rows if row['object_uuid'] != - full_sync._CANARY_NETWORK_ID] - - def _test_no_full_sync_when_canary_in_journal(self, state): - self._mock_canary_missing() - self._mock_l2_resources() - db.create_pending_row(self.db_session, odl_const.ODL_NETWORK, - full_sync._CANARY_NETWORK_ID, - odl_const.ODL_CREATE, {}) - row = db.get_all_db_rows(self.db_session)[0] - db.update_db_row_state(self.db_session, row, state) - - full_sync.full_sync(self.db_session) - - rows = db.get_all_db_rows(self.db_session) - self.assertEqual([], self._filter_out_canary(rows)) - - def test_no_full_sync_when_canary_pending_creation(self): - self._test_no_full_sync_when_canary_in_journal(odl_const.PENDING) - - def test_no_full_sync_when_canary_is_processing(self): - self._test_no_full_sync_when_canary_in_journal(odl_const.PROCESSING) - - def test_client_error_propagates(self): - class TestException(Exception): - def __init__(self): - pass - - full_sync._CLIENT.get.side_effect = TestException() - self.assertRaises(TestException, full_sync.full_sync, self.db_session) - - def _mock_canary_missing(self): - get_return = mock.MagicMock() - get_return.status_code = requests.codes.not_found - full_sync._CLIENT.get.return_value = get_return - - def _assert_canary_created(self): - rows = db.get_all_db_rows(self.db_session) - self.assertTrue(any(r['object_uuid'] == full_sync._CANARY_NETWORK_ID - for r in rows)) - return rows - - def _test_full_sync_resources(self, expected_journal): - self._mock_canary_missing() - - full_sync.full_sync(self.db_session) - - rows = self._assert_canary_created() - rows = self._filter_out_canary(rows) - self.assertItemsEqual(expected_journal.keys(), - [row['object_type'] for row in rows]) - for row in rows: - self.assertEqual(expected_journal[row['object_type']], - row['object_uuid']) - - def test_full_sync_removes_pending_rows(self): - db.create_pending_row(self.db_session, odl_const.ODL_NETWORK, "uuid", - odl_const.ODL_CREATE, {'foo': 'bar'}) - self._test_full_sync_resources({}) - - def test_full_sync_no_resources(self): - self._test_full_sync_resources({}) - - def test_full_sync_l2_resources(self): - self._test_full_sync_resources(self._mock_l2_resources()) - - def _mock_router_port(self, port_id): - router_port = {'id': port_id, - 'device_id': '1', - 'tenant_id': '1', - 'fixed_ips': [{'subnet_id': '1'}]} - plugin_instance = self.plugin_mock.return_value - plugin_instance.get_ports.side_effect = ([], [router_port]) - - def _mock_l3_resources(self): - expected_journal = {odl_const.ODL_ROUTER: '1', - odl_const.ODL_FLOATINGIP: '2', - odl_const.ODL_ROUTER_INTF: '3'} - plugin_instance = self.l3_plugin_mock.return_value.get.return_value - plugin_instance.get_routers.return_value = [ - {'id': expected_journal[odl_const.ODL_ROUTER]}] - plugin_instance.get_floatingips.return_value = [ - {'id': expected_journal[odl_const.ODL_FLOATINGIP]}] - self._mock_router_port(expected_journal[odl_const.ODL_ROUTER_INTF]) - - return expected_journal - - def test_full_sync_l3_resources(self): - self._test_full_sync_resources(self._mock_l3_resources()) diff --git a/networking-odl/networking_odl/tests/unit/journal/test_maintenance.py b/networking-odl/networking_odl/tests/unit/journal/test_maintenance.py deleted file mode 100644 index eb823cd..0000000 --- a/networking-odl/networking_odl/tests/unit/journal/test_maintenance.py +++ /dev/null @@ -1,93 +0,0 @@ -# -# Copyright (C) 2016 Red Hat, Inc. -# -# 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 -import threading -from unittest2.case import TestCase - -from neutron.db import api as neutron_db_api -from neutron.tests.unit.testlib_api import SqlTestCaseLight - -from networking_odl.common import constants as odl_const -from networking_odl.db import models -from networking_odl.journal import maintenance - - -class MaintenanceThreadTestCase(SqlTestCaseLight, TestCase): - def setUp(self): - super(MaintenanceThreadTestCase, self).setUp() - self.db_session = neutron_db_api.get_session() - - row = models.OpendaylightMaintenance(state=odl_const.PENDING) - self.db_session.add(row) - self.db_session.flush() - - self.thread = maintenance.MaintenanceThread() - self.thread.maintenance_interval = 0.01 - - def test__execute_op_no_exception(self): - with mock.patch.object(maintenance, 'LOG') as mock_log: - operation = mock.MagicMock() - operation.__name__ = "test" - self.thread._execute_op(operation, self.db_session) - self.assertTrue(operation.called) - self.assertTrue(mock_log.info.called) - self.assertFalse(mock_log.exception.called) - - def test__execute_op_with_exception(self): - with mock.patch.object(maintenance, 'LOG') as mock_log: - operation = mock.MagicMock(side_effect=Exception()) - operation.__name__ = "test" - self.thread._execute_op(operation, self.db_session) - self.assertTrue(mock_log.exception.called) - - def test_thread_works(self): - callback_event = threading.Event() - count = [0] - - def callback_op(**kwargs): - count[0] += 1 - - # The following should be true on the second call, so we're making - # sure that the thread runs more than once. - if count[0] > 1: - callback_event.set() - - self.thread.register_operation(callback_op) - self.thread.start() - - # Make sure the callback event was called and not timed out - self.assertTrue(callback_event.wait(timeout=5)) - - def test_thread_continues_after_exception(self): - exception_event = threading.Event() - callback_event = threading.Event() - - def exception_op(**kwargs): - if not exception_event.is_set(): - exception_event.set() - raise Exception() - - def callback_op(**kwargs): - callback_event.set() - - for op in [exception_op, callback_op]: - self.thread.register_operation(op) - - self.thread.start() - - # Make sure the callback event was called and not timed out - self.assertTrue(callback_event.wait(timeout=5)) diff --git a/networking-odl/networking_odl/tests/unit/l3/test_l3_odl.py b/networking-odl/networking_odl/tests/unit/l3/test_l3_odl.py index 232864d..77a874f 100644 --- a/networking-odl/networking_odl/tests/unit/l3/test_l3_odl.py +++ b/networking-odl/networking_odl/tests/unit/l3/test_l3_odl.py @@ -22,7 +22,6 @@ import copy import mock from neutron.extensions import l3 -from neutron.extensions import l3_ext_gw_mode from neutron.tests.unit.api.v2 import test_base from neutron.tests.unit.extensions import base as test_extensions_base from webob import exc @@ -36,15 +35,11 @@ class Testodll3(test_extensions_base.ExtensionTestCase): def setUp(self): super(Testodll3, self).setUp() - # support ext-gw-mode - for key in l3.RESOURCE_ATTRIBUTE_MAP.keys(): - l3.RESOURCE_ATTRIBUTE_MAP[key].update( - l3_ext_gw_mode.EXTENDED_ATTRIBUTES_2_0.get(key, {})) self._setUpExtension( 'neutron.extensions.l3.RouterPluginBase', None, l3.RESOURCE_ATTRIBUTE_MAP, l3.L3, '', allow_pagination=True, allow_sorting=True, - supported_extension_aliases=['router', 'ext-gw-mode'], + supported_extension_aliases=['router'], use_quota=True) @staticmethod @@ -116,13 +111,13 @@ class Testodll3(test_extensions_base.ExtensionTestCase): content_type='application/%s' % self.fmt) instance.create_router.assert_called_once_with(mock.ANY, router=router) - self.assertEqual(exc.HTTPCreated.code, res.status_int) + self.assertEqual(res.status_int, exc.HTTPCreated.code) res = self.deserialize(res) self.assertIn('router', res) router = res['router'] - self.assertEqual(router_id, router['id']) - self.assertEqual("ACTIVE", router['status']) - self.assertEqual(True, router['admin_state_up']) + self.assertEqual(router['id'], router_id) + self.assertEqual(router['status'], "ACTIVE") + self.assertEqual(router['admin_state_up'], True) def test_update_router(self): router_id, router = self._get_router_test() @@ -144,14 +139,14 @@ class Testodll3(test_extensions_base.ExtensionTestCase): instance.update_router.assert_called_once_with(mock.ANY, router_id, router=router_request) - self.assertEqual(exc.HTTPOk.code, res.status_int) + self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('router', res) router = res['router'] - self.assertEqual(router_id, router['id']) - self.assertEqual("3c5bcddd-6af9-4e6b-9c3e-c153e521cab8", - router["external_gateway_info"]['network_id']) - self.assertEqual(True, router["external_gateway_info"]['enable_snat']) + self.assertEqual(router['id'], router_id) + self.assertEqual(router["external_gateway_info"]['network_id'], + "3c5bcddd-6af9-4e6b-9c3e-c153e521cab8") + self.assertEqual(router["external_gateway_info"]['enable_snat'], True) def test_delete_router(self): router_id, router = self._get_router_test() @@ -161,7 +156,7 @@ class Testodll3(test_extensions_base.ExtensionTestCase): res = self.api.delete(_get_path('routers', id=router_id, fmt=self.fmt)) instance.delete_router.assert_called_once_with(mock.ANY, router_id) - self.assertEqual(exc.HTTPNoContent.code, res.status_int) + self.assertEqual(res.status_int, exc.HTTPNoContent.code) def test_create_floating_ip(self): floating_ip_id, floating_ip = self._get_floating_ip_test() @@ -194,12 +189,12 @@ class Testodll3(test_extensions_base.ExtensionTestCase): assert_called_once_with(mock.ANY, floatingip=floating_ip_request) - self.assertEqual(exc.HTTPCreated.code, res.status_int) + self.assertEqual(res.status_int, exc.HTTPCreated.code) res = self.deserialize(res) self.assertIn('floatingip', res) floatingip = res['floatingip'] - self.assertEqual(floating_ip_id, floatingip['id']) - self.assertEqual("ACTIVE", floatingip['status']) + self.assertEqual(floatingip['id'], floating_ip_id) + self.assertEqual(floatingip['status'], "ACTIVE") def test_update_floating_ip(self): floating_ip_id, floating_ip = self._get_floating_ip_test() @@ -232,13 +227,13 @@ class Testodll3(test_extensions_base.ExtensionTestCase): floating_ip_id, floatingip=floating_ip_request) - self.assertEqual(exc.HTTPOk.code, res.status_int) + self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('floatingip', res) floatingip = res['floatingip'] - self.assertEqual(floating_ip_id, floatingip['id']) - self.assertIsNone(floatingip['port_id']) - self.assertIsNone(floatingip['fixed_ip_address']) + self.assertEqual(floatingip['id'], floating_ip_id) + self.assertEqual(floatingip['port_id'], None) + self.assertEqual(floatingip['fixed_ip_address'], None) def test_delete_floating_ip(self): floating_ip_id, floating_ip = self._get_floating_ip_test() @@ -250,7 +245,7 @@ class Testodll3(test_extensions_base.ExtensionTestCase): instance.delete_floatingip.assert_called_once_with(mock.ANY, floating_ip_id) - self.assertEqual(exc.HTTPNoContent.code, res.status_int) + self.assertEqual(res.status_int, exc.HTTPNoContent.code) def test_add_router_interface(self): router_id, router = self._get_router_test() @@ -274,11 +269,11 @@ class Testodll3(test_extensions_base.ExtensionTestCase): router_id, interface_info) - self.assertEqual(exc.HTTPOk.code, res.status_int) + self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) - self.assertEqual(router_id, res['id']) - self.assertEqual("a2f1f29d-571b-4533-907f-5803ab96ead1", - res['subnet_id']) + self.assertEqual(res['id'], router_id) + self.assertEqual(res['subnet_id'], + "a2f1f29d-571b-4533-907f-5803ab96ead1") def test_remove_router_interface(self): router_id, router = self._get_router_test() @@ -303,8 +298,8 @@ class Testodll3(test_extensions_base.ExtensionTestCase): router_id, interface_info) - self.assertEqual(exc.HTTPOk.code, res.status_int) + self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) - self.assertEqual(router_id, res['id']) - self.assertEqual("a2f1f29d-571b-4533-907f-5803ab96ead1", - res['subnet_id']) + self.assertEqual(res['id'], router_id) + self.assertEqual(res['subnet_id'], + "a2f1f29d-571b-4533-907f-5803ab96ead1") diff --git a/networking-odl/networking_odl/tests/unit/l3/test_l3_odl_v2.py b/networking-odl/networking_odl/tests/unit/l3/test_l3_odl_v2.py index da3f644..4f9061a 100644..100755 --- a/networking-odl/networking_odl/tests/unit/l3/test_l3_odl_v2.py +++ b/networking-odl/networking_odl/tests/unit/l3/test_l3_odl_v2.py @@ -78,7 +78,10 @@ class DataMatcher(object): def __init__(self, operation, object_type, object_dict): self._data = object_dict.copy() self._object_type = object_type - filters.filter_for_odl(object_type, operation, self._data) + filter_cls = filters.FILTER_MAP[object_type] + attr_filter = getattr(filter_cls, + 'filter_' + operation + '_attributes') + attr_filter(self._data) def __eq__(self, s): data = jsonutils.loads(s) @@ -87,9 +90,6 @@ class DataMatcher(object): else: return self._data == data[self._object_type] - def __ne__(self, s): - return not self.__eq__(s) - class OpenDaylightL3TestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase, base.BaseTestCase): diff --git a/networking-odl/networking_odl/tests/unit/ml2/config-ovs-external_ids.sh b/networking-odl/networking_odl/tests/unit/ml2/config-ovs-external_ids.sh deleted file mode 100755 index 15f9b93..0000000 --- a/networking-odl/networking_odl/tests/unit/ml2/config-ovs-external_ids.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/sh -# 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. - -uuid=$(sudo ovs-vsctl get Open_vSwitch . _uuid) - -# Test data -sudo ovs-vsctl set Open_vSwitch $uuid \ - external_ids:odl_os_hostconfig_hostid="devstack" - -# sudo ovs-vsctl set Open_vSwitch $uuid \ -# external_ids:odl_os_hostconfig_hosttype="ODL L2" - -config=$(cat <<____CONFIG -{"supported_vnic_types":[ - {"vnic_type":"normal","vif_type":"ovs","vif_details":{}}], - "allowed_network_types":["local","vlan","vxlan","gre"], - "bridge_mappings":{"physnet1":"br-ex"}} -____CONFIG -) - -echo config: $config - -sudo ovs-vsctl set Open_vSwitch $uuid \ - external_ids:odl_os_hostconfig_config_odl_l2="$config" diff --git a/networking-odl/networking_odl/tests/unit/ml2/odl_teststub.js b/networking-odl/networking_odl/tests/unit/ml2/odl_teststub.js deleted file mode 100644 index 1ee02d5..0000000 --- a/networking-odl/networking_odl/tests/unit/ml2/odl_teststub.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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. - * - * $nodejs odl_teststub.js - * - * local.conf or ml2_conf.ini should be set to the following: - * - * [ml2_odl] - * port_binding_controller = pseudo-agentdb-binding - * password = admin - * username = admin - * url = http://localhost:8080/controller/nb/v2/neutron - * restconf_uri = http://localhost:8125/ # for this stub - * - * To test with ODL *end to end* use below URL for restconf_uri and configure - * ovsdb external_ids using the test script: config-ovs-external_ids.sh - * - * http://localhost:8181/restconf/operational/neutron:neutron/hostconfigs - */ - -var http = require('http'); - -const PORT=8125; - -__test_odl_hconfig = {"hostconfigs": {"hostconfig": [ - {"host-id": "devstack", - "host-type": "ODL L2", - "config": { - "supported_vnic_types": [ - {"vnic_type": "normal", - "vif_type": "ovs", - "vif_details": {}}], - "allowed_network_types": ["local", "vlan", "vxlan", "gre"], - "bridge_mappings": {"physnet1":"br-ex"} - } - }] - }} - - -function handleRequest(req, res){ - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(__test_odl_hconfig)); -} - -var server = http.createServer(handleRequest); - -server.listen(PORT, function(){ - console.log("Server listening on: http://localhost:%s", PORT); - }); diff --git a/networking-odl/networking_odl/tests/unit/ml2/test_driver.py b/networking-odl/networking_odl/tests/unit/ml2/test_driver.py index 661eb55..e2ceda5 100644 --- a/networking-odl/networking_odl/tests/unit/ml2/test_driver.py +++ b/networking-odl/networking_odl/tests/unit/ml2/test_driver.py @@ -32,55 +32,55 @@ class TestODLShim(test_plugin.Ml2PluginV2TestCase): def test_create_network_postcommit(self): self.driver.create_network_postcommit(self.context) - self.driver.odl_drv.synchronize.assert_called_with(const.ODL_CREATE, + self.driver.odl_drv.synchronize.assert_called_with('create', const.ODL_NETWORKS, self.context) def test_update_network_postcommit(self): self.driver.update_network_postcommit(self.context) - self.driver.odl_drv.synchronize.assert_called_with(const.ODL_UPDATE, + self.driver.odl_drv.synchronize.assert_called_with('update', const.ODL_NETWORKS, self.context) def test_delete_network_postcommit(self): self.driver.delete_network_postcommit(self.context) - self.driver.odl_drv.synchronize.assert_called_with(const.ODL_DELETE, + self.driver.odl_drv.synchronize.assert_called_with('delete', const.ODL_NETWORKS, self.context) def test_create_subnet_postcommit(self): self.driver.create_subnet_postcommit(self.context) - self.driver.odl_drv.synchronize.assert_called_with(const.ODL_CREATE, + self.driver.odl_drv.synchronize.assert_called_with('create', const.ODL_SUBNETS, self.context) def test_update_subnet_postcommit(self): self.driver.update_subnet_postcommit(self.context) - self.driver.odl_drv.synchronize.assert_called_with(const.ODL_UPDATE, + self.driver.odl_drv.synchronize.assert_called_with('update', const.ODL_SUBNETS, self.context) def test_delete_subnet_postcommit(self): self.driver.delete_subnet_postcommit(self.context) - self.driver.odl_drv.synchronize.assert_called_with(const.ODL_DELETE, + self.driver.odl_drv.synchronize.assert_called_with('delete', const.ODL_SUBNETS, self.context) def test_create_port_postcommit(self): self.driver.create_port_postcommit(self.context) - self.driver.odl_drv.synchronize.assert_called_with(const.ODL_CREATE, + self.driver.odl_drv.synchronize.assert_called_with('create', const.ODL_PORTS, self.context) def test_update_port_postcommit(self): self.driver.update_port_postcommit(self.context) - self.driver.odl_drv.synchronize.assert_called_with(const.ODL_UPDATE, + self.driver.odl_drv.synchronize.assert_called_with('update', const.ODL_PORTS, self.context) def test_delete_port_postcommit(self): self.driver.delete_port_postcommit(self.context) - self.driver.odl_drv.synchronize.assert_called_with(const.ODL_DELETE, + self.driver.odl_drv.synchronize.assert_called_with('delete', const.ODL_PORTS, self.context) diff --git a/networking-odl/networking_odl/tests/unit/ml2/test_legacy_port_binding.py b/networking-odl/networking_odl/tests/unit/ml2/test_legacy_port_binding.py index 932c961..8ebda22 100644 --- a/networking-odl/networking_odl/tests/unit/ml2/test_legacy_port_binding.py +++ b/networking-odl/networking_odl/tests/unit/ml2/test_legacy_port_binding.py @@ -15,11 +15,10 @@ import mock -from neutron.extensions import portbindings +from neutron.common import constants as n_constants from neutron.plugins.common import constants from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2 import driver_context as ctx -from neutron_lib import constants as n_constants from networking_odl.ml2 import legacy_port_binding from networking_odl.tests import base @@ -74,16 +73,3 @@ class TestLegacyPortBindingManager(base.DietTestCase): port_context.set_binding.assert_called_once_with( self.valid_segment[api.ID], vif_type, mgr.vif_details, status=n_constants.PORT_STATUS_ACTIVE) - - def test_bind_port_unsupported_vnic_type(self): - network = mock.MagicMock(spec=api.NetworkContext) - port_context = mock.MagicMock( - spec=ctx.PortContext, - current={'id': 'CURRENT_CONTEXT_ID', - portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT}, - segments_to_bind=[self.valid_segment, self.invalid_segment], - network=network) - - mgr = legacy_port_binding.LegacyPortBindingManager() - mgr.bind_port(port_context) - port_context.set_binding.assert_not_called() diff --git a/networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl.py b/networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl.py index 95de10c..a012085 100644 --- a/networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl.py +++ b/networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl.py @@ -23,7 +23,7 @@ from oslo_serialization import jsonutils import requests import webob.exc -from neutron.db import segments_db +from neutron.common import constants as n_constants from neutron.extensions import portbindings from neutron.plugins.common import constants from neutron.plugins.ml2 import config as config @@ -33,7 +33,6 @@ from neutron.plugins.ml2 import plugin from neutron.tests import base from neutron.tests.unit.plugins.ml2 import test_plugin from neutron.tests.unit import testlib_api -from neutron_lib import constants as n_constants from networking_odl.common import client from networking_odl.common import constants as odl_const @@ -208,6 +207,12 @@ class OpenDaylightMechanismTestPortsV2(test_plugin.TestMl2PortsV2, expected_status=webob.exc.HTTPConflict.code, expected_error='PortBound') + def test_create_router_port_and_fail_create_postcommit(self): + # Skip this test case for now as a workaround. + # TODO(rzang): remove this once [1] gets in. + # [1]https://review.openstack.org/#/c/310682/ + self.skipTest("skip as a workaround") + class DataMatcher(object): @@ -223,9 +228,6 @@ class DataMatcher(object): data = jsonutils.loads(s) return self._data == data[self._object_type] - def __ne__(self, s): - return not self.__eq__(s) - class OpenDaylightSyncTestCase(OpenDaylightTestCase): @@ -376,8 +378,7 @@ class OpenDaylightMechanismDriverTestCase(base.BaseTestCase): context = self._get_mock_operation_context(object_type) url = '%s/%ss' % (config.cfg.CONF.ml2_odl.url, object_type) kwargs = {'url': url, - 'data': DataMatcher(odl_const.ODL_CREATE, object_type, - context)} + 'data': DataMatcher('create', object_type, context)} self._test_single_operation(method, context, status_code, exc_class, 'post', **kwargs) @@ -388,8 +389,7 @@ class OpenDaylightMechanismDriverTestCase(base.BaseTestCase): url = '%s/%ss/%s' % (config.cfg.CONF.ml2_odl.url, object_type, context.current['id']) kwargs = {'url': url, - 'data': DataMatcher(odl_const.ODL_UPDATE, object_type, - context)} + 'data': DataMatcher('update', object_type, context)} self._test_single_operation(method, context, status_code, exc_class, 'put', **kwargs) @@ -401,20 +401,18 @@ class OpenDaylightMechanismDriverTestCase(base.BaseTestCase): context.current['id']) kwargs = {'url': url, 'data': None} self._test_single_operation(method, context, status_code, exc_class, - odl_const.ODL_DELETE, **kwargs) + 'delete', **kwargs) def test_create_network_postcommit(self): - self._test_create_resource_postcommit(odl_const.ODL_NETWORK, + self._test_create_resource_postcommit('network', requests.codes.created) for status_code in (requests.codes.bad_request, requests.codes.unauthorized): self._test_create_resource_postcommit( - odl_const.ODL_NETWORK, status_code, - requests.exceptions.HTTPError) + 'network', status_code, requests.exceptions.HTTPError) def test_create_subnet_postcommit(self): - self._test_create_resource_postcommit(odl_const.ODL_SUBNET, - requests.codes.created) + self._test_create_resource_postcommit('subnet', requests.codes.created) for status_code in (requests.codes.bad_request, requests.codes.unauthorized, requests.codes.forbidden, @@ -422,12 +420,10 @@ class OpenDaylightMechanismDriverTestCase(base.BaseTestCase): requests.codes.conflict, requests.codes.not_implemented): self._test_create_resource_postcommit( - odl_const.ODL_SUBNET, status_code, - requests.exceptions.HTTPError) + 'subnet', status_code, requests.exceptions.HTTPError) def test_create_port_postcommit(self): - self._test_create_resource_postcommit(odl_const.ODL_PORT, - requests.codes.created) + self._test_create_resource_postcommit('port', requests.codes.created) for status_code in (requests.codes.bad_request, requests.codes.unauthorized, requests.codes.forbidden, @@ -436,34 +432,28 @@ class OpenDaylightMechanismDriverTestCase(base.BaseTestCase): requests.codes.not_implemented, requests.codes.service_unavailable): self._test_create_resource_postcommit( - odl_const.ODL_PORT, status_code, - requests.exceptions.HTTPError) + 'port', status_code, requests.exceptions.HTTPError) def test_update_network_postcommit(self): - self._test_update_resource_postcommit(odl_const.ODL_NETWORK, - requests.codes.ok) + self._test_update_resource_postcommit('network', requests.codes.ok) for status_code in (requests.codes.bad_request, requests.codes.forbidden, requests.codes.not_found): self._test_update_resource_postcommit( - odl_const.ODL_NETWORK, status_code, - requests.exceptions.HTTPError) + 'network', status_code, requests.exceptions.HTTPError) def test_update_subnet_postcommit(self): - self._test_update_resource_postcommit(odl_const.ODL_SUBNET, - requests.codes.ok) + self._test_update_resource_postcommit('subnet', requests.codes.ok) for status_code in (requests.codes.bad_request, requests.codes.unauthorized, requests.codes.forbidden, requests.codes.not_found, requests.codes.not_implemented): self._test_update_resource_postcommit( - odl_const.ODL_SUBNET, status_code, - requests.exceptions.HTTPError) + 'subnet', status_code, requests.exceptions.HTTPError) def test_update_port_postcommit(self): - self._test_update_resource_postcommit(odl_const.ODL_PORT, - requests.codes.ok) + self._test_update_resource_postcommit('port', requests.codes.ok) for status_code in (requests.codes.bad_request, requests.codes.unauthorized, requests.codes.forbidden, @@ -471,55 +461,50 @@ class OpenDaylightMechanismDriverTestCase(base.BaseTestCase): requests.codes.conflict, requests.codes.not_implemented): self._test_update_resource_postcommit( - odl_const.ODL_PORT, status_code, - requests.exceptions.HTTPError) + 'port', status_code, requests.exceptions.HTTPError) def test_delete_network_postcommit(self): - self._test_delete_resource_postcommit(odl_const.ODL_NETWORK, + self._test_delete_resource_postcommit('network', requests.codes.no_content) - self._test_delete_resource_postcommit(odl_const.ODL_NETWORK, + self._test_delete_resource_postcommit('network', requests.codes.not_found) for status_code in (requests.codes.unauthorized, requests.codes.conflict): self._test_delete_resource_postcommit( - odl_const.ODL_NETWORK, status_code, - requests.exceptions.HTTPError) + 'network', status_code, requests.exceptions.HTTPError) def test_delete_subnet_postcommit(self): - self._test_delete_resource_postcommit(odl_const.ODL_SUBNET, + self._test_delete_resource_postcommit('subnet', requests.codes.no_content) - self._test_delete_resource_postcommit(odl_const.ODL_SUBNET, + self._test_delete_resource_postcommit('subnet', requests.codes.not_found) for status_code in (requests.codes.unauthorized, requests.codes.conflict, requests.codes.not_implemented): self._test_delete_resource_postcommit( - odl_const.ODL_SUBNET, status_code, - requests.exceptions.HTTPError) + 'subnet', status_code, requests.exceptions.HTTPError) def test_delete_port_postcommit(self): - self._test_delete_resource_postcommit(odl_const.ODL_PORT, + self._test_delete_resource_postcommit('port', requests.codes.no_content) - self._test_delete_resource_postcommit(odl_const.ODL_PORT, + self._test_delete_resource_postcommit('port', requests.codes.not_found) for status_code in (requests.codes.unauthorized, requests.codes.forbidden, requests.codes.not_implemented): self._test_delete_resource_postcommit( - odl_const.ODL_PORT, status_code, - requests.exceptions.HTTPError) + 'port', status_code, requests.exceptions.HTTPError) def test_port_emtpy_tenant_id_work_around(self): """Validate the work around code of port creation""" plugin = mock.Mock() plugin_context = mock.Mock() - network = self._get_mock_operation_context( - odl_const.ODL_NETWORK).current - port = self._get_mock_operation_context(odl_const.ODL_PORT).current + network = self._get_mock_operation_context('network').current + port = self._get_mock_operation_context('port').current tenant_id = network['tenant_id'] port['tenant_id'] = '' - with mock.patch.object(segments_db, 'get_network_segments'): + with mock.patch.object(driver_context.db, 'get_network_segments'): context = driver_context.PortContext( plugin, plugin_context, port, network, {}, 0, None) self.mech.odl_drv.FILTER_MAP[ @@ -530,16 +515,15 @@ class OpenDaylightMechanismDriverTestCase(base.BaseTestCase): """Validate the filter code on update port operation""" items_to_filter = ['network_id', 'id', 'status', 'tenant_id'] plugin_context = mock.Mock() - network = self._get_mock_operation_context( - odl_const.ODL_NETWORK).current - subnet = self._get_mock_operation_context(odl_const.ODL_SUBNET).current - port = self._get_mock_operation_context(odl_const.ODL_PORT).current + network = self._get_mock_operation_context('network').current + subnet = self._get_mock_operation_context('subnet').current + port = self._get_mock_operation_context('port').current port['fixed_ips'] = [{'subnet_id': subnet['id'], 'ip_address': '10.0.0.10'}] port['mac_address'] = port['mac_address'].upper() orig_port = copy.deepcopy(port) - with mock.patch.object(segments_db, 'get_network_segments'): + with mock.patch.object(driver_context.db, 'get_network_segments'): context = driver_context.PortContext( plugin, plugin_context, port, network, {}, 0, None) self.mech.odl_drv.FILTER_MAP[ diff --git a/networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl_v2.py b/networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl_v2.py index 7e8c7fc..08cf653 100644 --- a/networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl_v2.py +++ b/networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl_v2.py @@ -12,14 +12,12 @@ # 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 datetime +from datetime import timedelta -from networking_odl.common import callback from networking_odl.common import client from networking_odl.common import constants as odl_const from networking_odl.common import filters from networking_odl.db import db -from networking_odl.journal import cleanup from networking_odl.journal import journal from networking_odl.ml2 import mech_driver_v2 @@ -29,7 +27,6 @@ from oslo_serialization import jsonutils import requests from neutron.db import api as neutron_db_api -from neutron import manager from neutron.plugins.ml2 import config as config from neutron.plugins.ml2 import plugin from neutron.tests.unit.plugins.ml2 import test_plugin @@ -37,6 +34,8 @@ from neutron.tests.unit import testlib_api cfg.CONF.import_group('ml2_odl', 'networking_odl.common.config') +HOST = 'fake-host' +PLUGIN_NAME = 'neutron.plugins.ml2.plugin.Ml2Plugin' SECURITY_GROUP = '2f9244b4-9bee-4e81-bc4a-3f3c2045b3d7' SG_FAKE_ID = 'sg_fake_uuid' SG_RULE_FAKE_ID = 'sg_rule_fake_uuid' @@ -124,21 +123,14 @@ class DataMatcher(object): else: self._data = context.current.copy() self._object_type = object_type - filters.filter_for_odl(object_type, operation, self._data) + filter_cls = filters.FILTER_MAP[object_type] + attr_filter = getattr(filter_cls, 'filter_%s_attributes' % operation) + attr_filter(self._data) def __eq__(self, s): data = jsonutils.loads(s) return self._data == data[self._object_type] - def __ne__(self, s): - return not self.__eq__(s) - - -class AttributeDict(dict): - def __init__(self, *args, **kwargs): - super(AttributeDict, self).__init__(*args, **kwargs) - self.__dict__ = self - class OpenDaylightMechanismDriverTestCase(OpenDaylightConfigBase): def setUp(self): @@ -209,19 +201,15 @@ class OpenDaylightMechanismDriverTestCase(OpenDaylightConfigBase): 'binding:vnic_type': 'normal', 'binding:vif_type': 'unbound', 'mac_address': '12:34:56:78:21:b6'} - _network = OpenDaylightMechanismDriverTestCase.\ - _get_mock_network_operation_context().current - _plugin = manager.NeutronManager.get_plugin() - _plugin.get_security_group = mock.Mock(return_value=SECURITY_GROUP) - _plugin.get_port = mock.Mock(return_value=current) - _plugin.get_network = mock.Mock(return_value=_network) - _plugin_context_mock = {'session': neutron_db_api.get_session()} - _network_context_mock = {'_network': _network} - context = {'current': AttributeDict(current), - '_plugin': _plugin, - '_plugin_context': AttributeDict(_plugin_context_mock), - '_network_context': AttributeDict(_network_context_mock)} - return AttributeDict(context) + context = mock.Mock(current=current) + context._plugin.get_security_group = mock.Mock( + return_value=SECURITY_GROUP) + context._plugin.get_port = mock.Mock(return_value=current) + context._plugin_context.session = neutron_db_api.get_session() + context._network_context = mock.Mock( + _network=OpenDaylightMechanismDriverTestCase. + _get_mock_network_operation_context().current) + return context @staticmethod def _get_mock_security_group_operation_context(): @@ -285,9 +273,7 @@ class OpenDaylightMechanismDriverTestCase(OpenDaylightConfigBase): context = self._get_mock_operation_context(object_type) if object_type in [odl_const.ODL_SG, odl_const.ODL_SG_RULE]: - res_type = [rt for rt in callback._RESOURCE_MAPPING.values() - if rt.singular == object_type][0] - self.mech.sync_from_callback(operation, res_type, + self.mech.sync_from_callback(operation, object_type + 's', context[object_type]['id'], context) else: method = getattr(self.mech, '%s_%s_precommit' % (operation, @@ -413,26 +399,6 @@ class OpenDaylightMechanismDriverTestCase(OpenDaylightConfigBase): self._test_object_operation_pending_another_object_operation( parent, odl_const.ODL_DELETE, child, odl_const.ODL_DELETE) - def _test_cleanup_processing_rows(self, last_retried, expected_state): - # Create a dummy network (creates db row in pending state). - self._call_operation_object(odl_const.ODL_CREATE, - odl_const.ODL_NETWORK) - - # Get pending row and mark as processing and update - # the last_retried time - row = db.get_all_db_rows_by_state(self.db_session, - odl_const.PENDING)[0] - row.last_retried = last_retried - db.update_db_row_state(self.db_session, row, odl_const.PROCESSING) - - # Test if the cleanup marks this in the desired state - # based on the last_retried timestamp - cleanup.JournalCleanup().cleanup_processing_rows(self.db_session) - - # Verify that the Db row is in the desired state - rows = db.get_all_db_rows_by_state(self.db_session, expected_state) - self.assertEqual(1, len(rows)) - def test_driver(self): for operation in [odl_const.ODL_CREATE, odl_const.ODL_UPDATE, odl_const.ODL_DELETE]: @@ -440,20 +406,6 @@ class OpenDaylightMechanismDriverTestCase(OpenDaylightConfigBase): odl_const.ODL_PORT]: self._test_operation_object(operation, object_type) - def test_port_precommit_no_tenant(self): - context = self._get_mock_operation_context(odl_const.ODL_PORT) - context.current['tenant_id'] = '' - - method = getattr(self.mech, 'create_port_precommit') - method(context) - - # Verify that the Db row has a tenant - rows = db.get_all_db_rows_by_state(self.db_session, odl_const.PENDING) - self.assertEqual(1, len(rows)) - _network = OpenDaylightMechanismDriverTestCase.\ - _get_mock_network_operation_context().current - self.assertEqual(_network['tenant_id'], rows[0]['data']['tenant_id']) - def test_network(self): self._test_object_type(odl_const.ODL_NETWORK) @@ -523,14 +475,6 @@ class OpenDaylightMechanismDriverTestCase(OpenDaylightConfigBase): def test_port_processing_network(self): self._test_object_type_processing_network(odl_const.ODL_PORT) - def test_cleanup_processing_rows_time_not_expired(self): - self._test_cleanup_processing_rows(datetime.datetime.utcnow(), - odl_const.PROCESSING) - - def test_cleanup_processing_rows_time_expired(self): - old_time = datetime.datetime.utcnow() - datetime.timedelta(hours=24) - self._test_cleanup_processing_rows(old_time, odl_const.PENDING) - def test_thread_call(self): """Verify that the sync thread method is called.""" @@ -549,7 +493,7 @@ class OpenDaylightMechanismDriverTestCase(OpenDaylightConfigBase): self._test_object_type(odl_const.ODL_SG_RULE) def _decrease_row_created_time(self, row): - row.created_at -= datetime.timedelta(hours=1) + row.created_at -= timedelta(hours=1) self.db_session.merge(row) self.db_session.flush() diff --git a/networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py b/networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py index fb83a7b..d342dde 100644 --- a/networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py +++ b/networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py @@ -21,11 +21,11 @@ from oslo_log import log from oslo_serialization import jsonutils import requests +from neutron.common import constants as n_constants from neutron.extensions import portbindings from neutron.plugins.common import constants from neutron.plugins.ml2 import driver_api from neutron.plugins.ml2 import driver_context -from neutron_lib import constants as n_constants from networking_odl.common import cache from networking_odl.ml2 import mech_driver diff --git a/networking-odl/networking_odl/tests/unit/ml2/test_ovsdb_topology.py b/networking-odl/networking_odl/tests/unit/ml2/test_ovsdb_topology.py index 228154d..5502e5a 100644 --- a/networking-odl/networking_odl/tests/unit/ml2/test_ovsdb_topology.py +++ b/networking-odl/networking_odl/tests/unit/ml2/test_ovsdb_topology.py @@ -19,11 +19,11 @@ import mock from oslo_log import log from oslo_serialization import jsonutils +from neutron.common import constants as n_constants from neutron.extensions import portbindings from neutron.plugins.common import constants from neutron.plugins.ml2 import driver_api from neutron.plugins.ml2 import driver_context -from neutron_lib import constants as n_constants from networking_odl.ml2 import ovsdb_topology from networking_odl.tests import base diff --git a/networking-odl/networking_odl/tests/unit/ml2/test_pseudo_agentdb_binding.py b/networking-odl/networking_odl/tests/unit/ml2/test_pseudo_agentdb_binding.py deleted file mode 100644 index d69150c..0000000 --- a/networking-odl/networking_odl/tests/unit/ml2/test_pseudo_agentdb_binding.py +++ /dev/null @@ -1,334 +0,0 @@ -# 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. - -from copy import deepcopy -import mock -from os import path as os_path -from string import Template - -from neutron.extensions import portbindings -from neutron.plugins.common import constants -from neutron.plugins.ml2 import config -from neutron.plugins.ml2 import driver_api as api -from neutron.plugins.ml2 import driver_context as ctx -from neutron_lib import constants as n_const - -from networking_odl.ml2 import pseudo_agentdb_binding -from networking_odl.tests import base - -AGENTDB_BINARY = 'neutron-odlagent-portbinding' -L2_TYPE = "ODL L2" - - -class TestPseudoAgentDBBindingController(base.DietTestCase): - """Test class for AgentDBPortBinding.""" - - # test data hostconfig and hostconfig-dbget - sample_odl_hconfigs = {"hostconfigs": {"hostconfig": [ - {"host-id": "devstack", - "host-type": "ODL L2", - "config": """{"supported_vnic_types": [ - {"vnic_type": "normal", "vif_type": "ovs", - "vif_details": {}}], - "allowed_network_types": [ - "local", "vlan", "vxlan", "gre"], - "bridge_mappings": {"physnet1": "br-ex"}}"""} - ]}} - - # Test data for string interpolation of substitutable identifers - # e.g. $PORT_ID identifier in the configurations JSON string below shall - # be substituted with portcontext.current['id'] eliminating the check - # for specific vif_type making port-binding truly switch agnostic. - # Refer: Python string templates and interpolation (string.Template) - sample_hconf_str_tmpl_subs_vpp = { - "host": "devstack", # host-id in ODL JSON - "agent_type": "ODL L2", # host-type in ODL JSON - # config in ODL JSON - "configurations": """{"supported_vnic_types": [ - {"vnic_type": "normal", "vif_type": "vhostuser", - "vif_details": { - "uuid": "TEST_UUID", - "has_datapath_type_netdev": true, - "support_vhost_user": true, - "port_prefix": "socket_", - "vhostuser_socket_dir": "/tmp", - "vhostuser_ovs_plug": true, - "vhostuser_mode": "server", - "vhostuser_socket": - "/tmp/socket_$PORT_ID" - }}], - "allowed_network_types": [ - "local", "vlan", "vxlan", "gre"], - "bridge_mappings": {"physnet1": "br-ex"}}""" - } - - sample_hconf_str_tmpl_subs_ovs = { - "host": "devstack", # host-id in ODL JSON - "agent_type": "ODL L2", # host-type in ODL JSON - # config in ODL JSON - "configurations": """{"supported_vnic_types": [ - {"vnic_type": "normal", "vif_type": "vhostuser", - "vif_details": { - "uuid": "TEST_UUID", - "has_datapath_type_netdev": true, - "support_vhost_user": true, - "port_prefix": "vhu_", - "vhostuser_socket_dir": "/var/run/openvswitch", - "vhostuser_ovs_plug": true, - "vhostuser_mode": "client", - "vhostuser_socket": - "/var/run/openvswitch/vhu_$PORT_ID" - }}], - "allowed_network_types": [ - "local", "vlan", "vxlan", "gre"], - "bridge_mappings": {"physnet1": "br-ex"}}""" - } - - sample_hconf_str_tmpl_nosubs = { - "host": "devstack", # host-id in ODL JSON - "agent_type": "ODL L2", # host-type in ODL JSON - # config in ODL JSON - "configurations": """{"supported_vnic_types": [ - {"vnic_type": "normal", "vif_type": "ovs", - "vif_details": { - "uuid": "TEST_UUID", - "has_datapath_type_netdev": true, - "support_vhost_user": true, - "port_prefix": "socket_", - "vhostuser_socket_dir": "/tmp", - "vhostuser_ovs_plug": true, - "vhostuser_mode": "server", - "vhostuser_socket": - "/var/run/openvswitch/PORT_NOSUBS" - }}], - "allowed_network_types": [ - "local", "vlan", "vxlan", "gre"], - "bridge_mappings": {"physnet1": "br-ex"}}""" - } - - # Test data for vanilla OVS - sample_hconfig_dbget_ovs = {"configurations": {"supported_vnic_types": [ - {"vnic_type": "normal", "vif_type": portbindings.VIF_TYPE_OVS, - "vif_details": { - "some_test_details": None - }}], - "allowed_network_types": ["local", "vlan", "vxlan", "gre"], - "bridge_mappings": {"physnet1": "br-ex"}}} - - # Test data for OVS-DPDK - sample_hconfig_dbget_ovs_dpdk = {"configurations": { - "supported_vnic_types": [{ - "vnic_type": "normal", - "vif_type": portbindings.VIF_TYPE_VHOST_USER, - "vif_details": { - "uuid": "TEST_UUID", - "has_datapath_type_netdev": True, - "support_vhost_user": True, - "port_prefix": "vhu_", - # Assumption: /var/run mounted as tmpfs - "vhostuser_socket_dir": "/var/run/openvswitch", - "vhostuser_ovs_plug": True, - "vhostuser_mode": "client", - "vhostuser_socket": "/var/run/openvswitch/vhu_$PORT_ID"}}], - "allowed_network_types": ["local", "vlan", "vxlan", "gre"], - "bridge_mappings": {"physnet1": "br-ex"}}} - - # Test data for VPP - sample_hconfig_dbget_vpp = {"configurations": {"supported_vnic_types": [ - {"vnic_type": "normal", "vif_type": portbindings.VIF_TYPE_VHOST_USER, - "vif_details": { - "uuid": "TEST_UUID", - "has_datapath_type_netdev": True, - "support_vhost_user": True, - "port_prefix": "socket_", - "vhostuser_socket_dir": "/tmp", - "vhostuser_ovs_plug": True, - "vhostuser_mode": "server", - "vhostuser_socket": "/tmp/socket_$PORT_ID" - }}], - "allowed_network_types": ["local", "vlan", "vxlan", "gre"], - "bridge_mappings": {"physnet1": "br-ex"}}} - - # test data valid and invalid segments - test_valid_segment = { - api.ID: 'API_ID', - api.NETWORK_TYPE: constants.TYPE_LOCAL, - api.SEGMENTATION_ID: 'API_SEGMENTATION_ID', - api.PHYSICAL_NETWORK: 'API_PHYSICAL_NETWORK'} - - test_invalid_segment = { - api.ID: 'API_ID', - api.NETWORK_TYPE: constants.TYPE_NONE, - api.SEGMENTATION_ID: 'API_SEGMENTATION_ID', - api.PHYSICAL_NETWORK: 'API_PHYSICAL_NETWORK'} - - def setUp(self): - """Setup test.""" - super(TestPseudoAgentDBBindingController, self).setUp() - - config.cfg.CONF.set_override('url', - 'http://localhost:8080' - '/controller/nb/v2/neutron', 'ml2_odl') - - fake_agents_db = mock.MagicMock() - fake_agents_db.create_or_update_agent = mock.MagicMock() - - self.mgr = pseudo_agentdb_binding.PseudoAgentDBBindingController( - db_plugin=fake_agents_db) - - def test_make_hostconf_uri(self): - """test make uri.""" - test_path = '/restconf/neutron:neutron/hostconfigs' - expected = "http://localhost:8080/restconf/neutron:neutron/hostconfigs" - test_uri = self.mgr._make_hostconf_uri(path=test_path) - - self.assertEqual(expected, test_uri) - - def test_update_agents_db(self): - """test agent update.""" - self.mgr._update_agents_db( - hostconfigs=self.sample_odl_hconfigs['hostconfigs']['hostconfig']) - self.mgr.agents_db.create_or_update_agent.assert_called_once() - - def test_is_valid_segment(self): - """Validate the _check_segment method.""" - all_network_types = [constants.TYPE_FLAT, constants.TYPE_GRE, - constants.TYPE_LOCAL, constants.TYPE_VXLAN, - constants.TYPE_VLAN, constants.TYPE_NONE] - - valid_types = { - network_type - for network_type in all_network_types - if self.mgr._is_valid_segment({api.NETWORK_TYPE: network_type}, { - 'allowed_network_types': [ - constants.TYPE_LOCAL, constants.TYPE_GRE, - constants.TYPE_VXLAN, constants.TYPE_VLAN]})} - - self.assertEqual({ - constants.TYPE_LOCAL, constants.TYPE_GRE, constants.TYPE_VXLAN, - constants.TYPE_VLAN}, valid_types) - - def test_bind_port_with_vif_type_ovs(self): - """test bind_port with vanilla ovs.""" - port_context = self._fake_port_context( - fake_segments=[self.test_invalid_segment, self.test_valid_segment]) - - vif_type = portbindings.VIF_TYPE_OVS - vif_details = {'some_test_details': None} - - self.mgr._hconfig_bind_port( - port_context, self.sample_hconfig_dbget_ovs) - - port_context.set_binding.assert_called_once_with( - self.test_valid_segment[api.ID], vif_type, - vif_details, status=n_const.PORT_STATUS_ACTIVE) - - def _set_pass_vif_details(self, port_context, vif_details): - """extract vif_details and update vif_details if needed.""" - vhostuser_socket_dir = vif_details.get( - 'vhostuser_socket_dir', '/var/run/openvswitch') - port_spec = vif_details.get( - 'port_prefix', 'vhu_') + port_context.current['id'] - socket_path = os_path.join(vhostuser_socket_dir, port_spec) - vif_details.update({portbindings.VHOST_USER_SOCKET: socket_path}) - - return vif_details - - def test_bind_port_with_vif_type_vhost_user(self): - """test bind_port with ovs-dpdk.""" - port_context = self._fake_port_context( - fake_segments=[self.test_invalid_segment, self.test_valid_segment], - host_agents=[deepcopy(self.sample_hconf_str_tmpl_subs_ovs)]) - - self.mgr.bind_port(port_context) - - pass_vif_type = portbindings.VIF_TYPE_VHOST_USER - pass_vif_details = self.sample_hconfig_dbget_ovs_dpdk[ - 'configurations']['supported_vnic_types'][0]['vif_details'] - self._set_pass_vif_details(port_context, pass_vif_details) - - port_context.set_binding.assert_called_once_with( - self.test_valid_segment[api.ID], pass_vif_type, - pass_vif_details, status=n_const.PORT_STATUS_ACTIVE) - - def test_bind_port_with_vif_type_vhost_user_vpp(self): - """test bind_port with vpp.""" - port_context = self._fake_port_context( - fake_segments=[self.test_invalid_segment, self.test_valid_segment], - host_agents=[deepcopy(self.sample_hconf_str_tmpl_subs_vpp)]) - - self.mgr.bind_port(port_context) - - pass_vif_type = portbindings.VIF_TYPE_VHOST_USER - pass_vif_details = self.sample_hconfig_dbget_vpp['configurations'][ - 'supported_vnic_types'][0]['vif_details'] - self._set_pass_vif_details(port_context, pass_vif_details) - - port_context.set_binding.assert_called_once_with( - self.test_valid_segment[api.ID], pass_vif_type, - pass_vif_details, status=n_const.PORT_STATUS_ACTIVE) - - def test_bind_port_without_valid_segment(self): - """test bind_port without a valid segment.""" - port_context = self._fake_port_context( - fake_segments=[self.test_invalid_segment]) - - self.mgr._hconfig_bind_port( - port_context, self.sample_hconfig_dbget_ovs) - - port_context.set_binding.assert_not_called() - - def test_no_str_template_substitution_in_configuration_string(self): - """Test for no identifier substituion in config JSON string.""" - port_context = self._fake_port_context( - fake_segments=[self.test_invalid_segment, self.test_valid_segment]) - - hconf_dict = self.mgr._substitute_hconfig_tmpl( - port_context, self.sample_hconf_str_tmpl_nosubs) - - test_string = hconf_dict['configurations'][ - 'supported_vnic_types'][0][ - 'vif_details'][portbindings.VHOST_USER_SOCKET] - - expected_str = '/var/run/openvswitch/PORT_NOSUBS' - - self.assertEqual(expected_str, test_string) - - def test_str_template_substitution_in_configuration_string(self): - """Test for identifier substitution in config JSON string.""" - port_context = self._fake_port_context( - fake_segments=[self.test_invalid_segment, self.test_valid_segment]) - - hconf_dict = self.mgr._substitute_hconfig_tmpl( - port_context, self.sample_hconf_str_tmpl_subs_vpp) - - test_string = hconf_dict['configurations'][ - 'supported_vnic_types'][0][ - 'vif_details'][portbindings.VHOST_USER_SOCKET] - - expected_str = Template('/tmp/socket_$PORT_ID') - expected_str = expected_str.safe_substitute({ - 'PORT_ID': port_context.current['id']}) - - self.assertEqual(expected_str, test_string) - - def _fake_port_context(self, fake_segments, host_agents=None): - network = mock.MagicMock(spec=api.NetworkContext) - return mock.MagicMock( - spec=ctx.PortContext, - current={'id': 'CONTEXT_ID', - portbindings.VNIC_TYPE: portbindings.VNIC_NORMAL}, - segments_to_bind=fake_segments, network=network, - host_agents=lambda agent_type: host_agents) |