diff mbox series

[ovs-dev,v8,3/4] controller: Consider plugging VIF on CMS request.

Message ID 20211102112916.3368168-4-frode.nordahl@canonical.com
State Superseded
Headers show
Series Introduce infrastructure for VIF plug providers | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes fail github build: failed

Commit Message

Frode Nordahl Nov. 2, 2021, 11:29 a.m. UTC
When OVN is linked with an appropriate VIF plug provider,
CMS can request an OVN controller to plug individual lports into
the Open vSwitch instance it manages.

The port and interface record will be maintained throughout the
lifetime of the lport and it will be removed on release of lport.

Signed-off-by: Frode Nordahl <frode.nordahl@canonical.com>
---
 controller/ovn-controller.c |  61 +++++++++++++++++-
 ovn-nb.xml                  |  21 +++++++
 tests/ovn-macros.at         |   2 +-
 tests/ovn.at                | 121 ++++++++++++++++++++++++++++++++++++
 4 files changed, 203 insertions(+), 2 deletions(-)

Comments

Han Zhou Nov. 5, 2021, 7:42 a.m. UTC | #1
On Tue, Nov 2, 2021 at 4:29 AM Frode Nordahl <frode.nordahl@canonical.com>
wrote:
>
> When OVN is linked with an appropriate VIF plug provider,
> CMS can request an OVN controller to plug individual lports into
> the Open vSwitch instance it manages.
>
> The port and interface record will be maintained throughout the
> lifetime of the lport and it will be removed on release of lport.
>
> Signed-off-by: Frode Nordahl <frode.nordahl@canonical.com>
> ---
>  controller/ovn-controller.c |  61 +++++++++++++++++-
>  ovn-nb.xml                  |  21 +++++++
>  tests/ovn-macros.at         |   2 +-
>  tests/ovn.at                | 121 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 203 insertions(+), 2 deletions(-)
>
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index dd09d57e2..b4d4636d8 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -106,6 +106,7 @@ static unixctl_cb_func debug_delay_nb_cfg_report;
>  #define IF_STATUS_MGR_UPDATE_STOPWATCH_NAME "if-status-mgr-update"
>  #define OFCTRL_SEQNO_RUN_STOPWATCH_NAME "ofctrl-seqno-run"
>  #define BFD_RUN_STOPWATCH_NAME "bfd-run"
> +#define VIF_PLUG_RUN_STOPWATCH_NAME "vif-plug-run"
>
>  #define OVS_NB_CFG_NAME "ovn-nb-cfg"
>  #define OVS_NB_CFG_TS_NAME "ovn-nb-cfg-ts"
> @@ -937,6 +938,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      binding_register_ovs_idl(ovs_idl);
>      bfd_register_ovs_idl(ovs_idl);
>      physical_register_ovs_idl(ovs_idl);
> +    vif_plug_register_ovs_idl(ovs_idl);
>  }
>
>  #define SB_NODES \
> @@ -3205,6 +3207,7 @@ main(int argc, char *argv[])
>      stopwatch_create(IF_STATUS_MGR_UPDATE_STOPWATCH_NAME, SW_MS);
>      stopwatch_create(OFCTRL_SEQNO_RUN_STOPWATCH_NAME, SW_MS);
>      stopwatch_create(BFD_RUN_STOPWATCH_NAME, SW_MS);
> +    stopwatch_create(VIF_PLUG_RUN_STOPWATCH_NAME, SW_MS);
>
>      /* Define inc-proc-engine nodes. */
>      ENGINE_NODE_CUSTOM_WITH_CLEAR_TRACK_DATA(ct_zones, "ct_zones");
> @@ -3440,6 +3443,11 @@ main(int argc, char *argv[])
>      };
>      struct if_status_mgr *if_mgr = ctrl_engine_ctx.if_mgr;
>
> +    struct shash vif_plug_deleted_iface_ids =
> +        SHASH_INITIALIZER(&vif_plug_deleted_iface_ids);
> +    struct shash vif_plug_changed_iface_ids =
> +        SHASH_INITIALIZER(&vif_plug_changed_iface_ids);
> +
>      char *ovn_version = ovn_get_internal_version();
>      VLOG_INFO("OVN internal version is : [%s]", ovn_version);
>
> @@ -3650,6 +3658,37 @@ main(int argc, char *argv[])
>                              ovsrec_port_table_get(ovs_idl_loop.idl),
>                              br_int, chassis,
&runtime_data->local_datapaths);
>                          stopwatch_stop(PATCH_RUN_STOPWATCH_NAME,
time_msec());
> +                        if (vif_plug_provider_has_providers() &&
ovs_idl_txn) {
> +                            struct vif_plug_ctx_in vif_plug_ctx_in = {
> +                                .ovs_idl_txn = ovs_idl_txn,
> +                                .sbrec_port_binding_by_name =
> +                                    sbrec_port_binding_by_name,
> +                                .sbrec_port_binding_by_requested_chassis
=
> +
 sbrec_port_binding_by_requested_chassis,
> +                                .ovsrec_port_by_interfaces =
> +                                    ovsrec_port_by_interfaces,
> +                                .ovs_table = ovs_table,
> +                                .br_int = br_int,
> +                                .iface_table =
> +                                    ovsrec_interface_table_get(
> +                                                    ovs_idl_loop.idl),
> +                                .chassis_rec = chassis,
> +                                .local_bindings =
> +
 &runtime_data->lbinding_data.bindings,
> +                            };
> +                            struct vif_plug_ctx_out vif_plug_ctx_out = {
> +                                .deleted_iface_ids =
> +                                    &vif_plug_deleted_iface_ids,
> +                                .changed_iface_ids =
> +                                    &vif_plug_changed_iface_ids,
> +                            };
> +                            stopwatch_start(VIF_PLUG_RUN_STOPWATCH_NAME,
> +                                            time_msec());
> +                            vif_plug_run(&vif_plug_ctx_in,
> +                                         &vif_plug_ctx_out);
> +                            stopwatch_stop(VIF_PLUG_RUN_STOPWATCH_NAME,
> +                                           time_msec());
> +                        }
>                          stopwatch_start(PINCTRL_RUN_STOPWATCH_NAME,
>                                          time_msec());
>                          pinctrl_run(ovnsb_idl_txn,
> @@ -3806,7 +3845,16 @@ main(int argc, char *argv[])
>              engine_set_force_recompute(true);
>          }
>
> -        if (ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop) == 1) {
> +        int ovs_txn_status =
ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
> +        if (!ovs_txn_status) {
> +            /* The transaction failed. */
> +            vif_plug_clear_deleted(
> +                    &vif_plug_deleted_iface_ids);
> +            vif_plug_clear_changed(
> +                    &vif_plug_changed_iface_ids);
> +        } else if (ovs_txn_status == 1) {
> +            /* The transaction committed successfully
> +             * (or it did not change anything in the database). */
>              ct_zones_data = engine_get_data(&en_ct_zones);
>              if (ct_zones_data) {
>                  struct shash_node *iter, *iter_next;
> @@ -3819,6 +3867,15 @@ main(int argc, char *argv[])
>                      }
>                  }
>              }
> +
> +            vif_plug_finish_deleted(
> +                    &vif_plug_deleted_iface_ids);
> +            vif_plug_finish_changed(
> +                    &vif_plug_changed_iface_ids);
> +        } else if (ovs_txn_status == -1) {
> +            /* The commit is still in progress */
> +        } else {
> +            OVS_NOT_REACHED();
>          }
>
>          ovsdb_idl_track_clear(ovnsb_idl_loop.idl);
> @@ -3894,6 +3951,8 @@ loop_done:
>      pinctrl_destroy();
>      patch_destroy();
>      if_status_mgr_destroy(if_mgr);
> +    shash_destroy(&vif_plug_deleted_iface_ids);
> +    shash_destroy(&vif_plug_changed_iface_ids);
>      vif_plug_provider_destroy_all();
>
>      ovsdb_idl_loop_destroy(&ovs_idl_loop);
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index e31578fb6..036ffa64f 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1031,6 +1031,27 @@
>              DHCP reply.
>            </p>
>          </column>
> +
> +        <group title="VIF Plugging Options">
> +          <column name="options" key="vif-plug-type">
> +            If set, OVN will attempt to perform plugging of this VIF.
In order
> +            to get this port plugged by the OVN controller, OVN must be
built
> +            with support for VIF plugging.  The default behavior is for
the CMS
> +            to do the VIF plugging.  Each VIF plug provider have their
own
> +            options namespaced by name, for example
"vif-plug:representor:key".
> +
> +            Please refer to the VIF plug provider documentation located
in
> +            Documentation/topics/vif-plug-providers/ for more
information.
> +          </column>
> +
> +          <column name="options" key="vif-plug-mtu-request">
> +            Requested MTU for plugged interfaces.  When set the OVN
controller
> +            will fill the <ref table="Interface" column="mtu_request"/>
column
> +            of the Open vSwitch database's
> +            <ref table="Interface" db="vswitch"/> table.  This in turn
will
> +            make OVS vswitchd update the MTU of the linked interface.
> +          </column>
> +        </group>
>        </group>
>
>        <group title="Virtual port Options">
> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
> index f06f2e68e..21b7d985e 100644
> --- a/tests/ovn-macros.at
> +++ b/tests/ovn-macros.at
> @@ -327,7 +327,7 @@ ovn_az_attach() {
>          -- --may-exist add-br br-int \
>          -- set bridge br-int fail-mode=secure
other-config:disable-in-band=true \
>          || return 1
> -    start_daemon ovn-controller || return 1
> +    start_daemon ovn-controller --enable-dummy-vif-plug || return 1
>  }
>
>  # ovn_attach NETWORK BRIDGE IP [MASKLEN]
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 7cfdede77..b93dbdcff 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -28789,3 +28789,124 @@ AT_CHECK([grep -q "Not claiming"
hv1/ovn-controller.log], [1], [])
>  OVN_CLEANUP([hv1])
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ovn-controller - VIF plugging])
> +AT_KEYWORDS([vif-plug])
> +
> +ovn_start
> +
> +net_add n1
> +sim_add hv1
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +
> +sim_add hv2
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.2
> +
> +check ovn-nbctl ls-add lsw0
> +
> +check ovn-nbctl lsp-add lsw0 lsp1
> +check ovn-nbctl lsp-set-addresses lsp1 "f0:00:00:00:00:01 172.16.0.101"
> +check ovn-nbctl lsp-set-options lsp1 \
> +    requested-chassis=hv1 \
> +    vif-plug-type=dummy \
> +    vif-plug-mtu-request=42
> +
> +check ovn-nbctl lsp-add lsw0 lsp2
> +check ovn-nbctl lsp-set-addresses lsp2 "f0:00:00:00:00:02 172.16.0.102"
> +check ovn-nbctl lsp-set-options lsp2 \
> +    requested-chassis=hv2 \
> +    vif-plug-type=dummy \
> +    vif-plug-mtu-request=42
> +
> +wait_for_ports_up lsp1 lsp2
> +
> +lsp1_uuid=$(ovn-nbctl --bare --columns _uuid find Logical_Switch_Port
name=lsp1)
> +iface1_uuid=$(as hv1 ovs-vsctl --bare --columns _uuid find Interface
name=lsp1)
> +
> +lsp2_uuid=$(ovn-nbctl --bare --columns _uuid find Logical_Switch_Port
name=lsp2)
> +iface2_uuid=$(as hv2 ovs-vsctl --bare --columns _uuid find Interface
name=lsp2)
> +
> +# Check that the lport was plugged
> +AT_CHECK([test xvalue = x$(as hv1 ovs-vsctl get Interface ${iface1_uuid}
options:vif-plug-dummy-option)], [0], [])
> +AT_CHECK([test x42 = x$(as hv1 ovs-vsctl get Interface ${iface1_uuid}
mtu_request)], [0], [])
> +
> +# Check that updating the lport updates the local iface
> +check ovn-nbctl --wait=hv lsp-set-options lsp1 \
> +    requested-chassis=hv1 \
> +    vif-plug-type=dummy \
> +    vif-plug-mtu-request=43
> +OVS_WAIT_UNTIL([
> +    test x43 = x$(as hv1 ovs-vsctl get Interface ${iface1_uuid}
mtu_request)
> +])
> +
> +# Check that local modification of iface will trigger ovn-controller to
update
> +# the iface record
> +check as hv1 ovs-vsctl set interface ${iface1_uuid} mtu_request=44
> +OVS_WAIT_UNTIL([
> +    test x43 = x$(as hv1 ovs-vsctl get Interface ${iface1_uuid}
mtu_request)
> +])
> +
> +# Check that pointing requested-chassis somewhere else will unplug the
port
> +check ovn-nbctl --wait=hv set Logical_Switch_Port lsp1 \
> +    options:requested-chassis=non-existent-chassis
> +OVS_WAIT_UNTIL([
> +    ! as hv1 ovs-vsctl get Interface ${iface1_uuid} _uuid
> +])
> +
> +# Check that removing an lport will unplug it
> +AT_CHECK([test x${iface2_uuid} = x$(as hv2 ovs-vsctl get Interface
${iface2_uuid} _uuid)], [0], [])
> +check ovn-nbctl --wait=hv lsp-del ${lsp2_uuid}
> +OVS_WAIT_UNTIL([
> +    ! as hv2 ovs-vsctl get Interface ${iface2_uuid} _uuid
> +])
> +
> +# Check that port is unplugged when we simulate presence of a port
previously
> +# plugged by us in local OVSDB with no record in SB DB.
> +check as hv2 ovs-vsctl \
> +    -- add-port br-int vif1
> +
> +# From one moment it's there...
> +vif1_uuid=$(as hv2 ovs-vsctl --bare --columns _uuid find Interface
name=vif1)
> +OVS_WAIT_UNTIL([
> +    as hv2 ovs-vsctl get Interface ${vif1_uuid} _uuid
> +])
> +
> +# Add the external-ids we expect
> +check as hv2 ovs-vsctl \
> +    -- set Interface ${vif1_uuid} \
> +           external-ids:ovn-plugged=dummy \
> +           external-ids:iface-id=non-existing-lsp
> +
> +# ...to the next moment it's gone.
> +OVS_WAIT_UNTIL([
> +    ! as hv2 ovs-vsctl get Interface ${vif1_uuid} _uuid
> +])
> +
> +# Check that a warning is logged when CMS requests plugging of an
interface
> +# with lbinding already plugged by someone else.
> +check as hv2 ovs-vsctl \
> +    -- add-port br-int vif3 \
> +    -- set Interface vif3 \
> +       external-ids:iface-id=lsp3
> +
> +check ovn-nbctl lsp-add lsw0 lsp3
> +check ovn-nbctl lsp-set-addresses lsp3 "f0:00:00:00:00:03 172.16.0.103"
> +check ovn-nbctl lsp-set-options lsp3 \
> +    requested-chassis=hv2
> +
> +wait_for_ports_up lsp3
> +
> +check ovn-nbctl --wait=hv lsp-set-options lsp3 \
> +    requested-chassis=hv2 \
> +    vif-plug-type=dummy
> +
> +OVS_WAIT_UNTIL([
> +    grep -q "CMS requested plugging of lport lsp3" hv2/ovn-controller.log
> +])
> +
> +OVN_CLEANUP([hv1],[hv2])
> +AT_CLEANUP
> +])
> --
> 2.32.0
>

