Expand SB.Template_Var records in two stages:
1. first expand them to local values in match/action strings
2. then reparse the expanded strings

For the case when a lflow references a Template_Var also track
references (similar to the ones maintained for multicast groups, address
sets, port_groups, port bindings).

Signed-off-by: Dumitru Ceara <[email protected]>
---
TODO:
- reduce the number of xstrdups.
- figure out a way to deal with templates in ovn-trace
- determine if we need to allow Template_Var to match against chassis
  hostname or other IDs.
- Make ofctrl_inject_pkt() work with template_vars.
- Make test-ovn work with template_vars.
---
 controller/lflow.c          |   66 ++++++++++--
 controller/lflow.h          |    4 +
 controller/ofctrl.c         |    9 +-
 controller/ofctrl.h         |    3 -
 controller/ovn-controller.c |  232 ++++++++++++++++++++++++++++++++++++++++++-
 include/ovn/expr.h          |    4 +
 include/ovn/lex.h           |   14 ++-
 lib/actions.c               |    9 +-
 lib/expr.c                  |   14 ++-
 lib/lex.c                   |   55 ++++++++++
 tests/ovn.at                |    2 
 tests/test-ovn.c            |   16 ++-
 utilities/ovn-trace.c       |   26 ++++-
 13 files changed, 417 insertions(+), 37 deletions(-)

diff --git a/controller/lflow.c b/controller/lflow.c
index 6055097b5..c1a30b2a4 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -80,6 +80,8 @@ convert_match_to_expr(const struct sbrec_logical_flow *,
                       const struct local_datapath *ldp,
                       struct expr **prereqs, const struct shash *addr_sets,
                       const struct shash *port_groups,
+                      const struct smap *template_vars,
+                      struct sset *template_vars_ref,
                       struct lflow_resource_ref *, bool *pg_addr_set_ref);
 static void
 add_matches_to_flow_table(const struct sbrec_logical_flow *,
@@ -597,10 +599,15 @@ consider_lflow_for_added_as_ips__(
         .n_tables = LOG_PIPELINE_LEN,
         .cur_ltable = lflow->table_id,
     };
+    struct sset template_vars_ref = SSET_INITIALIZER(&template_vars_ref);
     struct expr *prereqs = NULL;
     char *error;
 
-    error = ovnacts_parse_string(lflow->actions, &pp, &ovnacts, &prereqs);
+    char *actions_s = lexer_parse_template_string(xstrdup(lflow->actions),
+                                                  l_ctx_in->template_vars,
+                                                  &template_vars_ref);
+    error = ovnacts_parse_string(actions_s, &pp, &ovnacts, &prereqs);
+    free(actions_s);
     if (error) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
         VLOG_WARN_RL(&rl, "error parsing actions \"%s\": %s",
@@ -608,6 +615,7 @@ consider_lflow_for_added_as_ips__(
         free(error);
         ovnacts_free(ovnacts.data, ovnacts.size);
         ofpbuf_uninit(&ovnacts);
+        sset_destroy(&template_vars_ref);
         return true;
     }
 
@@ -671,6 +679,8 @@ consider_lflow_for_added_as_ips__(
     struct expr *expr = convert_match_to_expr(lflow, ldp, &prereqs,
                                               l_ctx_in->addr_sets,
                                               l_ctx_in->port_groups,
+                                              l_ctx_in->template_vars,
+                                              &template_vars_ref,
                                               l_ctx_out->lfrr, NULL);
     shash_replace((struct shash *)l_ctx_in->addr_sets, as_name, real_as);
     if (new_fake_as) {
@@ -742,6 +752,14 @@ done:
     ofpbuf_uninit(&ovnacts);
     expr_destroy(expr);
     expr_matches_destroy(&matches);
+
+    const char *tv_name;
+    SSET_FOR_EACH (tv_name, &template_vars_ref) {
+        lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_TEMPLATE, tv_name,
+                           &lflow->header_.uuid, 0);
+    }
+    sset_destroy(&template_vars_ref);
+
     return handled;
 }
 
@@ -781,8 +799,8 @@ consider_lflow_for_added_as_ips(
                                                as_name, as_ref_count,
                                                as_diff_added, dhcp_opts,
                                                dhcpv6_opts, nd_ra_opts,
-                                               controller_event_opts, l_ctx_in,
-                                               l_ctx_out)) {
+                                               controller_event_opts,
+                                               l_ctx_in, l_ctx_out)) {
             return false;
         }
     }
