From 8c77175ee91dba03b05f020373e29b43e89f2bad Mon Sep 17 00:00:00 2001
From: Shih-Hao Li <shihli@vmware.com>
Date: Thu, 12 Jan 2017 09:28:56 -0800
Subject: [PATCH] NSXV+NSXV3: add support for pluggable extensions

A new configuration variable nsx_extension_drivers
has been added. This is in the DEFAULT section. This enable us
to code support to add via configurations extensions, for
example dns_integration.

Co-authored-by: Shih-Hao Li <shihli@vmware.com>

Change-Id: Iea4715522d9c7cf327b7f1a751b78f14d5e06e75
---
 ...sx-extension-drivers-b1aedabe5296d4d0.yaml |   7 +
 vmware_nsx/common/config.py                   |   5 +
 vmware_nsx/common/driver_api.py               | 157 ++++++++++++++++++
 vmware_nsx/common/managers.py                 | 134 +++++++++++++++
 vmware_nsx/extension_drivers/__init__.py      |  16 ++
 vmware_nsx/plugins/nsx_v/plugin.py            |  55 +++++-
 vmware_nsx/plugins/nsx_v3/plugin.py           |  53 +++++-
 7 files changed, 419 insertions(+), 8 deletions(-)
 create mode 100644 releasenotes/notes/nsx-extension-drivers-b1aedabe5296d4d0.yaml
 create mode 100644 vmware_nsx/common/driver_api.py
 create mode 100644 vmware_nsx/common/managers.py
 create mode 100644 vmware_nsx/extension_drivers/__init__.py

diff --git a/releasenotes/notes/nsx-extension-drivers-b1aedabe5296d4d0.yaml b/releasenotes/notes/nsx-extension-drivers-b1aedabe5296d4d0.yaml
new file mode 100644
index 0000000000..c8157477c3
--- /dev/null
+++ b/releasenotes/notes/nsx-extension-drivers-b1aedabe5296d4d0.yaml
@@ -0,0 +1,7 @@
+---
+prelude: >
+    We have added a new configuration variable that will enable us to
+    enable existing extensions. The new configuration variable is
+    ``nsx_extension_drivers``. This is in the default section.
+    This is a list of extansion names. The code for the drivers
+    must be in the directory vmware_nsx.extension_drivers.
diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py
index e83eefc75d..c0ff316ef6 100644
--- a/vmware_nsx/common/config.py
+++ b/vmware_nsx/common/config.py
@@ -245,6 +245,11 @@ nsx_common_opts = [
                        "specify the id of resources. This should only "
                        "be enabled in order to allow one to migrate an "
                        "existing install of neutron to the nsx-v3 plugin.")),