Acked-by: Han Zhou <hzhou@ovn.org>
diff mbox series

Patch

diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index dd09d57e2..b4d4636d8 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -106,6 +106,7 @@  static unixctl_cb_func debug_delay_nb_cfg_report;
 #define IF_STATUS_MGR_UPDATE_STOPWATCH_NAME "if-status-mgr-update"
 #define OFCTRL_SEQNO_RUN_STOPWATCH_NAME "ofctrl-seqno-run"
 #define BFD_RUN_STOPWATCH_NAME "bfd-run"
+#define VIF_PLUG_RUN_STOPWATCH_NAME "vif-plug-run"
 
 #define OVS_NB_CFG_NAME "ovn-nb-cfg"
 #define OVS_NB_CFG_TS_NAME "ovn-nb-cfg-ts"
@@ -937,6 +938,7 @@  ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     binding_register_ovs_idl(ovs_idl);
     bfd_register_ovs_idl(ovs_idl);
     physical_register_ovs_idl(ovs_idl);
+    vif_plug_register_ovs_idl(ovs_idl);
 }
 
 #define SB_NODES \
@@ -3205,6 +3207,7 @@  main(int argc, char *argv[])
     stopwatch_create(IF_STATUS_MGR_UPDATE_STOPWATCH_NAME, SW_MS);
     stopwatch_create(OFCTRL_SEQNO_RUN_STOPWATCH_NAME, SW_MS);
     stopwatch_create(BFD_RUN_STOPWATCH_NAME, SW_MS);