@@ -1256,6 +1274,8 @@ convert_match_to_expr(const struct sbrec_logical_flow 
*lflow,
                       struct expr **prereqs,
                       const struct shash *addr_sets,
                       const struct shash *port_groups,
+                      const struct smap *template_vars,
+                      struct sset *template_vars_ref,
                       struct lflow_resource_ref *lfrr,
                       bool *pg_addr_set_ref)
 {
@@ -1263,11 +1283,16 @@ convert_match_to_expr(const struct sbrec_logical_flow 
*lflow,
     struct sset port_groups_ref = SSET_INITIALIZER(&port_groups_ref);
     char *error = NULL;
 
-    struct expr *e = expr_parse_string(lflow->match, &symtab, addr_sets,
+    char *match_s = lexer_parse_template_string(xstrdup(lflow->match),
+                                                template_vars,
+                                                template_vars_ref);
+    struct expr *e = expr_parse_string(match_s, &symtab, addr_sets,
                                        port_groups, &addr_sets_ref,
                                        &port_groups_ref,
                                        ldp->datapath->tunnel_key,
                                        &error);
+    free(match_s);
+
     struct shash_node *addr_sets_ref_node;
     SHASH_FOR_EACH (addr_sets_ref_node, &addr_sets_ref) {
         lflow_resource_add(lfrr, REF_TYPE_ADDRSET, addr_sets_ref_node->name,
@@ -1374,7 +1399,12 @@ consider_logical_flow__(const struct sbrec_logical_flow 
*lflow,
     struct expr *prereqs = NULL;
     char *error;
 
-    error = ovnacts_parse_string(lflow->actions, &pp, &ovnacts, &prereqs);
+    struct sset template_vars_ref = SSET_INITIALIZER(&template_vars_ref);
+    char *actions_s = lexer_parse_template_string(xstrdup(lflow->actions),
+                                                  l_ctx_in->template_vars,
+                                                  &template_vars_ref);
+    error = ovnacts_parse_string(actions_s, &pp, &ovnacts, &prereqs);
+    free(actions_s);
     if (error) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
         VLOG_WARN_RL(&rl, "error parsing actions \"%s\": %s",
@@ -1382,6 +1412,7 @@ consider_logical_flow__(const struct sbrec_logical_flow 
*lflow,
         free(error);
         ovnacts_free(ovnacts.data, ovnacts.size);
         ofpbuf_uninit(&ovnacts);
+        sset_destroy(&template_vars_ref);
         return;
     }
 
@@ -1431,7 +1462,10 @@ consider_logical_flow__(const struct sbrec_logical_flow 
*lflow,
     switch (lcv_type) {
     case LCACHE_T_NONE:
         expr = convert_match_to_expr(lflow, ldp, &prereqs, l_ctx_in->addr_sets,
-                                     l_ctx_in->port_groups, l_ctx_out->lfrr,
+                                     l_ctx_in->port_groups,
+                                     l_ctx_in->template_vars,
+                                     &template_vars_ref,
+                                     l_ctx_out->lfrr,
                                      &pg_addr_set_ref);
         if (!expr) {
             goto done;
@@ -1445,11 +1479,13 @@ consider_logical_flow__(const struct sbrec_logical_flow 
*lflow,
     }
 
     /* If caching is enabled and this is a not cached expr that doesn't refer
-     * to address sets or port groups, save it to potentially cache it later.
+     * to address sets, port groups, or template variables, save it to
+     * potentially cache it later.
      */
     if (lcv_type == LCACHE_T_NONE
             && lflow_cache_is_enabled(l_ctx_out->lflow_cache)
-            && !pg_addr_set_ref) {
+            && !pg_addr_set_ref
+            && sset_is_empty(&template_vars_ref)) {
         cached_expr = expr_clone(expr);
     }
 
@@ -1534,6 +1570,13 @@ done:
     expr_destroy(cached_expr);
     expr_matches_destroy(matches);
     free(matches);
+
+    const char *tv_name;
+    SSET_FOR_EACH (tv_name, &template_vars_ref) {
+        lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_TEMPLATE, tv_name,
+                           &lflow->header_.uuid, 0);
+    }
+    sset_destroy(&template_vars_ref);
 }
 
 static struct lflow_processed_node *
@@ -2331,8 +2374,7 @@ add_lb_ct_snat_hairpin_flows(struct ovn_controller_lb *lb,
 
 static void
 consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb,
-                          const struct hmap *local_datapaths,
-                          bool use_ct_mark,
+                          const struct hmap *local_datapaths, bool use_ct_mark,
                           struct ovn_desired_flow_table *flow_table,
                           struct simap *ids)
 {
@@ -2408,8 +2450,8 @@ add_lb_hairpin_flows(const struct 
sbrec_load_balancer_table *lb_table,
             ovs_assert(id_pool_alloc_id(pool, &id));
             simap_put(ids, lb->name, id);
         }
-        consider_lb_hairpin_flows(lb, local_datapaths, use_ct_mark,
-                                  flow_table, ids);
+        consider_lb_hairpin_flows(lb, local_datapaths, use_ct_mark, flow_table,
+                                  ids);
     }
 }
 
diff --git a/controller/lflow.h b/controller/lflow.h
index 342967917..d4480c2a2 100644
--- a/controller/lflow.h
+++ b/controller/lflow.h
@@ -84,7 +84,8 @@ enum ref_type {
     REF_TYPE_ADDRSET,
     REF_TYPE_PORTGROUP,
     REF_TYPE_PORTBINDING,
-    REF_TYPE_MC_GROUP
+    REF_TYPE_MC_GROUP,
+    REF_TYPE_TEMPLATE,
 };
 
 struct ref_lflow_node {
@@ -160,6 +161,7 @@ struct lflow_ctx_in {
     const struct sset *related_lport_ids;
     const struct shash *binding_lports;
     const struct hmap *chassis_tunnels;
+    const struct smap *template_vars;
     bool lb_hairpin_use_ct_mark;
 };
 
diff --git a/controller/ofctrl.c b/controller/ofctrl.c
index 432fe2d37..8fd1638d7 100644
--- a/controller/ofctrl.c
+++ b/controller/ofctrl.c
@@ -2899,7 +2899,8 @@ ofctrl_lookup_port(const void *br_int_, const char 
*port_name,
 char *
 ofctrl_inject_pkt(const struct ovsrec_bridge *br_int, const char *flow_s,
                   const struct shash *addr_sets,
-                  const struct shash *port_groups)
+                  const struct shash *port_groups,
+                  const struct smap *template_vars)
 {
     int version = rconn_get_version(swconn);
     if (version < 0) {
@@ -2907,9 +2908,13 @@ ofctrl_inject_pkt(const struct ovsrec_bridge *br_int, 
const char *flow_s,
     }
 
     struct flow uflow;
-    char *error = expr_parse_microflow(flow_s, &symtab, addr_sets,
+    char *flow_exp_s = lexer_parse_template_string(xstrdup(flow_s),
+                                                   template_vars,
+                                                   NULL);
+    char *error = expr_parse_microflow(flow_exp_s, &symtab, addr_sets,
                                        port_groups, ofctrl_lookup_port,
                                        br_int, &uflow);
+    free(flow_exp_s);
     if (error) {
         return error;
     }
diff --git a/controller/ofctrl.h b/controller/ofctrl.h
index 330b0b6ca..bd58bb07d 100644
--- a/controller/ofctrl.h
+++ b/controller/ofctrl.h
@@ -70,7 +70,8 @@ void ofctrl_ct_flush_zone(uint16_t zone_id);
 
 char *ofctrl_inject_pkt(const struct ovsrec_bridge *br_int,
                         const char *flow_s, const struct shash *addr_sets,
-                        const struct shash *port_groups);
+                        const struct shash *port_groups,
+                        const struct smap *template_vars);
 
 /* Flow table interfaces to the rest of ovn-controller. */
 
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 5449743e8..1fe0efa2b 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -978,7 +978,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     SB_NODE(load_balancer, "load_balancer") \
     SB_NODE(fdb, "fdb") \
     SB_NODE(meter, "meter") \
-    SB_NODE(static_mac_binding, "static_mac_binding")
+    SB_NODE(static_mac_binding, "static_mac_binding") \
+    SB_NODE(template_var, "template_var")
 
 enum sb_engine_node {
 #define SB_NODE(NAME, NAME_STR) SB_##NAME,
@@ -1537,6 +1538,167 @@ runtime_data_sb_datapath_binding_handler(struct 
engine_node *node OVS_UNUSED,
     return true;
 }
 
+struct ed_type_template_vars {
+    struct smap local_vars;
+
+    bool change_tracked;
+    struct sset new;
+    struct sset deleted;
+    struct sset updated;
+};
+
+static void
+template_vars_init(const struct sbrec_template_var_table *template_var_table,
+                   const struct sbrec_chassis *chassis,
+                   struct smap *local_vars)
+{
+    const struct sbrec_template_var *tv;
+    SBREC_TEMPLATE_VAR_TABLE_FOR_EACH (tv, template_var_table) {
+        if (!strcmp(tv->chassis, chassis->name)) {
+            smap_add(local_vars, tv->name, tv->value);
+        }
+    }
+}
+
+static void
+template_vars_clear(struct smap *local_vars)
+{
+    smap_clear(local_vars);
+}
+
+static void
+template_vars_update(const struct sbrec_template_var_table *template_var_table,
+                     const struct sbrec_chassis *chassis,
+                     struct smap *local_vars,
+                     struct sset *new, struct sset *deleted,
+                     struct sset *updated)
+{
+    const struct sbrec_template_var *tv;
+    SBREC_TEMPLATE_VAR_TABLE_FOR_EACH_TRACKED (tv, template_var_table) {
+        if (strcmp(tv->chassis, chassis->name)) {
+            continue;
+        }
+
+        if (sbrec_template_var_is_deleted(tv)) {
+            smap_remove(local_vars, tv->name);
+            sset_add(deleted, tv->name);
+        }
+    }
+
+    SBREC_TEMPLATE_VAR_TABLE_FOR_EACH_TRACKED (tv, template_var_table) {
+        if (strcmp(tv->chassis, chassis->name)) {
+            continue;
+        }
+
+        if (!sbrec_template_var_is_deleted(tv)) {
+            if (sbrec_template_var_is_new(tv)) {
+                sset_add(new, tv->name);
+            } else {
+                sset_add(updated, tv->name);
+            }
+            smap_replace(local_vars, tv->name, tv->value);
+        }
+    }
+}
+
+static void *
+en_template_vars_init(struct engine_node *node OVS_UNUSED,
+                      struct engine_arg *arg OVS_UNUSED)
+{
+    struct ed_type_template_vars *tv = xzalloc(sizeof *tv);
+    smap_init(&tv->local_vars);
+    tv->change_tracked = false;
+    sset_init(&tv->new);
+    sset_init(&tv->deleted);
+    sset_init(&tv->updated);
+    return tv;
+}
+
+static void
+en_template_vars_run(struct engine_node *node, void *data)
+{
+    struct ed_type_template_vars *tv = data;
+
+    struct ovsrec_open_vswitch_table *ovs_table =
+        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_open_vswitch", node));
+
+    const char *chassis_id = get_ovs_chassis_id(ovs_table);
+    ovs_assert(chassis_id);
+
+    const struct sbrec_chassis *chassis;
+    struct ovsdb_idl_index *sbrec_chassis_by_name =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_chassis", node),
+                "name");
+    chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+    ovs_assert(chassis);
+
+    template_vars_clear(&tv->local_vars);
+
+    const struct sbrec_template_var_table *tv_table =
+        EN_OVSDB_GET(engine_get_input("SB_template_var", node));
+    template_vars_init(tv_table, chassis, &tv->local_vars);
+    engine_set_node_state(node, EN_UPDATED);
+}
+
+static bool
+template_vars_sb_template_var_handler(struct engine_node *node, void *data)
+{
+    struct ed_type_template_vars *tv = data;
+
+    struct ovsrec_open_vswitch_table *ovs_table =
+        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_open_vswitch", node));
+
+    const char *chassis_id = get_ovs_chassis_id(ovs_table);
+    ovs_assert(chassis_id);
+
+    const struct sbrec_chassis *chassis;
+    struct ovsdb_idl_index *sbrec_chassis_by_name =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_chassis", node),
+                "name");
+    chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+    ovs_assert(chassis);
+
+    const struct sbrec_template_var_table *tv_table =
+        EN_OVSDB_GET(engine_get_input("SB_template_var", node));
+    template_vars_update(tv_table, chassis, &tv->local_vars,
+                         &tv->new, &tv->deleted, &tv->updated);
+
+    if (!sset_is_empty(&tv->new) || !sset_is_empty(&tv->deleted) ||
+            !sset_is_empty(&tv->updated)) {
+        engine_set_node_state(node, EN_UPDATED);
+    } else {
+        engine_set_node_state(node, EN_UNCHANGED);
+    }
+
+    tv->change_tracked = true;
+    return true;
+}
+
+static void
+en_template_vars_clear_tracked_data(void *data)
+{
+    struct ed_type_template_vars *tv = data;
+    sset_clear(&tv->new);
+    sset_clear(&tv->deleted);
+    sset_clear(&tv->updated);
+    tv->change_tracked = false;
+}
+
+static void
+en_template_vars_cleanup(void *data)
+{
+    struct ed_type_template_vars *tv = data;
+    smap_destroy(&tv->local_vars);
+
+    sset_destroy(&tv->new);
+    sset_destroy(&tv->deleted);
+    sset_destroy(&tv->updated);
+}
+
 struct ed_type_addr_sets {
     struct shash addr_sets;
     bool change_tracked;
@@ -2540,6 +2702,9 @@ init_lflow_ctx(struct engine_node *node,
     struct ed_type_northd_options *n_opts =
         engine_get_input_data("northd_options", node);
 
+    struct ed_type_template_vars *template_vars =
+        engine_get_input_data("template_vars", node);
+
     l_ctx_in->sbrec_multicast_group_by_name_datapath =
         sbrec_mc_group_by_name_dp;
     l_ctx_in->sbrec_logical_flow_by_logical_datapath =
@@ -2570,6 +2735,7 @@ init_lflow_ctx(struct engine_node *node,
     l_ctx_in->binding_lports = &rt_data->lbinding_data.lports;
     l_ctx_in->chassis_tunnels = &non_vif_data->chassis_tunnels;
     l_ctx_in->lb_hairpin_use_ct_mark = n_opts->lb_hairpin_use_ct_mark;
+    l_ctx_in->template_vars = &template_vars->local_vars;
 
     l_ctx_out->flow_table = &fo->flow_table;
     l_ctx_out->group_table = &fo->group_table;
@@ -2878,6 +3044,56 @@ lflow_output_port_groups_handler(struct engine_node 
*node, void *data)
     return true;
 }
 
+static bool
+lflow_output_template_vars_handler(struct engine_node *node, void *data)
+{
+    struct ed_type_template_vars *tv_data =
+        engine_get_input_data("template_vars", node);
+
+    struct ed_type_lflow_output *fo = data;
+
+    struct lflow_ctx_in l_ctx_in;
+    struct lflow_ctx_out l_ctx_out;
+    init_lflow_ctx(node, fo, &l_ctx_in, &l_ctx_out);
+
+    bool changed;
+    const char *ref_name;
+
+    if (!tv_data->change_tracked) {
+        return false;
+    }
+
+    SSET_FOR_EACH (ref_name, &tv_data->deleted) {
+        if (!lflow_handle_changed_ref(REF_TYPE_TEMPLATE, ref_name, &l_ctx_in,
+                                      &l_ctx_out, &changed)) {
+            return false;
+        }
+        if (changed) {
+            engine_set_node_state(node, EN_UPDATED);
+        }
+    }
+    SSET_FOR_EACH (ref_name, &tv_data->updated) {
+        if (!lflow_handle_changed_ref(REF_TYPE_TEMPLATE, ref_name, &l_ctx_in,
+                                      &l_ctx_out, &changed)) {
+            return false;
+        }
+        if (changed) {
+            engine_set_node_state(node, EN_UPDATED);
+        }
+    }
+    SSET_FOR_EACH (ref_name, &tv_data->new) {
+        if (!lflow_handle_changed_ref(REF_TYPE_TEMPLATE, ref_name, &l_ctx_in,
+                                      &l_ctx_out, &changed)) {
+            return false;
+        }
+        if (changed) {
+            engine_set_node_state(node, EN_UPDATED);
+        }
+    }
+
+    return true;
+}
+
 static bool
 lflow_output_runtime_data_handler(struct engine_node *node,
                                   void *data OVS_UNUSED)
@@ -3534,6 +3750,7 @@ main(int argc, char *argv[])
     stopwatch_create(VIF_PLUG_RUN_STOPWATCH_NAME, SW_MS);
 
     /* Define inc-proc-engine nodes. */
+    ENGINE_NODE_WITH_CLEAR_TRACK_DATA(template_vars, "template_vars");
     ENGINE_NODE_WITH_CLEAR_TRACK_DATA_IS_VALID(ct_zones, "ct_zones");
     ENGINE_NODE_WITH_CLEAR_TRACK_DATA(ovs_interface_shadow,
                                       "ovs_interface_shadow");
@@ -3558,6 +3775,10 @@ main(int argc, char *argv[])
 #undef OVS_NODE
 
     /* Add dependencies between inc-proc-engine nodes. */
+    engine_add_input(&en_template_vars, &en_ovs_open_vswitch, NULL);
+    engine_add_input(&en_template_vars, &en_sb_chassis, NULL);
+    engine_add_input(&en_template_vars, &en_sb_template_var,
+                     template_vars_sb_template_var_handler);
 
     engine_add_input(&en_addr_sets, &en_sb_address_set,
                      addr_sets_sb_address_set_handler);
@@ -3621,6 +3842,8 @@ main(int argc, char *argv[])
                      lflow_output_addr_sets_handler);
     engine_add_input(&en_lflow_output, &en_port_groups,
                      lflow_output_port_groups_handler);
+    engine_add_input(&en_lflow_output, &en_template_vars,
+                     lflow_output_template_vars_handler);
     engine_add_input(&en_lflow_output, &en_runtime_data,
                      lflow_output_runtime_data_handler);
     engine_add_input(&en_lflow_output, &en_non_vif_data,
@@ -4166,9 +4389,12 @@ main(int argc, char *argv[])
                     engine_get_data(&en_addr_sets);
                 struct ed_type_port_groups *pg_data =
                     engine_get_data(&en_port_groups);
-                if (br_int && chassis && as_data && pg_data) {
+                struct ed_type_template_vars *tv_data =
+                    engine_get_data(&en_template_vars);
+                if (br_int && chassis && as_data && pg_data && tv_data) {
                     char *error = ofctrl_inject_pkt(br_int, pending_pkt.flow_s,
-                        &as_data->addr_sets, &pg_data->port_groups_cs_local);
+                        &as_data->addr_sets, &pg_data->port_groups_cs_local,
+                        &tv_data->local_vars);
                     if (error) {
                         unixctl_command_reply_error(pending_pkt.conn, error);
                         free(error);
diff --git a/include/ovn/expr.h b/include/ovn/expr.h
index 3b141b034..80d95ff67 100644
--- a/include/ovn/expr.h
+++ b/include/ovn/expr.h
@@ -59,6 +59,7 @@
 #include "openvswitch/match.h"
 #include "openvswitch/meta-flow.h"
 #include "logical-fields.h"
+#include "smap.h"
 
 struct ds;
 struct expr;
@@ -521,7 +522,8 @@ union expr_constant {
     char *string;
 };
 
-bool expr_constant_parse(struct lexer *, const struct expr_field *,
+bool expr_constant_parse(struct lexer *,
+                         const struct expr_field *,
                          union expr_constant *);
 void expr_constant_format(const union expr_constant *,
                           enum expr_constant_type, struct ds *);
diff --git a/include/ovn/lex.h b/include/ovn/lex.h
index ecb7ace24..38c4aa9f3 100644
--- a/include/ovn/lex.h
+++ b/include/ovn/lex.h
@@ -23,6 +23,8 @@
  * This is a simple lexical analyzer (or tokenizer) for OVN match expressions
  * and ACLs. */
 
+#include "smap.h"
+#include "sset.h"
 #include "openvswitch/meta-flow.h"
 
 struct ds;
@@ -37,7 +39,8 @@ enum lex_type {
     LEX_T_INTEGER,              /* 12345 or 1.2.3.4 or ::1 or 01:02:03:04:05 */
     LEX_T_MASKED_INTEGER,       /* 12345/10 or 1.2.0.0/16 or ::2/127 or... */
     LEX_T_MACRO,                /* $NAME */
-    LEX_T_PORT_GROUP,            /* @NAME */
+    LEX_T_PORT_GROUP,           /* @NAME */
+    LEX_T_TEMPLATE,             /* ^NAME */
     LEX_T_ERROR,                /* invalid input */
 
     /* Bare tokens. */
@@ -86,9 +89,9 @@ struct lex_token {
     /* One of LEX_*. */
     enum lex_type type;
 
-    /* Meaningful for LEX_T_ID, LEX_T_STRING, LEX_T_ERROR, LEX_T_MACRO only.
-     * For these token types, 's' may point to 'buffer'; otherwise, it points
-     * to malloc()ed memory owned by the token.
+    /* Meaningful for LEX_T_ID, LEX_T_STRING, LEX_T_ERROR, LEX_T_MACRO,
+     * LEX_T_TEMPLATE only.  For these token types, 's' may point to 'buffer';
+     * otherwise, it points to malloc()ed memory owned by the token.
      *
      * Must be NULL for other token types.
      *
@@ -151,4 +154,7 @@ void lexer_syntax_error(struct lexer *, const char 
*message, ...)
 
 char *lexer_steal_error(struct lexer *);
 
+char *lexer_parse_template_string(char *s, const struct smap *template_vars,
+                                  struct sset *template_vars_ref);
+
 #endif /* ovn/lex.h */
diff --git a/lib/actions.c b/lib/actions.c
index aab044306..032ba0965 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -257,8 +257,8 @@ add_prerequisite(struct action_context *ctx, const char 
*prerequisite)
     struct expr *expr;
     char *error;
 
-    expr = expr_parse_string(prerequisite, ctx->pp->symtab, NULL, NULL,
-                             NULL, NULL, 0, &error);
+    expr = expr_parse_string(prerequisite, ctx->pp->symtab, NULL, NULL, NULL,
+                             NULL, 0, &error);
     ovs_assert(!error);
     ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, expr);
 }
@@ -4365,6 +4365,11 @@ parse_set_action(struct action_context *ctx)
 static bool
 parse_action(struct action_context *ctx)
 {
+    if (ctx->lexer->token.type == LEX_T_TEMPLATE) {
+        lexer_error(ctx->lexer, "Unexpanded template.");
+        return false;
+    }
+
     if (ctx->lexer->token.type != LEX_T_ID) {
         lexer_syntax_error(ctx->lexer, NULL);
         return false;
diff --git a/lib/expr.c b/lib/expr.c
index d1f9d28ca..1311b09fe 100644
--- a/lib/expr.c
+++ b/lib/expr.c
@@ -894,7 +894,10 @@ parse_constant(struct expr_context *ctx, struct 
expr_constant_set *cs,
         cs->as_name = NULL;
     }
 
-    if (ctx->lexer->token.type == LEX_T_STRING) {
+    if (ctx->lexer->token.type == LEX_T_TEMPLATE) {
+        lexer_error(ctx->lexer, "Unexpanded template.");
+        return false;
+    } else if (ctx->lexer->token.type == LEX_T_STRING) {
         if (!assign_constant_set_type(ctx, cs, EXPR_C_STRING)) {
             return false;
         }
@@ -978,7 +981,9 @@ expr_constant_parse(struct lexer *lexer, const struct 
expr_field *f,
         return false;
     }
 
-    struct expr_context ctx = { .lexer = lexer };
+    struct expr_context ctx = {
+        .lexer = lexer,
+    };
 
     struct expr_constant_set cs;
     memset(&cs, 0, sizeof cs);
@@ -1332,7 +1337,10 @@ expr_parse_primary(struct expr_context *ctx, bool 
*atomic)
         return e;
     }
 
-    if (ctx->lexer->token.type == LEX_T_ID) {
+    if (ctx->lexer->token.type == LEX_T_TEMPLATE) {
+        lexer_error(ctx->lexer, "Unexpanded template.");
+        return NULL;
+    } else if (ctx->lexer->token.type == LEX_T_ID) {
         struct expr_field f;
         enum expr_relop r;
         struct expr_constant_set c;
diff --git a/lib/lex.c b/lib/lex.c
index c84d52aa8..b01a7d75c 100644
--- a/lib/lex.c
+++ b/lib/lex.c
@@ -235,6 +235,10 @@ lex_token_format(const struct lex_token *token, struct ds 
*s)
         ds_put_format(s, "@%s", token->s);
         break;
 
+    case LEX_T_TEMPLATE:
+        ds_put_format(s, "^%s", token->s);
+        break;
+
     case LEX_T_LPAREN:
         ds_put_cstr(s, "(");
         break;
@@ -588,6 +592,18 @@ lex_parse_port_group(const char *p, struct lex_token 
*token)
     return lex_parse_id(p, LEX_T_PORT_GROUP, token);
 }
 
+static const char *
+lex_parse_template(const char *p, struct lex_token *token)
+{
+    p++;
+    if (!lex_is_id1(*p)) {
+        lex_error(token, "`^' must be followed by a valid identifier.");
+        return p;
+    }
+
+    return lex_parse_id(p, LEX_T_TEMPLATE, token);
+}
+
 /* Initializes 'token' and parses the first token from the beginning of
  * null-terminated string 'p' into 'token'.  Stores a pointer to the start of
  * the token (after skipping white space and comments, if any) into '*startp'.
@@ -766,6 +782,10 @@ next:
         p = lex_parse_port_group(p, token);
         break;
 
+    case '^':
+        p = lex_parse_template(p, token);
+        break;
+
     case ':':
         if (p[1] != ':') {
             token->type = LEX_T_COLON;
@@ -1031,3 +1051,38 @@ lexer_steal_error(struct lexer *lexer)
     lexer->error = NULL;
     return error;
 }
+
+/* Takes ownership of 's' and expands all templates that are encountered
+ * in the contents of 's', if possible.  Adds the encountered template names
+ * to 'template_vars_ref'.
+ */
+char *
+lexer_parse_template_string(char *s, const struct smap *template_vars,
+                            struct sset *template_vars_ref)
+{
+    /* No '^' means no templates. */
+    if (!strchr(s, '^')) {
+        return s;
+    }
+
+    struct ds expanded = DS_EMPTY_INITIALIZER;
+
+    struct lexer lexer;
+    lexer_init(&lexer, s);
+
+    while (lexer_get(&lexer) != LEX_T_END) {
+        if (lexer.token.type == LEX_T_TEMPLATE) {
+            ds_put_cstr(&expanded, smap_get_def(template_vars, lexer.token.s,
+                                                lexer.token.s));
+            if (template_vars_ref) {
+                sset_add(template_vars_ref, lexer.token.s);
+            }
+        } else {
+            lex_token_format(&lexer.token, &expanded);
+        }
+    }
+
+    lexer_destroy(&lexer);
+    free(s);
+    return ds_steal_cstr(&expanded);
+}
\ No newline at end of file
diff --git a/tests/ovn.at b/tests/ovn.at
index 3ba6ced4e..1bf5da6d5 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -226,7 +226,7 @@ fe:x => error("Invalid numeric constant.")
 & => error("`&' is only valid as part of `&&'.")
 | => error("`|' is only valid as part of `||'.")
 
-^ => error("Invalid character `^' in input.")
+^ => error("`^' must be followed by a valid identifier.")
 ])
 AT_CAPTURE_FILE([input.txt])
 sed 's/ =>.*//' test-cases.txt > input.txt
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index a241f150d..7842cd915 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -291,12 +291,14 @@ test_parse_expr__(int steps)
     struct shash symtab;
     struct shash addr_sets;
     struct shash port_groups;
+    struct smap template_vars;
     struct simap ports;
     struct ds input;
 
     create_symtab(&symtab);
     create_addr_sets(&addr_sets);
     create_port_groups(&port_groups);
+    smap_init(&template_vars);
 
     simap_init(&ports);
     simap_put(&ports, "eth0", 5);
@@ -355,6 +357,7 @@ test_parse_expr__(int steps)
     shash_destroy(&addr_sets);
     expr_const_sets_destroy(&port_groups);
     shash_destroy(&port_groups);
+    smap_destroy(&template_vars);
 }
 
 static void
@@ -913,8 +916,8 @@ test_tree_shape_exhaustively(struct expr *expr, struct 
shash *symtab,
             expr_format(expr, &s);
 
             char *error;
-            modified = expr_parse_string(ds_cstr(&s), symtab, NULL,
-                                         NULL, NULL, NULL, 0, &error);
+            modified = expr_parse_string(ds_cstr(&s), symtab, NULL, NULL, NULL,
+                                         NULL, 0, &error);
             if (error) {
                 fprintf(stderr, "%s fails to parse (%s)\n",
                         ds_cstr(&s), error);
@@ -1286,6 +1289,8 @@ test_parse_actions(struct ovs_cmdl_context *ctx 
OVS_UNUSED)
     struct ds input;
     bool ok = true;
 
+    struct smap template_vars = SMAP_INITIALIZER(&template_vars);
+
     create_symtab(&symtab);
     create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts, &event_opts);
 
@@ -1322,7 +1327,10 @@ test_parse_actions(struct ovs_cmdl_context *ctx 
OVS_UNUSED)
             .n_tables = 24,
             .cur_ltable = 10,
         };
-        error = ovnacts_parse_string(ds_cstr(&input), &pp, &ovnacts, &prereqs);
+        char *exp_input = lexer_parse_template_string(xstrdup(ds_cstr(&input)),
+                                                      &template_vars,
+                                                      NULL);
+        error = ovnacts_parse_string(exp_input, &pp, &ovnacts, &prereqs);
         if (!error) {
             /* Convert the parsed representation back to a string and print it,
              * if it's different from the input. */
@@ -1409,6 +1417,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx 
OVS_UNUSED)
         expr_destroy(prereqs);
         ovnacts_free(ovnacts.data, ovnacts.size);
         ofpbuf_uninit(&ovnacts);
+        free(exp_input);
     }
     ds_destroy(&input);
 
@@ -1419,6 +1428,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx 
OVS_UNUSED)
     dhcp_opts_destroy(&dhcpv6_opts);
     nd_ra_opts_destroy(&nd_ra_opts);
     controller_event_opts_destroy(&event_opts);
+    smap_destroy(&template_vars);
     ovn_extend_table_destroy(&group_table);
     ovn_extend_table_destroy(&meter_table);
     exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index baf489202..c8836d313 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -505,6 +505,7 @@ static struct hmap dhcp_opts;   /* Contains "struct 
gen_opts_map"s. */
 static struct hmap dhcpv6_opts; /* Contains "struct gen_opts_map"s. */
 static struct hmap nd_ra_opts; /* Contains "struct gen_opts_map"s. */
 static struct controller_event_options event_opts;
+static struct smap template_vars;
 
 static struct ovntrace_datapath *
 ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid)
@@ -955,9 +956,13 @@ parse_lflow_for_datapath(const struct sbrec_logical_flow 
*sblf,
 
         char *error;
         struct expr *match;
-        match = expr_parse_string(sblf->match, &symtab, &address_sets,
+        char *match_s = lexer_parse_template_string(xstrdup(sblf->match),
+                                                    &template_vars,
+                                                    NULL);
+        match = expr_parse_string(match_s, &symtab, &address_sets,
                                   &port_groups, NULL, NULL, dp->tunnel_key,
                                   &error);
+        free(match_s);
         if (error) {
             VLOG_WARN("%s: parsing expression failed (%s)",
                       sblf->match, error);
@@ -980,7 +985,11 @@ parse_lflow_for_datapath(const struct sbrec_logical_flow 
*sblf,
         uint64_t stub[1024 / 8];
         struct ofpbuf ovnacts = OFPBUF_STUB_INITIALIZER(stub);
         struct expr *prereqs;
-        error = ovnacts_parse_string(sblf->actions, &pp, &ovnacts, &prereqs);
+        char *actions_s = lexer_parse_template_string(xstrdup(sblf->actions),
+                                                      &template_vars,
+                                                      NULL);
+        error = ovnacts_parse_string(actions_s, &pp, &ovnacts, &prereqs);
+        free(actions_s);
         if (error) {
             VLOG_WARN("%s: parsing actions failed (%s)", sblf->actions, error);
             free(error);
@@ -1078,6 +1087,7 @@ read_gen_opts(void)
     nd_ra_opts_init(&nd_ra_opts);
 
     controller_event_opts_init(&event_opts);
+    smap_init(&template_vars);
 }
 
 static void
@@ -3422,9 +3432,13 @@ trace_parse(const char *dp_s, const char *flow_s,
          *
          * First make sure that the expression parses. */
         char *error;
-        struct expr *e = expr_parse_string(flow_s, &symtab, &address_sets,
+        char *flow_exp_s = lexer_parse_template_string(xstrdup(flow_s),
+                                                       &template_vars,
+                                                       NULL);
+        struct expr *e = expr_parse_string(flow_exp_s, &symtab, &address_sets,
                                            &port_groups, NULL, NULL, 0,
                                            &error);
+        free(flow_exp_s);
         if (!e) {
             return trace_parse_error(error);
         }
@@ -3449,9 +3463,13 @@ trace_parse(const char *dp_s, const char *flow_s,
         free(port_name);
     }
 
-    char *error = expr_parse_microflow(flow_s, &symtab, &address_sets,
+    char *flow_exp_s = lexer_parse_template_string(xstrdup(flow_s),
+                                                   &template_vars,
+                                                   NULL);
+    char *error = expr_parse_microflow(flow_exp_s, &symtab, &address_sets,
                                        &port_groups, ovntrace_lookup_port,
                                        *dpp, uflow);
+    free(flow_exp_s);
     if (error) {
         return trace_parse_error(error);
     }


_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to