diff mbox series

[ovs-dev,v5,3/3] northd: add drop sampling

Message ID 20221104154941.365187-4-amorenoz@redhat.com
State Changes Requested
Delegated to: Dumitru Ceara
Headers show
Series Add ovn drop debugging | expand

Checks

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

Commit Message

Adrian Moreno Nov. 4, 2022, 3:49 p.m. UTC
Two new options are added to NB_Global table that enable drop
sampling by specifying the collector_set_id and the obs_domain_id of
the sample actions added to all drop flows.

For drops coming from an lflow, the sample has the following fields:
- obs_domain_id (32-bit): obs_domain_id << 8 | datapath_key
  - 8 most significant bits: the obs_domain_id specified in the
    NB_Global options.
  - 24 least significant bits: the datapath key.
- obs_point_id: the cookie (first 32-bits of the lflow's UUID).

For drops that are inserted by ovn-controller without any associated
lflow, the sample will have the follwing fields:
- obs_domain_id (32-bit): obs_domain_id << 8
  - 8 most significant bits: the obs_domain_id specified in the
    NB_Global options.
  - 24 least significant bits: 0.
- obs_point_id: The openflow table number.

Adding this configuration is not enough to make OVS sample drops. The
apropriate configuration IPFIX needs to be added to those chassis that
you wish to sample from. See man(5) ovs-vswitchd.conf for more details.

Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
---
 NEWS                        |  2 +
 controller/ovn-controller.c | 44 +++++++++++++++++
 controller/physical.c       | 44 +++++++++++++----
 controller/physical.h       |  6 +++
 northd/automake.mk          |  2 +
 northd/debug.c              | 98 +++++++++++++++++++++++++++++++++++++
 northd/debug.h              | 30 ++++++++++++
 northd/northd.c             | 77 ++++++++++++++++-------------
 northd/ovn-northd.8.xml     | 26 ++++++++++
 ovn-nb.xml                  | 28 +++++++++++
 ovn-sb.xml                  | 29 +++++++++++
 tests/ovn.at                | 67 ++++++++++++++++---------
 12 files changed, 384 insertions(+), 69 deletions(-)
 create mode 100644 northd/debug.c
 create mode 100644 northd/debug.h

Comments

0-day Robot Nov. 4, 2022, 4:03 p.m. UTC | #1
Bleep bloop.  Greetings Adrian Moreno, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
ERROR: Inappropriate spacing around cast
#77 FILE: controller/ovn-controller.c:3200:
        (struct sbrec_sb_global_table *)EN_OVSDB_GET(

ERROR: Inappropriate spacing around cast
#107 FILE: controller/ovn-controller.c:3439:
        (struct sbrec_sb_global_table *)EN_OVSDB_GET(

Lines checked: 1023, Warnings: 0, Errors: 2


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
Dumitru Ceara Nov. 18, 2022, 2:55 p.m. UTC | #2
On 11/4/22 16:49, Adrian Moreno wrote:
> Two new options are added to NB_Global table that enable drop
> sampling by specifying the collector_set_id and the obs_domain_id of
> the sample actions added to all drop flows.
> 
> For drops coming from an lflow, the sample has the following fields:
> - obs_domain_id (32-bit): obs_domain_id << 8 | datapath_key
>   - 8 most significant bits: the obs_domain_id specified in the
>     NB_Global options.
>   - 24 least significant bits: the datapath key.
> - obs_point_id: the cookie (first 32-bits of the lflow's UUID).
> 
> For drops that are inserted by ovn-controller without any associated
> lflow, the sample will have the follwing fields:
> - obs_domain_id (32-bit): obs_domain_id << 8
>   - 8 most significant bits: the obs_domain_id specified in the
>     NB_Global options.
>   - 24 least significant bits: 0.
> - obs_point_id: The openflow table number.
> 
> Adding this configuration is not enough to make OVS sample drops. The
> apropriate configuration IPFIX needs to be added to those chassis that
> you wish to sample from. See man(5) ovs-vswitchd.conf for more details.
> 
> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
> ---
>  NEWS                        |  2 +
>  controller/ovn-controller.c | 44 +++++++++++++++++
>  controller/physical.c       | 44 +++++++++++++----
>  controller/physical.h       |  6 +++
>  northd/automake.mk          |  2 +
>  northd/debug.c              | 98 +++++++++++++++++++++++++++++++++++++
>  northd/debug.h              | 30 ++++++++++++
>  northd/northd.c             | 77 ++++++++++++++++-------------
>  northd/ovn-northd.8.xml     | 26 ++++++++++
>  ovn-nb.xml                  | 28 +++++++++++
>  ovn-sb.xml                  | 29 +++++++++++
>  tests/ovn.at                | 67 ++++++++++++++++---------
>  12 files changed, 384 insertions(+), 69 deletions(-)
>  create mode 100644 northd/debug.c
>  create mode 100644 northd/debug.h
> 
> diff --git a/NEWS b/NEWS
> index 224a7b83e..6c4573b50 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -1,5 +1,7 @@
>  Post v22.09.0
>  -------------
> +  - ovn-northd: Add configuration knobs to enable drop sampling using OVS's
> +    per-flow IPFIX sampling.
>  
>  OVN v22.09.0 - 16 Sep 2022
>  --------------------------
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 8895c7a2b..686d5fa86 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -3150,6 +3150,8 @@ lflow_output_sb_meter_handler(struct engine_node *node, void *data)
>  struct ed_type_pflow_output {
>      /* Desired physical flows. */
>      struct ovn_desired_flow_table flow_table;
> +    /* Drop debugging options. */
> +    struct physical_debug debug;
>  };
>  
>  static void init_physical_ctx(struct engine_node *node,
> @@ -3194,6 +3196,12 @@ static void init_physical_ctx(struct engine_node *node,
>          chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
>      }
>  
> +    struct sbrec_sb_global_table *sb_global_table =
> +        (struct sbrec_sb_global_table *)EN_OVSDB_GET(

0-day bot reported this missing space after cast.

> +            engine_get_input("SB_sb_global", node));
> +    const struct sbrec_sb_global *sb_global =
> +        sbrec_sb_global_table_first(sb_global_table);
> +
>      ovs_assert(br_int && chassis);
>  
>      struct ed_type_ct_zones *ct_zones_data =
> @@ -3215,6 +3223,13 @@ static void init_physical_ctx(struct engine_node *node,
>      p_ctx->local_bindings = &rt_data->lbinding_data.bindings;
>      p_ctx->patch_ofports = &non_vif_data->patch_ofports;
>      p_ctx->chassis_tunnels = &non_vif_data->chassis_tunnels;
> +    p_ctx->debug.collector_set_id = smap_get_uint(&sb_global->options,
> +                                                  "debug_drop_collector_set",
> +                                                  0);
> +
> +    p_ctx->debug.obs_domain_id = smap_get_uint(&sb_global->options,
> +                                               "debug_drop_domain_id",
> +                                               0);
>  }
>  
>  static void *
> @@ -3417,6 +3432,33 @@ pflow_output_activated_ports_handler(struct engine_node *node, void *data)
>      return true;
>  }
>  
> +static bool
> +pflow_output_sb_sb_global_handler(struct engine_node *node, void *data)
> +{
> +    struct sbrec_sb_global_table *sb_global_table =
> +        (struct sbrec_sb_global_table *)EN_OVSDB_GET(

Here too.

Thanks,
Dumitru
Adrian Moreno Nov. 21, 2022, 2:28 p.m. UTC | #3
On 11/18/22 15:55, Dumitru Ceara wrote:
> On 11/4/22 16:49, Adrian Moreno wrote:
>> Two new options are added to NB_Global table that enable drop
>> sampling by specifying the collector_set_id and the obs_domain_id of
>> the sample actions added to all drop flows.
>>
>> For drops coming from an lflow, the sample has the following fields:
>> - obs_domain_id (32-bit): obs_domain_id << 8 | datapath_key
>>    - 8 most significant bits: the obs_domain_id specified in the
>>      NB_Global options.
>>    - 24 least significant bits: the datapath key.
>> - obs_point_id: the cookie (first 32-bits of the lflow's UUID).
>>
>> For drops that are inserted by ovn-controller without any associated
>> lflow, the sample will have the follwing fields:
>> - obs_domain_id (32-bit): obs_domain_id << 8
>>    - 8 most significant bits: the obs_domain_id specified in the
>>      NB_Global options.
>>    - 24 least significant bits: 0.
>> - obs_point_id: The openflow table number.
>>
>> Adding this configuration is not enough to make OVS sample drops. The
>> apropriate configuration IPFIX needs to be added to those chassis that
>> you wish to sample from. See man(5) ovs-vswitchd.conf for more details.
>>
>> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
>> ---
>>   NEWS                        |  2 +
>>   controller/ovn-controller.c | 44 +++++++++++++++++
>>   controller/physical.c       | 44 +++++++++++++----
>>   controller/physical.h       |  6 +++
>>   northd/automake.mk          |  2 +
>>   northd/debug.c              | 98 +++++++++++++++++++++++++++++++++++++
>>   northd/debug.h              | 30 ++++++++++++
>>   northd/northd.c             | 77 ++++++++++++++++-------------
>>   northd/ovn-northd.8.xml     | 26 ++++++++++
>>   ovn-nb.xml                  | 28 +++++++++++
>>   ovn-sb.xml                  | 29 +++++++++++
>>   tests/ovn.at                | 67 ++++++++++++++++---------
>>   12 files changed, 384 insertions(+), 69 deletions(-)
>>   create mode 100644 northd/debug.c
>>   create mode 100644 northd/debug.h
>>
>> diff --git a/NEWS b/NEWS
>> index 224a7b83e..6c4573b50 100644
>> --- a/NEWS
>> +++ b/NEWS
>> @@ -1,5 +1,7 @@
>>   Post v22.09.0
>>   -------------
>> +  - ovn-northd: Add configuration knobs to enable drop sampling using OVS's
>> +    per-flow IPFIX sampling.
>>   
>>   OVN v22.09.0 - 16 Sep 2022
>>   --------------------------
>> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
>> index 8895c7a2b..686d5fa86 100644
>> --- a/controller/ovn-controller.c
>> +++ b/controller/ovn-controller.c
>> @@ -3150,6 +3150,8 @@ lflow_output_sb_meter_handler(struct engine_node *node, void *data)
>>   struct ed_type_pflow_output {
>>       /* Desired physical flows. */
>>       struct ovn_desired_flow_table flow_table;
>> +    /* Drop debugging options. */
>> +    struct physical_debug debug;
>>   };
>>   
>>   static void init_physical_ctx(struct engine_node *node,
>> @@ -3194,6 +3196,12 @@ static void init_physical_ctx(struct engine_node *node,
>>           chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
>>       }
>>   
>> +    struct sbrec_sb_global_table *sb_global_table =
>> +        (struct sbrec_sb_global_table *)EN_OVSDB_GET(
> 
> 0-day bot reported this missing space after cast.
>

Actually, I don't think the cast is even needed.
I'll remove it.


>> +            engine_get_input("SB_sb_global", node));
>> +    const struct sbrec_sb_global *sb_global =
>> +        sbrec_sb_global_table_first(sb_global_table);
>> +
>>       ovs_assert(br_int && chassis);
>>   
>>       struct ed_type_ct_zones *ct_zones_data =
>> @@ -3215,6 +3223,13 @@ static void init_physical_ctx(struct engine_node *node,
>>       p_ctx->local_bindings = &rt_data->lbinding_data.bindings;
>>       p_ctx->patch_ofports = &non_vif_data->patch_ofports;
>>       p_ctx->chassis_tunnels = &non_vif_data->chassis_tunnels;
>> +    p_ctx->debug.collector_set_id = smap_get_uint(&sb_global->options,
>> +                                                  "debug_drop_collector_set",
>> +                                                  0);
>> +
>> +    p_ctx->debug.obs_domain_id = smap_get_uint(&sb_global->options,
>> +                                               "debug_drop_domain_id",
>> +                                               0);
>>   }
>>   
>>   static void *
>> @@ -3417,6 +3432,33 @@ pflow_output_activated_ports_handler(struct engine_node *node, void *data)
>>       return true;
>>   }
>>   
>> +static bool
>> +pflow_output_sb_sb_global_handler(struct engine_node *node, void *data)
>> +{
>> +    struct sbrec_sb_global_table *sb_global_table =
>> +        (struct sbrec_sb_global_table *)EN_OVSDB_GET(
> 
> Here too.
> 
> Thanks,
> Dumitru
>
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 224a7b83e..6c4573b50 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,7 @@ 
 Post v22.09.0
 -------------
+  - ovn-northd: Add configuration knobs to enable drop sampling using OVS's
+    per-flow IPFIX sampling.
 
 OVN v22.09.0 - 16 Sep 2022
 --------------------------
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 8895c7a2b..686d5fa86 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -3150,6 +3150,8 @@  lflow_output_sb_meter_handler(struct engine_node *node, void *data)
 struct ed_type_pflow_output {
     /* Desired physical flows. */
     struct ovn_desired_flow_table flow_table;
+    /* Drop debugging options. */
+    struct physical_debug debug;
 };
 
 static void init_physical_ctx(struct engine_node *node,
@@ -3194,6 +3196,12 @@  static void init_physical_ctx(struct engine_node *node,
         chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
     }
 
+    struct sbrec_sb_global_table *sb_global_table =
+        (struct sbrec_sb_global_table *)EN_OVSDB_GET(
+            engine_get_input("SB_sb_global", node));
+    const struct sbrec_sb_global *sb_global =
+        sbrec_sb_global_table_first(sb_global_table);
+
     ovs_assert(br_int && chassis);
 
     struct ed_type_ct_zones *ct_zones_data =
@@ -3215,6 +3223,13 @@  static void init_physical_ctx(struct engine_node *node,
     p_ctx->local_bindings = &rt_data->lbinding_data.bindings;
     p_ctx->patch_ofports = &non_vif_data->patch_ofports;
     p_ctx->chassis_tunnels = &non_vif_data->chassis_tunnels;
+    p_ctx->debug.collector_set_id = smap_get_uint(&sb_global->options,
+                                                  "debug_drop_collector_set",
+                                                  0);
+
+    p_ctx->debug.obs_domain_id = smap_get_uint(&sb_global->options,
+                                               "debug_drop_domain_id",
+                                               0);
 }
 
 static void *
@@ -3417,6 +3432,33 @@  pflow_output_activated_ports_handler(struct engine_node *node, void *data)
     return true;
 }
 
+static bool
+pflow_output_sb_sb_global_handler(struct engine_node *node, void *data)
+{
+    struct sbrec_sb_global_table *sb_global_table =
+        (struct sbrec_sb_global_table *)EN_OVSDB_GET(
+            engine_get_input("SB_sb_global", node));
+    const struct sbrec_sb_global *sb_global =
+        sbrec_sb_global_table_first(sb_global_table);
+
+    struct ed_type_pflow_output *pfo = data;
+
+    uint32_t collector_set_id = smap_get_uint(&sb_global->options,
+                                              "debug_drop_collector_set",
+                                              0);
+    uint32_t obs_domain_id = smap_get_uint(&sb_global->options,
+                                           "debug_drop_domain_id",
+                                           0);
+
+    if (pfo->debug.collector_set_id != collector_set_id ||
+        pfo->debug.obs_domain_id != obs_domain_id) {
+        engine_set_node_state(node, EN_UPDATED);
+        pfo->debug.collector_set_id = collector_set_id;
+        pfo->debug.obs_domain_id = obs_domain_id;
+    }
+    return true;
+}
+
 static void *
 en_flow_output_init(struct engine_node *node OVS_UNUSED,
                     struct engine_arg *arg OVS_UNUSED)
@@ -3759,6 +3801,8 @@  main(int argc, char *argv[])
     engine_add_input(&en_pflow_output, &en_mff_ovn_geneve, NULL);
     engine_add_input(&en_pflow_output, &en_ovs_open_vswitch, NULL);
     engine_add_input(&en_pflow_output, &en_ovs_bridge, NULL);
+    engine_add_input(&en_pflow_output, &en_sb_sb_global,
+                     pflow_output_sb_sb_global_handler);
 
     engine_add_input(&en_northd_options, &en_sb_sb_global,
                      en_northd_options_sb_sb_global_handler);
diff --git a/controller/physical.c b/controller/physical.c
index 415d16b76..eb9f01716 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -834,14 +834,32 @@  put_zones_ofpacts(const struct zone_ids *zone_ids, struct ofpbuf *ofpacts_p)
 }
 
 static void
-add_default_drop_flow(uint8_t table_id,
+put_drop(const struct physical_debug *debug, uint8_t table_id,
+         struct ofpbuf *ofpacts)
+{
+    if (debug->collector_set_id) {
+        struct ofpact_sample *os = ofpact_put_SAMPLE(ofpacts);
+        os->probability = UINT16_MAX;
+        os->collector_set_id = debug->collector_set_id;
+        os->obs_domain_id = (debug->obs_domain_id << 24);
+        os->obs_point_id = table_id;
+    }
+}
+
+static void
+add_default_drop_flow(const struct physical_ctx *p_ctx,
+                      uint8_t table_id,
                       struct ovn_desired_flow_table *flow_table)
 {
     struct match match = MATCH_CATCHALL_INITIALIZER;
     struct ofpbuf ofpacts;
     ofpbuf_init(&ofpacts, 0);
+
+    put_drop(&p_ctx->debug, table_id, &ofpacts);
     ofctrl_add_flow(flow_table, table_id, 0, 0, &match,
                     &ofpacts, hc_uuid);
+
+    ofpbuf_uninit(&ofpacts);
 }
 
 static void
@@ -849,6 +867,7 @@  put_local_common_flows(uint32_t dp_key,
                        const struct sbrec_port_binding *pb,
                        const struct sbrec_port_binding *parent_pb,
                        const struct zone_ids *zone_ids,
+                       const struct physical_debug *debug,
                        struct ofpbuf *ofpacts_p,
                        struct ovn_desired_flow_table *flow_table)
 {
@@ -884,6 +903,7 @@  put_local_common_flows(uint32_t dp_key,
      * and the MLF_ALLOW_LOOPBACK flag is not set. */
     match_init_catchall(&match);
     ofpbuf_clear(ofpacts_p);
+    put_drop(debug, OFTABLE_CHECK_LOOPBACK, ofpacts_p);
     match_set_metadata(&match, htonll(dp_key));
     match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
                          0, MLF_ALLOW_LOOPBACK);
@@ -1155,6 +1175,7 @@  consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                       const struct hmap *chassis_tunnels,
                       const struct sbrec_port_binding *binding,
                       const struct sbrec_chassis *chassis,
+                      const struct physical_debug *debug,
                       struct ovn_desired_flow_table *flow_table,
                       struct ofpbuf *ofpacts_p)
 {
@@ -1178,7 +1199,7 @@  consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
 
         struct zone_ids binding_zones = get_zone_ids(binding, ct_zones);
         put_local_common_flows(dp_key, binding, NULL, &binding_zones,
-                               ofpacts_p, flow_table);
+                               debug, ofpacts_p, flow_table);
 
         ofpbuf_clear(ofpacts_p);
         match_outport_dp_and_port_keys(&match, dp_key, port_key);
@@ -1354,7 +1375,7 @@  consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
         /* Pass the parent port binding if the port is a nested
          * container. */
         put_local_common_flows(dp_key, binding, parent_port, &zone_ids,
-                               ofpacts_p, flow_table);
+                               debug, ofpacts_p, flow_table);
 
         /* Table 0, Priority 150 and 100.
          * ==============================
@@ -1486,6 +1507,7 @@  consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
 
             /* Drop LOCAL_ONLY traffic leaking through localnet ports. */
             ofpbuf_clear(ofpacts_p);
+            put_drop(debug, OFTABLE_CHECK_LOOPBACK, ofpacts_p);
             match_outport_dp_and_port_keys(&match, dp_key, port_key);
             match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
                                  MLF_LOCAL_ONLY, MLF_LOCAL_ONLY);
@@ -1883,7 +1905,8 @@  physical_eval_port_binding(struct physical_ctx *p_ctx,
                           p_ctx->local_bindings,
                           p_ctx->patch_ofports,
                           p_ctx->chassis_tunnels,
-                          pb, p_ctx->chassis, flow_table, &ofpacts);
+                          pb, p_ctx->chassis, &p_ctx->debug,
+                          flow_table, &ofpacts);
     ofpbuf_uninit(&ofpacts);
 }
 
@@ -2006,7 +2029,8 @@  physical_run(struct physical_ctx *p_ctx,
                               p_ctx->local_bindings,
                               p_ctx->patch_ofports,
                               p_ctx->chassis_tunnels, binding,
-                              p_ctx->chassis, flow_table, &ofpacts);
+                              p_ctx->chassis, &p_ctx->debug,
+                              flow_table, &ofpacts);
     }
 
     /* Handle output to multicast groups, in tables 37 and 38. */
@@ -2130,7 +2154,7 @@  physical_run(struct physical_ctx *p_ctx,
      *
      * Drop packets tha do not match any tunnel in_port.
      */
-    add_default_drop_flow(OFTABLE_PHY_TO_LOG, flow_table);
+    add_default_drop_flow(p_ctx, OFTABLE_PHY_TO_LOG, flow_table);
 
     /* Table 37, priority 150.
      * =======================
@@ -2182,7 +2206,7 @@  physical_run(struct physical_ctx *p_ctx,
      *
      * Drop packets that do not match previous flows.
      */
-    add_default_drop_flow(OFTABLE_LOCAL_OUTPUT, flow_table);
+    add_default_drop_flow(p_ctx, OFTABLE_LOCAL_OUTPUT, flow_table);
 
     /* Table 39, Priority 0.
      * =======================
@@ -2215,20 +2239,20 @@  physical_run(struct physical_ctx *p_ctx,
      *
      * Drop packets that do not match previous flows.
      */
-    add_default_drop_flow(OFTABLE_LOG_TO_PHY, flow_table);
+    add_default_drop_flow(p_ctx, OFTABLE_LOG_TO_PHY, flow_table);
 
     /* Table 68, priority 0.
      * ======================
      *
      * Drop packets that do not match previous flows.
      */
-    add_default_drop_flow(OFTABLE_CHK_LB_HAIRPIN, flow_table);
+    add_default_drop_flow(p_ctx, OFTABLE_CHK_LB_HAIRPIN, flow_table);
 
     /* Table 70, priority 0.
      * ======================
      *
      * Drop packets that do not match previous flows.
      */
-    add_default_drop_flow(OFTABLE_CT_SNAT_HAIRPIN, flow_table);
+    add_default_drop_flow(p_ctx, OFTABLE_CT_SNAT_HAIRPIN, flow_table);
     ofpbuf_uninit(&ofpacts);
 }
diff --git a/controller/physical.h b/controller/physical.h
index 1b8f1ea55..f450dca94 100644
--- a/controller/physical.h
+++ b/controller/physical.h
@@ -43,6 +43,11 @@  struct local_nonvif_data;
 #define OVN_GENEVE_TYPE 0x80     /* Critical option. */
 #define OVN_GENEVE_LEN 4
 
+struct physical_debug {
+    uint32_t collector_set_id;
+    uint32_t obs_domain_id;
+};
+
 struct physical_ctx {
     struct ovsdb_idl_index *sbrec_port_binding_by_name;
     struct ovsdb_idl_index *sbrec_port_binding_by_datapath;
@@ -59,6 +64,7 @@  struct physical_ctx {
     struct shash *local_bindings;
     struct simap *patch_ofports;
     struct hmap *chassis_tunnels;
+    struct physical_debug debug;
 };
 
 void physical_register_ovs_idl(struct ovsdb_idl *);
diff --git a/northd/automake.mk b/northd/automake.mk
index 81582867d..14cf525d8 100644
--- a/northd/automake.mk
+++ b/northd/automake.mk
@@ -1,6 +1,8 @@ 
 # ovn-northd
 bin_PROGRAMS += northd/ovn-northd
 northd_ovn_northd_SOURCES = \
+	northd/debug.c \
+	northd/debug.h \
 	northd/mac-binding-aging.c \
 	northd/mac-binding-aging.h \
 	northd/northd.c \
diff --git a/northd/debug.c b/northd/debug.c
new file mode 100644
index 000000000..59da5d4f6
--- /dev/null
+++ b/northd/debug.c
@@ -0,0 +1,98 @@ 
+#include <config.h>
+
+#include <string.h>
+
+#include "debug.h"
+
+#include "openvswitch/dynamic-string.h"
+#include "openvswitch/vlog.h"
+#include "smap.h"
+
+VLOG_DEFINE_THIS_MODULE(debug)
+
+struct debug_config {
+    bool enabled;
+    uint32_t collector_set_id;
+    uint32_t observation_domain_id;
+    struct ds drop_action;
+};
+
+static struct debug_config config;
+
+static bool
+debug_enabled(void)
+{
+    return config.collector_set_id != 0;
+}
+
+void
+init_debug_config(const struct nbrec_nb_global *nb)
+{
+
+    const struct smap *options = &nb->options;
+    uint32_t collector_set_id = smap_get_uint(options,
+                                              "debug_drop_collector_set",
+                                              0);
+    uint32_t observation_domain_id = smap_get_uint(options,
+                                                   "debug_drop_domain_id",
+                                                   0);
+
+    if (collector_set_id != config.collector_set_id ||
+        observation_domain_id != config.observation_domain_id ||
+        !config.drop_action.length) {
+
+        if (observation_domain_id >= UINT8_MAX) {
+            VLOG_ERR("Observation domain id must be an 8-bit number");
+            return;
+        }
+
+        config.collector_set_id = collector_set_id;
+        config.observation_domain_id = observation_domain_id;
+
+        ds_clear(&config.drop_action);
+
+        if (debug_enabled()) {
+            ds_put_format(&config.drop_action,
+                          "sample(probability=65535,"
+                          "collector_set=%d,"
+                          "obs_domain=%d,"
+                          "obs_point=$cookie); ",
+                          config.collector_set_id,
+                          config.observation_domain_id);
+
+            ds_put_cstr(&config.drop_action, "/* drop */");
+            VLOG_DBG("Debug drop sampling: enabled");
+        } else {
+            ds_put_cstr(&config.drop_action, "drop;");
+            VLOG_DBG("Debug drop sampling: disabled");
+        }
+    }
+}
+
+void
+destroy_debug_config(void)
+{
+    if (config.drop_action.string) {
+        ds_destroy(&config.drop_action);
+        ds_init(&config.drop_action);
+    }
+}
+
+const char *
+debug_drop_action(void) {
+    if (OVS_UNLIKELY(debug_enabled())) {
+        return ds_cstr_ro(&config.drop_action);
+    } else {
+        return "drop;";
+    }
+}
+
+const char *
+debug_implicit_drop_action(void)
+{
+    if (OVS_UNLIKELY(debug_enabled())) {
+        return ds_cstr_ro(&config.drop_action);
+    } else {
+        return "/* drop */";
+    }
+}
diff --git a/northd/debug.h b/northd/debug.h
new file mode 100644
index 000000000..c1a5e5aad
--- /dev/null
+++ b/northd/debug.h
@@ -0,0 +1,30 @@ 
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NORTHD_DEBUG_H
+#define NORTHD_DEBUG_H 1
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "lib/ovn-nb-idl.h"
+#include "openvswitch/dynamic-string.h"
+
+void init_debug_config(const struct nbrec_nb_global *nb);
+void destroy_debug_config(void);
+
+const char *debug_drop_action(void);
+const char *debug_implicit_drop_action(void);
+
+#endif /* NORTHD_DEBUG_H */
diff --git a/northd/northd.c b/northd/northd.c
index 4b1829d37..b45f19689 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -17,6 +17,7 @@ 
 #include <stdlib.h>
 #include <stdio.h>
 
+#include "debug.h"
 #include "bitmap.h"
 #include "dirs.h"
 #include "ipam.h"
@@ -3827,7 +3828,7 @@  build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
         if (!n_active_backends) {
             if (!lb_vip->empty_backend_rej) {
                 ds_clear(action);
-                ds_put_cstr(action, "drop;");
+                ds_put_cstr(action, debug_drop_action());
                 skip_hash_fields = true;
             } else {
                 reject = true;
@@ -5161,7 +5162,7 @@  __ovn_lflow_add_default_drop(struct hmap *lflow_map,
                              enum ovn_stage stage,
                              const char *where)
 {
-    ovn_lflow_add_at(lflow_map, od, stage, 0, "1", "drop;",
+        ovn_lflow_add_at(lflow_map, od, stage, 0, "1", debug_drop_action(),
                          NULL, NULL, NULL, where );
 }
 
@@ -6392,7 +6393,7 @@  consider_acl(struct hmap *lflows, struct ovn_datapath *od,
             } else {
                 ds_put_format(match, " && (%s)", acl->match);
                 build_acl_log(actions, acl, meter_groups);
-                ds_put_cstr(actions, "/* drop */");
+                ds_put_cstr(actions, debug_implicit_drop_action());
                 ovn_lflow_add_with_hint(lflows, od, stage,
                                         acl->priority + OVN_ACL_PRI_OFFSET,
                                         ds_cstr(match), ds_cstr(actions),
@@ -6420,7 +6421,7 @@  consider_acl(struct hmap *lflows, struct ovn_datapath *od,
             } else {
                 ds_put_format(match, " && (%s)", acl->match);
                 build_acl_log(actions, acl, meter_groups);
-                ds_put_cstr(actions, "/* drop */");
+                ds_put_cstr(actions, debug_implicit_drop_action());
                 ovn_lflow_add_with_hint(lflows, od, stage,
                                         acl->priority + OVN_ACL_PRI_OFFSET,
                                         ds_cstr(match), ds_cstr(actions),
@@ -6437,7 +6438,7 @@  consider_acl(struct hmap *lflows, struct ovn_datapath *od,
                                        actions, &acl->header_, meter_groups);
             } else {
                 build_acl_log(actions, acl, meter_groups);
-                ds_put_cstr(actions, "/* drop */");
+                ds_put_cstr(actions, debug_implicit_drop_action());
                 ovn_lflow_add_with_hint(lflows, od, stage,
                                         acl->priority + OVN_ACL_PRI_OFFSET,
                                         acl->match, ds_cstr(actions),
@@ -6673,9 +6674,9 @@  build_acls(struct ovn_datapath *od, const struct chassis_features *features,
                       use_ct_inv_match ? "ct.inv || " : "",
                       ct_blocked_match);
         ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
-                      ds_cstr(&match), "drop;");
+                      ds_cstr(&match), debug_drop_action());
         ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
-                      ds_cstr(&match), "drop;");
+                      ds_cstr(&match),  debug_drop_action());
 
         /* Ingress and Egress ACL Table (Priority 65535 - 3).
          *
@@ -7774,7 +7775,7 @@  build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
                         rp->lsp_addrs[k].ipv4_addrs[l].addr_s);
                     ovn_lflow_add_with_lport_and_hint(
                         lflows, op->od, S_SWITCH_IN_EXTERNAL_PORT, 100,
-                        ds_cstr(&match), "drop;", port->key,
+                        ds_cstr(&match),  debug_drop_action(), port->key,
                         &op->nbsp->header_);
                 }
                 for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv6_addrs; l++) {
@@ -7790,7 +7791,7 @@  build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
                         rp->lsp_addrs[k].ipv6_addrs[l].addr_s);
                     ovn_lflow_add_with_lport_and_hint(
                         lflows, op->od, S_SWITCH_IN_EXTERNAL_PORT, 100,
-                        ds_cstr(&match), "drop;", port->key,
+                        ds_cstr(&match), debug_drop_action(), port->key,
                         &op->nbsp->header_);
                 }
 
@@ -7805,7 +7806,8 @@  build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
                 ovn_lflow_add_with_lport_and_hint(lflows, op->od,
                                                   S_SWITCH_IN_EXTERNAL_PORT,
                                                   100, ds_cstr(&match),
-                                                  "drop;", port->key,
+                                                  debug_drop_action(),
+                                                  port->key,
                                                   &op->nbsp->header_);
             }
         }
@@ -7843,7 +7845,7 @@  build_lswitch_flows(const struct hmap *datapaths,
                           "outport = \""MC_UNKNOWN "\"; output;");
         } else {
             ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50,
-                          "outport == \"none\"", "drop;");
+                          "outport == \"none\"",  debug_drop_action());
         }
         ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 0, "1",
                       "output;");
@@ -7886,18 +7888,18 @@  build_lswitch_lflows_admission_control(struct ovn_datapath *od,
         if (!is_vlan_transparent(od)) {
             /* Block logical VLANs. */
             ovn_lflow_add(lflows, od, S_SWITCH_IN_CHECK_PORT_SEC, 100,
-                          "vlan.present", "drop;");
+                          "vlan.present", debug_drop_action());
         }
 
         /* Broadcast/multicast source address is invalid. */
         ovn_lflow_add(lflows, od, S_SWITCH_IN_CHECK_PORT_SEC, 100,
-                      "eth.src[40]", "drop;");
+                      "eth.src[40]", debug_drop_action());
 
         ovn_lflow_add(lflows, od, S_SWITCH_IN_CHECK_PORT_SEC, 50, "1",
                       REGBIT_PORT_SEC_DROP" = check_in_port_sec(); next;");
 
         ovn_lflow_add(lflows, od, S_SWITCH_IN_APPLY_PORT_SEC, 50,
-                      REGBIT_PORT_SEC_DROP" == 1", "drop;");
+                      REGBIT_PORT_SEC_DROP" == 1", debug_drop_action());
 
         ovn_lflow_add(lflows, od, S_SWITCH_IN_APPLY_PORT_SEC, 0, "1", "next;");
     }
@@ -8426,7 +8428,7 @@  build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
                  */
                 if (!mcast_sw_info->flood_relay &&
                         !mcast_sw_info->flood_static) {
-                    ds_put_cstr(actions, "drop;");
+                    ds_put_cstr(actions, debug_drop_action());
                 }
 
                 ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
@@ -8951,7 +8953,7 @@  build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od,
                       out_port->json_key);
 
     } else if (!strcmp(rule->action, "drop")) {
-        ds_put_cstr(&actions, "drop;");
+        ds_put_cstr(&actions, debug_drop_action());
     } else if (!strcmp(rule->action, "allow")) {
         uint32_t pkt_mark = ovn_smap_get_uint(&rule->options, "pkt_mark", 0);
         if (pkt_mark) {
@@ -9801,7 +9803,7 @@  add_route(struct hmap *lflows, struct ovn_datapath *od,
     struct ds common_actions = DS_EMPTY_INITIALIZER;
     struct ds actions = DS_EMPTY_INITIALIZER;
     if (is_discard_route) {
-        ds_put_format(&actions, "drop;");
+        ds_put_cstr(&actions, debug_drop_action());
     } else {
         ds_put_format(&common_actions, REG_ECMP_GROUP_ID" = 0; %s = ",
                       is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6);
@@ -10584,7 +10586,7 @@  build_lrouter_arp_flow(struct ovn_datapath *od, struct ovn_port *op,
         ds_put_format(&match, " && %s", ds_cstr(extra_match));
     }
     if (drop) {
-        ds_put_format(&actions, "drop;");
+        ds_put_cstr(&actions, debug_drop_action());
     } else {
         ds_put_format(&actions,
                       "eth.dst = eth.src; "
@@ -10640,7 +10642,7 @@  build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
     }
 
     if (drop) {
-        ds_put_format(&actions, "drop;");
+        ds_put_cstr(&actions, debug_drop_action());
         ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, priority,
                                 ds_cstr(&match), ds_cstr(&actions), hint);
     } else {
@@ -10790,7 +10792,7 @@  build_lrouter_drop_own_dest(struct ovn_port *op, enum ovn_stage stage,
 
             char *match = xasprintf("ip4.dst == {%s}", ds_cstr(&match_ips));
             ovn_lflow_add_with_hint(lflows, op->od, stage, priority,
-                                    match, "drop;",
+                                    match, debug_drop_action(),
                                     &op->nbrp->header_);
             free(match);
         }
@@ -10819,7 +10821,7 @@  build_lrouter_drop_own_dest(struct ovn_port *op, enum ovn_stage stage,
 
             char *match = xasprintf("ip6.dst == {%s}", ds_cstr(&match_ips));
             ovn_lflow_add_with_hint(lflows, op->od, stage, priority,
-                                    match, "drop;",
+                                    match, debug_drop_action(),
                                     &op->nbrp->header_);
             free(match);
         }
@@ -10987,7 +10989,7 @@  build_adm_ctrl_flows_for_lrouter(
         /* Logical VLANs not supported.
          * Broadcast/multicast source address is invalid. */
         ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100,
-                      "vlan.present || eth.src[40]", "drop;");
+                      "vlan.present || eth.src[40]", debug_drop_action());
 
         /* Default action for L2 security is to drop. */
         ovn_lflow_add_default_drop(lflows, od, S_ROUTER_IN_ADMISSION);
@@ -11580,7 +11582,7 @@  build_mcast_lookup_flows_for_lrouter(
          * i.e., router solicitation and router advertisement.
          */
         ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 10550,
-                      "nd_rs || nd_ra", "drop;");
+                      "nd_rs || nd_ra", debug_drop_action());
         if (!od->mcast_info.rtr.relay) {
             return;
         }
@@ -11627,13 +11629,13 @@  build_mcast_lookup_flows_for_lrouter(
                 ds_put_format(match, "eth.src == %s && igmp",
                               op->lrp_networks.ea_s);
                 ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 10550,
-                              ds_cstr(match), "drop;");
+                              ds_cstr(match), debug_drop_action());
 
                 ds_clear(match);
                 ds_put_format(match, "eth.src == %s && (mldv1 || mldv2)",
                               op->lrp_networks.ea_s);
                 ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 10550,
-                              ds_cstr(match), "drop;");
+                              ds_cstr(match), debug_drop_action());
             }
 
             ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 10460,