+    cfg.ListOpt('nsx_extension_drivers',
+                default=[],
+                help=_("An ordered list of extension driver "
+                       "entrypoints to be loaded from the "
+                       "vmware_nsx.extension_drivers namespace.")),
 ]
 
 nsx_v3_opts = [
diff --git a/vmware_nsx/common/driver_api.py b/vmware_nsx/common/driver_api.py
new file mode 100644
index 0000000000..c5f9c50143
--- /dev/null
+++ b/vmware_nsx/common/driver_api.py
@@ -0,0 +1,157 @@
+# Copyright (c) 2013 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 abc
+
+import six
+
+
+@six.add_metaclass(abc.ABCMeta)
+class ExtensionDriver(object):
+    """Define stable abstract interface for extension drivers.
+    An extension driver extends the core resources implemented by the
+    plugin with additional attributes. Methods that process create
+    and update operations for these resources validate and persist
+    values for extended attributes supplied through the API. Other
+    methods extend the resource dictionaries returned from the API
+    operations with the values of the extended attributes.
+    """
+
+    @abc.abstractmethod
+    def initialize(self):
+        """Perform driver initialization.
+        Called after all drivers have been loaded and the database has
+        been initialized. No abstract methods defined below will be
+        called prior to this method being called.
+        """
+        pass
+
+    @property
+    def extension_alias(self):
+        """Supported extension alias.
+        Return the alias identifying the core API extension supported
+        by this driver. Do not declare if API extension handling will
+        be left to a service plugin, and we just need to provide
+        core resource extension and updates.
+        """
+        pass
+
+    def process_create_network(self, plugin_context, data, result):
+        """Process extended attributes for create network.
+        :param plugin_context: plugin request context
+        :param data: dictionary of incoming network data
+        :param result: network dictionary to extend
+        Called inside transaction context on plugin_context.session to
+        validate and persist any extended network attributes defined by this
+        driver. Extended attribute values must also be added to
+        result.
+        """
+        pass
+
+    def process_create_subnet(self, plugin_context, data, result):
+        """Process extended attributes for create subnet.
+        :param plugin_context: plugin request context
+        :param data: dictionary of incoming subnet data
+        :param result: subnet dictionary to extend
+        Called inside transaction context on plugin_context.session to
+        validate and persist any extended subnet attributes defined by this
+        driver. Extended attribute values must also be added to
+        result.
+        """
+        pass
+
+    def process_create_port(self, plugin_context, data, result):
+        """Process extended attributes for create port.
+        :param plugin_context: plugin request context
+        :param data: dictionary of incoming port data
+        :param result: port dictionary to extend
+        Called inside transaction context on plugin_context.session to
+        validate and persist any extended port attributes defined by this
+        driver. Extended attribute values must also be added to
+        result.
+        """
+        pass
+
+    def process_update_network(self, plugin_context, data, result):
+        """Process extended attributes for update network.
+        :param plugin_context: plugin request context
+        :param data: dictionary of incoming network data
+        :param result: network dictionary to extend
+        Called inside transaction context on plugin_context.session to
+        validate and update any extended network attributes defined by this
+        driver. Extended attribute values, whether updated or not,
+        must also be added to result.
+        """
+        pass
+
+    def process_update_subnet(self, plugin_context, data, result):
+        """Process extended attributes for update subnet.
+        :param plugin_context: plugin request context
+        :param data: dictionary of incoming subnet data
+        :param result: subnet dictionary to extend
+        Called inside transaction context on plugin_context.session to
+        validate and update any extended subnet attributes defined by this
+        driver. Extended attribute values, whether updated or not,
+        must also be added to result.
+        """
+        pass
+
+    def process_update_port(self, plugin_context, data, result):
+        """Process extended attributes for update port.
+        :param plugin_context: plugin request context
+        :param data: dictionary of incoming port data
+        :param result: port dictionary to extend
+        Called inside transaction context on plugin_context.session to
+        validate and update any extended port attributes defined by this
+        driver. Extended attribute values, whether updated or not,
+        must also be added to result.
+        """
+        pass
+
+    def extend_network_dict(self, session, base_model, result):
+        """Add extended attributes to network dictionary.
+        :param session: database session
+        :param base_model: network model data
+        :param result: network dictionary to extend
+        Called inside transaction context on session to add any
+        extended attributes defined by this driver to a network
+        dictionary to be used for driver calls and/or
+        returned as the result of a network operation.
+        """
+        pass
+
+    def extend_subnet_dict(self, session, base_model, result):
+        """Add extended attributes to subnet dictionary.
+        :param session: database session
+        :param base_model: subnet model data
+        :param result: subnet dictionary to extend
+        Called inside transaction context on session to add any
+        extended attributes defined by this driver to a subnet
+        dictionary to be used for driver calls and/or
+        returned as the result of a subnet operation.
+        """
+        pass
+
+    def extend_port_dict(self, session, base_model, result):
+        """Add extended attributes to port dictionary.
+        :param session: database session
+        :param base_model: port model data
+        :param result: port dictionary to extend
+        Called inside transaction context on session to add any
+        extended attributes defined by this driver to a port
+        dictionary to be used for driver calls
+        and/or returned as the result of a port operation.
+        """
+        pass
diff --git a/vmware_nsx/common/managers.py b/vmware_nsx/common/managers.py
new file mode 100644
index 0000000000..f9139349d9
--- /dev/null
+++ b/vmware_nsx/common/managers.py
@@ -0,0 +1,134 @@
+# Copyright (c) 2013 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_utils import excutils
+import stevedore
+
+from vmware_nsx._i18n import _LE, _LI
+
+LOG = log.getLogger(__name__)
+
+
+class ExtensionManager(stevedore.named.NamedExtensionManager):
+    """Manage extension drivers using drivers."""
+
+    def __init__(self):
+        # Ordered list of extension drivers, defining
+        # the order in which the drivers are called.
+        self.ordered_ext_drivers = []
+
+        LOG.info(_LI("Configured extension driver names: %s"),
+                 cfg.CONF.nsx_extension_drivers)
+        super(ExtensionManager, self).__init__('vmware_nsx.extension_drivers',
+                                               cfg.CONF.nsx_extension_drivers,
+                                               invoke_on_load=True,
+                                               name_order=True)
+        LOG.info(_LI("Loaded extension driver names: %s"), self.names())
+        self._register_drivers()
+
+    def _register_drivers(self):
+        """Register all extension drivers.
+
+        This method should only be called once in the ExtensionManager
+        constructor.
+        """
+        for ext in self:
+            self.ordered_ext_drivers.append(ext)
+        LOG.info(_LI("Registered extension drivers: %s"),
+                 [driver.name for driver in self.ordered_ext_drivers])
+
+    def initialize(self):
+        # Initialize each driver in the list.
+        for driver in self.ordered_ext_drivers:
+            LOG.info(_LI("Initializing extension driver '%s'"), driver.name)
+            driver.obj.initialize()
+
+    def extension_aliases(self):
+        exts = []
+        for driver in self.ordered_ext_drivers:
+            alias = driver.obj.extension_alias
+            if alias:
+                exts.append(alias)
+                LOG.info(_LI("Got %(alias)s extension from driver '%(drv)s'"),
+                         {'alias': alias, 'drv': driver.name})
+        return exts
+
+    def _call_on_ext_drivers(self, method_name, plugin_context, data, result):
+        """Helper method for calling a method across all extension drivers."""
+        for driver in self.ordered_ext_drivers:
+            try:
+                getattr(driver.obj, method_name)(plugin_context, data, result)
+            except Exception:
+                with excutils.save_and_reraise_exception():
+                    LOG.info(_LI("Extension driver '%(name)s' failed in "
+                             "%(method)s"),
+                             {'name': driver.name, 'method': method_name})
+
+    def process_create_network(self, plugin_context, data, result):
+        """Notify all extension drivers during network creation."""
+        self._call_on_ext_drivers("process_create_network", plugin_context,
+                                  data, result)
+
+    def process_update_network(self, plugin_context, data, result):
+        """Notify all extension drivers during network update."""
+        self._call_on_ext_drivers("process_update_network", plugin_context,
+                                  data, result)
+
+    def process_create_subnet(self, plugin_context, data, result):
+        """Notify all extension drivers during subnet creation."""
+        self._call_on_ext_drivers("process_create_subnet", plugin_context,
+                                  data, result)
+
+    def process_update_subnet(self, plugin_context, data, result):
+        """Notify all extension drivers during subnet update."""
+        self._call_on_ext_drivers("process_update_subnet", plugin_context,
+                                  data, result)
+
+    def process_create_port(self, plugin_context, data, result):
+        """Notify all extension drivers during port creation."""
+        self._call_on_ext_drivers("process_create_port", plugin_context,
+                                  data, result)
+
+    def process_update_port(self, plugin_context, data, result):
+        """Notify all extension drivers during port update."""
+        self._call_on_ext_drivers("process_update_port", plugin_context,
+                                  data, result)
+
+    def _call_on_dict_driver(self, method_name, session, base_model, result):
+        for driver in self.ordered_ext_drivers:
+            try:
+                getattr(driver.obj, method_name)(session, base_model, result)
+            except Exception:
+                LOG.error(_LE("Extension driver '%(name)s' failed in "
+                          "%(method)s"),
+                          {'name': driver.name, 'method': method_name})
+                raise
+
+    def extend_network_dict(self, session, base_model, result):
+        """Notify all extension drivers to extend network dictionary."""
+        self._call_on_dict_driver("extend_network_dict", session, base_model,
+                                  result)
+
+    def extend_subnet_dict(self, session, base_model, result):
+        """Notify all extension drivers to extend subnet dictionary."""
+        self._call_on_dict_driver("extend_subnet_dict", session, base_model,
+                                  result)
+
+    def extend_port_dict(self, session, base_model, result):
+        """Notify all extension drivers to extend port dictionary."""
+        self._call_on_dict_driver("extend_port_dict", session, base_model,
+                                  result)
diff --git a/vmware_nsx/extension_drivers/__init__.py b/vmware_nsx/extension_drivers/__init__.py
new file mode 100644
index 0000000000..9722701001
--- /dev/null
+++ b/vmware_nsx/extension_drivers/__init__.py
@@ -0,0 +1,16 @@
+#  Licensed under the Apache License, Version 2.0 (the "License"); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+
+import os
+
+NSX_EXT_PATH = os.path.join(os.path.dirname(__file__), 'extensions')
diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py
index 90830203e5..744df2d523 100644
--- a/vmware_nsx/plugins/nsx_v/plugin.py
+++ b/vmware_nsx/plugins/nsx_v/plugin.py
@@ -85,6 +85,7 @@ from vmware_nsx._i18n import _, _LE, _LI, _LW
 from vmware_nsx.common import config  # noqa
 from vmware_nsx.common import exceptions as nsx_exc
 from vmware_nsx.common import locking
+from vmware_nsx.common import managers as nsx_managers
 from vmware_nsx.common import nsx_constants
 from vmware_nsx.common import nsxv_constants
 from vmware_nsx.common import utils as c_utils
@@ -186,11 +187,15 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         router=l3_db_models.Router,
         floatingip=l3_db_models.FloatingIP)
     def __init__(self):
