[ovs-dev,RFC,2/3] OVN: introduce send_event() action
diff mbox series

Message ID e9703656aed0fa943f4e66523f88bb64f5b6170d.1558021382.git.lorenzo.bianconi@redhat.com
State RFC
Headers show
Series
  • OVN: add Controller Events
Related show

Commit Message

Lorenzo Bianconi May 16, 2019, 4:05 p.m. UTC
Add send_event() ovn action in order to allow ovs-vswitchd to report
CMS related events.
This commit introduces a new event, empty_lb_backends. This event is
raised if a received packet is destined for a load balancer VIP that has
no configured backend destinations. For this event, the event info
includes the load balancer VIP, the load balancer UUID, and the
transport protocol.
The use case for this particular event is for the CMS to supply backend
resources to handle this traffic. For example, in Openshift, this event
can be used to spin up new containers to handle the incoming traffic.

Signed-off-by: Mark Michelson <mmichels@redhat.com>
Co-authored-by: Mark Michelson <mmichels@redhat.com>
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
---
 include/ovn/actions.h     |  17 +++-
 ovn/controller/lflow.c    |   8 ++
 ovn/controller/pinctrl.c  | 109 ++++++++++++++++++++++++
 ovn/lib/actions.c         | 169 ++++++++++++++++++++++++++++++++++++++
 ovn/lib/ovn-l7.h          |  46 +++++++++++
 ovn/utilities/ovn-trace.c |   3 +
 tests/ovn.at              |  10 +++
 tests/test-ovn.c          |  11 ++-
 8 files changed, 370 insertions(+), 3 deletions(-)

Comments

Numan Siddique May 21, 2019, 7:40 a.m. UTC | #1
On Thu, May 16, 2019 at 9:45 PM Lorenzo Bianconi <
lorenzo.bianconi@redhat.com> wrote:

> Add send_event() ovn action in order to allow ovs-vswitchd to report
> CMS related events.
> This commit introduces a new event, empty_lb_backends. This event is
> raised if a received packet is destined for a load balancer VIP that has
> no configured backend destinations. For this event, the event info
> includes the load balancer VIP, the load balancer UUID, and the
> transport protocol.
> The use case for this particular event is for the CMS to supply backend
> resources to handle this traffic. For example, in Openshift, this event
> can be used to spin up new containers to handle the incoming traffic.
>
> Signed-off-by: Mark Michelson <mmichels@redhat.com>
> Co-authored-by: Mark Michelson <mmichels@redhat.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
>

Hi Lorenzo,

Overall this series looks good to me. I haven't done any code review yet.

I have few initial comments.

How about renaming the action - "send_event" to "trigger_event"  ? Just a
suggestion.