@@ -11657,7 +11659,7 @@  build_mcast_lookup_flows_for_lrouter(
                           "};");
         } else {
             ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 10450,
-                          "ip4.mcast || ip6.mcast", "drop;");
+                          "ip4.mcast || ip6.mcast", debug_drop_action());
         }
     }
 }
@@ -12483,7 +12485,7 @@  build_misc_local_traffic_drop_flows_for_lrouter(
                       "ip4.dst == 127.0.0.0/8 || "
                       "ip4.src == 0.0.0.0/8 || "
                       "ip4.dst == 0.0.0.0/8",
-                      "drop;");
+                      debug_drop_action());
 
         /* Drop ARP packets (priority 85). ARP request packets for router's own
          * IPs are handled with priority-90 flows.
@@ -12491,7 +12493,7 @@  build_misc_local_traffic_drop_flows_for_lrouter(
          * IPs are handled with priority-90 flows.
          */
         ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85,
-                      "arp || nd", "drop;");
+                      "arp || nd", debug_drop_action());
 
         /* Allow IPv6 multicast traffic that's supposed to reach the
          * router pipeline (e.g., router solicitations).
@@ -12501,21 +12503,22 @@  build_misc_local_traffic_drop_flows_for_lrouter(
 
         /* Drop other reserved multicast. */
         ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83,