+        self._extension_manager = nsx_managers.ExtensionManager()
         super(NsxVPluginV2, self).__init__()
         self.init_is_complete = False
         registry.subscribe(self.init_complete,
                            resources.PROCESS,
                            events.AFTER_INIT)
+        self._extension_manager.initialize()
+        self.supported_extension_aliases.extend(
+            self._extension_manager.extension_aliases())
         self.metadata_proxy_handler = None
         config.validate_nsxv_config_options()
         neutron_extensions.append_api_extensions_path(
@@ -266,6 +271,16 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         if c_utils.is_nsxv_version_6_2(self.nsx_v.vcns.get_version()):
             self.supported_extension_aliases.append("provider-security-group")
 
+    # Register extend dict methods for network and port resources.
+    # Each extension driver that supports extend attribute for the resources
+    # can add those attribute to the result.
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+        attr.NETWORKS, ['_ext_extend_network_dict'])
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+        attr.PORTS, ['_ext_extend_port_dict'])
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+        attr.SUBNETS, ['_ext_extend_subnet_dict'])
+
     def init_complete(self, resource, event, trigger, **kwargs):
         has_metadata_cfg = (
             cfg.CONF.nsxv.nova_metadata_ips
@@ -334,6 +349,22 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         self.start_rpc_listeners_called = True
         return self.conn.consume_in_threads()
 
+    def _ext_extend_network_dict(self, result, netdb):
+        session = db_api.get_session()
+        with session.begin(subtransactions=True):
+            self._extension_manager.extend_network_dict(session, netdb, result)
+
+    def _ext_extend_port_dict(self, result, portdb):
+        session = db_api.get_session()
+        with session.begin(subtransactions=True):
+            self._extension_manager.extend_port_dict(session, portdb, result)
+
+    def _ext_extend_subnet_dict(self, result, subnetdb):
+        session = db_api.get_session()
+        with session.begin(subtransactions=True):
+            self._extension_manager.extend_subnet_dict(
+                session, subnetdb, result)
+
     def _create_security_group_container(self):
         name = "OpenStack Security Group container"
         with locking.LockManager.get_lock('security-group-container-init'):
@@ -1006,6 +1037,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
             with context.session.begin(subtransactions=True):
                 new_net = super(NsxVPluginV2, self).create_network(context,
                                                                    network)
+                self._extension_manager.process_create_network(
+                    context, net_data, new_net)
                 # Process port security extension
                 self._process_network_port_security_create(
                     context, net_data, new_net)
@@ -1283,6 +1316,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         with context.session.begin(subtransactions=True):
             net_res = super(NsxVPluginV2, self).update_network(context, id,
                                                                network)
+            self._extension_manager.process_update_network(context, net_attrs,
+                                                           net_res)
             self._process_network_port_security_update(
                 context, net_attrs, net_res)
             self._process_l3_update(context, net_res, net_attrs)
@@ -1342,13 +1377,18 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
 
     @db_api.retry_db_errors
     def base_create_port(self, context, port):
-        return super(NsxVPluginV2, self).create_port(context, port)
+        created_port = super(NsxVPluginV2, self).create_port(context, port)
+        self._extension_manager.process_create_port(
+            context, port['port'], created_port)
+        return created_port
 
     def create_port(self, context, port):
         port_data = port['port']
         with context.session.begin(subtransactions=True):
             # First we allocate port in neutron database
             neutron_db = super(NsxVPluginV2, self).create_port(context, port)
+            self._extension_manager.process_create_port(
+                context, port_data, neutron_db)
             # Port port-security is decided by the port-security state on the
             # network it belongs to, unless specifically specified here
             if validators.is_attr_set(port_data.get(psec.PORTSECURITY)):
@@ -1591,6 +1631,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         with context.session.begin(subtransactions=True):
             ret_port = super(NsxVPluginV2, self).update_port(
                 context, id, port)
+            self._extension_manager.process_update_port(
+                context, port_data, ret_port)
             # copy values over - except fixed_ips as
             # they've already been processed
             updates_fixed_ips = port['port'].pop('fixed_ips', [])
@@ -1994,6 +2036,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         with locking.LockManager.get_lock(subnet['subnet']['network_id']):
             with locking.LockManager.get_lock('nsx-edge-pool'):
                 s = super(NsxVPluginV2, self).create_subnet(context, subnet)
+            self._extension_manager.process_create_subnet(
+                context, subnet['subnet'], s)
             if s['enable_dhcp']:
                 try:
                     self._process_subnet_ext_attr_create(
@@ -2075,6 +2119,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                                          orig_enable_dhcp=enable_dhcp,
                                          orig_host_routes=orig_host_routes)
         subnet = super(NsxVPluginV2, self).update_subnet(context, id, subnet)
+        self._extension_manager.process_update_subnet(context, s, subnet)
         update_dhcp_config = self._process_subnet_ext_attr_update(
             context.session, subnet, s)
         if (gateway_ip != subnet['gateway_ip'] or update_dhcp_config or
@@ -2715,9 +2760,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         since the actual backend work was already done by the router driver,
         and it may cause a deadlock.
         """
-        super(NsxVPluginV2, self).update_port(context,
-                                              router.gw_port['id'],
-                                              {'port': {'fixed_ips': ext_ips}})
+        port_data = {'fixed_ips': ext_ips}
+        updated_port = super(NsxVPluginV2, self).update_port(
+            context, router.gw_port['id'], {'port': port_data})
+        self._extension_manager.process_update_port(
+            context, port_data, updated_port)
         context.session.expire(router.gw_port)
 
     def _update_router_gw_info(self, context, router_id, info,
diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py
index cc9c5c006a..4e77609b44 100644
--- a/vmware_nsx/plugins/nsx_v3/plugin.py
+++ b/vmware_nsx/plugins/nsx_v3/plugin.py
@@ -22,6 +22,7 @@ from neutron.api.rpc.callbacks import resources as callbacks_resources
 from neutron.api.rpc.handlers import dhcp_rpc
 from neutron.api.rpc.handlers import metadata_rpc
 from neutron.api.rpc.handlers import resources_rpc
+from neutron.api.v2 import attributes
 from neutron.callbacks import events
 from neutron.callbacks import exceptions as callback_exc
 from neutron.callbacks import registry
@@ -33,6 +34,7 @@ from neutron.db import _utils as db_utils
 from neutron.db import agents_db
 from neutron.db import agentschedulers_db
 from neutron.db import allowedaddresspairs_db as addr_pair_db
+from neutron.db import api as db_api
 from neutron.db import db_base_plugin_v2
 from neutron.db import dns_db
 from neutron.db import external_net_db
@@ -77,6 +79,7 @@ from vmware_nsx.api_replay import utils as api_replay_utils
 from vmware_nsx.common import config  # noqa
 from vmware_nsx.common import exceptions as nsx_exc
 from vmware_nsx.common import locking
+from vmware_nsx.common import managers
 from vmware_nsx.common import utils
 from vmware_nsx.db import db as nsx_db
 from vmware_nsx.db import extended_security_group
@@ -166,9 +169,12 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
         router=l3_db_models.Router,
         floatingip=l3_db_models.FloatingIP)
     def __init__(self):
+        self._extension_manager = managers.ExtensionManager()
         super(NsxV3Plugin, self).__init__()
         LOG.info(_LI("Starting NsxV3Plugin"))
-
+        self._extension_manager.initialize()
+        self.supported_extension_aliases.extend(
+            self._extension_manager.extension_aliases())
         self.nsxlib = v3_utils.get_nsxlib_wrapper()
         # reinitialize the cluster upon fork for api workers to ensure each
         # process has its own keepalive loops + state
@@ -224,6 +230,16 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
         # Register NSXv3 trunk driver to support trunk extensions
         self.trunk_driver = trunk_driver.NsxV3TrunkDriver.create(self)
 
+    # Register extend dict methods for network and port resources.
+    # Each extension driver that supports extend attribute for the resources
+    # can add those attribute to the result.
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+        attributes.NETWORKS, ['_ext_extend_network_dict'])
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+        attributes.PORTS, ['_ext_extend_port_dict'])
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+        attributes.SUBNETS, ['_ext_extend_subnet_dict'])
+
     def _init_nsx_profiles(self):
         LOG.debug("Initializing NSX v3 port spoofguard switching profile")
         if not self._init_port_security_profile():
@@ -538,6 +554,22 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
 
         return self.conn.consume_in_threads()
 
+    def _ext_extend_network_dict(self, result, netdb):
+        session = db_api.get_session()
+        with session.begin(subtransactions=True):
+            self._extension_manager.extend_network_dict(session, netdb, result)
+
+    def _ext_extend_port_dict(self, result, portdb):
+        session = db_api.get_session()
+        with session.begin(subtransactions=True):
+            self._extension_manager.extend_port_dict(session, portdb, result)
+
+    def _ext_extend_subnet_dict(self, result, subnetdb):
+        session = db_api.get_session()
+        with session.begin(subtransactions=True):
+            self._extension_manager.extend_subnet_dict(
+                session, subnetdb, result)
+
     def _validate_provider_create(self, context, network_data):
         is_provider_net = any(
             validators.is_attr_set(network_data.get(f))
@@ -726,7 +758,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
                 # Create network in Neutron
                 created_net = super(NsxV3Plugin, self).create_network(context,
                                                                       network)
-
+                self._extension_manager.process_create_network(
+                    context, net_data, created_net)
                 if psec.PORTSECURITY not in net_data:
                     net_data[psec.PORTSECURITY] = True
                 self._process_network_port_security_create(
@@ -891,7 +924,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
             self._assert_on_external_net_with_qos(net_data)
         updated_net = super(NsxV3Plugin, self).update_network(context, id,
                                                               network)
-
+        self._extension_manager.process_update_network(context, net_data,
+                                                       updated_net)
         if psec.PORTSECURITY in network['network']:
             self._process_network_port_security_update(
                 context, network['network'], updated_net)
@@ -1201,6 +1235,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
                     if self._has_no_dhcp_enabled_subnet(context, network):
                         created_subnet = super(
                             NsxV3Plugin, self).create_subnet(context, subnet)
+                        self._extension_manager.process_create_subnet(context,
+                            subnet['subnet'], created_subnet)
                         self._enable_native_dhcp(context, network,
                                                  created_subnet)
                         msg = None
@@ -1259,6 +1295,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
                                 updated_subnet = super(
                                     NsxV3Plugin, self).update_subnet(
                                     context, subnet_id, subnet)
+                                self._extension_manager.process_update_subnet(
+                                    context, subnet['subnet'], updated_subnet)
                                 self._enable_native_dhcp(context, network,
                                                          updated_subnet)
                                 msg = None
@@ -1279,10 +1317,14 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
                         updated_subnet = super(
                             NsxV3Plugin, self).update_subnet(
                             context, subnet_id, subnet)
+                        self._extension_manager.process_update_subnet(
+                            context, subnet['subnet'], updated_subnet)
 
         if not updated_subnet:
             updated_subnet = super(NsxV3Plugin, self).update_subnet(
                 context, subnet_id, subnet)
+            self._extension_manager.process_update_subnet(
+                context, subnet['subnet'], updated_subnet)
 
         # Check if needs to update logical DHCP server for native DHCP.
         if (cfg.CONF.nsx_v3.native_dhcp_metadata and
@@ -1881,6 +1923,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
                 self._assert_on_external_net_port_with_qos(port_data)
 
             neutron_db = super(NsxV3Plugin, self).create_port(context, port)
+            self._extension_manager.process_create_port(
+                context, port_data, neutron_db)
             port["port"].update(neutron_db)
 
             (is_psec_on, has_ip) = self._create_port_preprocess_security(
@@ -2242,7 +2286,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
             old_mac_learning_state = original_port.get(mac_ext.MAC_LEARNING)
             updated_port = super(NsxV3Plugin, self).update_port(context,
                                                                 id, port)
-
+            self._extension_manager.process_update_port(context, port['port'],
+                                                        updated_port)
             # copy values over - except fixed_ips as
             # they've already been processed
             port['port'].pop('fixed_ips', None)