+    stopwatch_create(VIF_PLUG_RUN_STOPWATCH_NAME, SW_MS);
 
     /* Define inc-proc-engine nodes. */
     ENGINE_NODE_CUSTOM_WITH_CLEAR_TRACK_DATA(ct_zones, "ct_zones");
@@ -3440,6 +3443,11 @@  main(int argc, char *argv[])
     };
     struct if_status_mgr *if_mgr = ctrl_engine_ctx.if_mgr;
 
+    struct shash vif_plug_deleted_iface_ids =
+        SHASH_INITIALIZER(&vif_plug_deleted_iface_ids);
+    struct shash vif_plug_changed_iface_ids =
+        SHASH_INITIALIZER(&vif_plug_changed_iface_ids);
+
     char *ovn_version = ovn_get_internal_version();
     VLOG_INFO("OVN internal version is : [%s]", ovn_version);
 
@@ -3650,6 +3658,37 @@  main(int argc, char *argv[])
                             ovsrec_port_table_get(ovs_idl_loop.idl),
                             br_int, chassis, &runtime_data->local_datapaths);
                         stopwatch_stop(PATCH_RUN_STOPWATCH_NAME, time_msec());
+                        if (vif_plug_provider_has_providers() && ovs_idl_txn) {
+                            struct vif_plug_ctx_in vif_plug_ctx_in = {
+                                .ovs_idl_txn = ovs_idl_txn,
+                                .sbrec_port_binding_by_name =
+                                    sbrec_port_binding_by_name,
+                                .sbrec_port_binding_by_requested_chassis =
+                                    sbrec_port_binding_by_requested_chassis,
+                                .ovsrec_port_by_interfaces =
+                                    ovsrec_port_by_interfaces,
+                                .ovs_table = ovs_table,
+                                .br_int = br_int,
+                                .iface_table =
+                                    ovsrec_interface_table_get(
+                                                    ovs_idl_loop.idl),
+                                .chassis_rec = chassis,
+                                .local_bindings =
+                                        &runtime_data->lbinding_data.bindings,
+                            };
+                            struct vif_plug_ctx_out vif_plug_ctx_out = {
+                                .deleted_iface_ids =
+                                    &vif_plug_deleted_iface_ids,
+                                .changed_iface_ids =
+                                    &vif_plug_changed_iface_ids,
+                            };
+                            stopwatch_start(VIF_PLUG_RUN_STOPWATCH_NAME,
+                                            time_msec());
+                            vif_plug_run(&vif_plug_ctx_in,
+                                         &vif_plug_ctx_out);
+                            stopwatch_stop(VIF_PLUG_RUN_STOPWATCH_NAME,
+                                           time_msec());
+                        }
                         stopwatch_start(PINCTRL_RUN_STOPWATCH_NAME,
                                         time_msec());
                         pinctrl_run(ovnsb_idl_txn,
@@ -3806,7 +3845,16 @@  main(int argc, char *argv[])
             engine_set_force_recompute(true);
         }
 
