Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/1551377/?format=api
{ "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" ] }