The action send_event takes the event_type as an integer. I would suggest
to take a string value instead.
something like -  send_event(event = "lb_no_backends",  vip = "10.0.0.1:80
<http://10.0.0.1/>", protocol...)

Thanks
Numan


---
>  include/ovn/actions.h     |  17 +++-
>  ovn/controller/lflow.c    |   8 ++
>  ovn/controller/pinctrl.c  | 109 ++++++++++++++++++++++++
>  ovn/lib/actions.c         | 169 ++++++++++++++++++++++++++++++++++++++
>  ovn/lib/ovn-l7.h          |  46 +++++++++++
>  ovn/utilities/ovn-trace.c |   3 +
>  tests/ovn.at              |  10 +++
>  tests/test-ovn.c          |  11 ++-
>  8 files changed, 370 insertions(+), 3 deletions(-)
>
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index e07ad9aa3..0d5920023 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -83,7 +83,8 @@ struct ovn_extend_table;
>      OVNACT(ND_NS,             ovnact_nest)            \
>      OVNACT(SET_METER,         ovnact_set_meter)       \
>      OVNACT(OVNFIELD_LOAD,     ovnact_load)            \
> -    OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger)
> +    OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger) \
> +    OVNACT(SEND_EVENT,        ovnact_controller_event)
>
>  /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
>  enum OVS_PACKED_ENUM ovnact_type {
> @@ -318,6 +319,14 @@ struct ovnact_check_pkt_larger {
>      struct expr_field dst;      /* 1-bit destination field. */
>  };
>
> +/* OVNACT_EVENT. */
> +struct ovnact_controller_event {
> +    struct ovnact ovnact;
> +    int event_type;   /* controller event type */
> +    struct ovnact_gen_option *options;
> +    size_t n_options;
> +};
> +
>  /* Internal use by the helpers below. */
>  void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
>  void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
> @@ -486,6 +495,9 @@ enum action_opcode {
>       * The actions, in OpenFlow 1.3 format, follow the action_header.
>       */
>      ACTION_OPCODE_ICMP4_ERROR,
> +
> +    /* "send_event (event_type)" */
> +    ACTION_OPCODE_EVENT,
>  };
>
>  /* Header. */
> @@ -515,6 +527,9 @@ struct ovnact_parse_params {
>      /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */
>      const struct hmap *nd_ra_opts;
>
> +    /* Array of hmap of 'struct gen_opts_map' to support 'send_event'
> action */
> +    const struct controller_event_options *controller_event_opts;
> +
>      /* Each OVN flow exists in a logical table within a logical pipeline.
>       * These parameters express this context for a set of OVN actions
> being
>       * parsed:
> diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
> index 661407bcc..8d7f51204 100644
> --- a/ovn/controller/lflow.c
> +++ b/ovn/controller/lflow.c
> @@ -70,6 +70,7 @@ static void consider_logical_flow(
>      struct hmap *dhcp_opts,
>      struct hmap *dhcpv6_opts,
>      struct hmap *nd_ra_opts,
> +    struct controller_event_options *controller_event_opts,
>      const struct shash *addr_sets,
>      const struct shash *port_groups,
>      const struct sset *active_tunnels,
> @@ -173,11 +174,15 @@ add_logical_flows(
>      struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);
>      nd_ra_opts_init(&nd_ra_opts);
>
> +    struct controller_event_options controller_event_opts;
> +    controller_event_opts_init(&controller_event_opts);
> +
>      SBREC_LOGICAL_FLOW_TABLE_FOR_EACH (lflow, logical_flow_table) {
>          consider_logical_flow(sbrec_multicast_group_by_name_datapath,
>                                sbrec_port_binding_by_name,
>                                lflow, local_datapaths,
>                                chassis, &dhcp_opts, &dhcpv6_opts,
> &nd_ra_opts,
> +                              &controller_event_opts,
>                                addr_sets, port_groups, active_tunnels,
>                                local_lport_ids, &conj_id_ofs,
>                                flow_table, group_table, meter_table);
> @@ -186,6 +191,7 @@ add_logical_flows(
>      dhcp_opts_destroy(&dhcp_opts);
>      dhcp_opts_destroy(&dhcpv6_opts);
>      nd_ra_opts_destroy(&nd_ra_opts);
> +    controller_event_opts_destroy(&controller_event_opts);
>  }
>
>  static void
> @@ -198,6 +204,7 @@ consider_logical_flow(
>      struct hmap *dhcp_opts,
>      struct hmap *dhcpv6_opts,
>      struct hmap *nd_ra_opts,
> +    struct controller_event_options *controller_event_opts,
>      const struct shash *addr_sets,
>      const struct shash *port_groups,
>      const struct sset *active_tunnels,
> @@ -237,6 +244,7 @@ consider_logical_flow(
>          .dhcp_opts = dhcp_opts,
>          .dhcpv6_opts = dhcpv6_opts,
>          .nd_ra_opts = nd_ra_opts,
> +        .controller_event_opts = controller_event_opts,
>
>          .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
>          .n_tables = LOG_PIPELINE_LEN,
> diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
> index ca191d961..6cc98c617 100644
> --- a/ovn/controller/pinctrl.c
> +++ b/ovn/controller/pinctrl.c
> @@ -208,6 +208,10 @@ static void pinctrl_handle_put_icmp4_frag_mtu(struct
> rconn *swconn,
>                                                struct ofputil_packet_in
> *pin,
>                                                struct ofpbuf *userdata,
>                                                struct ofpbuf
> *continuation);
> +static void
> +pinctrl_handle_event(struct ofpbuf *userdata)
> +    OVS_REQUIRES(pinctrl_mutex);
> +static void wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn);
>  static void init_ipv6_ras(void);
>  static void destroy_ipv6_ras(void);
>  static void ipv6_ra_wait(long long int send_ipv6_ra_time);
> @@ -1870,6 +1874,12 @@ process_packet_in(struct rconn *swconn, const
> struct ofp_header *msg)
>                                            &pin, &userdata, &continuation);
>          break;
>
> +    case ACTION_OPCODE_EVENT:
> +        ovs_mutex_lock(&pinctrl_mutex);
> +        pinctrl_handle_event(&userdata);
> +        ovs_mutex_unlock(&pinctrl_mutex);
> +        break;
> +
>      default:
>          VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
>                       ntohl(ah->opcode));
> @@ -2378,6 +2388,7 @@ void
>  pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn)
>  {
>      wait_put_mac_bindings(ovnsb_idl_txn);
> +    wait_controller_event(ovnsb_idl_txn);
>      int64_t new_seq = seq_read(pinctrl_main_seq);
>      seq_wait(pinctrl_main_seq, new_seq);
>  }
> @@ -3417,3 +3428,101 @@ exit:
>          dp_packet_delete(pkt_out);
>      }
>  }
> +
> +static void
> +wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn)
> +{
> +    if (!ovnsb_idl_txn) {
> +        return;
> +    }
> +
> +    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
> +        if (!hmap_is_empty(&event_table[i])) {
> +            poll_immediate_wake();
> +            break;
> +        }
> +    }
> +}
> +
> +static bool
> +pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata)
> +{
> +    struct controller_event_opt_header *userdata_opt;
> +    uint32_t hash = 0;
> +    char *vip = NULL;
> +    char *protocol = NULL;
> +    char *load_balancer = NULL;
> +
> +    while (userdata->size) {
> +        userdata_opt = ofpbuf_try_pull(userdata, sizeof *userdata_opt);
> +        if (!userdata_opt) {
> +            return false;
> +        }
> +        size_t size = ntohs(userdata_opt->size);
> +        char *userdata_opt_data = ofpbuf_try_pull(userdata, size);
> +        if (!userdata_opt_data) {
> +            return false;
> +        }
> +        switch (ntohs(userdata_opt->opt_code)) {
> +        case EMPTY_LB_VIP:
> +            vip = xmemdup0(userdata_opt_data, size);
> +            break;
> +        case EMPTY_LB_PROTOCOL:
> +            protocol = xmemdup0(userdata_opt_data, size);
> +            break;
> +        case EMPTY_LB_LOAD_BALANCER:
> +            load_balancer = xmemdup0(userdata_opt_data, size);
> +            break;
> +        default:
> +            OVS_NOT_REACHED();
> +        }
> +        hash = hash_bytes(userdata_opt_data, size, hash);
> +    }
> +    ovs_assert(vip && protocol && load_balancer);
> +
> +    struct empty_lb_backends_event *event;
> +
> +    event = pinctrl_find_empty_lb_backends_event(vip, protocol,
> +                                                 load_balancer, hash);
> +    if (!event) {
> +        if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >=
> 1000) {
> +            COVERAGE_INC(pinctrl_drop_controller_event);
> +            return false;
> +        }
> +
> +        event = xzalloc(sizeof *event);
> +        hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS],
> +                    &event->hmap_node, hash);
> +        event->vip = vip;
> +        event->protocol = protocol;
> +        event->load_balancer = load_balancer;
> +        notify_pinctrl_main();
> +    } else {
> +        free(vip);
> +        free(protocol);
> +        free(load_balancer);
> +    }
> +    return true;
> +}
> +
> +static void
> +pinctrl_handle_event(struct ofpbuf *userdata)
> +    OVS_REQUIRES(pinctrl_mutex)
> +{
> +    ovs_be32 *pevent;
> +
> +    pevent = ofpbuf_try_pull(userdata, sizeof *pevent);
> +    if (!pevent) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "event not present in the userdata");
> +        return;
> +    }
> +
> +    switch (ntohl(*pevent)) {
> +    case OVN_EVENT_EMPTY_LB_BACKENDS:
> +        pinctrl_handle_empty_lb_backends_opts(userdata);
> +        break;
> +    default:
> +        return;
> +    }
> +}
> diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
> index d5909911d..1603b5d57 100644
> --- a/ovn/lib/actions.c
> +++ b/ovn/lib/actions.c
> @@ -38,6 +38,8 @@
>  #include "packets.h"
>  #include "openvswitch/shash.h"
>  #include "simap.h"
> +#include "uuid.h"
> +#include "socket-util.h"
>
>  VLOG_DEFINE_THIS_MODULE(actions);
>
> @@ -1258,6 +1260,20 @@ format_CLONE(const struct ovnact_nest *nest, struct
> ds *s)
>      format_nested_action(nest, "clone", s);
>  }
>
> +static void
> +format_SEND_EVENT(const struct ovnact_controller_event *event,
> +                  struct ds *s)
> +{
> +    ds_put_format(s, "send_event(event = %d", event->event_type);
> +    for (const struct ovnact_gen_option *o = event->options;
> +         o < &event->options[event->n_options]; o++) {
> +        ds_put_cstr(s, ", ");
> +        ds_put_format(s, "%s = ", o->option->name);
> +        expr_constant_set_format(&o->value, s);
> +    }
> +    ds_put_cstr(s, ");");
> +}
> +
>  static void
>  encode_nested_actions(const struct ovnact_nest *on,
>                        const struct ovnact_encode_params *ep,
> @@ -1361,6 +1377,52 @@ encode_CLONE(const struct ovnact_nest *on,
>      ofpact_finish_CLONE(ofpacts, &clone);
>  }
>
> +static void
> +encode_event_empty_lb_backends_opts(struct ofpbuf *ofpacts,
> +        const struct ovnact_controller_event *event)
> +{
> +    for (const struct ovnact_gen_option *o = event->options;
> +         o < &event->options[event->n_options]; o++) {
> +        struct controller_event_opt_header *hdr =
> +            ofpbuf_put_uninit(ofpacts, sizeof *hdr);
> +        const union expr_constant *c = o->value.values;
> +        size_t size;
> +        hdr->opt_code = htons(o->option->code);
> +        if (!strcmp(o->option->type, "str")) {
> +            size = strlen(c->string);
> +            hdr->size = htons(size);
> +            ofpbuf_put(ofpacts, c->string, size);
> +        } else {
> +            /* All empty_lb_backends fields are of type 'str' */
> +            OVS_NOT_REACHED();
> +        }
> +    }
> +}
> +
> +static void
> +encode_SEND_EVENT(const struct ovnact_controller_event *event,
> +                  const struct ovnact_encode_params *ep OVS_UNUSED,
> +                  struct ofpbuf *ofpacts)
> +{
> +    size_t oc_offset;
> +
> +    oc_offset = encode_start_controller_op(ACTION_OPCODE_EVENT, false,
> +                                           NX_CTLR_NO_METER, ofpacts);
> +    ovs_be32 ofs = htonl(event->event_type);
> +    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
> +
> +    switch (event->event_type) {
> +    case OVN_EVENT_EMPTY_LB_BACKENDS:
> +        encode_event_empty_lb_backends_opts(ofpacts, event);
> +        break;
> +    case OVN_EVENT_MAX:
> +    default:
> +        OVS_NOT_REACHED();
> +    }
> +
> +    encode_finish_controller_op(oc_offset, ofpacts);
> +}
> +
>  static void
>  ovnact_nest_free(struct ovnact_nest *on)
>  {
> @@ -1575,6 +1637,111 @@ free_gen_options(struct ovnact_gen_option
> *options, size_t n)
>      free(options);
>  }
>
> +static void
> +validate_empty_lb_backends(struct action_context *ctx,
> +                           const struct ovnact_gen_option *options,
> +                           size_t n_options)
> +{
> +    for (const struct ovnact_gen_option *o = options;
> +         o < &options[n_options]; o++) {
> +        const union expr_constant *c = o->value.values;
> +        struct sockaddr_storage ss;
> +        struct uuid uuid;
> +
> +        if (o->value.n_values > 1 || !c->string) {
> +            lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
> +                        o->option->name);
> +            return;
> +        }
> +
> +        switch (o->option->code) {
> +        case EMPTY_LB_VIP:
> +            if (!inet_parse_active(c->string, 0, &ss, false)) {
> +                lexer_error(ctx->lexer, "Invalid load balancer VIP '%s'",
> +                            c->string);
> +                return;
> +            }
> +            break;
> +        case EMPTY_LB_PROTOCOL:
> +            if (strcmp(c->string, "tcp") && strcmp(c->string, "udp")) {
> +                lexer_error(ctx->lexer,
> +                    "Load balancer protocol '%s' is not 'tcp' or 'udp'",
> +                    c->string);
> +                return;
> +            }
> +            break;
> +        case EMPTY_LB_LOAD_BALANCER:
> +            if (!uuid_from_string(&uuid, c->string)) {
> +                lexer_error(ctx->lexer, "Load balancer '%s' is not a
> UUID",
> +                            c->string);
> +                return;
> +            }
> +            break;
> +        }
> +    }
> +}
> +
> +static void
> +parse_send_event(struct action_context *ctx,
> +                 struct ovnact_controller_event *event)
> +{
> +    int event_type = 0;
> +
> +    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
> +
> +    /* Event type must be listed first */
> +    if (!lexer_match_id(ctx->lexer, "event")) {
> +        lexer_syntax_error(ctx->lexer, "Expecting 'event' option");
> +        return;
> +    }
> +    if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
> +        return;
> +    }
> +    if (!lexer_force_int(ctx->lexer, &event_type)) {
> +        return;
> +    }
> +
> +    if (event_type < 0 || event_type >= OVN_EVENT_MAX) {
> +        lexer_syntax_error(ctx->lexer, "Unknown event '%d'", event_type);
> +        return;
> +    }
> +
> +    event->event_type = event_type;
> +    lexer_match(ctx->lexer, LEX_T_COMMA);
> +
> +    size_t allocated_options = 0;
> +    while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> +        if (event->n_options >= allocated_options) {
> +            event->options = x2nrealloc(event->options,
> &allocated_options,
> +                                     sizeof *event->options);
> +        }
> +
> +        struct ovnact_gen_option *o = &event->options[event->n_options++];
> +        memset(o, 0, sizeof *o);
> +        parse_gen_opt(ctx, o,
> +
> &ctx->pp->controller_event_opts->event_opts[event_type],
> +                      event_to_string(event_type));
> +        if (ctx->lexer->error) {
> +            return;
> +        }
> +
> +        lexer_match(ctx->lexer, LEX_T_COMMA);
> +    }
> +
> +    switch (event_type) {
> +    case OVN_EVENT_EMPTY_LB_BACKENDS:
> +        validate_empty_lb_backends(ctx, event->options, event->n_options);
> +        break;
> +    default:
> +        OVS_NOT_REACHED();
> +    }
> +}
> +
> +static void
> +ovnact_controller_event_free(struct ovnact_controller_event *event
> OVS_UNUSED)
> +{
> +}
> +
>  static void
>  parse_put_opts(struct action_context *ctx, const struct expr_field *dst,
>                 struct ovnact_put_opts *po, const struct hmap *gen_opts,
> @@ -2511,6 +2678,8 @@ parse_action(struct action_context *ctx)
>          parse_LOG(ctx);
>      } else if (lexer_match_id(ctx->lexer, "set_meter")) {
>          parse_set_meter_action(ctx);
> +    } else if (lexer_match_id(ctx->lexer, "send_event")) {
> +        parse_send_event(ctx, ovnact_put_SEND_EVENT(ctx->ovnacts));
>      } else {
>          lexer_syntax_error(ctx->lexer, "expecting action");
>      }
> diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h
> index c24201ef0..4ff458e90 100644
> --- a/ovn/lib/ovn-l7.h
> +++ b/ovn/lib/ovn-l7.h
> @@ -22,6 +22,7 @@
>  #include <netinet/icmp6.h>
>  #include "openvswitch/hmap.h"
>  #include "hash.h"
> +#include "ovn/logical-fields.h"
>
>  /* Generic options map which is used to store dhcpv4 opts and dhcpv6
> opts. */
>  struct gen_opts_map {
> @@ -272,4 +273,49 @@ nd_ra_opts_init(struct hmap *nd_ra_opts)
>      nd_ra_opt_add(nd_ra_opts, "mtu", ND_OPT_MTU, "uint32");
>  }
>
> +#define EMPTY_LB_VIP           1
> +#define EMPTY_LB_PROTOCOL      2
> +#define EMPTY_LB_LOAD_BALANCER 3
> +
> +/* Used in the OpenFlow PACKET_IN userdata */
> +struct controller_event_opt_header {
> +    ovs_be16 opt_code;
> +    ovs_be16 size;
> +};
> +
> +struct controller_event_options {
> +    struct hmap event_opts[OVN_EVENT_MAX];
> +};
> +
> +static inline void
> +controller_event_opt_add(struct controller_event_options *event_opts,
> +                         enum ovn_controller_event event_type, char
> *opt_name,
> +                         size_t opt_code, char *opt_type)
> +{
> +    gen_opt_add(&event_opts->event_opts[event_type], opt_name, opt_code,
> +                opt_type);
> +}
> +
> +static inline void
> +controller_event_opts_init(struct controller_event_options *opts)
> +{
> +    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
> +        hmap_init(&opts->event_opts[i]);
> +    }
> +    controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS, "vip",
> +                             EMPTY_LB_VIP, "str");
> +    controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS,
> "protocol",
> +                             EMPTY_LB_PROTOCOL, "str");
> +    controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS,
> +                             "load_balancer", EMPTY_LB_LOAD_BALANCER,
> "str");
> +}
> +
> +static inline void
> +controller_event_opts_destroy(struct controller_event_options *opts)
> +{
> +    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
> +        gen_opts_destroy(&opts->event_opts[i]);
> +    }
> +}
> +
>  #endif /* OVN_DHCP_H */
> diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
> index 9718077aa..49c3511de 100644
> --- a/ovn/utilities/ovn-trace.c
> +++ b/ovn/utilities/ovn-trace.c
> @@ -2135,6 +2135,9 @@ trace_actions(const struct ovnact *ovnacts, size_t
> ovnacts_len,
>              execute_ovnfield_load(ovnact_get_OVNFIELD_LOAD(a), super);
>              break;
>
> +        case OVNACT_SEND_EVENT:
> +            break;
> +
>          case OVNACT_CHECK_PKT_LARGER:
>              break;
>          }
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 6499df3d3..1c54dd920 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -1333,6 +1333,16 @@ tcp_reset { };
>      encodes as controller(userdata=00.00.00.0b.00.00.00.00)
>      has prereqs tcp
>
> +# send_event
> +send_event(event = 0, vip = "10.0.0.1:80", protocol = "tcp",
> load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c");
> +    encodes as
> controller(userdata=00.00.00.0f.00.00.00.00.00.00.00.00.00.01.00.0b.31.30.2e.30.2e.30.2e.31.3a.38.30.00.02.00.03.74.63.70.00.03.00.24.31.32.33.34.35.36.37.38.2d.61.62.63.64.2d.39.38.37.36.2d.66.65.64.63.2d.31.31.31.31.39.66.38.65.37.64.36.63)
> +
> +# Testing invalid vip results in extra error messages from socket-util.c
> +send_event(event = 0, vip = "10.0.0.1:80", protocol = "sctp",
> load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c");
> +    Load balancer protocol 'sctp' is not 'tcp' or 'udp'
> +send_event(event = 0, vip = "10.0.0.1:80", protocol = "tcp",
> load_balancer = "bacon");
> +    Load balancer 'bacon' is not a UUID
> +
>  # Contradictionary prerequisites (allowed but not useful):
>  ip4.src = ip6.src[0..31];
>      encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[]
> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> index 7cce9c2ae..619bd3f78 100644
> --- a/tests/test-ovn.c
> +++ b/tests/test-ovn.c
> @@ -157,7 +157,8 @@ create_symtab(struct shash *symtab)
>
>  static void
>  create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
> -                struct hmap *nd_ra_opts)
> +                struct hmap *nd_ra_opts,
> +                struct controller_event_options *event_opts)
>  {
>      hmap_init(dhcp_opts);
>      dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4");
> @@ -197,6 +198,9 @@ create_gen_opts(struct hmap *dhcp_opts, struct hmap
> *dhcpv6_opts,
>      /* IPv6 ND RA options. */
>      hmap_init(nd_ra_opts);
>      nd_ra_opts_init(nd_ra_opts);
> +
> +    /* OVN controller events options. */
> +    controller_event_opts_init(event_opts);
>  }
>
>  static void
> @@ -1228,12 +1232,13 @@ test_parse_actions(struct ovs_cmdl_context *ctx
> OVS_UNUSED)
>      struct hmap dhcp_opts;
>      struct hmap dhcpv6_opts;
>      struct hmap nd_ra_opts;
> +    struct controller_event_options event_opts;
>      struct simap ports;
>      struct ds input;
>      bool ok = true;
>
>      create_symtab(&symtab);
> -    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts);
> +    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts, &event_opts);
>
>      /* Initialize group ids. */
>      struct ovn_extend_table group_table;
> @@ -1263,6 +1268,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx
> OVS_UNUSED)
>              .dhcp_opts = &dhcp_opts,
>              .dhcpv6_opts = &dhcpv6_opts,
>              .nd_ra_opts = &nd_ra_opts,
> +            .controller_event_opts = &event_opts,
>              .n_tables = 24,
>              .cur_ltable = 10,
>          };
> @@ -1350,6 +1356,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx
> OVS_UNUSED)
>      dhcp_opts_destroy(&dhcp_opts);
>      dhcp_opts_destroy(&dhcpv6_opts);
>      nd_ra_opts_destroy(&nd_ra_opts);
> +    controller_event_opts_destroy(&event_opts);
>      ovn_extend_table_destroy(&group_table);
>      ovn_extend_table_destroy(&meter_table);
>      exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
> --
> 2.20.1
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>