-        if (ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop) == 1) {
+        int ovs_txn_status = ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
+        if (!ovs_txn_status) {
+            /* The transaction failed. */
+            vif_plug_clear_deleted(
+                    &vif_plug_deleted_iface_ids);
+            vif_plug_clear_changed(
+                    &vif_plug_changed_iface_ids);
+        } else if (ovs_txn_status == 1) {
+            /* The transaction committed successfully
+             * (or it did not change anything in the database). */
             ct_zones_data = engine_get_data(&en_ct_zones);
             if (ct_zones_data) {
                 struct shash_node *iter, *iter_next;
@@ -3819,6 +3867,15 @@  main(int argc, char *argv[])
                     }
                 }
             }
+
+            vif_plug_finish_deleted(
+                    &vif_plug_deleted_iface_ids);
+            vif_plug_finish_changed(
+                    &vif_plug_changed_iface_ids);
+        } else if (ovs_txn_status == -1) {
+            /* The commit is still in progress */
+        } else {
+            OVS_NOT_REACHED();
         }
 
         ovsdb_idl_track_clear(ovnsb_idl_loop.idl);
@@ -3894,6 +3951,8 @@  loop_done:
     pinctrl_destroy();
     patch_destroy();
     if_status_mgr_destroy(if_mgr);
+    shash_destroy(&vif_plug_deleted_iface_ids);
+    shash_destroy(&vif_plug_changed_iface_ids);
     vif_plug_provider_destroy_all();
 
     ovsdb_idl_loop_destroy(&ovs_idl_loop);
