get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/1524525/
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 1524525,
    "url": "http://patchwork.ozlabs.org/api/patches/1524525/",
    "web_url": "http://patchwork.ozlabs.org/project/ovn/patch/20210903192748.1408062-7-frode.nordahl@canonical.com/",
    "project": {
        "id": 68,
        "url": "http://patchwork.ozlabs.org/api/projects/68/",
        "name": "Open Virtual Network development",
        "link_name": "ovn",
        "list_id": "ovs-dev.openvswitch.org",
        "list_email": "ovs-dev@openvswitch.org",
        "web_url": "http://openvswitch.org/",
        "scm_url": "",
        "webscm_url": "",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<20210903192748.1408062-7-frode.nordahl@canonical.com>",
    "list_archive_url": null,
    "date": "2021-09-03T19:27:45",
    "name": "[ovs-dev,v4,6/9] lib: Add infrastructure for plugging providers.",
    "commit_ref": null,
    "pull_url": null,
    "state": "changes-requested",
    "archived": false,
    "hash": "f9a3a8eb1273e2a6270a26f8c23064d78f41fcca",
    "submitter": {
        "id": 77851,
        "url": "http://patchwork.ozlabs.org/api/people/77851/",
        "name": "Frode Nordahl",
        "email": "frode.nordahl@canonical.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/ovn/patch/20210903192748.1408062-7-frode.nordahl@canonical.com/mbox/",
    "series": [
        {
            "id": 260950,
            "url": "http://patchwork.ozlabs.org/api/series/260950/",
            "web_url": "http://patchwork.ozlabs.org/project/ovn/list/?series=260950",
            "date": "2021-09-03T19:27:42",
            "name": "Introduce infrastructure for plugging providers",
            "version": 4,
            "mbox": "http://patchwork.ozlabs.org/series/260950/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/1524525/comments/",
    "check": "fail",
    "checks": "http://patchwork.ozlabs.org/api/patches/1524525/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<ovs-dev-bounces@openvswitch.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "dev@openvswitch.org"
        ],
        "Delivered-To": [
            "patchwork-incoming@bilbo.ozlabs.org",
            "ovs-dev@lists.linuxfoundation.org"
        ],
        "Authentication-Results": [
            "ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n unprotected) header.d=canonical.com header.i=@canonical.com\n header.a=rsa-sha256 header.s=20210705 header.b=Hl0RMO3U;\n\tdkim-atps=neutral",
            "ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN>)"
        ],
        "Received": [
            "from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest\n SHA256)\n\t(No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 4H1SWY1CRqz9sCD\n\tfor <incoming@patchwork.ozlabs.org>; Sat,  4 Sep 2021 05:28:24 +1000 (AEST)",
            "from localhost (localhost [127.0.0.1])\n\tby smtp3.osuosl.org (Postfix) with ESMTP id 9C0526166B;\n\tFri,  3 Sep 2021 19:28:22 +0000 (UTC)",
            "from smtp3.osuosl.org ([127.0.0.1])\n\tby localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id px0anL4y--f0; Fri,  3 Sep 2021 19:28:15 +0000 (UTC)",
            "from lists.linuxfoundation.org (lf-lists.osuosl.org\n [IPv6:2605:bc80:3010:104::8cd3:938])\n\tby smtp3.osuosl.org (Postfix) with ESMTPS id ACC6E61667;\n\tFri,  3 Sep 2021 19:28:10 +0000 (UTC)",
            "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id E47EBC002D;\n\tFri,  3 Sep 2021 19:28:02 +0000 (UTC)",
            "from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 01EEEC002A\n for <dev@openvswitch.org>; Fri,  3 Sep 2021 19:27:57 +0000 (UTC)",
            "from localhost (localhost [127.0.0.1])\n by smtp3.osuosl.org (Postfix) with ESMTP id E402B6157B\n for <dev@openvswitch.org>; Fri,  3 Sep 2021 19:27:56 +0000 (UTC)",
            "from smtp3.osuosl.org ([127.0.0.1])\n by localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024)\n with ESMTP id HHltLCZAgUs7 for <dev@openvswitch.org>;\n Fri,  3 Sep 2021 19:27:54 +0000 (UTC)",
            "from smtp-relay-canonical-1.canonical.com\n (smtp-relay-canonical-1.canonical.com [185.125.188.121])\n by smtp3.osuosl.org (Postfix) with ESMTPS id 0FB8361542\n for <dev@openvswitch.org>; Fri,  3 Sep 2021 19:27:54 +0000 (UTC)",
            "from frode-threadripper.. (1.general.frode.uk.vpn [10.172.193.250])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest\n SHA256)\n (No client certificate requested)\n by smtp-relay-canonical-1.canonical.com (Postfix) with ESMTPSA id 0A6C940666;\n Fri,  3 Sep 2021 19:27:50 +0000 (UTC)"
        ],
        "X-Virus-Scanned": [
            "amavisd-new at osuosl.org",
            "amavisd-new at osuosl.org"
        ],
        "X-Greylist": "domain auto-whitelisted by SQLgrey-1.8.0",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=canonical.com;\n s=20210705; t=1630697271;\n bh=hlQb/VRq+wyXqDTO6J1oh+L1ZewUBD+Bi+uRj2KnStU=;\n h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References:\n MIME-Version;\n b=Hl0RMO3UeDHdH0d5/CywWEsKuPF5Xsy1cbhx7nUqo55NE0YP+SenZnJgCCPywL7yU\n a979BDNfh8to7sFZqSMl9I0r0WJFxdQ8QJ2/yHDKIV9JE3hYdCQ3wgCWib8gs/UVBR\n O/S5piIu4U65B5QJzJBX8X7spcOswSVeAgmSvTYEdatOu8rAcQhBTxgDavC89quZXS\n SYqptQNwy1zF5rhArvYg/U+NQLsUagFLh5X3VkJ6muXwqJ9wnaSOvG6xhIEUZaCgr+\n A8Zrb6SdRbTJeW0nOazvoCt3+XntEopVn0Yorip2v/gzx+jfjJ6arjlnToWYRzJeeF\n TMqPncWg6AO1Q==",
        "From": "Frode Nordahl <frode.nordahl@canonical.com>",
        "To": "dev@openvswitch.org",
        "Date": "Fri,  3 Sep 2021 21:27:45 +0200",
        "Message-Id": "<20210903192748.1408062-7-frode.nordahl@canonical.com>",
        "X-Mailer": "git-send-email 2.32.0",
        "In-Reply-To": "<20210903192748.1408062-1-frode.nordahl@canonical.com>",
        "References": "<20210903192748.1408062-1-frode.nordahl@canonical.com>",
        "MIME-Version": "1.0",
        "Subject": "[ovs-dev] [PATCH ovn v4 6/9] lib: Add infrastructure for plugging\n\tproviders.",
        "X-BeenThere": "ovs-dev@openvswitch.org",
        "X-Mailman-Version": "2.1.15",
        "Precedence": "list",
        "List-Id": "<ovs-dev.openvswitch.org>",
        "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>",
        "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>",
        "List-Post": "<mailto:ovs-dev@openvswitch.org>",
        "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>",
        "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>",
        "Content-Type": "text/plain; charset=\"us-ascii\"",
        "Content-Transfer-Encoding": "7bit",
        "Errors-To": "ovs-dev-bounces@openvswitch.org",
        "Sender": "\"dev\" <ovs-dev-bounces@openvswitch.org>"
    },
    "content": "New module contains the infrastructure for registering plugging\nclasses which may be hosted inside or outside the core OVN\nrepository.  The data structures and functions for interacting\nwith these plugging classes also live there.\n\nExtend build system to allow enabling building of built-in plugging\nproviders and linking an externally built plugging provider.\n\nSigned-off-by: Frode Nordahl <frode.nordahl@canonical.com>\n---\n Documentation/automake.mk                     |   1 +\n Documentation/topics/index.rst                |   1 +\n Documentation/topics/plug_providers/index.rst |  32 +++\n .../topics/plug_providers/plug-providers.rst  | 199 ++++++++++++++\n acinclude.m4                                  |  49 ++++\n configure.ac                                  |   2 +\n lib/automake.mk                               |  12 +-\n lib/plug-dummy.c                              | 123 +++++++++\n lib/plug-dummy.h                              |  33 +++\n lib/plug-provider.h                           |  98 +++++++\n lib/plug.c                                    | 255 ++++++++++++++++++\n lib/plug.h                                    | 107 ++++++++\n lib/test-plug.c                               |  72 +++++\n ovn-architecture.7.xml                        |  35 ++-\n tests/automake.mk                             |   4 +-\n tests/ovn-plug.at                             |   8 +\n 16 files changed, 1016 insertions(+), 15 deletions(-)\n create mode 100644 Documentation/topics/plug_providers/index.rst\n create mode 100644 Documentation/topics/plug_providers/plug-providers.rst\n create mode 100644 lib/plug-dummy.c\n create mode 100644 lib/plug-dummy.h\n create mode 100644 lib/plug-provider.h\n create mode 100644 lib/plug.c\n create mode 100644 lib/plug.h\n create mode 100644 lib/test-plug.c\n create mode 100644 tests/ovn-plug.at",
    "diff": "diff --git a/Documentation/automake.mk b/Documentation/automake.mk\nindex b3fd3d62b..92a843d76 100644\n--- a/Documentation/automake.mk\n+++ b/Documentation/automake.mk\n@@ -28,6 +28,7 @@ DOC_SOURCE = \\\n \tDocumentation/topics/ovn-news-2.8.rst \\\n \tDocumentation/topics/role-based-access-control.rst \\\n \tDocumentation/topics/debugging-ddlog.rst \\\n+\tDocumentation/topics/plug_providers/plug-providers.rst \\\n \tDocumentation/howto/index.rst \\\n \tDocumentation/howto/docker.rst \\\n \tDocumentation/howto/firewalld.rst \\\ndiff --git a/Documentation/topics/index.rst b/Documentation/topics/index.rst\nindex d58d5618b..12bd113b7 100644\n--- a/Documentation/topics/index.rst\n+++ b/Documentation/topics/index.rst\n@@ -41,6 +41,7 @@ OVN\n    high-availability\n    role-based-access-control\n    ovn-news-2.8\n+   plug_providers/index\n    testing\n \n .. list-table::\ndiff --git a/Documentation/topics/plug_providers/index.rst b/Documentation/topics/plug_providers/index.rst\nnew file mode 100644\nindex 000000000..837eeae15\n--- /dev/null\n+++ b/Documentation/topics/plug_providers/index.rst\n@@ -0,0 +1,32 @@\n+..\n+      Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n+      not use this file except in compliance with the License. You may obtain\n+      a copy of the License at\n+\n+          http://www.apache.org/licenses/LICENSE-2.0\n+\n+      Unless required by applicable law or agreed to in writing, software\n+      distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n+      WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n+      License for the specific language governing permissions and limitations\n+      under the License.\n+\n+      Convention for heading levels in OVN documentation:\n+\n+      =======  Heading 0 (reserved for the title in a document)\n+      -------  Heading 1\n+      ~~~~~~~  Heading 2\n+      +++++++  Heading 3\n+      '''''''  Heading 4\n+\n+      Avoid deeper levels because they do not render well.\n+\n+==============\n+Plug Providers\n+==============\n+\n+\n+.. toctree::\n+   :maxdepth: 2\n+\n+   plug-providers\ndiff --git a/Documentation/topics/plug_providers/plug-providers.rst b/Documentation/topics/plug_providers/plug-providers.rst\nnew file mode 100644\nindex 000000000..7b891156c\n--- /dev/null\n+++ b/Documentation/topics/plug_providers/plug-providers.rst\n@@ -0,0 +1,199 @@\n+..\n+      Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n+      not use this file except in compliance with the License. You may obtain\n+      a copy of the License at\n+\n+          http://www.apache.org/licenses/LICENSE-2.0\n+\n+      Unless required by applicable law or agreed to in writing, software\n+      distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n+      WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n+      License for the specific language governing permissions and limitations\n+      under the License.\n+\n+      Convention for heading levels in OVN documentation:\n+\n+      =======  Heading 0 (reserved for the title in a document)\n+      -------  Heading 1\n+      ~~~~~~~  Heading 2\n+      +++++++  Heading 3\n+      '''''''  Heading 4\n+\n+      Avoid deeper levels because they do not render well.\n+\n+==================\n+Plugging Providers\n+==================\n+\n+Traditionally it has been the CMSes responsibility to create VIFs as part of\n+instance life cycle, and subsequently manage plug/unplug operations on the\n+integration bridge following the conventions described in the\n+`Open vSwitch Integration Guide`_ for mapping of VIFs to OVN logical port.\n+\n+With the advent of NICs connected to multiple distinct CPUs we can have a\n+topology where the instance runs on one host and Open vSwitch and OVN runs on\n+a different host, the smartnic control plane CPU.  The host facing interfaces\n+will be visible to Open vSwitch and OVN as representor ports.\n+\n+The actions necessary for plugging and unplugging the representor port in\n+Open vSwitch running on the smartnic control plane CPU would be the same for\n+every CMS.\n+\n+Instead of every CMS having to develop their own version of an agent to do\n+the plugging, we provide a pluggable infrastructure in OVN that allows the\n+`ovn-controller` to perform the plugging on CMS direction.\n+\n+Hardware or platform specific details for initialization and lookup of\n+representor ports is provided by an plugging provider library hosted inside or\n+outside the core OVN repository, and linked at OVN build time.\n+\n+Life Cycle of an OVN plugged VIF\n+--------------------------------\n+\n+1. CMS creates a record in the OVN Northbound Logical_Switch_Port table with\n+   the options column containing the `plug-type` key with a value corresponding\n+   to the `const char *type` provided by the plugging provider implementation\n+   as well as a `requested-chassis` key with a value pointing at the name or\n+   hostname of the chassis it wants the VIF plugged on.  Additional plugging\n+   provider specific key/value pairs must be provided for successful lookup.\n+\n+2. `ovn-northd` looks up the name or hostname provided in the\n+   `requested-chassis` option and fills the OVN Southbound Port_Binding\n+   requested_chassis column, it also copies relevant options over to the\n+   Port_Binding record.\n+\n+3. `ovn-controller` monitors Southbound Port_Binding entries wth a\n+   requested_chassis column pointing at its chassis UUID and when it encounters\n+   a entry with option `plug-type` and it has registered a plug provider\n+   matching that type it will act on it even if no local binding exists yet.\n+\n+4. It will fill the `struct plug_port_ctx_in` as defined in `lib/plug.h` with\n+   `op_type` set to 'PLUG_OP_CREATE' and make a call to the plug providers\n+   `plug_port_prepare` callback function.  Plug provider performs lookup and\n+   fills the `struct plug_port_ctx_out` as defined in `lib/plug.h`.\n+\n+5. `ovn-controller` creates a port and interface record in the local OVSDB\n+   using the details provided by the plug provider and also adds\n+   `external-ids:iface-id` with value matching the logical port name and\n+   `external-ids:ovn-plugged` with value matching the logical port `plug-type`.\n+   When the port creation is done a call will first be made to the plug\n+   providers `plug_port_finish` function and then to the\n+   `plug_port_ctx_destroy` function to free any memory allocated by the plug\n+   implementation.\n+\n+6. The Open vSwitch vswitchd will assign a ofport to the newly created\n+   interface and on the next `ovn-controller` main loop iteration flows will be\n+   installed.\n+\n+7. On any change to the Southbound Port_Binding record or full recomputation\n+   the `ovn-controller` will in addition to normal flow processing make a call\n+   to the plug provider again similar to the first creation in case anything\n+   needs updating for the interface record.\n+\n+8. The port will be unplugged when an event occurs which would make the\n+   `ovn-controller` release a logical port, for example the Logical_Switch_Port\n+   and Port_Binding entry disappearing from the database or its\n+   `requested_chassis` column being pointed to a different chassis.\n+\n+\n+The plug provider interface\n+---------------------------\n+\n+The interface between internals of OVN and a plugging provider is a set of\n+callbacks as defined by the `struct plug_class` in `lib/plug-provider.h`.\n+\n+It is important to note that these callbacks will be called in the critical\n+path of the `ovn-controller` processing loop, so care must be taken to make the\n+implementation as efficient as possible, and under no circumstance can any of\n+the callback functions make calls that block.\n+\n+On `ovn-controller` startup, plug providers made available at build time will\n+be registered by the identifier provided in the `const char *type` pointer, at\n+this time the `init` function pointer will be called if it is non-NULL.\n+\n+> **Note**: apart from the `const char *type` pointer, no attempt will be made\n+to access plug provider data or functions before the call to the `init` has\n+been made.\n+\n+On `ovn-controller` exit, the plug providers registered in the above mentioned\n+procedure will have their `destroy` function pointer called if it is non-NULL.\n+\n+If the plug provider has internal lookup tables that need to be maintained they\n+can define a `run` function which will be called as part of the\n+`ovn-controller` incremental processing engine loop.  If the changes\n+encountered can be safely processed incrementally the function should return\n+'true', if the changes cannot be processed incrementally, for example because\n+the change would impact already handled interfaces that we might not process in\n+a while, the function should return 'false' to force the `ovn-controller` to\n+perform a full recomputation.\n+\n+On update of Interface records the `ovn-controller` will pass on a `sset`\n+to the `ovsport_update_iface` function containing options the plug\n+implementation finds pertinent to maintain for successful operation.  This\n+`sset` is retrieved by making a call to the plug implementation\n+`plug_get_maintained_iface_options` function pointer if it is non-NULL.  This\n+allows presence of other users of the OVSDB maintaining a different set of\n+options on the same set of Interface records without wiping out their changes.\n+\n+Before creating or updating an existing interface record the plug provider\n+`plug_port_prepare` function pointer will be called with valid pointers to\n+`struct plug_port_ctx_in` and `struct plug_port_ctx_out` data structures.  If\n+the plug provider implementation is able to perform lookup it should fill the\n+`struct plug_port_ctx_out` data structure and return 'true'.  The\n+`ovn-controller` will then create or update  the port/interface records and\n+then call `plug_port_finish` and `plug_port_ctx_destroy`.  If the plug provider\n+implementation is unable to perform lookup or prepare the desired resource it\n+should return 'false' which will tell the `ovn-controller` to not create or\n+update the port, in this case it will also not call `plug_port_finish`, it will\n+however make a call to `plug_port_ctx_destroy`.\n+\n+Before removing port and interface records previously plugged by the\n+`ovn-controller` as identified by presence of the Interface\n+`external-ids:ovn-plugged` key, the `ovn-controller` will look up the\n+`plug-type` from `external-ids:ovn-plugged`, fill `struct plug_port_ctx_in`\n+with `op_type` set to 'PLUG_OP_REMOVE' and make a call to `plug_port_prepare`.\n+After the port and interface has been removed a call will be made to\n+`plug_port_finish`.  Both calls will be made with the pointer to\n+`plug_port_ctx_out` set to 'NULL', and no call will be made to\n+`plug_port_ctx_destroy`.\n+\n+Building with in-tree plugging providers\n+----------------------------------------\n+\n+Plugging providers hosted in the OVN repository living under\n+`lib/plug_providers`:\n+\n+To enable them, provide the `--enable-plug-providers` command line option to\n+the configure script when building OVN.\n+\n+Building with an externally provided plugging provider\n+------------------------------------------------------\n+\n+There is also infrastructure in place to support linking OVN with an externally\n+built plugging provider.\n+\n+This external plugging provider must define a NULL-terminated array of pointers\n+to `struct plug_class` data structures named `plug_provider_classes`.  Example:\n+\n+.. code-block:: C\n+\n+   const struct plug_class *plug_provider_classes[] = {\n+       &plug_foo,\n+       NULL,\n+   };\n+\n+The name of the repostiroy for the external plugging provider should be the\n+same as the name of the library it produces, and the built library artifact\n+should be placed in lib/.libs.  Example:\n+\n+.. code-block:: none\n+\n+   ovn-vif-foo/\n+   ovn-vif-foo/lib/.libs/libovn-vif-foo.la\n+\n+To enable such a plugging provider provide the\n+`--with-plug-provider=/path/to/ovn-vif-foo` command line option to the\n+configure script when building OVN.\n+\n+.. LINKS\n+.. _Open vSwitch Integration Guide: https://docs.openvswitch.org/en/latest/topics/integration/\ndiff --git a/acinclude.m4 b/acinclude.m4\nindex e7f829520..793a073d1 100644\n--- a/acinclude.m4\n+++ b/acinclude.m4\n@@ -441,3 +441,52 @@ AC_DEFUN([OVN_CHECK_OVS], [\n   AC_MSG_CHECKING([OVS version])\n   AC_MSG_RESULT([$OVSVERSION])\n ])\n+\n+dnl OVN_CHECK_PLUG_PROVIDER\n+dnl\n+dnl Check for external plug provider\n+AC_DEFUN([OVN_CHECK_PLUG_PROVIDER], [\n+  AC_ARG_VAR([PLUG_PROVIDER])\n+  AC_ARG_WITH(\n+    [plug-provider],\n+    [AC_HELP_STRING([--with-plug-provider=/path/to/provider/repository],\n+                    [Specify path to a configured and built plug provider repository])],\n+    [if test \"$withval\" = yes; then\n+       if test -z \"$PLUG_PROVIDER\"; then\n+         AC_MSG_ERROR([To build with external plug provider, specify the path to a configured and built plug provider repository --with-plug-provider or in \\$PLUG_PROVIDER]),\n+       fi\n+       PLUG_PROVIDER=\"$(realpath $PLUG_PROVIDER)\"\n+     else\n+       PLUG_PROVIDER=\"$(realpath $withval)\"\n+     fi\n+     _plug_provider_name=\"$(basename $PLUG_PROVIDER)\"\n+     if test ! -f \"$PLUG_PROVIDER/lib/.libs/lib${_plug_provider_name}.la\"; then\n+       AC_MSG_ERROR([$withval is not a configured and built plug provider library repository])\n+     fi\n+     PLUG_PROVIDER_LDFLAGS=\"-L$PLUG_PROVIDER/lib/.libs -l$_plug_provider_name\"\n+    ],\n+    [PLUG_PROVIDER=no])\n+  AC_MSG_CHECKING([for plug provider])\n+  AC_MSG_RESULT([$PLUG_PROVIDER])\n+  AC_SUBST([PLUG_PROVIDER_LDFLAGS])\n+  AM_CONDITIONAL([HAVE_PLUG_PROVIDER], [test \"$PLUG_PROVIDER\" != no])\n+  if test \"$PLUG_PROVIDER\" != no; then\n+    AC_DEFINE([HAVE_PLUG_PROVIDER], [1],\n+              [Build and link with external plug provider])\n+  fi\n+])\n+\n+dnl OVN_ENABLE_PLUG\n+dnl\n+dnl Enable built-in plug providers\n+AC_DEFUN([OVN_ENABLE_PLUG], [\n+    AC_ARG_ENABLE(\n+      [plug-providers],\n+      [AC_HELP_STRING([--enable-plug-providers], [Enable building of built-in plug providers])],\n+      [], [enable_plug=no])\n+    AM_CONDITIONAL([ENABLE_PLUG], [test \"$enable_plug\" != no])\n+    if test \"$enable_plug\" != no; then\n+      AC_DEFINE([ENABLE_PLUG], [1],\n+                [Build built-in plug providers])\n+    fi\n+])\ndiff --git a/configure.ac b/configure.ac\nindex df0b98295..7f3274e59 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -172,6 +172,8 @@ OVS_ENABLE_SPARSE\n OVS_CHECK_DDLOG([0.38])\n OVS_CHECK_PRAGMA_MESSAGE\n OVN_CHECK_OVS\n+OVN_CHECK_PLUG_PROVIDER\n+OVN_ENABLE_PLUG\n OVS_CTAGS_IDENTIFIERS\n AC_SUBST([OVS_CFLAGS])\n AC_SUBST([OVS_LDFLAGS])\ndiff --git a/lib/automake.mk b/lib/automake.mk\nindex ddfe33948..086fbd62d 100644\n--- a/lib/automake.mk\n+++ b/lib/automake.mk\n@@ -3,6 +3,11 @@ lib_libovn_la_LDFLAGS = \\\n         $(OVS_LTINFO) \\\n         -Wl,--version-script=$(top_builddir)/lib/libovn.sym \\\n         $(AM_LDFLAGS)\n+\n+if HAVE_PLUG_PROVIDER\n+lib_libovn_la_LDFLAGS += $(PLUG_PROVIDER_LDFLAGS)\n+endif\n+\n lib_libovn_la_SOURCES = \\\n \tlib/acl-log.c \\\n \tlib/acl-log.h \\\n@@ -32,7 +37,12 @@ lib_libovn_la_SOURCES = \\\n \tlib/inc-proc-eng.h \\\n \tlib/lb.c \\\n \tlib/lb.h \\\n-\tlib/stopwatch-names.h\n+\tlib/stopwatch-names.h \\\n+\tlib/plug-provider.h \\\n+\tlib/plug.h \\\n+\tlib/plug.c \\\n+\tlib/plug-dummy.h \\\n+\tlib/plug-dummy.c\n nodist_lib_libovn_la_SOURCES = \\\n \tlib/ovn-dirs.c \\\n \tlib/ovn-nb-idl.c \\\ndiff --git a/lib/plug-dummy.c b/lib/plug-dummy.c\nnew file mode 100644\nindex 000000000..505c37c9c\n--- /dev/null\n+++ b/lib/plug-dummy.c\n@@ -0,0 +1,123 @@\n+/*\n+ * Copyright (c) 2021 Canonical\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at:\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#include <config.h>\n+#include \"plug-dummy.h\"\n+#include \"plug-provider.h\"\n+#include \"plug.h\"\n+\n+#include <stdint.h>\n+\n+#include \"openvswitch/vlog.h\"\n+#include \"smap.h\"\n+#include \"sset.h\"\n+\n+#ifndef IFNAMSIZ\n+#define IFNAMSIZ 16\n+#endif\n+\n+VLOG_DEFINE_THIS_MODULE(plug_dummy);\n+\n+static struct sset plug_dummy_maintained_iface_options;\n+\n+static int\n+plug_dummy_init(void)\n+{\n+    sset_init(&plug_dummy_maintained_iface_options);\n+    sset_add(&plug_dummy_maintained_iface_options, \"plug-dummy-option\");\n+\n+    return 0;\n+}\n+\n+static int\n+plug_dummy_destroy(void)\n+{\n+    sset_destroy(&plug_dummy_maintained_iface_options);\n+\n+    return 0;\n+}\n+\n+static const struct sset*\n+plug_dummy_get_maintained_iface_options(void)\n+{\n+    return &plug_dummy_maintained_iface_options;\n+}\n+\n+static bool\n+plug_dummy_run(struct plug_class *plug)\n+{\n+    VLOG_DBG(\"plug_dummy_run(%p)\", plug);\n+\n+    return true;\n+}\n+\n+static bool\n+plug_dummy_port_prepare(const struct plug_port_ctx_in *ctx_in,\n+                       struct plug_port_ctx_out *ctx_out)\n+{\n+    VLOG_DBG(\"plug_dummy_port_prepare: %s\", ctx_in->lport_name);\n+\n+    if (ctx_in->op_type == PLUG_OP_CREATE) {\n+        size_t lport_name_len = strlen(ctx_in->lport_name);\n+        ctx_out->name = xzalloc(IFNAMSIZ);\n+        memcpy(ctx_out->name, ctx_in->lport_name,\n+               (lport_name_len < IFNAMSIZ) ? lport_name_len : IFNAMSIZ - 1);\n+        ctx_out->type = xstrdup(\"internal\");\n+        ctx_out->iface_options = xmalloc(sizeof *ctx_out->iface_options);\n+        smap_init(ctx_out->iface_options);\n+        smap_add(ctx_out->iface_options, \"plug-dummy-option\", \"value\");\n+    }\n+\n+    return true;\n+}\n+\n+static void\n+plug_dummy_port_finish(const struct plug_port_ctx_in *ctx_in,\n+                      struct plug_port_ctx_out *ctx_out OVS_UNUSED)\n+{\n+    VLOG_DBG(\"plug_dummy_port_finish: %s\", ctx_in->lport_name);\n+}\n+\n+static void\n+plug_dummy_port_ctx_destroy(const struct plug_port_ctx_in *ctx_in,\n+                           struct plug_port_ctx_out *ctx_out)\n+{\n+    VLOG_DBG(\"plug_dummy_port_ctx_destroy: %s\", ctx_in->lport_name);\n+    ovs_assert(ctx_in->op_type == PLUG_OP_CREATE);\n+    free(ctx_out->name);\n+    free(ctx_out->type);\n+    smap_destroy(ctx_out->iface_options);\n+    free(ctx_out->iface_options);\n+}\n+\n+const struct plug_class plug_dummy_class = {\n+    .type = \"dummy\",\n+    .init = plug_dummy_init,\n+    .destroy = plug_dummy_destroy,\n+    .plug_get_maintained_iface_options =\n+        plug_dummy_get_maintained_iface_options,\n+    .run = plug_dummy_run,\n+    .plug_port_prepare = plug_dummy_port_prepare,\n+    .plug_port_finish = plug_dummy_port_finish,\n+    .plug_port_ctx_destroy = plug_dummy_port_ctx_destroy,\n+};\n+\n+void\n+plug_dummy_enable(void)\n+{\n+    plug_register_provider(&plug_dummy_class);\n+}\n+\ndiff --git a/lib/plug-dummy.h b/lib/plug-dummy.h\nnew file mode 100644\nindex 000000000..6ea33671e\n--- /dev/null\n+++ b/lib/plug-dummy.h\n@@ -0,0 +1,33 @@\n+/*\n+ * Copyright (c) 2021 Canonical\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at:\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#ifndef PLUG_DUMMY_H\n+#define PLUG_DUMMY_H 1\n+\n+/*\n+ * The dummy plugger, allows for experimenting with plugging in a sandbox */\n+\n+#ifdef  __cplusplus\n+extern \"C\" {\n+#endif\n+\n+void plug_dummy_enable(void);\n+\n+#ifdef  __cplusplus\n+}\n+#endif\n+\n+#endif /* plug-dummy.h */\ndiff --git a/lib/plug-provider.h b/lib/plug-provider.h\nnew file mode 100644\nindex 000000000..487534ee5\n--- /dev/null\n+++ b/lib/plug-provider.h\n@@ -0,0 +1,98 @@\n+/*\n+ * Copyright (c) 2021 Canonical\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at:\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#ifndef PLUG_PROVIDER_H\n+#define PLUG_PROVIDER_H 1\n+\n+/* Provider interface to pluggers.  A plugger implementation performs lookup\n+ * and/or initialization of ports, typically representor ports, using generic\n+ * non-blocking hardware interfaces.  This allows the ovn-controller to, upon\n+ * the CMS's request, create ports and interfaces in the chassis's Open vSwitch\n+ * instances (also known as vif plugging).\n+ */\n+\n+#include <stdbool.h>\n+\n+#include \"plug.h\"\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+struct plug_class {\n+    /* Type of plugger in this class. */\n+    const char *type;\n+\n+    /* Called when the plug provider is registered, typically at program\n+     * startup.\n+     *\n+     * This function may be set to null if a plug class needs no\n+     * initialization at registration time. */\n+    int (*init)(void);\n+\n+    /* Called when the plug provider is unregistered, typically at program\n+     * exit.\n+     *\n+     * This function may be set to null if a plug class needs no\n+     * de-initialization at unregister time.*/\n+    int (*destroy)(void);\n+\n+    /* Performs periodic work needed by plugger, if any is necessary.  Returns\n+     * 'true; if the changes encountered could be handled incrementally,\n+     * 'false' otherwise.\n+     *\n+     * Note that returning 'false' will instruct the incremental processing\n+     * engine to perform a full recomputation. */\n+    bool (*run)(struct plug_class *);\n+\n+    /* Retrieve Interface options this plugger will maintain.  This set is used\n+     * to know which items to remove when maintaining the database record. */\n+    const struct sset * (*plug_get_maintained_iface_options)(void);\n+\n+    /* Pass plug_port_ctx_in to plug implementation to prepare for port\n+     * creation/update.\n+     *\n+     * The plug implemantation can perform lookup or any per port\n+     * initialization and should fill plug_port_ctx_out with data required for\n+     * port/interface creation.  The plug implementation should return true if\n+     * it wants the caller to create/update a port/interface, false otherwise.\n+     *\n+     * Data in the plug_port_ctx_out struct is owned by the plugging library,\n+     * and a call must be made to the plug_port_ctx_destroy callback to free\n+     * up any allocations when done with port creation/update.\n+     */\n+    bool (*plug_port_prepare)(const struct plug_port_ctx_in *,\n+                              struct plug_port_ctx_out *);\n+\n+    /* Notify plugging library that port update is done. */\n+    void (*plug_port_finish)(const struct plug_port_ctx_in *,\n+                             struct plug_port_ctx_out *);\n+\n+    /* Free any allocations made by the plug_port_prepare callback. */\n+    void (*plug_port_ctx_destroy)(const struct plug_port_ctx_in *,\n+                                  struct plug_port_ctx_out *);\n+};\n+\n+extern const struct plug_class plug_dummy_class;\n+#ifdef HAVE_PLUG_PROVIDER\n+extern const struct plug_class *plug_provider_classes[];\n+#endif\n+\n+#ifdef  __cplusplus\n+}\n+#endif\n+\n+#endif /* plug-provider.h */\ndiff --git a/lib/plug.c b/lib/plug.c\nnew file mode 100644\nindex 000000000..c0c34214e\n--- /dev/null\n+++ b/lib/plug.c\n@@ -0,0 +1,255 @@\n+/*\n+ * Copyright (c) 2021 Canonical\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at:\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#include <config.h>\n+#include \"plug-provider.h\"\n+#include \"plug.h\"\n+\n+#include <errno.h>\n+#include <stdint.h>\n+#include <string.h>\n+\n+#include \"openvswitch/vlog.h\"\n+#include \"openvswitch/shash.h\"\n+#include \"smap.h\"\n+#include \"sset.h\"\n+#include \"lib/inc-proc-eng.h\"\n+\n+VLOG_DEFINE_THIS_MODULE(plug);\n+\n+#ifdef ENABLE_PLUG\n+static const struct plug_class *base_plug_classes[] = {\n+};\n+#endif\n+\n+static struct shash plug_classes = SHASH_INITIALIZER(&plug_classes);\n+\n+/* Protects the 'plug_classes' shash. */\n+static struct ovs_mutex plug_classes_mutex = OVS_MUTEX_INITIALIZER;\n+\n+/* Initialize the the plug infrastructure by registering known plug classes */\n+void\n+plug_initialize(void)\n+{\n+    static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;\n+\n+    if (ovsthread_once_start(&once)) {\n+#ifdef ENABLE_PLUG\n+        /* Register built-in plug provider classes */\n+        for (int i = 0; i < ARRAY_SIZE(base_plug_classes); i++) {\n+            plug_register_provider(base_plug_classes[i]);\n+        }\n+#endif\n+#ifdef HAVE_PLUG_PROVIDER\n+        /* Register external plug provider classes.\n+         *\n+         * Note that we cannot use the ARRAY_SIZE macro here as\n+         * plug_provider_classes is defined in external code which is not\n+         * available at compile time.  The convention is to use a\n+         * NULL-terminated array instead. */\n+        for (const struct plug_class **pp = plug_provider_classes;\n+             pp && *pp;\n+             pp++)\n+        {\n+            plug_register_provider(*pp);\n+        }\n+#endif\n+        ovsthread_once_done(&once);\n+    }\n+}\n+\n+static int\n+plug_register_provider__(const struct plug_class *new_class)\n+{\n+    struct plug_class *plug_class;\n+    int error;\n+\n+    if (shash_find(&plug_classes, new_class->type)) {\n+        VLOG_WARN(\"attempted to register duplicate plug provider: %s\",\n+                  new_class->type);\n+        return EEXIST;\n+    }\n+\n+    error = new_class->init ? new_class->init() : 0;\n+    if (error) {\n+        VLOG_WARN(\"failed to initialize %s plug class: %s\",\n+                  new_class->type, ovs_strerror(error));\n+        return error;\n+    }\n+\n+    plug_class = xmalloc(sizeof *plug_class);\n+    memcpy(plug_class, new_class, sizeof *plug_class);\n+\n+    shash_add(&plug_classes, new_class->type, plug_class);\n+\n+    return 0;\n+}\n+\n+/* Register the new plug provider referred to in 'new_class' and perform any\n+ * class level initialization as specified in its plug_class. */\n+int\n+plug_register_provider(const struct plug_class *new_class)\n+{\n+    int error;\n+\n+    ovs_mutex_lock(&plug_classes_mutex);\n+    error = plug_register_provider__(new_class);\n+    ovs_mutex_unlock(&plug_classes_mutex);\n+\n+    return error;\n+}\n+\n+static int\n+plug_unregister_provider__(const char *type)\n+{\n+    int error;\n+    struct shash_node *node;\n+    struct plug_class *plug_class;\n+\n+    node = shash_find(&plug_classes, type);\n+    if (!node) {\n+        return EINVAL;\n+    }\n+\n+    plug_class = node->data;\n+    error = plug_class->destroy ? plug_class->destroy() : 0;\n+    if (error) {\n+        VLOG_WARN(\"failed to destroy %s plug class: %s\",\n+                  plug_class->type, ovs_strerror(error));\n+        return error;\n+    }\n+\n+    shash_delete(&plug_classes, node);\n+    free(plug_class);\n+\n+    return 0;\n+}\n+\n+/* Unregister the plug provider identified by 'type' and perform any class\n+ * level de-initialization as specified in its plug_class. */\n+int\n+plug_unregister_provider(const char *type)\n+{\n+    int error;\n+\n+    ovs_mutex_lock(&plug_classes_mutex);\n+    error = plug_unregister_provider__(type);\n+    ovs_mutex_unlock(&plug_classes_mutex);\n+\n+    return error;\n+}\n+\n+const struct plug_class *\n+plug_get_provider(const char *type)\n+{\n+    struct plug_class *plug_class;\n+\n+    ovs_mutex_lock(&plug_classes_mutex);\n+    plug_class = shash_find_data(&plug_classes, type);\n+    ovs_mutex_unlock(&plug_classes_mutex);\n+\n+    return plug_class;\n+}\n+\n+/* De-initialize and unregister the plug provider classes. */\n+void\n+plug_destroy_all(void)\n+{\n+    struct shash_node *node, *next;\n+\n+    SHASH_FOR_EACH_SAFE (node, next, &plug_classes) {\n+        struct plug_class *plug_class = node->data;\n+        plug_unregister_provider(plug_class->type);\n+    }\n+}\n+\n+/* Get the class level 'maintained_iface_options' set. */\n+const struct sset *\n+plug_get_maintained_iface_options(const struct plug_class *plug_class)\n+{\n+    return plug_class->plug_get_maintained_iface_options();\n+}\n+\n+/* Prepare the logical port as identified by 'ctx_in' for port creation, update\n+ * or removal as specified by 'ctx_in->op_type'.\n+ *\n+ * When 'ctx_in->op_type' is PLUG_OP_CREATE the plug implementation must fill\n+ * 'ctx_out' with data to apply to the interface record maintained by OVN on\n+ * its behalf.\n+ *\n+ * When 'ctx_in_op_type' is PLUG_OP_REMOVE 'ctx_out' should be set to NULL and\n+ * the plug implementation must not attempt to use 'ctx_out'.\n+ *\n+ * The data in 'ctx_out' is owned by the plug implementation, and a call must\n+ * be made to plug_port_ctx_destroy when done with it. */\n+bool\n+plug_port_prepare(const struct plug_class *plug_class,\n+                  const struct plug_port_ctx_in *ctx_in,\n+                  struct plug_port_ctx_out *ctx_out)\n+{\n+    if (ctx_out) {\n+        memset(ctx_out, 0, sizeof(*ctx_out));\n+    }\n+    return plug_class->plug_port_prepare(ctx_in, ctx_out);\n+}\n+\n+/* Notify the plug implementation that a port creation, update or removal has\n+ * been completed */\n+void\n+plug_port_finish(const struct plug_class *plug_class,\n+                 const struct plug_port_ctx_in *ctx_in,\n+                 struct plug_port_ctx_out *ctx_out)\n+{\n+    plug_class->plug_port_finish(ctx_in, ctx_out);\n+}\n+\n+/* Free any data allocated to 'ctx_out' in a prevous call to\n+ * plug_port_prepare. */\n+void\n+plug_port_ctx_destroy(const struct plug_class *plug_class,\n+                      const struct plug_port_ctx_in *ctx_in,\n+                      struct plug_port_ctx_out *ctx_out)\n+{\n+    plug_class->plug_port_ctx_destroy(ctx_in, ctx_out);\n+}\n+\n+/* Iterate over registered plug provider classes and call their 'run'\n+ * function if defined.\n+ *\n+ * If any of the classes report that something has changed we will trigger a\n+ * recompute. */\n+void\n+en_plug_provider_run(struct engine_node *inc_eng_node,\n+                     void *inc_eng_data OVS_UNUSED)\n+{\n+    struct shash_node *node, *next;\n+    bool handled = true;\n+\n+    SHASH_FOR_EACH_SAFE (node, next, &plug_classes) {\n+        struct plug_class *plug_class = node->data;\n+        if (plug_class->run && !plug_class->run(plug_class)) {\n+            handled = false;\n+        }\n+    }\n+\n+    if (!handled) {\n+        /* as we do not have a change handler registered in the incremental\n+         * processing engine this will trigger a recompute. */\n+        engine_set_node_state(inc_eng_node, EN_UPDATED);\n+    } else {\n+        engine_set_node_state(inc_eng_node, EN_UNCHANGED);\n+    }\n+}\ndiff --git a/lib/plug.h b/lib/plug.h\nnew file mode 100644\nindex 000000000..fef4c7f64\n--- /dev/null\n+++ b/lib/plug.h\n@@ -0,0 +1,107 @@\n+/*\n+ * Copyright (c) 2021 Canonical\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at:\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#ifndef PLUG_H\n+#define PLUG_H 1\n+\n+/*\n+ * Plug, the plugging interface.  This module contains the infrastructure for\n+ * registering and instantiating plugging classes which may be hosted inside\n+ * or outside the core OVN repository.  The data structures and functions for\n+ * interacting with these plugging classes also live here.\n+ */\n+\n+#include \"smap.h\"\n+\n+#ifdef  __cplusplus\n+extern \"C\" {\n+#endif\n+\n+struct plug_class;\n+struct ovsdb_idl_txn;\n+struct ovsrec_bridge;\n+\n+enum plug_op_type {\n+    PLUG_OP_CREATE = 1, /* Port is created or updated */\n+    PLUG_OP_REMOVE,     /* Port is removed from this chassis */\n+};\n+\n+struct plug_port_ctx_in {\n+    /* Operation being performed */\n+    enum plug_op_type op_type;\n+\n+    /* These are provided so that the plug implementation may make decisions\n+     * based on environmental factors such as settings in the open-vswitch\n+     * table and datapath type settings on the integration bridge. */\n+    const struct ovsrec_open_vswitch_table *ovs_table;\n+    const struct ovsrec_bridge *br_int;\n+\n+    /* Name of logical port, can be useful for plugging library to track any\n+     * per port resource initialization. */\n+    const char *lport_name;\n+\n+    /* Logical port options, while OVN will forward the contents verbatim from\n+     * the Southbound database, the convention is for the plugging library to\n+     * only make decisions based on the plug-* options. */\n+    const struct smap *lport_options;\n+\n+    /* When OVN knows about an existing interface record associated with this\n+     * lport, these will be filled in with information about it. */\n+    const char *iface_name;\n+    const char *iface_type;\n+    const struct smap *iface_options;\n+};\n+\n+struct plug_port_ctx_out {\n+    /* The name to use for port and interface record. */\n+    char *name;\n+\n+    /* Type of interface to create. */\n+    char *type;\n+\n+    /* Options to set on the interface record. */\n+    struct smap *iface_options;\n+};\n+\n+\n+void plug_initialize(void);\n+int plug_register_provider(const struct plug_class *);\n+int plug_unregister_provider(const char *type);\n+void plug_destroy_all(void);\n+const struct plug_class * plug_get_provider(const char *);\n+\n+const struct sset * plug_get_maintained_iface_options(\n+    const struct plug_class *plug_class);\n+\n+bool plug_port_prepare(const struct plug_class *,\n+                       const struct plug_port_ctx_in *,\n+                       struct plug_port_ctx_out *);\n+void plug_port_finish(const struct plug_class *,\n+                      const struct plug_port_ctx_in *,\n+                      struct plug_port_ctx_out *);\n+void plug_port_ctx_destroy(const struct plug_class *,\n+                           const struct plug_port_ctx_in *,\n+                           struct plug_port_ctx_out *);\n+\n+struct engine_node;\n+\n+void en_plug_provider_run(struct engine_node *, void *);\n+\n+#ifdef  __cplusplus\n+}\n+#endif\n+\n+#endif /* plug.h */\ndiff --git a/lib/test-plug.c b/lib/test-plug.c\nnew file mode 100644\nindex 000000000..bddce23c4\n--- /dev/null\n+++ b/lib/test-plug.c\n@@ -0,0 +1,72 @@\n+/* Copyright (c) 2021, Canonical\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at:\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#include <config.h>\n+#include <errno.h>\n+\n+#include \"plug.h\"\n+#include \"plug-dummy.h\"\n+#include \"plug-provider.h\"\n+#include \"smap.h\"\n+#include \"sset.h\"\n+#include \"tests/ovstest.h\"\n+\n+static void\n+test_plug(struct ovs_cmdl_context *ctx OVS_UNUSED)\n+{\n+    const struct plug_class *plug_class;\n+\n+    ovs_assert(plug_unregister_provider(\"dummy\") == EINVAL);\n+\n+    ovs_assert(!plug_register_provider(&plug_dummy_class));\n+    plug_class = plug_get_provider(\"dummy\");\n+    ovs_assert(plug_register_provider(&plug_dummy_class) == EEXIST);\n+\n+    ovs_assert(sset_contains(plug_get_maintained_iface_options(plug_class),\n+                             \"plug-dummy-option\"));\n+\n+    struct smap fake_lport_options = SMAP_INITIALIZER(&fake_lport_options);\n+    struct plug_port_ctx_in ctx_in = {\n+        .op_type = PLUG_OP_CREATE,\n+        .lport_name = \"lsp1\",\n+        .lport_options = &fake_lport_options,\n+    };\n+    struct plug_port_ctx_out ctx_out;\n+    plug_port_prepare(plug_class, &ctx_in, &ctx_out);\n+    ovs_assert(!strcmp(ctx_out.name, \"lsp1\"));\n+    ovs_assert(!strcmp(ctx_out.type, \"internal\"));\n+    ovs_assert(!strcmp(smap_get(\n+            ctx_out.iface_options, \"plug-dummy-option\"), \"value\"));\n+\n+    plug_port_finish(plug_class, &ctx_in, &ctx_out);\n+    plug_port_ctx_destroy(plug_class, &ctx_in, &ctx_out);\n+    plug_destroy_all();\n+}\n+\n+static void\n+test_plug_main(int argc, char *argv[])\n+{\n+    set_program_name(argv[0]);\n+    static const struct ovs_cmdl_command commands[] = {\n+        {\"run\", NULL, 0, 0, test_plug, OVS_RO},\n+        {NULL, NULL, 0, 0, NULL, OVS_RO},\n+    };\n+    struct ovs_cmdl_context ctx;\n+    ctx.argc = argc - 1;\n+    ctx.argv = argv + 1;\n+    ovs_cmdl_run_command(&ctx, commands);\n+}\n+\n+OVSTEST_REGISTER(\"test-plug\", test_plug_main);\ndiff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml\nindex 3d2910358..152367a89 100644\n--- a/ovn-architecture.7.xml\n+++ b/ovn-architecture.7.xml\n@@ -67,8 +67,9 @@\n     <li>\n       One or more (usually many) <dfn>hypervisors</dfn>.  Hypervisors must run\n       Open vSwitch and implement the interface described in\n-      <code>Documentation/topics/integration.rst</code> in the OVN source tree.\n-      Any hypervisor platform supported by Open vSwitch is acceptable.\n+      <code>Documentation/topics/integration.rst</code> in the Open vSwitch\n+      source tree.  Any hypervisor platform supported by Open vSwitch is\n+      acceptable.\n     </li>\n \n     <li>\n@@ -318,11 +319,19 @@\n \n     <li>\n       On a hypervisor, any VIFs that are to be attached to logical networks.\n-      The hypervisor itself, or the integration between Open vSwitch and the\n-      hypervisor (described in\n-      <code>Documentation/topics/integration.rst</code>) takes care of this.\n-      (This is not part of OVN or new to OVN; this is pre-existing integration\n-      work that has already been done on hypervisors that support OVS.)\n+      For instances connected through software emulated ports such as TUN/TAP\n+      or VETH pairs, the hypervisor itself will normally create ports and plug\n+      them into the integration bridge.  For instances connected through\n+      representor ports, typically used with hardware offload, the\n+      <code>ovn-controller</code> may on CMS direction consult a plugging\n+      provider library for representor port lookup and plug them into the\n+      integration bridge (please refer to\n+      <code>Documentation/topics/plugging-providers.rst</code> for more\n+      information).  In both cases the conventions described in\n+      <code>Documentation/topics/integration.rst</code> in the Open vSwitch\n+      source tree is followed to ensure mapping between OVN logical port and\n+      VIF.  (This is pre-existing integration work that has already been done\n+      on hypervisors that support OVS.)\n     </li>\n \n     <li>\n@@ -921,12 +930,12 @@\n       Eventually, a user powers on the VM that owns the VIF.  On the hypervisor\n       where the VM is powered on, the integration between the hypervisor and\n       Open vSwitch (described in\n-      <code>Documentation/topics/integration.rst</code>) adds the VIF to the OVN\n-      integration bridge and stores <var>vif-id</var> in\n-      <code>external_ids</code>:<code>iface-id</code> to indicate that the\n-      interface is an instantiation of the new VIF.  (None of this code is new\n-      in OVN; this is pre-existing integration work that has already been done\n-      on hypervisors that support OVS.)\n+      <code>Documentation/topics/integration.rst</code> in the Open vSwitch\n+      source tree) adds the VIF to the OVN integration bridge and stores\n+      <var>vif-id</var> in <code>external_ids</code>:<code>iface-id</code> to\n+      indicate that the interface is an instantiation of the new VIF.  (None of\n+      this code is new in OVN; this is pre-existing integration work that has\n+      already been done on hypervisors that support OVS.)\n     </li>\n \n     <li>\ndiff --git a/tests/automake.mk b/tests/automake.mk\nindex 5b890d644..ad8978541 100644\n--- a/tests/automake.mk\n+++ b/tests/automake.mk\n@@ -38,7 +38,8 @@ TESTSUITE_AT = \\\n \ttests/ovn-ipam.at \\\n \ttests/ovn-features.at \\\n \ttests/ovn-lflow-cache.at \\\n-\ttests/ovn-ipsec.at\n+\ttests/ovn-ipsec.at \\\n+\ttests/ovn-plug.at\n \n SYSTEM_KMOD_TESTSUITE_AT = \\\n \ttests/system-common-macros.at \\\n@@ -248,6 +249,7 @@ tests_ovstest_SOURCES = \\\n \tcontroller/ofctrl-seqno.c \\\n \tcontroller/ofctrl-seqno.h \\\n \tlib/test-ovn-features.c \\\n+\tlib/test-plug.c \\\n \tnorthd/test-ipam.c \\\n \tnorthd/ipam.c \\\n \tnorthd/ipam.h\ndiff --git a/tests/ovn-plug.at b/tests/ovn-plug.at\nnew file mode 100644\nindex 000000000..d5c6a1b6d\n--- /dev/null\n+++ b/tests/ovn-plug.at\n@@ -0,0 +1,8 @@\n+#\n+# Unit tests for the lib/plug.c module.\n+#\n+AT_BANNER([OVN unit tests - plug])\n+\n+AT_SETUP([unit test -- plugging infrastructure tests])\n+AT_CHECK([ovstest test-plug run], [0], [])\n+AT_CLEANUP\n",
    "prefixes": [
        "ovs-dev",
        "v4",
        "6/9"
    ]
}