Patch
diff mbox series

diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index e07ad9aa3..0d5920023 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -83,7 +83,8 @@  struct ovn_extend_table;
     OVNACT(ND_NS,             ovnact_nest)            \
     OVNACT(SET_METER,         ovnact_set_meter)       \
     OVNACT(OVNFIELD_LOAD,     ovnact_load)            \
-    OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger)
+    OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger) \
+    OVNACT(SEND_EVENT,        ovnact_controller_event)
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -318,6 +319,14 @@  struct ovnact_check_pkt_larger {
     struct expr_field dst;      /* 1-bit destination field. */
 };
 
+/* OVNACT_EVENT. */
+struct ovnact_controller_event {
+    struct ovnact ovnact;
+    int event_type;   /* controller event type */
+    struct ovnact_gen_option *options;
+    size_t n_options;
+};
+
 /* Internal use by the helpers below. */
 void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
 void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
@@ -486,6 +495,9 @@  enum action_opcode {
      * The actions, in OpenFlow 1.3 format, follow the action_header.
      */
     ACTION_OPCODE_ICMP4_ERROR,
+
+    /* "send_event (event_type)" */
+    ACTION_OPCODE_EVENT,
 };
 
 /* Header. */
@@ -515,6 +527,9 @@  struct ovnact_parse_params {
     /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */
     const struct hmap *nd_ra_opts;
 
+    /* Array of hmap of 'struct gen_opts_map' to support 'send_event' action */
+    const struct controller_event_options *controller_event_opts;
+
     /* Each OVN flow exists in a logical table within a logical pipeline.
      * These parameters express this context for a set of OVN actions being
      * parsed:
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index 661407bcc..8d7f51204 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -70,6 +70,7 @@  static void consider_logical_flow(
     struct hmap *dhcp_opts,
     struct hmap *dhcpv6_opts,
     struct hmap *nd_ra_opts,
+    struct controller_event_options *controller_event_opts,
     const struct shash *addr_sets,
     const struct shash *port_groups,
     const struct sset *active_tunnels,
@@ -173,11 +174,15 @@  add_logical_flows(
     struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);
     nd_ra_opts_init(&nd_ra_opts);
 
+    struct controller_event_options controller_event_opts;
+    controller_event_opts_init(&controller_event_opts);
+
     SBREC_LOGICAL_FLOW_TABLE_FOR_EACH (lflow, logical_flow_table) {
         consider_logical_flow(sbrec_multicast_group_by_name_datapath,
                               sbrec_port_binding_by_name,
                               lflow, local_datapaths,
                               chassis, &dhcp_opts, &dhcpv6_opts, &nd_ra_opts,
+                              &controller_event_opts,
                               addr_sets, port_groups, active_tunnels,
                               local_lport_ids, &conj_id_ofs,
                               flow_table, group_table, meter_table);
@@ -186,6 +191,7 @@  add_logical_flows(
     dhcp_opts_destroy(&dhcp_opts);
     dhcp_opts_destroy(&dhcpv6_opts);
     nd_ra_opts_destroy(&nd_ra_opts);
+    controller_event_opts_destroy(&controller_event_opts);
 }
 
 static void
@@ -198,6 +204,7 @@  consider_logical_flow(
     struct hmap *dhcp_opts,
     struct hmap *dhcpv6_opts,
     struct hmap *nd_ra_opts,
+    struct controller_event_options *controller_event_opts,
     const struct shash *addr_sets,
     const struct shash *port_groups,
     const struct sset *active_tunnels,
@@ -237,6 +244,7 @@  consider_logical_flow(
         .dhcp_opts = dhcp_opts,
         .dhcpv6_opts = dhcpv6_opts,
         .nd_ra_opts = nd_ra_opts,
+        .controller_event_opts = controller_event_opts,
 
         .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
         .n_tables = LOG_PIPELINE_LEN,
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index ca191d961..6cc98c617 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -208,6 +208,10 @@  static void pinctrl_handle_put_icmp4_frag_mtu(struct rconn *swconn,
                                               struct ofputil_packet_in *pin,
                                               struct ofpbuf *userdata,
                                               struct ofpbuf *continuation);
+static void
+pinctrl_handle_event(struct ofpbuf *userdata)
+    OVS_REQUIRES(pinctrl_mutex);
+static void wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn);
 static void init_ipv6_ras(void);
 static void destroy_ipv6_ras(void);
 static void ipv6_ra_wait(long long int send_ipv6_ra_time);
@@ -1870,6 +1874,12 @@  process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
                                           &pin, &userdata, &continuation);
         break;
 
+    case ACTION_OPCODE_EVENT:
+        ovs_mutex_lock(&pinctrl_mutex);
+        pinctrl_handle_event(&userdata);
+        ovs_mutex_unlock(&pinctrl_mutex);
+        break;
+
     default:
         VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
                      ntohl(ah->opcode));
@@ -2378,6 +2388,7 @@  void
 pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn)
 {
     wait_put_mac_bindings(ovnsb_idl_txn);
+    wait_controller_event(ovnsb_idl_txn);
     int64_t new_seq = seq_read(pinctrl_main_seq);
     seq_wait(pinctrl_main_seq, new_seq);
 }
@@ -3417,3 +3428,101 @@  exit:
         dp_packet_delete(pkt_out);
     }
 }
+
+static void
+wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn)
+{
+    if (!ovnsb_idl_txn) {
+        return;
+    }
+
+    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
+        if (!hmap_is_empty(&event_table[i])) {
+            poll_immediate_wake();
+            break;
+        }
+    }
+}
+
+static bool
+pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata)
+{
+    struct controller_event_opt_header *userdata_opt;
+    uint32_t hash = 0;
+    char *vip = NULL;
+    char *protocol = NULL;
+    char *load_balancer = NULL;
+
+    while (userdata->size) {
+        userdata_opt = ofpbuf_try_pull(userdata, sizeof *userdata_opt);
+        if (!userdata_opt) {
+            return false;
+        }
+        size_t size = ntohs(userdata_opt->size);
+        char *userdata_opt_data = ofpbuf_try_pull(userdata, size);
+        if (!userdata_opt_data) {
+            return false;
+        }
+        switch (ntohs(userdata_opt->opt_code)) {
+        case EMPTY_LB_VIP:
+            vip = xmemdup0(userdata_opt_data, size);
+            break;
+        case EMPTY_LB_PROTOCOL:
+            protocol = xmemdup0(userdata_opt_data, size);
+            break;
+        case EMPTY_LB_LOAD_BALANCER:
+            load_balancer = xmemdup0(userdata_opt_data, size);
+            break;
+        default:
+            OVS_NOT_REACHED();
+        }
+        hash = hash_bytes(userdata_opt_data, size, hash);
+    }
+    ovs_assert(vip && protocol && load_balancer);
+
+    struct empty_lb_backends_event *event;
+
+    event = pinctrl_find_empty_lb_backends_event(vip, protocol,
+                                                 load_balancer, hash);
+    if (!event) {
+        if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >= 1000) {
+            COVERAGE_INC(pinctrl_drop_controller_event);
+            return false;
+        }
+
+        event = xzalloc(sizeof *event);
+        hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS],
+                    &event->hmap_node, hash);
+        event->vip = vip;
+        event->protocol = protocol;
+        event->load_balancer = load_balancer;
+        notify_pinctrl_main();
+    } else {
+        free(vip);
+        free(protocol);
+        free(load_balancer);
+    }
+    return true;
+}
+
+static void
+pinctrl_handle_event(struct ofpbuf *userdata)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    ovs_be32 *pevent;
+
+    pevent = ofpbuf_try_pull(userdata, sizeof *pevent);
+    if (!pevent) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "event not present in the userdata");
+        return;
+    }
+
+    switch (ntohl(*pevent)) {
+    case OVN_EVENT_EMPTY_LB_BACKENDS:
+        pinctrl_handle_empty_lb_backends_opts(userdata);
+        break;
+    default:
+        return;
+    }
+}
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index d5909911d..1603b5d57 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -38,6 +38,8 @@ 
 #include "packets.h"
 #include "openvswitch/shash.h"
 #include "simap.h"
+#include "uuid.h"
+#include "socket-util.h"
 
 VLOG_DEFINE_THIS_MODULE(actions);
 
@@ -1258,6 +1260,20 @@  format_CLONE(const struct ovnact_nest *nest, struct ds *s)
     format_nested_action(nest, "clone", s);
 }
 
+static void
+format_SEND_EVENT(const struct ovnact_controller_event *event,
+                  struct ds *s)
+{
+    ds_put_format(s, "send_event(event = %d", event->event_type);
+    for (const struct ovnact_gen_option *o = event->options;
+         o < &event->options[event->n_options]; o++) {
+        ds_put_cstr(s, ", ");
+        ds_put_format(s, "%s = ", o->option->name);
+        expr_constant_set_format(&o->value, s);
+    }
+    ds_put_cstr(s, ");");
+}
+
 static void
 encode_nested_actions(const struct ovnact_nest *on,
                       const struct ovnact_encode_params *ep,
@@ -1361,6 +1377,52 @@  encode_CLONE(const struct ovnact_nest *on,
     ofpact_finish_CLONE(ofpacts, &clone);
 }
 
+static void
+encode_event_empty_lb_backends_opts(struct ofpbuf *ofpacts,
+        const struct ovnact_controller_event *event)
+{
+    for (const struct ovnact_gen_option *o = event->options;
+         o < &event->options[event->n_options]; o++) {
+        struct controller_event_opt_header *hdr =
+            ofpbuf_put_uninit(ofpacts, sizeof *hdr);
+        const union expr_constant *c = o->value.values;
+        size_t size;
+        hdr->opt_code = htons(o->option->code);
+        if (!strcmp(o->option->type, "str")) {
+            size = strlen(c->string);
+            hdr->size = htons(size);
+            ofpbuf_put(ofpacts, c->string, size);
+        } else {
+            /* All empty_lb_backends fields are of type 'str' */
+            OVS_NOT_REACHED();
+        }
+    }
+}
+
+static void
+encode_SEND_EVENT(const struct ovnact_controller_event *event,
+                  const struct ovnact_encode_params *ep OVS_UNUSED,
+                  struct ofpbuf *ofpacts)
+{
+    size_t oc_offset;
+
+    oc_offset = encode_start_controller_op(ACTION_OPCODE_EVENT, false,
+                                           NX_CTLR_NO_METER, ofpacts);
+    ovs_be32 ofs = htonl(event->event_type);
+    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
+
+    switch (event->event_type) {
+    case OVN_EVENT_EMPTY_LB_BACKENDS:
+        encode_event_empty_lb_backends_opts(ofpacts, event);
+        break;
+    case OVN_EVENT_MAX:
+    default:
+        OVS_NOT_REACHED();
+    }
+
+    encode_finish_controller_op(oc_offset, ofpacts);
+}
+
 static void
 ovnact_nest_free(struct ovnact_nest *on)
 {
@@ -1575,6 +1637,111 @@  free_gen_options(struct ovnact_gen_option *options, size_t n)
     free(options);
 }
 