diff --git a/ovn-nb.xml b/ovn-nb.xml
index e31578fb6..036ffa64f 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1031,6 +1031,27 @@ 
             DHCP reply.
           </p>
         </column>
+
+        <group title="VIF Plugging Options">
+          <column name="options" key="vif-plug-type">
+            If set, OVN will attempt to perform plugging of this VIF.  In order
+            to get this port plugged by the OVN controller, OVN must be built
+            with support for VIF plugging.  The default behavior is for the CMS
+            to do the VIF plugging.  Each VIF plug provider have their own
+            options namespaced by name, for example "vif-plug:representor:key".
+
+            Please refer to the VIF plug provider documentation located in
+            Documentation/topics/vif-plug-providers/ for more information.
+          </column>
+
+          <column name="options" key="vif-plug-mtu-request">
+            Requested MTU for plugged interfaces.  When set the OVN controller
+            will fill the <ref table="Interface" column="mtu_request"/> column
+            of the Open vSwitch database's
+            <ref table="Interface" db="vswitch"/> table.  This in turn will
+            make OVS vswitchd update the MTU of the linked interface.
+          </column>
+        </group>
       </group>
 
       <group title="Virtual port Options">
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index f06f2e68e..21b7d985e 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -327,7 +327,7 @@  ovn_az_attach() {
         -- --may-exist add-br br-int \
         -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
         || return 1
-    start_daemon ovn-controller || return 1
+    start_daemon ovn-controller --enable-dummy-vif-plug || return 1
 }
 
 # ovn_attach NETWORK BRIDGE IP [MASKLEN]
