summaryrefslogtreecommitdiffstats
path: root/networking-odl/networking_odl
diff options
context:
space:
mode:
Diffstat (limited to 'networking-odl/networking_odl')
-rw-r--r--networking-odl/networking_odl/cmd/__init__.py0
-rw-r--r--networking-odl/networking_odl/cmd/set_ovs_hostconfigs.py123
-rwxr-xr-xnetworking-odl/networking_odl/cmd/test_setup_hostconfig.sh3
-rw-r--r--networking-odl/networking_odl/common/cache.py3
-rw-r--r--networking-odl/networking_odl/common/callback.py94
-rw-r--r--networking-odl/networking_odl/common/client.py6
-rw-r--r--networking-odl/networking_odl/common/config.py24
-rw-r--r--networking-odl/networking_odl/common/constants.py2
-rw-r--r--networking-odl/networking_odl/common/exceptions.py2
-rw-r--r--networking-odl/networking_odl/common/filters.py227
-rw-r--r--networking-odl/networking_odl/common/lightweight_testing.py5
-rw-r--r--networking-odl/networking_odl/db/db.py71
-rw-r--r--networking-odl/networking_odl/db/migration/alembic_migrations/versions/EXPAND_HEAD2
-rw-r--r--networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/contract/383acb0d38a0_initial_contract.py4
-rw-r--r--networking-odl/networking_odl/db/migration/alembic_migrations/versions/mitaka/expand/37e242787ae5_opendaylight_neutron_mechanism_driver_.py10
-rw-r--r--networking-odl/networking_odl/db/migration/alembic_migrations/versions/newton/expand/703dbf02afde_add_journal_maintenance_table.py52
-rw-r--r--networking-odl/networking_odl/db/models.py11
-rw-r--r--networking-odl/networking_odl/journal/cleanup.py46
-rw-r--r--networking-odl/networking_odl/journal/dependency_validations.py22
-rw-r--r--networking-odl/networking_odl/journal/full_sync.py114
-rw-r--r--networking-odl/networking_odl/journal/journal.py67
-rw-r--r--networking-odl/networking_odl/journal/maintenance.py73
-rw-r--r--networking-odl/networking_odl/l3/l3_odl.py2
-rwxr-xr-x[-rw-r--r--]networking-odl/networking_odl/l3/l3_odl_v2.py2
-rw-r--r--networking-odl/networking_odl/lbaas/driver_v1.py58
-rw-r--r--networking-odl/networking_odl/ml2/legacy_port_binding.py9
-rw-r--r--networking-odl/networking_odl/ml2/mech_driver.py106
-rw-r--r--networking-odl/networking_odl/ml2/mech_driver_v2.py117
-rw-r--r--networking-odl/networking_odl/ml2/network_topology.py23
-rw-r--r--networking-odl/networking_odl/ml2/ovsdb_topology.py6
-rw-r--r--networking-odl/networking_odl/ml2/pseudo_agentdb_binding.py263
-rw-r--r--networking-odl/networking_odl/ml2/vpp_ml2.tarbin0 -> 24064 bytes
-rw-r--r--networking-odl/networking_odl/ml2/vpp_topology.py194
-rw-r--r--networking-odl/networking_odl/tests/unit/common/test_callback.py136
-rw-r--r--networking-odl/networking_odl/tests/unit/common/test_lightweight_testing.py6
-rw-r--r--networking-odl/networking_odl/tests/unit/db/test_db.py63
-rw-r--r--networking-odl/networking_odl/tests/unit/journal/__init__.py0
-rw-r--r--networking-odl/networking_odl/tests/unit/journal/test_dependency_validations.py44
-rw-r--r--networking-odl/networking_odl/tests/unit/journal/test_full_sync.py152
-rw-r--r--networking-odl/networking_odl/tests/unit/journal/test_maintenance.py93
-rw-r--r--networking-odl/networking_odl/tests/unit/l3/test_l3_odl.py59
-rwxr-xr-x[-rw-r--r--]networking-odl/networking_odl/tests/unit/l3/test_l3_odl_v2.py8
-rwxr-xr-xnetworking-odl/networking_odl/tests/unit/ml2/config-ovs-external_ids.sh37
-rw-r--r--networking-odl/networking_odl/tests/unit/ml2/odl_teststub.js62
-rw-r--r--networking-odl/networking_odl/tests/unit/ml2/test_driver.py18
-rw-r--r--networking-odl/networking_odl/tests/unit/ml2/test_legacy_port_binding.py16
-rw-r--r--networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl.py92
-rw-r--r--networking-odl/networking_odl/tests/unit/ml2/test_mechanism_odl_v2.py90
-rw-r--r--networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py2
-rw-r--r--networking-odl/networking_odl/tests/unit/ml2/test_ovsdb_topology.py2
-rw-r--r--networking-odl/networking_odl/tests/unit/ml2/test_pseudo_agentdb_binding.py334
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
new file mode 100644
index 0000000..e181208
--- /dev/null
+++ b/networking-odl/networking_odl/ml2/vpp_ml2.tar
Binary files differ
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)