+static void
+validate_empty_lb_backends(struct action_context *ctx,
+                           const struct ovnact_gen_option *options,
+                           size_t n_options)
+{
+    for (const struct ovnact_gen_option *o = options;
+         o < &options[n_options]; o++) {
+        const union expr_constant *c = o->value.values;
+        struct sockaddr_storage ss;
+        struct uuid uuid;
+
+        if (o->value.n_values > 1 || !c->string) {
+            lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
+                        o->option->name);
+            return;
+        }
+
+        switch (o->option->code) {
+        case EMPTY_LB_VIP:
+            if (!inet_parse_active(c->string, 0, &ss, false)) {
+                lexer_error(ctx->lexer, "Invalid load balancer VIP '%s'",
+                            c->string);
+                return;
+            }
+            break;
+        case EMPTY_LB_PROTOCOL:
+            if (strcmp(c->string, "tcp") && strcmp(c->string, "udp")) {
+                lexer_error(ctx->lexer,
+                    "Load balancer protocol '%s' is not 'tcp' or 'udp'",
+                    c->string);
+                return;
+            }
+            break;
+        case EMPTY_LB_LOAD_BALANCER:
+            if (!uuid_from_string(&uuid, c->string)) {
+                lexer_error(ctx->lexer, "Load balancer '%s' is not a UUID",
+                            c->string);
+                return;
+            }
+            break;
+        }
+    }
+}
+
+static void
+parse_send_event(struct action_context *ctx,
+                 struct ovnact_controller_event *event)
+{
+    int event_type = 0;
+
+    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+
+    /* Event type must be listed first */
+    if (!lexer_match_id(ctx->lexer, "event")) {
+        lexer_syntax_error(ctx->lexer, "Expecting 'event' option");
+        return;
+    }
+    if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
+        return;
+    }
+    if (!lexer_force_int(ctx->lexer, &event_type)) {
+        return;
+    }
+
+    if (event_type < 0 || event_type >= OVN_EVENT_MAX) {
+        lexer_syntax_error(ctx->lexer, "Unknown event '%d'", event_type);
+        return;
+    }
+
+    event->event_type = event_type;
+    lexer_match(ctx->lexer, LEX_T_COMMA);
+
+    size_t allocated_options = 0;
+    while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+        if (event->n_options >= allocated_options) {
+            event->options = x2nrealloc(event->options, &allocated_options,
+                                     sizeof *event->options);
+        }
+
+        struct ovnact_gen_option *o = &event->options[event->n_options++];
+        memset(o, 0, sizeof *o);
+        parse_gen_opt(ctx, o,
+                      &ctx->pp->controller_event_opts->event_opts[event_type],
+                      event_to_string(event_type));
+        if (ctx->lexer->error) {
+            return;
+        }
+
+        lexer_match(ctx->lexer, LEX_T_COMMA);
+    }
+
+    switch (event_type) {
+    case OVN_EVENT_EMPTY_LB_BACKENDS:
+        validate_empty_lb_backends(ctx, event->options, event->n_options);
+        break;
+    default:
+        OVS_NOT_REACHED();
+    }
+}
+
+static void
+ovnact_controller_event_free(struct ovnact_controller_event *event OVS_UNUSED)
+{
+}
+
 static void
 parse_put_opts(struct action_context *ctx, const struct expr_field *dst,
                struct ovnact_put_opts *po, const struct hmap *gen_opts,
@@ -2511,6 +2678,8 @@  parse_action(struct action_context *ctx)
         parse_LOG(ctx);
     } else if (lexer_match_id(ctx->lexer, "set_meter")) {
         parse_set_meter_action(ctx);
+    } else if (lexer_match_id(ctx->lexer, "send_event")) {
+        parse_send_event(ctx, ovnact_put_SEND_EVENT(ctx->ovnacts));
     } else {
         lexer_syntax_error(ctx->lexer, "expecting action");
     }
diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h
index c24201ef0..4ff458e90 100644
--- a/ovn/lib/ovn-l7.h
+++ b/ovn/lib/ovn-l7.h
@@ -22,6 +22,7 @@ 
 #include <netinet/icmp6.h>
 #include "openvswitch/hmap.h"
 #include "hash.h"
+#include "ovn/logical-fields.h"
 
 /* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */
 struct gen_opts_map {
@@ -272,4 +273,49 @@  nd_ra_opts_init(struct hmap *nd_ra_opts)
     nd_ra_opt_add(nd_ra_opts, "mtu", ND_OPT_MTU, "uint32");
 }
 
+#define EMPTY_LB_VIP           1
+#define EMPTY_LB_PROTOCOL      2
+#define EMPTY_LB_LOAD_BALANCER 3
+
+/* Used in the OpenFlow PACKET_IN userdata */
+struct controller_event_opt_header {
+    ovs_be16 opt_code;
+    ovs_be16 size;
+};
+
+struct controller_event_options {
+    struct hmap event_opts[OVN_EVENT_MAX];
+};
+
+static inline void
+controller_event_opt_add(struct controller_event_options *event_opts,
+                         enum ovn_controller_event event_type, char *opt_name,
+                         size_t opt_code, char *opt_type)
+{
+    gen_opt_add(&event_opts->event_opts[event_type], opt_name, opt_code,
+                opt_type);
+}
+
+static inline void
+controller_event_opts_init(struct controller_event_options *opts)
+{
+    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
+        hmap_init(&opts->event_opts[i]);
+    }
+    controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS, "vip",
+                             EMPTY_LB_VIP, "str");
+    controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS, "protocol",
+                             EMPTY_LB_PROTOCOL, "str");
+    controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS,
+                             "load_balancer", EMPTY_LB_LOAD_BALANCER, "str");
+}
+
+static inline void
+controller_event_opts_destroy(struct controller_event_options *opts)
+{
+    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
+        gen_opts_destroy(&opts->event_opts[i]);
+    }
+}
+
 #endif /* OVN_DHCP_H */
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index 9718077aa..49c3511de 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -2135,6 +2135,9 @@  trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
             execute_ovnfield_load(ovnact_get_OVNFIELD_LOAD(a), super);
             break;
 