diff --git a/tests/ovn.at b/tests/ovn.at
index 7cfdede77..b93dbdcff 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -28789,3 +28789,124 @@  AT_CHECK([grep -q "Not claiming" hv1/ovn-controller.log], [1], [])
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn-controller - VIF plugging])
+AT_KEYWORDS([vif-plug])
+
+ovn_start
+
+net_add n1
+sim_add hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+
+sim_add hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+
+check ovn-nbctl ls-add lsw0
+
+check ovn-nbctl lsp-add lsw0 lsp1
+check ovn-nbctl lsp-set-addresses lsp1 "f0:00:00:00:00:01 172.16.0.101"
+check ovn-nbctl lsp-set-options lsp1 \
+    requested-chassis=hv1 \
+    vif-plug-type=dummy \
+    vif-plug-mtu-request=42
+
+check ovn-nbctl lsp-add lsw0 lsp2
+check ovn-nbctl lsp-set-addresses lsp2 "f0:00:00:00:00:02 172.16.0.102"
+check ovn-nbctl lsp-set-options lsp2 \
+    requested-chassis=hv2 \
+    vif-plug-type=dummy \
+    vif-plug-mtu-request=42
+
+wait_for_ports_up lsp1 lsp2
+
+lsp1_uuid=$(ovn-nbctl --bare --columns _uuid find Logical_Switch_Port name=lsp1)
+iface1_uuid=$(as hv1 ovs-vsctl --bare --columns _uuid find Interface name=lsp1)
+
+lsp2_uuid=$(ovn-nbctl --bare --columns _uuid find Logical_Switch_Port name=lsp2)
+iface2_uuid=$(as hv2 ovs-vsctl --bare --columns _uuid find Interface name=lsp2)
+
+# Check that the lport was plugged
+AT_CHECK([test xvalue = x$(as hv1 ovs-vsctl get Interface ${iface1_uuid} options:vif-plug-dummy-option)], [0], [])
+AT_CHECK([test x42 = x$(as hv1 ovs-vsctl get Interface ${iface1_uuid} mtu_request)], [0], [])
+
+# Check that updating the lport updates the local iface
+check ovn-nbctl --wait=hv lsp-set-options lsp1 \
+    requested-chassis=hv1 \
+    vif-plug-type=dummy \
+    vif-plug-mtu-request=43
+OVS_WAIT_UNTIL([
+    test x43 = x$(as hv1 ovs-vsctl get Interface ${iface1_uuid} mtu_request)
+])
+
+# Check that local modification of iface will trigger ovn-controller to update
+# the iface record
+check as hv1 ovs-vsctl set interface ${iface1_uuid} mtu_request=44
+OVS_WAIT_UNTIL([
+    test x43 = x$(as hv1 ovs-vsctl get Interface ${iface1_uuid} mtu_request)
+])
+
+# Check that pointing requested-chassis somewhere else will unplug the port
+check ovn-nbctl --wait=hv set Logical_Switch_Port lsp1 \
+    options:requested-chassis=non-existent-chassis
+OVS_WAIT_UNTIL([
+    ! as hv1 ovs-vsctl get Interface ${iface1_uuid} _uuid
+])
+
+# Check that removing an lport will unplug it
+AT_CHECK([test x${iface2_uuid} = x$(as hv2 ovs-vsctl get Interface ${iface2_uuid} _uuid)], [0], [])
+check ovn-nbctl --wait=hv lsp-del ${lsp2_uuid}
+OVS_WAIT_UNTIL([
+    ! as hv2 ovs-vsctl get Interface ${iface2_uuid} _uuid
+])
+
+# Check that port is unplugged when we simulate presence of a port previously
+# plugged by us in local OVSDB with no record in SB DB.
+check as hv2 ovs-vsctl \
+    -- add-port br-int vif1
+
+# From one moment it's there...
+vif1_uuid=$(as hv2 ovs-vsctl --bare --columns _uuid find Interface name=vif1)
+OVS_WAIT_UNTIL([
+    as hv2 ovs-vsctl get Interface ${vif1_uuid} _uuid
+])
+
+# Add the external-ids we expect
+check as hv2 ovs-vsctl \
+    -- set Interface ${vif1_uuid} \
+           external-ids:ovn-plugged=dummy \
+           external-ids:iface-id=non-existing-lsp
+
+# ...to the next moment it's gone.
+OVS_WAIT_UNTIL([
+    ! as hv2 ovs-vsctl get Interface ${vif1_uuid} _uuid
+])
+
+# Check that a warning is logged when CMS requests plugging of an interface
+# with lbinding already plugged by someone else.
+check as hv2 ovs-vsctl \
+    -- add-port br-int vif3 \
+    -- set Interface vif3 \
+       external-ids:iface-id=lsp3
+
+check ovn-nbctl lsp-add lsw0 lsp3
+check ovn-nbctl lsp-set-addresses lsp3 "f0:00:00:00:00:03 172.16.0.103"
+check ovn-nbctl lsp-set-options lsp3 \
+    requested-chassis=hv2
+
+wait_for_ports_up lsp3
+
+check ovn-nbctl --wait=hv lsp-set-options lsp3 \
+    requested-chassis=hv2 \
+    vif-plug-type=dummy
+
+OVS_WAIT_UNTIL([
+    grep -q "CMS requested plugging of lport lsp3" hv2/ovn-controller.log
+])
+
+OVN_CLEANUP([hv1],[hv2])
+AT_CLEANUP
+])