get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 1551377,
    "url": "http://patchwork.ozlabs.org/api/patches/1551377/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/ovn/patch/20211105133019.1991280-2-frode.nordahl@canonical.com/",
    "project": {
        "id": 68,
        "url": "http://patchwork.ozlabs.org/api/projects/68/?format=api",
        "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": "<20211105133019.1991280-2-frode.nordahl@canonical.com>",
    "list_archive_url": null,
    "date": "2021-11-05T13:30:16",
    "name": "[ovs-dev,v9,1/4] lib: Add infrastructure for VIF plug providers.",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "bc79a340c0a40788c82444ef2859319c878ef006",
    "submitter": {
        "id": 77851,
        "url": "http://patchwork.ozlabs.org/api/people/77851/?format=api",
        "name": "Frode Nordahl",
        "email": "frode.nordahl@canonical.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/ovn/patch/20211105133019.1991280-2-frode.nordahl@canonical.com/mbox/",
    "series": [
        {
            "id": 270562,
            "url": "http://patchwork.ozlabs.org/api/series/270562/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/ovn/list/?series=270562",
            "date": "2021-11-05T13:30:19",
            "name": "Introduce infrastructure for VIF plug providers.",
            "version": 9,
            "mbox": "http://patchwork.ozlabs.org/series/270562/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/1551377/comments/",
    "check": "fail",
    "checks": "http://patchwork.ozlabs.org/api/patches/1551377/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": [
            "bilbo.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=Rz4aI3Fa;\n\tdkim-atps=neutral",
            "ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=140.211.166.138; helo=smtp1.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN>)",
            "smtp4.osuosl.org (amavisd-new);\n dkim=pass (2048-bit key) header.d=canonical.com"
        ],
        "Received": [
            "from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138])\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 bilbo.ozlabs.org (Postfix) with ESMTPS id 4Hm1bz6XKbz9sf8\n\tfor <incoming@patchwork.ozlabs.org>; Sat,  6 Nov 2021 00:30:55 +1100 (AEDT)",
            "from localhost (localhost [127.0.0.1])\n\tby smtp1.osuosl.org (Postfix) with ESMTP id CDFCA825DF;\n\tFri,  5 Nov 2021 13:30:53 +0000 (UTC)",
            "from smtp1.osuosl.org ([127.0.0.1])\n\tby localhost (smtp1.osuosl.org [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id b_9PqkmwfMGJ; Fri,  5 Nov 2021 13:30:45 +0000 (UTC)",
            "from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp1.osuosl.org (Postfix) with ESMTPS id 59FBA826BB;\n\tFri,  5 Nov 2021 13:30:42 +0000 (UTC)",
            "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 843A8C0042;\n\tFri,  5 Nov 2021 13:30:40 +0000 (UTC)",
            "from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 220ABC000E\n for <dev@openvswitch.org>; Fri,  5 Nov 2021 13:30:38 +0000 (UTC)",
            "from localhost (localhost [127.0.0.1])\n by smtp4.osuosl.org (Postfix) with ESMTP id 57DC540762\n for <dev@openvswitch.org>; Fri,  5 Nov 2021 13:30:37 +0000 (UTC)",
            "from smtp4.osuosl.org ([127.0.0.1])\n by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024)\n with ESMTP id Vm-_VMtnkbDf for <dev@openvswitch.org>;\n Fri,  5 Nov 2021 13:30:33 +0000 (UTC)",
            "from smtp-relay-canonical-0.canonical.com\n (smtp-relay-canonical-0.canonical.com [185.125.188.120])\n by smtp4.osuosl.org (Postfix) with ESMTPS id 17DBA40755\n for <dev@openvswitch.org>; Fri,  5 Nov 2021 13:30:32 +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-0.canonical.com (Postfix) with ESMTPSA id 003143F1D4;\n Fri,  5 Nov 2021 13:30:27 +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=1636119028;\n bh=PmstvvPxbpfC52KLC8CjEXK5JYGUPp5GBb/m660uNRY=;\n h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References:\n MIME-Version;\n b=Rz4aI3FaoEwPxQuWgL1wHn1wzcRq8fpbvOKOYYblpZataF9B3lSSsGT37qBOGVq1C\n 8xkrJfRXLgKQEKuzp5gYEb6XpM65cZK+3PyupZ3ndQ14ZW+jHRL+gM21LYdCYxw1CL\n i2MIo5HXuOf6SVAdxW0WWJ09cLYsKEX05rdIsza2ZFmqCbapQxLX2P9Ke1iXcaxR8R\n TJvgIvEDbGNDXE4RuGqfau1HPnTDcIBJbfLhNuyTwQ0WpZ3fBfbh+MmR1cPxd/YF9P\n DqqoEPtglEpcSqKh6kWU4V4vE7zm32IUdFN2Kiq2fEcswCx2EknVRpY+7AqeTeQMGK\n QXWuHjv74gNkQ==",
        "From": "Frode Nordahl <frode.nordahl@canonical.com>",
        "To": "dev@openvswitch.org",
        "Date": "Fri,  5 Nov 2021 14:30:16 +0100",
        "Message-Id": "<20211105133019.1991280-2-frode.nordahl@canonical.com>",
        "X-Mailer": "git-send-email 2.32.0",
        "In-Reply-To": "<20211105133019.1991280-1-frode.nordahl@canonical.com>",
        "References": "<20211105133019.1991280-1-frode.nordahl@canonical.com>",
        "MIME-Version": "1.0",
        "Subject": "[ovs-dev] [PATCH ovn v9 1/4] lib: Add infrastructure for VIF plug\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 lib/vif-plug-provider module contains the infrastructure for\nregistering VIF plug provider classes which may be hosted inside\nor outside the core OVN repository.\n\nNew controller/vif-plug module adds internal interface for\ninteracting with the VIF plug providers.\n\nExtend build system to allow building of built-in VIF plug\nproviders and linking an externally built VIF plug provider.\n\nSigned-off-by: Frode Nordahl <frode.nordahl@canonical.com>\n---\n Documentation/automake.mk                     |   2 +\n Documentation/topics/index.rst                |   1 +\n .../topics/vif-plug-providers/index.rst       |  32 +\n .../vif-plug-providers/vif-plug-providers.rst | 209 ++++++\n acinclude.m4                                  |  49 ++\n configure.ac                                  |   2 +\n controller/automake.mk                        |   4 +-\n controller/test-vif-plug.c                    |  72 ++\n controller/vif-plug.c                         | 634 ++++++++++++++++++\n controller/vif-plug.h                         |  80 +++\n lib/automake.mk                               |  10 +-\n lib/vif-plug-provider.c                       | 204 ++++++\n lib/vif-plug-provider.h                       | 163 +++++\n lib/vif-plug-providers/dummy/vif-plug-dummy.c | 120 ++++\n ovn-architecture.7.xml                        |  35 +-\n tests/automake.mk                             |  13 +-\n tests/ovn-vif-plug.at                         |   8 +\n 17 files changed, 1622 insertions(+), 16 deletions(-)\n create mode 100644 Documentation/topics/vif-plug-providers/index.rst\n create mode 100644 Documentation/topics/vif-plug-providers/vif-plug-providers.rst\n create mode 100644 controller/test-vif-plug.c\n create mode 100644 controller/vif-plug.c\n create mode 100644 controller/vif-plug.h\n create mode 100644 lib/vif-plug-provider.c\n create mode 100644 lib/vif-plug-provider.h\n create mode 100644 lib/vif-plug-providers/dummy/vif-plug-dummy.c\n create mode 100644 tests/ovn-vif-plug.at",
    "diff": "diff --git a/Documentation/automake.mk b/Documentation/automake.mk\nindex b3fd3d62b..704c80671 100644\n--- a/Documentation/automake.mk\n+++ b/Documentation/automake.mk\n@@ -28,6 +28,8 @@ 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/vif-plug-providers/index.rst \\\n+\tDocumentation/topics/vif-plug-providers/vif-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..e9e49c742 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+   vif-plug-providers/index\n    testing\n \n .. list-table::\ndiff --git a/Documentation/topics/vif-plug-providers/index.rst b/Documentation/topics/vif-plug-providers/index.rst\nnew file mode 100644\nindex 000000000..b7552ac4c\n--- /dev/null\n+++ b/Documentation/topics/vif-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+VIF Plug Providers\n+==================\n+\n+\n+.. toctree::\n+   :maxdepth: 2\n+\n+   vif-plug-providers\ndiff --git a/Documentation/topics/vif-plug-providers/vif-plug-providers.rst b/Documentation/topics/vif-plug-providers/vif-plug-providers.rst\nnew file mode 100644\nindex 000000000..77ecf7e0f\n--- /dev/null\n+++ b/Documentation/topics/vif-plug-providers/vif-plug-providers.rst\n@@ -0,0 +1,209 @@\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+VIF Plug 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 `vif-plug-type` key with a value\n+   corresponding to the `const char *type` provided by the VIF plug provider\n+   implementation as well as a `requested-chassis` key with a value pointing at\n+   the name or hostname of the chassis it wants the VIF plugged on.  Additional\n+   VIF plug provider specific key/value pairs must be provided for successful\n+   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 with a\n+   requested_chassis column pointing at its chassis UUID.  When it encounters\n+   an entry with option `vif-plug-type` and it has registered a VIF plug\n+   provider matching that type, it will act on it even if no local binding\n+   exists yet.\n+\n+4. It will fill the `struct vif_plug_port_ctx_in` as defined in\n+   `lib/vif-plug.h` with `op_type` set to 'PLUG_OP_CREATE' and make a call to\n+   the VIF plug providers `vif_plug_port_prepare` callback function.  VIF plug\n+   provider performs lookup and fills the `struct vif_plug_port_ctx_out` as\n+   defined in `lib/vif-plug.h`.\n+\n+5. `ovn-controller` creates a port and interface record in the local OVSDB\n+   using the details provided by the VIF 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\n+   `vif-plug-type`.  When the port creation is done a call will first be made\n+   to the VIF plug providers `vif_plug_port_finish` function and then to the\n+   `vif_plug_port_ctx_destroy` function to free any memory allocated by the VIF\n+   plug 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 each main loop iteration the `ovn-controller` will in addition to normal\n+   flow processing make a call to the VIF plug provider again similar to the\n+   first creation in case anything 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 VIF plug provider interface\n+-------------------------------\n+\n+The interface between internals of OVN and a VIF plug provider is a set of\n+callbacks as defined by the `struct vif_plug_class` in\n+`lib/vif-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, VIF plug providers made available at build time\n+will be registered by the identifier provided in the `const char *type`\n+pointer, at this time the `init` function pointer will be called if it is\n+non-NULL.\n+\n+> **Note**: apart from the `const char *type` pointer, no attempt will be made\n+            to access VIF plug provider data or functions before the call to\n+            the `init` has been made.\n+\n+On `ovn-controller` exit, the VIF plug providers registered in the above\n+mentioned procedure will have their `destroy` function pointer called if it is\n+non-NULL.\n+\n+If the VIF plug provider has internal lookup tables that need to be maintained\n+they can define a `run` function which will be called as part of the\n+`ovn-controller` main loop.  If there are any changes encountered the function\n+should return 'true' to signal that further processing is necessary, 'false'\n+otherwise.\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+`vif_plug_get_maintained_iface_options` function pointer if it is non-NULL.\n+This 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 VIF plug provider\n+`vif_plug_port_prepare` function pointer will be called with valid pointers to\n+`struct vif_plug_port_ctx_in` and `struct vif_plug_port_ctx_out` data\n+structures.  If the VIF plug provider implementation is able to perform lookup\n+it should fill the `struct vif_plug_port_ctx_out` data structure and return\n+'true'.  The `ovn-controller` will then create or update the port/interface\n+records and then call `vif_plug_port_finish` when the transactions commits and\n+`vif_plug_port_ctx_destroy` to free any allocated memory.  If the VIF plug\n+provider implementation is unable to perform lookup or prepare the desired\n+resource at this time, it should return 'false' which will tell the\n+`ovn-controller` to not plug the port, in this case it will not call\n+`vif_plug_port_finish` nor `vif_plug_port_ctx_destroy`.\n+\n+> **Note**: The VIF plug provider implementation should exhaust all\n+            non-blocking options to succeed with lookup from within the\n+            `vif_plug_port_prepare` handler, including refreshing lookup\n+            tables if necessary.\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+`vif-plug-type` from `external-ids:ovn-plugged`, fill\n+`struct vif_plug_port_ctx_in` with `op_type` set to 'PLUG_OP_REMOVE' and make a\n+call to `vif_plug_port_prepare`.  After the port and interface has been removed\n+a call will be made to `vif_plug_port_finish`.  Both calls will be made with\n+the pointer to `vif_plug_port_ctx_out` set to 'NULL', and no call will be made\n+to `vif_plug_port_ctx_destroy`.\n+\n+Building with in-tree VIF plug providers\n+----------------------------------------\n+\n+VIF plug providers hosted in the OVN repository live under\n+`lib/vif-plug-providers`:\n+\n+To enable them, provide the `--enable-vif-plug-providers` command line option\n+to the configure script when building OVN.\n+\n+Building with an externally provided VIF plug provider\n+------------------------------------------------------\n+\n+There is also infrastructure in place to support linking OVN with an externally\n+built VIF plug provider.\n+\n+This external VIF plug provider must define a NULL-terminated array of pointers\n+to `struct vif_plug_class` data structures named `vif_plug_provider_classes`.\n+Example:\n+\n+.. code-block:: none\n+\n+   const struct vif_plug_class *vif_plug_provider_classes[] = {\n+       &vif_plug_foo,\n+       NULL,\n+   };\n+\n+The name of the repository for the external VIF plug 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 VIF plug provider provide the\n+`--with-vif-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:\n+   https://docs.openvswitch.org/en/latest/topics/integration/\ndiff --git a/acinclude.m4 b/acinclude.m4\nindex e7f829520..3e4a54086 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_VIF_PLUG_PROVIDER\n+dnl\n+dnl Check for external VIF plug provider\n+AC_DEFUN([OVN_CHECK_VIF_PLUG_PROVIDER], [\n+  AC_ARG_VAR([VIF_PLUG_PROVIDER])\n+  AC_ARG_WITH(\n+    [vif-plug-provider],\n+    [AC_HELP_STRING([--with-vif-plug-provider=/path/to/provider/repository],\n+                    [Specify path to a configured and built VIF plug provider repository])],\n+    [if test \"$withval\" = yes; then\n+       if test -z \"$VIF_PLUG_PROVIDER\"; then\n+         AC_MSG_ERROR([To build with external VIF plug provider, specify the path to a configured and built plug provider repository --with-vif-plug-provider or in \\$VIF_PLUG_PROVIDER]),\n+       fi\n+       VIF_PLUG_PROVIDER=\"$(realpath $VIF_PLUG_PROVIDER)\"\n+     else\n+       VIF_PLUG_PROVIDER=\"$(realpath $withval)\"\n+     fi\n+     _vif_plug_provider_name=\"$(basename $VIF_PLUG_PROVIDER)\"\n+     if test ! -f \"$VIF_PLUG_PROVIDER/lib/.libs/lib${_vif_plug_provider_name}.la\"; then\n+       AC_MSG_ERROR([$withval is not a configured and built VIF plug provider library repository])\n+     fi\n+     VIF_PLUG_PROVIDER_LDFLAGS=\"-L$VIF_PLUG_PROVIDER/lib/.libs -l$_vif_plug_provider_name\"\n+    ],\n+    [VIF_PLUG_PROVIDER=no])\n+  AC_MSG_CHECKING([for VIF plug provider])\n+  AC_MSG_RESULT([$VIF_PLUG_PROVIDER])\n+  AC_SUBST([VIF_PLUG_PROVIDER_LDFLAGS])\n+  AM_CONDITIONAL([HAVE_VIF_PLUG_PROVIDER], [test \"$VIF_PLUG_PROVIDER\" != no])\n+  if test \"$VIF_PLUG_PROVIDER\" != no; then\n+    AC_DEFINE([HAVE_VIF_PLUG_PROVIDER], [1],\n+              [Build and link with external VIF plug provider])\n+  fi\n+])\n+\n+dnl OVN_ENABLE_VIF_PLUG\n+dnl\n+dnl Enable built-in plug providers\n+AC_DEFUN([OVN_ENABLE_VIF_PLUG], [\n+    AC_ARG_ENABLE(\n+      [vif-plug-providers],\n+      [AC_HELP_STRING([--enable-vif-plug-providers], [Enable building of built-in VIF plug providers])],\n+      [], [enable_vif_plug=no])\n+    AM_CONDITIONAL([ENABLE_VIF_PLUG], [test \"$enable_vif_plug\" != no])\n+    if test \"$enable_vif_plug\" != no; then\n+      AC_DEFINE([ENABLE_VIF_PLUG], [1],\n+                [Build built-in VIF plug providers])\n+    fi\n+])\ndiff --git a/configure.ac b/configure.ac\nindex d1b9b4d55..5a3a5987b 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -172,6 +172,8 @@ OVS_ENABLE_SPARSE\n OVS_CHECK_DDLOG([0.47])\n OVS_CHECK_PRAGMA_MESSAGE\n OVN_CHECK_OVS\n+OVN_CHECK_VIF_PLUG_PROVIDER\n+OVN_ENABLE_VIF_PLUG\n OVS_CTAGS_IDENTIFIERS\n AC_SUBST([OVS_CFLAGS])\n AC_SUBST([OVS_LDFLAGS])\ndiff --git a/controller/automake.mk b/controller/automake.mk\nindex ad2d68af2..9f9b49fe0 100644\n--- a/controller/automake.mk\n+++ b/controller/automake.mk\n@@ -37,7 +37,9 @@ controller_ovn_controller_SOURCES = \\\n \tcontroller/local_data.c \\\n \tcontroller/local_data.h \\\n \tcontroller/ovsport.h \\\n-\tcontroller/ovsport.c\n+\tcontroller/ovsport.c \\\n+\tcontroller/vif-plug.h \\\n+\tcontroller/vif-plug.c\n \n controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la\n man_MANS += controller/ovn-controller.8\ndiff --git a/controller/test-vif-plug.c b/controller/test-vif-plug.c\nnew file mode 100644\nindex 000000000..01ff37d8f\n--- /dev/null\n+++ b/controller/test-vif-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 \"vif-plug.h\"\n+#include \"vif-plug-provider.h\"\n+#include \"smap.h\"\n+#include \"sset.h\"\n+#include \"tests/ovstest.h\"\n+\n+static void\n+test_vif_plug(struct ovs_cmdl_context *ctx OVS_UNUSED)\n+{\n+    const struct vif_plug_class *vif_plug_class;\n+\n+    ovs_assert(vif_plug_provider_unregister(\"dummy\") == EINVAL);\n+\n+    ovs_assert(!vif_plug_provider_register(&vif_plug_dummy_class));\n+    vif_plug_class = vif_plug_provider_get(\"dummy\");\n+    ovs_assert(vif_plug_provider_register(&vif_plug_dummy_class) == EEXIST);\n+\n+    ovs_assert(\n+        sset_contains(\n+            vif_plug_get_maintained_iface_options(vif_plug_class),\n+            \"plug-dummy-option\"));\n+\n+    struct vif_plug_port_ctx_in ctx_in = {\n+        .op_type = PLUG_OP_CREATE,\n+        .lport_name = \"lsp1\",\n+        .lport_options = SMAP_INITIALIZER(&ctx_in.lport_options),\n+    };\n+    struct vif_plug_port_ctx_out ctx_out;\n+    vif_plug_port_prepare(vif_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, \"vif-plug-dummy-option\"), \"value\"));\n+\n+    vif_plug_port_finish(vif_plug_class, &ctx_in, &ctx_out);\n+    vif_plug_port_ctx_destroy(vif_plug_class, &ctx_in, &ctx_out);\n+    vif_plug_provider_destroy_all();\n+}\n+\n+static void\n+test_vif_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_vif_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-vif-plug\", test_vif_plug_main);\ndiff --git a/controller/vif-plug.c b/controller/vif-plug.c\nnew file mode 100644\nindex 000000000..1015a4c13\n--- /dev/null\n+++ b/controller/vif-plug.c\n@@ -0,0 +1,634 @@\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+\n+/* OVS includes */\n+#include \"lib/vswitch-idl.h\"\n+#include \"openvswitch/shash.h\"\n+#include \"openvswitch/vlog.h\"\n+\n+/* OVN includes */\n+#include \"binding.h\"\n+#include \"lib/ovn-sb-idl.h\"\n+#include \"lport.h\"\n+#include \"ovsport.h\"\n+#include \"vif-plug.h\"\n+#include \"vif-plug-provider.h\"\n+\n+VLOG_DEFINE_THIS_MODULE(vif_plug);\n+\n+#define OVN_PLUGGED_EXT_ID \"ovn-plugged\"\n+#define VIF_PLUG_OPTION_TYPE \"vif-plug-type\"\n+#define VIF_PLUG_OPTION_MTU_REQUEST \"vif-plug-mtu-request\"\n+\n+void\n+vif_plug_register_ovs_idl(struct ovsdb_idl *ovs_idl)\n+{\n+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_mtu_request);\n+}\n+\n+/* Get the class level 'maintained_iface_options' set. */\n+const struct sset *\n+vif_plug_get_maintained_iface_options(\n+        const struct vif_plug_class *vif_plug_class)\n+{\n+    return vif_plug_class->vif_plug_get_maintained_iface_options ?\n+           vif_plug_class->vif_plug_get_maintained_iface_options() : NULL;\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 vif_plug_port_ctx_destroy when done with it. */\n+bool\n+vif_plug_port_prepare(const struct vif_plug_class *vif_plug_class,\n+                      const struct vif_plug_port_ctx_in *ctx_in,\n+                      struct vif_plug_port_ctx_out *ctx_out)\n+{\n+    return vif_plug_class->vif_plug_port_prepare(ctx_in, ctx_out);\n+}\n+\n+/* Notify the VIF plug implementation that a port creation, update or removal\n+ * has been committed to the database. */\n+void\n+vif_plug_port_finish(const struct vif_plug_class *vif_plug_class,\n+                     const struct vif_plug_port_ctx_in *ctx_in,\n+                     struct vif_plug_port_ctx_out *ctx_out)\n+{\n+    vif_plug_class->vif_plug_port_finish(ctx_in, ctx_out);\n+}\n+\n+/* Free any data allocated to 'ctx_out' in a prevous call to\n+ * vif_plug_port_prepare. */\n+void\n+vif_plug_port_ctx_destroy(const struct vif_plug_class *vif_plug_class,\n+                          const struct vif_plug_port_ctx_in *ctx_in,\n+                          struct vif_plug_port_ctx_out *ctx_out)\n+{\n+    vif_plug_class->vif_plug_port_ctx_destroy(ctx_in, ctx_out);\n+}\n+\n+static struct vif_plug_port_ctx *\n+build_port_ctx(const struct vif_plug_class *vif_plug,\n+                  const enum vif_plug_op_type op_type,\n+                  const struct vif_plug_ctx_in *vif_plug_ctx_in,\n+                  const struct sbrec_port_binding *pb,\n+                  const struct ovsrec_interface *iface,\n+                  const char *iface_id)\n+{\n+    struct vif_plug_port_ctx *new_ctx = xzalloc(\n+        sizeof *new_ctx);\n+\n+    new_ctx->vif_plug = vif_plug;\n+    new_ctx->vif_plug_port_ctx_in.op_type = op_type;\n+    new_ctx->vif_plug_port_ctx_in.ovs_table = vif_plug_ctx_in->ovs_table;\n+    new_ctx->vif_plug_port_ctx_in.br_int = vif_plug_ctx_in->br_int;\n+    new_ctx->vif_plug_port_ctx_in.lport_name = pb ?\n+        xstrdup(pb->logical_port) : iface_id ? xstrdup(iface_id) : NULL;\n+    /* Prepare vif_plug_port_ctx_in smaps for use.\n+     *\n+     * Note that smap_init does not allocate memory.  Any memory allocated by\n+     * putting data into the vif_plug_port_ctx_in smaps will be destroyed by\n+     * calls to smap_destroy in destroy_port_ctx */\n+    smap_init(&new_ctx->vif_plug_port_ctx_in.lport_options);\n+    smap_init(&new_ctx->vif_plug_port_ctx_in.iface_options);\n+\n+    if (pb) {\n+        smap_clone(&new_ctx->vif_plug_port_ctx_in.lport_options,\n+                   &pb->options);\n+    }\n+\n+    if (iface) {\n+        new_ctx->vif_plug_port_ctx_in.iface_name = xstrdup(iface->name);\n+        new_ctx->vif_plug_port_ctx_in.iface_type = xstrdup(iface->type);\n+        smap_clone(&new_ctx->vif_plug_port_ctx_in.iface_options,\n+                   &iface->options);\n+    }\n+\n+    /* Prepare vif_plug_port_ctx_out smaps for use.\n+     *\n+     * Note that smap_init does not allocate memory.  Any memory allocated by\n+     * putting data into the vif_plug_port_ctx_out smaps is the responsibility\n+     * of the VIF plug provider through a call to vif_plug_port_ctx_destroy. */\n+    smap_init(&new_ctx->vif_plug_port_ctx_out.iface_options);\n+\n+    return new_ctx;\n+}\n+\n+static void\n+destroy_port_ctx(struct vif_plug_port_ctx *ctx)\n+{\n+    smap_destroy(&ctx->vif_plug_port_ctx_in.lport_options);\n+    smap_destroy(&ctx->vif_plug_port_ctx_in.iface_options);\n+    if (ctx->vif_plug_port_ctx_in.lport_name) {\n+        free((char *)ctx->vif_plug_port_ctx_in.lport_name);\n+    }\n+    if (ctx->vif_plug_port_ctx_in.iface_name) {\n+        free((char *)ctx->vif_plug_port_ctx_in.iface_name);\n+    }\n+    if (ctx->vif_plug_port_ctx_in.iface_type) {\n+        free((char *)ctx->vif_plug_port_ctx_in.iface_type);\n+    }\n+    /* Note that data associated with ctx->vif_plug_port_ctx_out must be\n+     * destroyed by the plug provider implementation with a call to\n+     * vif_plug_port_ctx_destroy prior to calling this function */\n+    free(ctx);\n+}\n+\n+/* Our contract with the VIF plug provider is that vif_plug_port_finish\n+ * will be called with vif_plug_port_ctx_* objects once the transaction\n+ * commits.  To handle this we keep track of in-flight deletions\n+ * and changes.  The tracking data will be cleared after commit at the end of\n+ * the ovn-controller main loop. */\n+static void\n+transact_delete_port(const struct vif_plug_ctx_in *vif_plug_ctx_in,\n+                     const struct vif_plug_ctx_out *vif_plug_ctx_out,\n+                     const struct vif_plug_port_ctx *vif_plug_port_ctx,\n+                     const struct ovsrec_port *port)\n+{\n+    shash_add(vif_plug_ctx_out->deleted_iface_ids,\n+              vif_plug_port_ctx->vif_plug_port_ctx_in.lport_name,\n+              vif_plug_port_ctx);\n+    ovsport_remove(vif_plug_ctx_in->br_int, port);\n+}\n+\n+static void\n+transact_create_port(const struct vif_plug_ctx_in *vif_plug_ctx_in,\n+                     const struct vif_plug_ctx_out *vif_plug_ctx_out,\n+                     const struct vif_plug_port_ctx *vif_plug_port_ctx,\n+                     const struct smap *iface_external_ids,\n+                     const int64_t mtu_request)\n+{\n+    shash_add(vif_plug_ctx_out->changed_iface_ids,\n+              vif_plug_port_ctx->vif_plug_port_ctx_in.lport_name,\n+              vif_plug_port_ctx);\n+    ovsport_create(vif_plug_ctx_in->ovs_idl_txn, vif_plug_ctx_in->br_int,\n+                   vif_plug_port_ctx->vif_plug_port_ctx_out.name,\n+                   vif_plug_port_ctx->vif_plug_port_ctx_out.type,\n+                   NULL, iface_external_ids,\n+                   &vif_plug_port_ctx->vif_plug_port_ctx_out.iface_options,\n+                   mtu_request);\n+}\n+\n+static void\n+transact_update_port(const struct ovsrec_interface *iface_rec,\n+                     const struct vif_plug_ctx_in *vif_plug_ctx_in OVS_UNUSED,\n+                     const struct vif_plug_ctx_out *vif_plug_ctx_out,\n+                     const struct vif_plug_port_ctx *vif_plug_port_ctx,\n+                     const struct smap *iface_external_ids,\n+                     const int64_t mtu_request)\n+{\n+    shash_add(vif_plug_ctx_out->changed_iface_ids,\n+              vif_plug_port_ctx->vif_plug_port_ctx_in.lport_name,\n+              vif_plug_port_ctx);\n+    ovsport_update_iface(\n+        iface_rec,\n+        vif_plug_port_ctx->vif_plug_port_ctx_out.type,\n+        iface_external_ids,\n+        NULL,\n+        &vif_plug_port_ctx->vif_plug_port_ctx_out.iface_options,\n+        vif_plug_get_maintained_iface_options(\n+            vif_plug_port_ctx->vif_plug),\n+        mtu_request);\n+}\n+\n+\n+static bool\n+consider_unplug_iface(const struct ovsrec_interface *iface,\n+                      const struct sbrec_port_binding *pb,\n+                      struct vif_plug_ctx_in *vif_plug_ctx_in,\n+                      struct vif_plug_ctx_out *vif_plug_ctx_out)\n+{\n+    const char *vif_plug_type = smap_get(&iface->external_ids,\n+                                         OVN_PLUGGED_EXT_ID);\n+    const char *iface_id = smap_get(&iface->external_ids, \"iface-id\");\n+    const struct ovsrec_port *port = ovsport_lookup_by_interface(\n+        vif_plug_ctx_in->ovsrec_port_by_interfaces,\n+        (struct ovsrec_interface *) iface);\n+\n+    if (vif_plug_type && iface_id && port) {\n+        const struct vif_plug_class *vif_plug;\n+        if (!(vif_plug = vif_plug_provider_get(vif_plug_type))) {\n+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n+            VLOG_WARN_RL(&rl,\n+                         \"Unable to open VIF plug provider for \"\n+                         \"%s %s iface-id %s\",\n+                         VIF_PLUG_OPTION_TYPE, vif_plug_type, iface_id);\n+            /* While we are unable to handle this, asking for a recompute\n+             * will not change that fact. */\n+            return true;\n+        }\n+        if (!vif_plug_ctx_in->chassis_rec || !vif_plug_ctx_in->br_int\n+            || !vif_plug_ctx_in->ovs_idl_txn)\n+        {\n+            /* Some of our prerequisites are not available, ask for a\n+             * recompute. */\n+            return false;\n+        }\n+\n+        /* Our contract with the VIF plug provider is that vif_plug_port_finish\n+         * will be called with a vif_plug_port_ctx_in object once the\n+         * transaction commits.\n+         *\n+         * Since this happens asynchronously we need to allocate memory for\n+         * and duplicate any database references so that they stay valid.\n+         *\n+         * The data is freed with a call to destroy_port_ctx after the\n+         * transaction completes at the end of the ovn-controller main\n+         * loop. */\n+        struct vif_plug_port_ctx *vif_plug_port_ctx = build_port_ctx(\n+            vif_plug, PLUG_OP_REMOVE, vif_plug_ctx_in, pb, iface, iface_id);\n+\n+        if (!vif_plug_port_prepare(vif_plug,\n+                                   &vif_plug_port_ctx->vif_plug_port_ctx_in,\n+                                   NULL)) {\n+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n+            VLOG_INFO_RL(&rl,\n+                         \"Not unplugging iface %s (iface-id %s) on direction \"\n+                         \"from VIF plug provider.\",\n+                         iface->name, iface_id);\n+            destroy_port_ctx(vif_plug_port_ctx);\n+            return true;\n+        }\n+        VLOG_INFO(\"Unplugging port %s from %s for iface-id %s on this \"\n+                  \"chassis.\",\n+                  port->name,\n+                  vif_plug_ctx_in->br_int->name,\n+                  iface_id);\n+\n+        /* Add and track delete operation to the transaction */\n+        transact_delete_port(vif_plug_ctx_in, vif_plug_ctx_out,\n+                             vif_plug_port_ctx, port);\n+        return true;\n+    }\n+    return true;\n+}\n+\n+static int64_t\n+get_plug_mtu_request(const struct smap *lport_options)\n+{\n+    return smap_get_int(lport_options, VIF_PLUG_OPTION_MTU_REQUEST, 0);\n+}\n+\n+static bool\n+consider_plug_lport_create__(const struct vif_plug_class *vif_plug,\n+                             const struct smap *iface_external_ids,\n+                             const struct sbrec_port_binding *pb,\n+                             struct vif_plug_ctx_in *vif_plug_ctx_in,\n+                             struct vif_plug_ctx_out *vif_plug_ctx_out)\n+{\n+    if (!vif_plug_ctx_in->chassis_rec || !vif_plug_ctx_in->br_int\n+        || !vif_plug_ctx_in->ovs_idl_txn) {\n+        /* Some of our prerequisites are not available, ask for a recompute. */\n+        return false;\n+    }\n+\n+    /* Our contract with the VIF plug provider is that vif_plug_port_finish\n+     * will be called with vif_plug_port_ctx_in and vif_plug_port_ctx_out\n+     * objects once the transaction commits.\n+     *\n+     * Since this happens asynchronously we need to allocate memory for\n+     * and duplicate any database references so that they stay valid.\n+     *\n+     * The data is freed with a call to destroy_port_ctx after the\n+     * transaction completes at the end of the ovn-controller main\n+     * loop. */\n+    struct vif_plug_port_ctx *vif_plug_port_ctx = build_port_ctx(\n+        vif_plug, PLUG_OP_CREATE, vif_plug_ctx_in, pb, NULL, NULL);\n+\n+    if (!vif_plug_port_prepare(vif_plug,\n+                           &vif_plug_port_ctx->vif_plug_port_ctx_in,\n+                           &vif_plug_port_ctx->vif_plug_port_ctx_out)) {\n+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n+        VLOG_INFO_RL(&rl,\n+                     \"Not plugging lport %s on direction from VIF plug \"\n+                     \"provider.\",\n+                     pb->logical_port);\n+        destroy_port_ctx(vif_plug_port_ctx);\n+        return true;\n+    }\n+\n+    VLOG_INFO(\"Plugging port %s into %s for lport %s on this \"\n+              \"chassis.\",\n+              vif_plug_port_ctx->vif_plug_port_ctx_out.name,\n+              vif_plug_ctx_in->br_int->name,\n+              pb->logical_port);\n+    transact_create_port(vif_plug_ctx_in, vif_plug_ctx_out,\n+                         vif_plug_port_ctx,\n+                         iface_external_ids,\n+                         get_plug_mtu_request(&pb->options));\n+    return true;\n+}\n+\n+static bool\n+consider_plug_lport_update__(const struct vif_plug_class *vif_plug,\n+                             const struct smap *iface_external_ids,\n+                             const struct sbrec_port_binding *pb,\n+                             struct local_binding *lbinding,\n+                             struct vif_plug_ctx_in *vif_plug_ctx_in,\n+                             struct vif_plug_ctx_out *vif_plug_ctx_out)\n+{\n+    if (!vif_plug_ctx_in->chassis_rec || !vif_plug_ctx_in->br_int\n+        || !vif_plug_ctx_in->ovs_idl_txn) {\n+        /* Some of our prerequisites are not available, ask for a recompute. */\n+        return false;\n+    }\n+    /* Our contract with the VIF plug provider is that vif_plug_port_finish\n+     * will be called with vif_plug_port_ctx_in and vif_plug_port_ctx_out\n+     * objects once the transaction commits.\n+     *\n+     * Since this happens asynchronously we need to allocate memory for\n+     * and duplicate any database references so that they stay valid.\n+     *\n+     * The data is freed with a call to destroy_port_ctx after the\n+     * transaction completes at the end of the ovn-controller main\n+     * loop. */\n+    struct vif_plug_port_ctx *vif_plug_port_ctx = build_port_ctx(\n+        vif_plug, PLUG_OP_CREATE, vif_plug_ctx_in, pb, NULL, NULL);\n+\n+    if (!vif_plug_port_prepare(vif_plug,\n+                               &vif_plug_port_ctx->vif_plug_port_ctx_in,\n+                               &vif_plug_port_ctx->vif_plug_port_ctx_out)) {\n+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n+        VLOG_INFO_RL(&rl,\n+                     \"Not updating lport %s on direction from VIF plug \"\n+                     \"provider.\",\n+                     pb->logical_port);\n+        destroy_port_ctx(vif_plug_port_ctx);\n+        return true;\n+    }\n+\n+    if (strcmp(lbinding->iface->name,\n+               vif_plug_port_ctx->vif_plug_port_ctx_out.name)) {\n+        VLOG_WARN(\"Attempt of incompatible change to existing \"\n+                  \"port detected, please recreate port: %s\",\n+                   pb->logical_port);\n+        vif_plug_port_ctx_destroy(vif_plug,\n+                                  &vif_plug_port_ctx->vif_plug_port_ctx_in,\n+                                  &vif_plug_port_ctx->vif_plug_port_ctx_out);\n+        destroy_port_ctx(vif_plug_port_ctx);\n+        return false;\n+    }\n+    VLOG_DBG(\"updating iface for: %s\", pb->logical_port);\n+    transact_update_port(lbinding->iface, vif_plug_ctx_in, vif_plug_ctx_out,\n+                         vif_plug_port_ctx, iface_external_ids,\n+                         get_plug_mtu_request(&pb->options));\n+\n+    return true;\n+}\n+\n+static bool\n+consider_plug_lport(const struct sbrec_port_binding *pb,\n+                    struct local_binding *lbinding,\n+                    struct vif_plug_ctx_in *vif_plug_ctx_in,\n+                    struct vif_plug_ctx_out *vif_plug_ctx_out)\n+{\n+    bool ret = true;\n+    if (lport_can_bind_on_this_chassis(vif_plug_ctx_in->chassis_rec, pb)\n+        && pb->requested_chassis == vif_plug_ctx_in->chassis_rec) {\n+        const char *vif_plug_type = smap_get(&pb->options,\n+                                             VIF_PLUG_OPTION_TYPE);\n+        if (!vif_plug_type) {\n+            /* Nothing for us to do and we don't need a recompute. */\n+            return true;\n+        }\n+\n+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n+        const struct vif_plug_class *vif_plug;\n+        if (!(vif_plug = vif_plug_provider_get(vif_plug_type))) {\n+            VLOG_WARN_RL(&rl,\n+                         \"Unable to open VIF plug provider for %s: '%s' \"\n+                         \"lport %s\",\n+                         VIF_PLUG_OPTION_TYPE,\n+                         vif_plug_type,\n+                         pb->logical_port);\n+            /* While we are unable to handle this, asking for a recompute will\n+             * not change that fact. */\n+            return true;\n+        }\n+        const struct smap iface_external_ids = SMAP_CONST2(\n+                &iface_external_ids,\n+                OVN_PLUGGED_EXT_ID, vif_plug_type,\n+                \"iface-id\", pb->logical_port);\n+        if (lbinding && lbinding->iface) {\n+            if (!smap_get(&lbinding->iface->external_ids,\n+                          OVN_PLUGGED_EXT_ID))\n+            {\n+                VLOG_WARN_RL(&rl,\n+                             \"CMS requested plugging of lport %s, but a port \"\n+                             \"that is not maintained by OVN already exsist \"\n+                             \"in local vSwitch: \"UUID_FMT,\n+                             pb->logical_port,\n+                             UUID_ARGS(&lbinding->iface->header_.uuid));\n+                return false;\n+            }\n+            ret = consider_plug_lport_update__(vif_plug, &iface_external_ids,\n+                                               pb, lbinding, vif_plug_ctx_in,\n+                                               vif_plug_ctx_out);\n+        } else {\n+            ret = consider_plug_lport_create__(vif_plug, &iface_external_ids,\n+                                               pb, vif_plug_ctx_in,\n+                                               vif_plug_ctx_out);\n+        }\n+    }\n+\n+    return ret;\n+}\n+\n+static bool\n+vif_plug_iface_touched_this_txn(\n+        const struct vif_plug_ctx_out *vif_plug_ctx_out,\n+        const char *iface_id)\n+{\n+    return shash_find(vif_plug_ctx_out->changed_iface_ids, iface_id)\n+           || shash_find(vif_plug_ctx_out->deleted_iface_ids, iface_id);\n+}\n+\n+static bool\n+vif_plug_handle_lport_vif(const struct sbrec_port_binding *pb,\n+                          struct vif_plug_ctx_in *vif_plug_ctx_in,\n+                          struct vif_plug_ctx_out *vif_plug_ctx_out,\n+                          bool can_unplug)\n+{\n+    if (vif_plug_iface_touched_this_txn(vif_plug_ctx_out, pb->logical_port)) {\n+        return true;\n+    }\n+    bool handled = true;\n+    struct local_binding *lbinding = local_binding_find(\n+        vif_plug_ctx_in->local_bindings, pb->logical_port);\n+\n+    if (lport_can_bind_on_this_chassis(vif_plug_ctx_in->chassis_rec, pb)) {\n+        handled &= consider_plug_lport(pb, lbinding,\n+                                       vif_plug_ctx_in, vif_plug_ctx_out);\n+    } else if (can_unplug && lbinding && lbinding->iface) {\n+        handled &= consider_unplug_iface(lbinding->iface, pb,\n+                                         vif_plug_ctx_in, vif_plug_ctx_out);\n+    }\n+    return handled;\n+}\n+\n+static bool\n+vif_plug_handle_iface(const struct ovsrec_interface *iface_rec,\n+                      struct vif_plug_ctx_in *vif_plug_ctx_in,\n+                      struct vif_plug_ctx_out *vif_plug_ctx_out,\n+                      bool can_unplug)\n+{\n+    bool handled = true;\n+    const char *vif_plug_type = smap_get(&iface_rec->external_ids,\n+                                         OVN_PLUGGED_EXT_ID);\n+    const char *iface_id = smap_get(&iface_rec->external_ids, \"iface-id\");\n+    if (!vif_plug_type || !iface_id\n+        || vif_plug_iface_touched_this_txn(vif_plug_ctx_out, iface_id)) {\n+        return true;\n+    }\n+    struct local_binding *lbinding = local_binding_find(\n+        vif_plug_ctx_in->local_bindings, iface_id);\n+    const struct sbrec_port_binding *pb = lport_lookup_by_name(\n+        vif_plug_ctx_in->sbrec_port_binding_by_name, iface_id);\n+    if (pb && lbinding\n+        && lport_can_bind_on_this_chassis(vif_plug_ctx_in->chassis_rec, pb)) {\n+        /* Something changed on a interface we have previously plugged,\n+         * consider updating it */\n+        handled &= consider_plug_lport(pb, lbinding,\n+                                       vif_plug_ctx_in, vif_plug_ctx_out);\n+    } else if (can_unplug\n+               && (!pb\n+                   || !lport_can_bind_on_this_chassis(\n+                       vif_plug_ctx_in->chassis_rec, pb))) {\n+        /* No lport for this interface or it is destined for different chassis,\n+         * consuder unplugging it */\n+        handled &= consider_unplug_iface(iface_rec, pb,\n+                                         vif_plug_ctx_in, vif_plug_ctx_out);\n+    }\n+    return handled;\n+}\n+\n+/* On initial startup or on IDL reconnect, several rounds of the main loop may\n+ * run before data is actually loaded in the IDL.  This situation is currently\n+ * not reflected in a call to ovsdb_idl_has_ever_connected().  Until we find\n+ * the root of this issue we need this counter so that we do not erronously\n+ * unplug ports because the data is just not loaded yet.\n+ */\n+#define VIF_PLUG_PRIME_IDL_COUNT_SEEED 10\n+static int vif_plug_prime_idl_count = VIF_PLUG_PRIME_IDL_COUNT_SEEED;\n+\n+void\n+vif_plug_reset_idl_prime_counter(void)\n+{\n+    vif_plug_prime_idl_count = VIF_PLUG_PRIME_IDL_COUNT_SEEED;\n+}\n+\n+void\n+vif_plug_run(struct vif_plug_ctx_in *vif_plug_ctx_in,\n+             struct vif_plug_ctx_out *vif_plug_ctx_out)\n+{\n+    if (vif_plug_prime_idl_count && --vif_plug_prime_idl_count > 0) {\n+        VLOG_DBG(\"vif_plug_run: vif_plug_prime_idl_count=%d, will not unplug \"\n+                 \"ports in this iteration.\", vif_plug_prime_idl_count);\n+    }\n+\n+    if (!vif_plug_ctx_in->chassis_rec) {\n+        return;\n+    }\n+    const struct ovsrec_interface *iface_rec;\n+    OVSREC_INTERFACE_TABLE_FOR_EACH (iface_rec,\n+                                     vif_plug_ctx_in->iface_table) {\n+        vif_plug_handle_iface(iface_rec, vif_plug_ctx_in, vif_plug_ctx_out,\n+                              !vif_plug_prime_idl_count);\n+    }\n+\n+    struct sbrec_port_binding *target =\n+        sbrec_port_binding_index_init_row(\n+            vif_plug_ctx_in->sbrec_port_binding_by_requested_chassis);\n+    sbrec_port_binding_index_set_requested_chassis(\n+        target,\n+        vif_plug_ctx_in->chassis_rec);\n+    const struct sbrec_port_binding *pb;\n+    SBREC_PORT_BINDING_FOR_EACH_EQUAL (\n+            pb, target,\n+            vif_plug_ctx_in->sbrec_port_binding_by_requested_chassis) {\n+        enum en_lport_type lport_type = get_lport_type(pb);\n+        if (lport_type == LP_VIF) {\n+            vif_plug_handle_lport_vif(pb, vif_plug_ctx_in, vif_plug_ctx_out,\n+                                      !vif_plug_prime_idl_count);\n+        }\n+    }\n+    sbrec_port_binding_index_destroy_row(target);\n+}\n+\n+static void\n+vif_plug_finish_deleted__(struct shash *deleted_iface_ids, bool txn_success)\n+{\n+    struct shash_node *node, *next;\n+    SHASH_FOR_EACH_SAFE (node, next, deleted_iface_ids) {\n+        struct vif_plug_port_ctx *vif_plug_port_ctx = node->data;\n+        if (txn_success) {\n+            vif_plug_port_finish(vif_plug_port_ctx->vif_plug,\n+                             &vif_plug_port_ctx->vif_plug_port_ctx_in,\n+                             NULL);\n+        }\n+        shash_delete(deleted_iface_ids, node);\n+        destroy_port_ctx(vif_plug_port_ctx);\n+    }\n+}\n+\n+void\n+vif_plug_clear_deleted(struct shash *deleted_iface_ids) {\n+    vif_plug_finish_deleted__(deleted_iface_ids, false);\n+}\n+\n+void\n+vif_plug_finish_deleted(struct shash *deleted_iface_ids) {\n+    vif_plug_finish_deleted__(deleted_iface_ids, true);\n+}\n+\n+static void\n+vif_plug_finish_changed__(struct shash *changed_iface_ids, bool txn_success)\n+{\n+    struct shash_node *node, *next;\n+    SHASH_FOR_EACH_SAFE (node, next, changed_iface_ids) {\n+        struct vif_plug_port_ctx *vif_plug_port_ctx = node->data;\n+        if (txn_success) {\n+            vif_plug_port_finish(vif_plug_port_ctx->vif_plug,\n+                                 &vif_plug_port_ctx->vif_plug_port_ctx_in,\n+                                 &vif_plug_port_ctx->vif_plug_port_ctx_out);\n+        }\n+        vif_plug_port_ctx_destroy(vif_plug_port_ctx->vif_plug,\n+                                  &vif_plug_port_ctx->vif_plug_port_ctx_in,\n+                                  &vif_plug_port_ctx->vif_plug_port_ctx_out);\n+        shash_delete(changed_iface_ids, node);\n+        destroy_port_ctx(vif_plug_port_ctx);\n+    }\n+}\n+\n+void\n+vif_plug_clear_changed(struct shash *deleted_iface_ids) {\n+    vif_plug_finish_changed__(deleted_iface_ids, false);\n+}\n+\n+void\n+vif_plug_finish_changed(struct shash *deleted_iface_ids) {\n+    vif_plug_finish_changed__(deleted_iface_ids, true);\n+}\ndiff --git a/controller/vif-plug.h b/controller/vif-plug.h\nnew file mode 100644\nindex 000000000..76063591b\n--- /dev/null\n+++ b/controller/vif-plug.h\n@@ -0,0 +1,80 @@\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 VIF_PLUG_H\n+#define VIF_PLUG_H 1\n+\n+/*\n+ * VIF Plug, the controller internal interface to the VIF plug provider\n+ * infrastructure.\n+ */\n+\n+#include \"openvswitch/shash.h\"\n+#include \"smap.h\"\n+\n+#ifdef  __cplusplus\n+extern \"C\" {\n+#endif\n+\n+struct vif_plug_ctx_in {\n+    struct ovsdb_idl_txn *ovs_idl_txn;\n+    struct ovsdb_idl_index *sbrec_port_binding_by_name;\n+    struct ovsdb_idl_index *sbrec_port_binding_by_requested_chassis;\n+    struct ovsdb_idl_index *ovsrec_port_by_interfaces;\n+    const struct ovsrec_open_vswitch_table *ovs_table;\n+    const struct ovsrec_bridge *br_int;\n+    const struct ovsrec_interface_table *iface_table;\n+    const struct sbrec_chassis *chassis_rec;\n+    const struct shash *local_bindings;\n+};\n+\n+struct vif_plug_ctx_out {\n+    struct shash *deleted_iface_ids;\n+    struct shash *changed_iface_ids;\n+};\n+\n+struct vif_plug_class;\n+struct vif_plug_port_ctx_out;\n+struct vif_plug_port_ctx_in;\n+\n+const struct sset * vif_plug_get_maintained_iface_options(\n+    const struct vif_plug_class *);\n+\n+bool vif_plug_port_prepare(const struct vif_plug_class *,\n+                           const struct vif_plug_port_ctx_in *,\n+                           struct vif_plug_port_ctx_out *);\n+void vif_plug_port_finish(const struct vif_plug_class *,\n+                          const struct vif_plug_port_ctx_in *,\n+                          struct vif_plug_port_ctx_out *);\n+void vif_plug_port_ctx_destroy(const struct vif_plug_class *,\n+                           const struct vif_plug_port_ctx_in *,\n+                           struct vif_plug_port_ctx_out *);\n+\n+struct ovsdb_idl;\n+\n+void vif_plug_register_ovs_idl(struct ovsdb_idl *ovs_idl);\n+void vif_plug_run(struct vif_plug_ctx_in *, struct vif_plug_ctx_out *);\n+void vif_plug_clear_changed(struct shash *deleted_iface_ids);\n+void vif_plug_finish_changed(struct shash *changed_iface_ids);\n+void vif_plug_clear_deleted(struct shash *deleted_iface_ids);\n+void vif_plug_finish_deleted(struct shash *changed_iface_ids);\n+void vif_plug_reset_idl_prime_counter(void);\n+\n+#ifdef  __cplusplus\n+}\n+#endif\n+\n+#endif /* vif-plug.h */\ndiff --git a/lib/automake.mk b/lib/automake.mk\nindex 9f9f447d5..829aedfc5 100644\n--- a/lib/automake.mk\n+++ b/lib/automake.mk\n@@ -4,6 +4,11 @@ lib_libovn_la_LDFLAGS = \\\n         -Wl,--version-script=$(top_builddir)/lib/libovn.sym \\\n         $(OVS_LIBDIR)/libopenvswitch.la \\\n         $(AM_LDFLAGS)\n+\n+if HAVE_VIF_PLUG_PROVIDER\n+lib_libovn_la_LDFLAGS += $(VIF_PLUG_PROVIDER_LDFLAGS)\n+endif\n+\n lib_libovn_la_SOURCES = \\\n \tlib/acl-log.c \\\n \tlib/acl-log.h \\\n@@ -33,7 +38,10 @@ 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/vif-plug-provider.h \\\n+\tlib/vif-plug-provider.c \\\n+\tlib/vif-plug-providers/dummy/vif-plug-dummy.c\n nodist_lib_libovn_la_SOURCES = \\\n \tlib/ovn-dirs.c \\\n \tlib/ovn-nb-idl.c \\\ndiff --git a/lib/vif-plug-provider.c b/lib/vif-plug-provider.c\nnew file mode 100644\nindex 000000000..798e90e26\n--- /dev/null\n+++ b/lib/vif-plug-provider.c\n@@ -0,0 +1,204 @@\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 \"vif-plug-provider.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(vif_plug_provider);\n+\n+#ifdef ENABLE_VIF_PLUG\n+static const struct vif_plug_class *base_vif_plug_classes[] = {\n+};\n+#endif\n+\n+static struct shash vif_plug_classes = SHASH_INITIALIZER(&vif_plug_classes);\n+\n+/* Protects the 'vif_plug_classes' shash. */\n+static struct ovs_mutex vif_plug_classes_mutex = OVS_MUTEX_INITIALIZER;\n+\n+/* Initialize the the VIF plug infrastructure by registering known classes */\n+void\n+vif_plug_provider_initialize(void)\n+{\n+    static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;\n+\n+    if (ovsthread_once_start(&once)) {\n+#ifdef ENABLE_VIF_PLUG\n+        /* Register built-in VIF plug provider classes */\n+        for (int i = 0; i < ARRAY_SIZE(base_vif_plug_classes); i++) {\n+            vif_plug_provider_register(base_vif_plug_classes[i]);\n+        }\n+#endif\n+#ifdef HAVE_VIF_PLUG_PROVIDER\n+        /* Register external VIF plug provider classes.\n+         *\n+         * Note that we cannot use the ARRAY_SIZE macro here as\n+         * vif_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 vif_plug_class **pp = vif_plug_provider_classes;\n+             pp && *pp;\n+             pp++)\n+        {\n+            vif_plug_provider_register(*pp);\n+        }\n+#endif\n+        ovsthread_once_done(&once);\n+    }\n+}\n+\n+static int\n+vif_plug_provider_register__(const struct vif_plug_class *new_class)\n+{\n+    struct vif_plug_class *vif_plug_class;\n+    int error;\n+\n+    if (shash_find(&vif_plug_classes, new_class->type)) {\n+        VLOG_WARN(\"attempted to register duplicate VIF 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 VIF plug provider class: %s\",\n+                  new_class->type, ovs_strerror(error));\n+        return error;\n+    }\n+\n+    vif_plug_class = xmalloc(sizeof *vif_plug_class);\n+    memcpy(vif_plug_class, new_class, sizeof *vif_plug_class);\n+\n+    shash_add(&vif_plug_classes, new_class->type, vif_plug_class);\n+\n+    return 0;\n+}\n+\n+/* Register the new VIF plug provider referred to in 'new_class' and perform\n+ * any class level initialization as specified in its vif_plug_class. */\n+int\n+vif_plug_provider_register(const struct vif_plug_class *new_class)\n+{\n+    int error;\n+\n+    ovs_mutex_lock(&vif_plug_classes_mutex);\n+    error = vif_plug_provider_register__(new_class);\n+    ovs_mutex_unlock(&vif_plug_classes_mutex);\n+\n+    return error;\n+}\n+\n+static int\n+vif_plug_provider_unregister__(const char *type)\n+{\n+    int error;\n+    struct shash_node *node;\n+    struct vif_plug_class *vif_plug_class;\n+\n+    node = shash_find(&vif_plug_classes, type);\n+    if (!node) {\n+        return EINVAL;\n+    }\n+\n+    vif_plug_class = node->data;\n+    error = vif_plug_class->destroy ? vif_plug_class->destroy() : 0;\n+    if (error) {\n+        VLOG_WARN(\"failed to destroy %s VIF plug class: %s\",\n+                  vif_plug_class->type, ovs_strerror(error));\n+        return error;\n+    }\n+\n+    shash_delete(&vif_plug_classes, node);\n+    free(vif_plug_class);\n+\n+    return 0;\n+}\n+\n+/* Unregister the VIF plug provider identified by 'type' and perform any class\n+ * level de-initialization as specified in its vif_plug_class. */\n+int\n+vif_plug_provider_unregister(const char *type)\n+{\n+    int error;\n+\n+    ovs_mutex_lock(&vif_plug_classes_mutex);\n+    error = vif_plug_provider_unregister__(type);\n+    ovs_mutex_unlock(&vif_plug_classes_mutex);\n+\n+    return error;\n+}\n+\n+/* Check whether there are any VIF plug providers registered */\n+bool\n+vif_plug_provider_has_providers(void)\n+{\n+    return !shash_is_empty(&vif_plug_classes);\n+}\n+\n+const struct vif_plug_class *\n+vif_plug_provider_get(const char *type)\n+{\n+    struct vif_plug_class *vif_plug_class;\n+\n+    ovs_mutex_lock(&vif_plug_classes_mutex);\n+    vif_plug_class = shash_find_data(&vif_plug_classes, type);\n+    ovs_mutex_unlock(&vif_plug_classes_mutex);\n+\n+    return vif_plug_class;\n+}\n+\n+/* Iterate over VIF plug providers and call their run function.\n+ *\n+ * Returns 'true' if any of the providers run functions return 'true', 'false'\n+ * otherwise.\n+ *\n+ * A return value of 'true' means that data has changed. */\n+bool\n+vif_plug_provider_run_all(void)\n+{\n+    struct shash_node *node, *next;\n+    bool changed = false;\n+\n+    SHASH_FOR_EACH_SAFE (node, next, &vif_plug_classes) {\n+        struct vif_plug_class *vif_plug_class = node->data;\n+        if (vif_plug_class->run && vif_plug_class->run(vif_plug_class)) {\n+            changed = true;\n+        }\n+    }\n+    return changed;\n+}\n+\n+/* De-initialize and unregister the VIF plug provider classes. */\n+void\n+vif_plug_provider_destroy_all(void)\n+{\n+    struct shash_node *node, *next;\n+\n+    SHASH_FOR_EACH_SAFE (node, next, &vif_plug_classes) {\n+        struct vif_plug_class *vif_plug_class = node->data;\n+        vif_plug_provider_unregister(vif_plug_class->type);\n+    }\n+}\ndiff --git a/lib/vif-plug-provider.h b/lib/vif-plug-provider.h\nnew file mode 100644\nindex 000000000..c0c74b073\n--- /dev/null\n+++ b/lib/vif-plug-provider.h\n@@ -0,0 +1,163 @@\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 VIF_PLUG_PROVIDER_H\n+#define VIF_PLUG_PROVIDER_H 1\n+\n+/* Interface for VIF plug providers.\n+ *\n+ * A VIF plug provider implementation performs lookup and/or initialization of\n+ * ports, typically representor ports, using generic non-blocking hardware\n+ * interfaces.  This allows the ovn-controller to, upon the CMS's request,\n+ * create ports and interfaces in the chassis's Open vSwitch instances (also\n+ * known as vif plugging).\n+ *\n+ * This module contains the infrastructure for registering VIF plug providers\n+ * which may be hosted inside or outside the core OVN repository.\n+ */\n+\n+#include <stdbool.h>\n+\n+#include \"smap.h\"\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+struct vif_plug_class;\n+struct ovsdb_idl_txn;\n+struct ovsrec_bridge;\n+\n+enum vif_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 vif_plug_port_ctx_in {\n+    /* Operation being performed */\n+    enum vif_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 vif-plug-* options. */\n+    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+    struct smap iface_options;\n+};\n+\n+struct vif_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+struct vif_plug_port_ctx {\n+    const struct vif_plug_class *vif_plug;\n+    struct vif_plug_port_ctx_in vif_plug_port_ctx_in;\n+    struct vif_plug_port_ctx_out vif_plug_port_ctx_out;\n+};\n+\n+struct vif_plug_class {\n+    /* Type of VIF plug provider in this class. */\n+    const char *type;\n+\n+    /* Called when the VIF plug provider is registered, typically at program\n+     * startup.\n+     *\n+     * This function may be set to null if a VIF plug class needs no\n+     * initialization at registration time. */\n+    int (*init)(void);\n+\n+    /* Called when the VIF 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 VIF plug provider, if any is necessary.\n+     * Returns 'true; if anything (i.e. lookup tables) changed, 'false'\n+     * otherwise. */\n+    bool (*run)(struct vif_plug_class *);\n+\n+    /* Retrieve Interface options this VIF plug provider will maintain.  This\n+     * sset is used to know which items to remove when maintaining the database\n+     * record. */\n+    const struct sset * (*vif_plug_get_maintained_iface_options)(void);\n+\n+    /* Pass vif_plug_port_ctx_in to VIF plug provider implementation to prepare\n+     * for port creation/update.\n+     *\n+     * The VIF plug provider implemantation can perform lookup or any per port\n+     * initialization and should fill vif_plug_port_ctx_out with data required\n+     * for port/interface creation.  The VIF plug implementation should return\n+     * 'true' if it wants the caller to create/update a port/interface, 'false'\n+     * otherwise.\n+     *\n+     * Data in the vif_plug_port_ctx_out struct is owned by the VIF plug\n+     * provider, and a call must be made to the vif_plug_port_ctx_destroy\n+     * callback to free up any allocations when done with port creation/update.\n+     */\n+    bool (*vif_plug_port_prepare)(const struct vif_plug_port_ctx_in *,\n+                                  struct vif_plug_port_ctx_out *);\n+\n+    /* Notify VIF plug provider that port update is committed to OVSDB. */\n+    void (*vif_plug_port_finish)(const struct vif_plug_port_ctx_in *,\n+                                 struct vif_plug_port_ctx_out *);\n+\n+    /* Free any allocations made by the vif_plug_port_prepare callback. */\n+    void (*vif_plug_port_ctx_destroy)(const struct vif_plug_port_ctx_in *,\n+                                      struct vif_plug_port_ctx_out *);\n+};\n+\n+extern const struct vif_plug_class vif_plug_dummy_class;\n+#ifdef HAVE_VIF_PLUG_PROVIDER\n+extern const struct vif_plug_class *vif_plug_provider_classes[];\n+#endif\n+\n+void vif_plug_provider_initialize(void);\n+int vif_plug_provider_register(const struct vif_plug_class *);\n+int vif_plug_provider_unregister(const char *type);\n+bool vif_plug_provider_has_providers(void);\n+const struct vif_plug_class * vif_plug_provider_get(const char *);\n+bool vif_plug_provider_run_all(void);\n+void vif_plug_provider_destroy_all(void);\n+void vif_plug_dummy_enable(void);\n+\n+#ifdef  __cplusplus\n+}\n+#endif\n+\n+#endif /* vif-plug-provider.h */\ndiff --git a/lib/vif-plug-providers/dummy/vif-plug-dummy.c b/lib/vif-plug-providers/dummy/vif-plug-dummy.c\nnew file mode 100644\nindex 000000000..bc8953e98\n--- /dev/null\n+++ b/lib/vif-plug-providers/dummy/vif-plug-dummy.c\n@@ -0,0 +1,120 @@\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 \"lib/vif-plug-provider.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(vif_plug_dummy);\n+\n+static struct sset vif_plug_dummy_maintained_iface_options;\n+\n+static int\n+vif_plug_dummy_init(void)\n+{\n+    sset_init(&vif_plug_dummy_maintained_iface_options);\n+    sset_add(&vif_plug_dummy_maintained_iface_options,\n+             \"vif-plug-dummy-option\");\n+\n+    return 0;\n+}\n+\n+static int\n+vif_plug_dummy_destroy(void)\n+{\n+    sset_destroy(&vif_plug_dummy_maintained_iface_options);\n+\n+    return 0;\n+}\n+\n+static const struct sset*\n+vif_plug_dummy_get_maintained_iface_options(void)\n+{\n+    return &vif_plug_dummy_maintained_iface_options;\n+}\n+\n+static bool\n+vif_plug_dummy_run(struct vif_plug_class *plug)\n+{\n+    VLOG_DBG(\"vif_plug_dummy_run(%p)\", plug);\n+\n+    return false;\n+}\n+\n+static bool\n+vif_plug_dummy_port_prepare(const struct vif_plug_port_ctx_in *ctx_in,\n+                            struct vif_plug_port_ctx_out *ctx_out)\n+{\n+    VLOG_DBG(\"vif_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+        smap_init(&ctx_out->iface_options);\n+        smap_add(&ctx_out->iface_options, \"vif-plug-dummy-option\", \"value\");\n+    }\n+\n+    return true;\n+}\n+\n+static void\n+vif_plug_dummy_port_finish(const struct vif_plug_port_ctx_in *ctx_in,\n+                           struct vif_plug_port_ctx_out *ctx_out OVS_UNUSED)\n+{\n+    VLOG_DBG(\"vif_plug_dummy_port_finish: %s\", ctx_in->lport_name);\n+}\n+\n+static void\n+vif_plug_dummy_port_ctx_destroy(const struct vif_plug_port_ctx_in *ctx_in,\n+                                struct vif_plug_port_ctx_out *ctx_out)\n+{\n+    VLOG_DBG(\"vif_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+}\n+\n+const struct vif_plug_class vif_plug_dummy_class = {\n+    .type = \"dummy\",\n+    .init = vif_plug_dummy_init,\n+    .destroy = vif_plug_dummy_destroy,\n+    .vif_plug_get_maintained_iface_options =\n+        vif_plug_dummy_get_maintained_iface_options,\n+    .run = vif_plug_dummy_run,\n+    .vif_plug_port_prepare = vif_plug_dummy_port_prepare,\n+    .vif_plug_port_finish = vif_plug_dummy_port_finish,\n+    .vif_plug_port_ctx_destroy = vif_plug_dummy_port_ctx_destroy,\n+};\n+\n+void\n+vif_plug_dummy_enable(void)\n+{\n+    vif_plug_provider_register(&vif_plug_dummy_class);\n+}\n+\ndiff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml\nindex a71798f68..ef8d669a2 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 VIF plug\n+      provider for representor port lookup and plug them into the integration\n+      bridge (please refer to\n+      <code>Documentation/topics/vif-plug-providers/vif-plug-providers.rst\n+      </code> for more information).  In both cases the conventions described\n+      in <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 c4a7c0a5b..685d78c5b 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-vif-plug.at\n \n SYSTEM_KMOD_TESTSUITE_AT = \\\n \ttests/system-common-macros.at \\\n@@ -243,13 +244,23 @@ tests_ovstest_SOURCES = \\\n \ttests/test-ovn.c \\\n \tcontroller/test-lflow-cache.c \\\n \tcontroller/test-ofctrl-seqno.c \\\n+\tcontroller/test-vif-plug.c \\\n \tlib/test-ovn-features.c \\\n \tnorthd/test-ipam.c\n \n tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \\\n     $(OVS_LIBDIR)/libopenvswitch.la lib/libovn.la \\\n+\tcontroller/binding.$(OBJEXT) \\\n+\tcontroller/encaps.$(OBJEXT) \\\n+\tcontroller/ha-chassis.$(OBJEXT) \\\n+\tcontroller/if-status.$(OBJEXT) \\\n \tcontroller/lflow-cache.$(OBJEXT) \\\n+\tcontroller/local_data.$(OBJEXT) \\\n+\tcontroller/lport.$(OBJEXT) \\\n \tcontroller/ofctrl-seqno.$(OBJEXT) \\\n+\tcontroller/ovsport.$(OBJEXT) \\\n+\tcontroller/patch.$(OBJEXT) \\\n+\tcontroller/vif-plug.$(OBJEXT) \\\n \tnorthd/ipam.$(OBJEXT)\n \n # Python tests.\ndiff --git a/tests/ovn-vif-plug.at b/tests/ovn-vif-plug.at\nnew file mode 100644\nindex 000000000..86b0b4b84\n--- /dev/null\n+++ b/tests/ovn-vif-plug.at\n@@ -0,0 +1,8 @@\n+#\n+# Unit tests for the lib/vif-plug-provider.c and controller/vif-plug.c modules.\n+#\n+AT_BANNER([OVN unit tests - vif-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",
        "v9",
        "1/4"
    ]
}