+        case OVNACT_SEND_EVENT:
+            break;
+
         case OVNACT_CHECK_PKT_LARGER:
             break;
         }
diff --git a/tests/ovn.at b/tests/ovn.at
index 6499df3d3..1c54dd920 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1333,6 +1333,16 @@  tcp_reset { };
     encodes as controller(userdata=00.00.00.0b.00.00.00.00)
     has prereqs tcp
 
+# send_event
+send_event(event = 0, vip = "10.0.0.1:80", protocol = "tcp", load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c");
+    encodes as controller(userdata=00.00.00.0f.00.00.00.00.00.00.00.00.00.01.00.0b.31.30.2e.30.2e.30.2e.31.3a.38.30.00.02.00.03.74.63.70.00.03.00.24.31.32.33.34.35.36.37.38.2d.61.62.63.64.2d.39.38.37.36.2d.66.65.64.63.2d.31.31.31.31.39.66.38.65.37.64.36.63)
+
+# Testing invalid vip results in extra error messages from socket-util.c
+send_event(event = 0, vip = "10.0.0.1:80", protocol = "sctp", load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c");
+    Load balancer protocol 'sctp' is not 'tcp' or 'udp'
+send_event(event = 0, vip = "10.0.0.1:80", protocol = "tcp", load_balancer = "bacon");
+    Load balancer 'bacon' is not a UUID
+
 # Contradictionary prerequisites (allowed but not useful):
 ip4.src = ip6.src[0..31];
     encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[]
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index 7cce9c2ae..619bd3f78 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -157,7 +157,8 @@  create_symtab(struct shash *symtab)
 
 static void
 create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
-                struct hmap *nd_ra_opts)
+                struct hmap *nd_ra_opts,
+                struct controller_event_options *event_opts)
 {
     hmap_init(dhcp_opts);
     dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4");
@@ -197,6 +198,9 @@  create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
     /* IPv6 ND RA options. */
     hmap_init(nd_ra_opts);
     nd_ra_opts_init(nd_ra_opts);
+
+    /* OVN controller events options. */
+    controller_event_opts_init(event_opts);
 }
 
 static void
@@ -1228,12 +1232,13 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
     struct hmap dhcp_opts;
     struct hmap dhcpv6_opts;
     struct hmap nd_ra_opts;
+    struct controller_event_options event_opts;
     struct simap ports;
     struct ds input;
     bool ok = true;
 
     create_symtab(&symtab);
-    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts);
+    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts, &event_opts);
 
     /* Initialize group ids. */
     struct ovn_extend_table group_table;
@@ -1263,6 +1268,7 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
             .dhcp_opts = &dhcp_opts,
             .dhcpv6_opts = &dhcpv6_opts,
             .nd_ra_opts = &nd_ra_opts,
+            .controller_event_opts = &event_opts,
             .n_tables = 24,
             .cur_ltable = 10,
         };
@@ -1350,6 +1356,7 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
     dhcp_opts_destroy(&dhcp_opts);
     dhcp_opts_destroy(&dhcpv6_opts);
     nd_ra_opts_destroy(&nd_ra_opts);
+    controller_event_opts_destroy(&event_opts);
     ovn_extend_table_destroy(&group_table);
     ovn_extend_table_destroy(&meter_table);
     exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);