Message ID | e9703656aed0fa943f4e66523f88bb64f5b6170d.1558021382.git.lorenzo.bianconi@redhat.com |
---|---|
State | RFC |
Headers | show |
Series | OVN: add Controller Events | expand |
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 >
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);