-                      "ip6.mcast_rsvd", "drop;");
+                      "ip6.mcast_rsvd", debug_drop_action());
 
         /* Allow other multicast if relay enabled (priority 82). */
         ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 82,
                       "ip4.mcast || ip6.mcast",
-                      od->mcast_info.rtr.relay ? "next;" : "drop;");
+                      (od->mcast_info.rtr.relay ? "next;" :
+                                                  debug_drop_action()));
 
         /* Drop Ethernet local broadcast.  By definition this traffic should
          * not be forwarded.*/
         ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50,
-                      "eth.bcast", "drop;");
+                      "eth.bcast", debug_drop_action());
 
         /* TTL discard */
         ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
-                      "ip4 && ip.ttl == {0, 1}", "drop;");
+                      "ip4 && ip.ttl == {0, 1}", debug_drop_action());
 
         /* Pass other traffic not already handled to the next table for
          * routing. */
@@ -12777,7 +12780,7 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
             op_put_v4_networks(match, op, true);
             ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0");
             ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-                                    ds_cstr(match), "drop;",
+                                    ds_cstr(match), debug_drop_action(),
                                     &op->nbrp->header_);
 
             /* ICMP echo reply.  These flows reply to ICMP echo requests
@@ -13834,8 +13837,8 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
             struct ovn_port *op = ovn_port_find(ports, nat->logical_port);
             if (op && op->nbsp && !strcmp(op->nbsp->type, "virtual")) {
                 ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
-                                        80, ds_cstr(match), "drop;",
-                                        &nat->header_);
+                                        80, ds_cstr(match),
+                                        debug_drop_action(), &nat->header_);
             }
             ds_put_format(match, " && is_chassis_resident(\"%s\")",
                           nat->logical_port);
@@ -15565,6 +15568,7 @@  northd_destroy(struct northd_data *data)
 
     destroy_datapaths_and_ports(&data->datapaths, &data->ports,
                                 &data->lr_list);
+    destroy_debug_config();
 }
 
 static void
@@ -15648,6 +15652,9 @@  ovnnb_db_run(struct northd_input *input_data,
                                               false);
 
     build_chassis_features(input_data, &data->features);
+
+    init_debug_config(nb);
+
     build_datapaths(input_data, ovnsb_txn, &data->datapaths, &data->lr_list);
     build_lbs(input_data, &data->datapaths, &data->lbs, &data->lb_groups);
     build_ports(input_data, ovnsb_txn, sbrec_chassis_by_name,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index cb996279f..68a01829c 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -4847,4 +4847,30 @@  clone {
       </ul>
     </p>
 
+    <h1>Drop sampling</h1>
+
+    <p>
+      As described in the previous section, there are several places where
+      ovn-northd might decided to drop a packet by explicitly creating a
+      <code>Logical_Flow</code> with the <code>drop;</code> action.
+    </p>
+
+    <p>
+      When debug drop-sampling has been cofigured in the OVN Northbound
+      database, the ovn-northd will replace all the <code>drop;</code>
+      actions with a <code>sample(priority=65535, collector_set=<var>id</var>,
+      obs_domain=<var>obs_id</var>, obs_point=@cookie)</code> action, where:
+      <ul>
+        <li>
+        <var>id</var> is the value the <code>debug_drop_collector_set</code>
+        option configured in the OVN Northbound.
+        </li>
+        <li>
+        <var>obs_id</var> has it's 8 most significant bits equal to the value
+        of the <code>debug_drop_domain_id</code> option in the OVN Northbound
+        and it's 24 least significant bits equal to the datapath's tunnel key.
+        </li>
+      </ul>
+    </p>
+
 </manpage>
diff --git a/ovn-nb.xml b/ovn-nb.xml
index f41e9d7c0..548e2ddb0 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -264,6 +264,34 @@ 
         </p>
       </column>
 
+      <column name="options" key="debug_drop_domain_id">
+        <p>
+          If set to a 8-bit number and if
+          <code>debug_drop_collector_set</code> is also configured,
+          <code>ovn-northd</code> will add a <code>sample</code> action to
+          every logical flow that contains a 'drop' action.
+          The 8 most significant bits of the observation_domain_id field will
+          be those specified in the
+          <code> debug_drop_domain_id</code>.
+          The 24 least significant bits of the observation_domain_id field will
+          be the datapath's key.
+        </p>
+        <p>
+          The observation_point_id will be set to the first 32 bits of the
+          logical flow's UUID.
+        </p>
+      </column>
+
+      <column name="options" key="debug_drop_collector_set">
+        <p>
+          If set to a 32-bit number <code>ovn-northd</code> will add a
+          <code>sample</code> action to every logical flow that contains a
+          'drop' action. The sample action will have the specified
+          collector_set_id. The value must match that of the local OVS
+          configuration as described in <code>ovs-actions</code>(7).
+        </p>
+      </column>
+
       <group title="Options for configuring interconnection route advertisement">
         <p>
           These options control how routes are advertised between OVN
diff --git a/ovn-sb.xml b/ovn-sb.xml
index a09891d10..60d623093 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -198,6 +198,35 @@ 
           BFD option <code>mult</code> value to use when configuring BFD on
           tunnel interfaces.
         </column>
+
+        <column name="options" key="debug_drop_domain_id">
+          <p>
+            If set to a 8-bit number and if
+            <code>debug_drop_collector_set</code> is also configured,
+            <code>ovn-controller</code> will add a <code>sample</code> action
+            to every flow that does not come from a logical flow that contains
+            a 'drop' action.
+            The 8 most significant bits of the observation_domain_id field will
+            be those specified in the
+            <code> debug_drop_domain_id</code>.
+            The 24 least significant bits of the observation_domain_id field
+            will be zero.
+          </p>
+          <p>
+            The observation_point_id will be set to the OpenFlow table number.
+          </p>
+        </column>
+
+        <column name="options" key="debug_drop_collector_set">
+          <p>
+            If set to a 32-bit number <code>ovn-controller</code> will add a
+            <code>sample</code> action to every flow that does not come from
+            a logical flow that contains a 'drop' action.
+            The sample action will have the specified collector_set_id.
+            The value must match that of the local OVS configuration as
+            described in <code>ovs-actions</code>(7).
+          </p>
+        </column>
       </group>
 
       <group title="Options for configuring Load Balancers">
diff --git a/tests/ovn.at b/tests/ovn.at
index 0a6489ea0..caafee89a 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -33055,6 +33055,43 @@  check_default_flows() {
     done
 }
 
+# Check that every drop flow gets sampled.
+check_sample_drops() {
+
+    check ovn-nbctl -- remove NB_Global . options debug_drop_collector_set \
+                    -- remove NB_Global . options debug_drop_domain_id
+    check ovn-nbctl --wait=hv sync
+
+    ovs-ofctl dump-flows --no-stats br-int > oflows_nosample
+    AT_CAPTURE_FILE([oflows_nosample])
+    # Take match part of flows that contain "drop".
+    drop_matches="$(grep 'drop' oflows_nosample | grep -oP 'table=\d*, priority=.* ')"
+
+    check ovn-nbctl -- set NB_Global . options:debug_drop_collector_set="123" \
+                    -- set NB_Global . options:debug_drop_domain_id="1"
+    check ovn-nbctl --wait=hv sync
+
+    ovs-ofctl dump-flows --no-stats br-int > oflows_sample
+    AT_CAPTURE_FILE([oflows_sample])
+
+    # Check that every drop has now contains a "sample" action.
+    for flow in "$drop_matches"; do
+        AT_CHECK([grep -q "$flow actions=.*sample.*" oflows_sample], [0], [ignore], [ignore], [echo "Flow $flow has a drop and did not get sampled"])
+    done
+}
+
+check_drops() {
+    as hv1
+    check_default_flows
+    as hv2
+    check_default_flows
+
+    as hv1
+    check_sample_drops
+    as hv2
+    check_sample_drops
+}
+
 # Logical network:
 # Two LRs - R1 and R2 that are connected to each other as peers in 20.0.0.0/24
 # network. R1 has a switchs ls1 (191.168.1.0/24) connected to it.
@@ -33120,10 +33157,7 @@  ovs-vsctl -- add-port br-int hv2-vif1 -- \
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
 
-as hv1
-check_default_flows
-as hv2
-check_default_flows
+check_debug
 
 # Add stateless ACL
 check ovn-nbctl --wait=sb \
@@ -33131,10 +33165,7 @@  check ovn-nbctl --wait=sb \
 check ovn-nbctl --wait=sb \
                 -- acl-add ls2 from-lport 100 'ip4' allow-stateless
 
-as hv1
-check_default_flows
-as hv2
-check_default_flows
+check_debug
 
 check ovn-nbctl --wait=sb acl-del ls1
 check ovn-nbctl --wait=sb acl-del ls2
@@ -33145,10 +33176,7 @@  check ovn-nbctl --wait=sb \
 check ovn-nbctl --wait=sb \
                 -- acl-add ls2 from-lport 100 "udp" allow-related
 
-as hv1
-check_default_flows
-as hv2
-check_default_flows
+check_debug
 
 check ovn-nbctl --wait=sb acl-del ls1
 check ovn-nbctl --wait=sb acl-del ls2
@@ -33162,10 +33190,7 @@  check ovn-nbctl --wait=sb \
     -- lb-add lb2 "10.0.1.1" "10.0.1.2" \
     -- ls-lb-add ls2 lb2
 
-as hv1
-check_default_flows
-as hv2
-check_default_flows
+check_drops
 
 # LB + stateless ACL
 check ovn-nbctl --wait=sb \
@@ -33173,10 +33198,7 @@  check ovn-nbctl --wait=sb \
 check ovn-nbctl --wait=sb \
                 -- acl-add ls2 from-lport 100 'ip4' allow-stateless
 
-as hv1
-check_default_flows
-as hv2
-check_default_flows
+check_drops
 
 check ovn-nbctl --wait=sb acl-del ls1
 check ovn-nbctl --wait=sb acl-del ls2
@@ -33187,10 +33209,7 @@  check ovn-nbctl --wait=sb \
 check ovn-nbctl --wait=sb \
                 -- acl-add ls2 from-lport 100 "udp" allow-related
 
-as hv1
-check_default_flows
-as hv2
-check_default_flows
+check_drops
 
 OVN_CLEANUP([hv1],[hv2])
 AT_CLEANUP