From dfa5f431ecc8c35bfdb4792e4cea205eda6bfbe4 Mon Sep 17 00:00:00 2001 From: Nils Goroll Date: Wed, 3 Nov 2021 16:56:06 +0100 Subject: [PATCH] Add CLI access to vmod objects with the "tell" command Runtime modification of vmod object properties has been a long standing item on our wishlist. For example, #3652 is about a use case to change a custom director property, which is not covered by the director health state. Another simple example is to instruct a vmod object to emit log messages for tracing only when needed. This commit implements a basic interface for CLI access to vmod objects: VMOD objects now can have a single $Cli method, and the CLI gets a command to tell messages by invoking that method. vmod $Cli method ---------------- VMOD object classes gain a special method type $Cli, which is almost identical to $Method, except that only one method is supported per class, and only the specific signature $Cli INT cli_method(STRANDS) is supported. The cli method receives input via the single STRANDS arguments. It is expected to write output to ctx->msg and return the CLI status. cli tell command ---------------- The tell command takes an optional vcl name, object name and message to send. Individual message arguments are passed as constituents of the STRANDS argument to the object's cli method. demo ---- A new test case demos the functionality: The debug.obj class has gained a cli method which just returns the instance name followed by the original message: varnish> help tell 200 tell [.] ... Tell to from the given or the active vcl varnish> tell obj0 is there anybody out there? 200 obj0: is there anybody out there? varnish> tell whoisit hello? 300 No object named whoisit found varnish> tell obj0 fail 300 You asked me to fail --- bin/varnishd/cache/cache_vcl.c | 71 ++++++++++++++++++++++++++++++++++ bin/varnishd/cache/cache_vpi.c | 53 +++++++++++++++++++++++++ include/tbl/cli_cmds.h | 10 +++++ include/tbl/symbol_kind.h | 1 + include/vcc_interface.h | 5 +++ lib/libvcc/vcc_vmod.h | 5 ++- lib/libvcc/vcc_vmod_sym.c | 17 ++++++-- lib/libvcc/vcc_xref.c | 9 ++++- lib/libvcc/vmodtool.py | 42 ++++++++++++++++++++ vmod/tests/debug_c00001.vtc | 32 +++++++++++++++ vmod/vmod_debug.vcc | 2 + vmod/vmod_debug_obj.c | 24 ++++++++++++ 12 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 vmod/tests/debug_c00001.vtc diff --git a/bin/varnishd/cache/cache_vcl.c b/bin/varnishd/cache/cache_vcl.c index 262b4853812..504c3cfdf3b 100644 --- a/bin/varnishd/cache/cache_vcl.c +++ b/bin/varnishd/cache/cache_vcl.c @@ -1016,6 +1016,76 @@ vcl_cli_show(struct cli *cli, const char * const *av, void *priv) } } +// "tell [.] ...", + +static void v_matchproto_(cli_func_t) +vcl_cli_tell(struct cli *cli, const char * const *av, void *priv) +{ + struct strands args; + const char *objname; + struct vcl *vcl; + struct vrt_ctx *ctx; + struct vsb *msg; + char *n; + int i; + + AZ(priv); + ASSERT_CLI(); + AN(av[2]); + AN(av[3]); + + objname = strchr(av[2], '.'); + if (objname) { + n = strndup(av[2], objname - av[2]); + objname++; + vcl = vcl_find(n); + if (vcl == NULL) { + VCLI_SetResult(cli, CLIS_CANT); + VCLI_Out(cli, "VCL %s not found", n); + REPLACE(n, NULL); + return; + } + REPLACE(n, NULL); + } else { + vcl = vcl_active; + objname = av[2]; + } + + AN(vcl); + AN(objname); + + if (vcl->label) + vcl = vcl->label; + AN(vcl); + + i = 0; + while (av[3 + i] != NULL) + i++; + + const char *p[i]; + args.n = i; + args.p = p; + + i = 0; + while (av[3 + i] != NULL) { + args.p[i] = av[3 + i]; + i++; + } + + ctx = VCL_Get_CliCtx(1); + ctx->vcl = vcl; + ctx->syntax = ctx->vcl->conf->syntax; + + i = VPI_Tell(ctx, objname, &args); + + msg = VCL_Rel_CliCtx(&ctx); + + VCLI_SetResult(cli, i); + // could have VCLI_Cat or VCLI_Vsb + VCLI_Out(cli, "%s", VSB_data(msg)); + VSB_destroy(&msg); +} + /*--------------------------------------------------------------------*/ static struct cli_proto vcl_cmds[] = { @@ -1026,6 +1096,7 @@ static struct cli_proto vcl_cmds[] = { { CLICMD_VCL_USE, "", vcl_cli_use }, { CLICMD_VCL_SHOW, "", vcl_cli_show }, { CLICMD_VCL_LABEL, "", vcl_cli_label }, + { CLICMD_TELL, "", vcl_cli_tell}, { NULL } }; diff --git a/bin/varnishd/cache/cache_vpi.c b/bin/varnishd/cache/cache_vpi.c index 9b3b3db1cc3..fc8386ebd9e 100644 --- a/bin/varnishd/cache/cache_vpi.c +++ b/bin/varnishd/cache/cache_vpi.c @@ -302,3 +302,56 @@ VPI_Call_End(VRT_CTX, unsigned n) AN(vbm); vbit_clr(vbm, n); } + +/*-------------------------------------------------------------------- + * tell interface. + * + * we all agree it does not quite fit the purpose of VPI, but it fits here + * better than anywhere else + * + * XXX: Replace instance info with a tree indexed by name + */ + +int +VPI_Tell(VRT_CTX, VCL_STRING objname, VCL_STRANDS msg) +{ + struct vcl *vcl; + const struct VCL_conf *conf; + const struct vpi_ii *ii; + vmod_cli_f *cli; + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + AN(objname); + + ASSERT_CLI(); + AZ(ctx->method); + AN(ctx->msg); + + vcl = ctx->vcl; + CHECK_OBJ_NOTNULL(vcl, VCL_MAGIC); + + conf = vcl->conf; + CHECK_OBJ_NOTNULL(conf, VCL_CONF_MAGIC); + + ii = conf->instance_info; + AN(ii); + while (ii->p != NULL) { + if (! strcmp(ii->name, objname)) + break; + ii++; + } + + if (ii->p == NULL) { + VSB_printf(ctx->msg, "No object named %s found\n", objname); + return (300); + } + if (ii->clip == NULL) { + VSB_printf(ctx->msg, "Object %s has no cli method\n", objname); + return (300); + } + + cli = (void *)*ii->clip; + AN(cli); + AN(*ii->p); + return(cli(ctx, (void *)*ii->p, msg)); +} diff --git a/include/tbl/cli_cmds.h b/include/tbl/cli_cmds.h index 6f5eb5c90ae..932f5c6a62d 100644 --- a/include/tbl/cli_cmds.h +++ b/include/tbl/cli_cmds.h @@ -451,6 +451,16 @@ CLI_CMD(PID, 0, 0 ) +CLI_CMD(TELL, + "tell", + "tell [.] ...", + "Tell to from the given or the active vcl", + " It is entirely up to the cli method implementation of the" + " respective vmod to interpret , implement any actions" + " and generate a response", + 2, -1 +) + #undef CLI_CMD /*lint -restore */ diff --git a/include/tbl/symbol_kind.h b/include/tbl/symbol_kind.h index c1f29fa98f9..594a5952c15 100644 --- a/include/tbl/symbol_kind.h +++ b/include/tbl/symbol_kind.h @@ -47,6 +47,7 @@ VCC_KIND(SUB, sub) VCC_KIND(VAR, var) VCC_KIND(VCL, vcl) VCC_KIND(VMOD, vmod) +VCC_KIND(CLI_METHOD, cli) #undef VCC_KIND /*lint -restore */ diff --git a/include/vcc_interface.h b/include/vcc_interface.h index 529ab9ed17e..5d49f457d77 100644 --- a/include/vcc_interface.h +++ b/include/vcc_interface.h @@ -93,6 +93,7 @@ void VPI_acl_log(VRT_CTX, const char *); struct vpi_ii { uintptr_t * p; const char * const name; + uintptr_t * clip; }; /* Compile time regexp */ @@ -118,3 +119,7 @@ enum vcl_func_fail_e VPI_Call_Check(VRT_CTX, const struct VCL_conf *conf, unsigned methods, unsigned n); void VPI_Call_Begin(VRT_CTX, unsigned n); void VPI_Call_End(VRT_CTX, unsigned n); + +// return value should be a VCLI_status_e +typedef VCL_INT vmod_cli_f(VRT_CTX, void *, VCL_STRANDS); +int VPI_Tell(VRT_CTX, VCL_STRING, VCL_STRANDS); diff --git a/lib/libvcc/vcc_vmod.h b/lib/libvcc/vcc_vmod.h index a1084c2f5f4..8e5bcb07a4c 100644 --- a/lib/libvcc/vcc_vmod.h +++ b/lib/libvcc/vcc_vmod.h @@ -36,6 +36,7 @@ STANZA(METHOD, method, SYM_METHOD) \ STANZA(OBJ, obj, SYM_OBJECT) \ STANZA(VMOD, vmod, SYM_NONE) \ - STANZA(RESTRICT, restrict, SYM_NONE) + STANZA(RESTRICT, restrict, SYM_NONE) \ + STANZA(CLI, cli, SYM_CLI_METHOD) -void vcc_VmodSymbols(struct vcc *tl, const struct symbol *sym); +void vcc_VmodSymbols(struct vcc *tl, struct symbol *sym); diff --git a/lib/libvcc/vcc_vmod_sym.c b/lib/libvcc/vcc_vmod_sym.c index e86a1a20e42..434c8053f4e 100644 --- a/lib/libvcc/vcc_vmod_sym.c +++ b/lib/libvcc/vcc_vmod_sym.c @@ -147,9 +147,10 @@ func_restrict(struct vcc *tl, struct symbol *sym, vcc_kind_t kind, const struct } static void -func_sym(struct vcc *tl, vcc_kind_t kind, const struct symbol *psym, - const struct vjsn_val *v, const struct vjsn_val *vv) +func_sym(struct vcc *tl, vcc_kind_t kind, struct symbol *psym, + const struct vjsn_val *v, const struct vjsn_val *vr) { + const struct vjsn_val *vv; struct symbol *sym; struct vsb *buf; @@ -189,16 +190,23 @@ func_sym(struct vcc *tl, vcc_kind_t kind, const struct symbol *psym, sym->eval_priv = v; v = VTAILQ_FIRST(&v->children); assert(vjsn_is_array(v)); + vv = v; v = VTAILQ_FIRST(&v->children); assert(vjsn_is_string(v)); sym->type = VCC_Type(v->value); AN(sym->type); sym->r_methods = VCL_MET_TASK_ALL; - func_restrict(tl, sym, kind, vv); + func_restrict(tl, sym, kind, vr); + if (kind == SYM_CLI_METHOD) { + vv = VTAILQ_NEXT(vv, list); + assert(vjsn_is_string(vv)); + AZ(psym->extra); + psym->extra = vv->value; + } } void -vcc_VmodSymbols(struct vcc *tl, const struct symbol *sym) +vcc_VmodSymbols(struct vcc *tl, struct symbol *sym) { const struct vjsn *vj; const struct vjsn_val *vv, *vv1, *vv2; @@ -265,6 +273,7 @@ vcc_Act_New(struct vcc *tl, struct token *t, struct symbol *sym) /* Scratch the generic INSTANCE type */ isym->type = osym->type; + isym->extra = osym->extra; CAST_OBJ_NOTNULL(vv, osym->eval_priv, VJSN_VAL_MAGIC); // vv = object name diff --git a/lib/libvcc/vcc_xref.c b/lib/libvcc/vcc_xref.c index 95f3e3dd370..41c1c280e11 100644 --- a/lib/libvcc/vcc_xref.c +++ b/lib/libvcc/vcc_xref.c @@ -405,7 +405,12 @@ vcc_instance_info(struct vcc *tl, const struct symbol *sym) AN(sym->rname); Fc(tl, 0, "\t{ .p = (uintptr_t *)&%s, .name = \"", sym->rname); VCC_SymName(tl->fc, sym); - Fc(tl, 0, "\" },\n"); + Fc(tl, 0, "\", .clip = "); + if (sym->extra) + Fc(tl, 0, "(uintptr_t *)&%s", sym->extra); + else + Fc(tl, 0, "NULL"); + Fc(tl, 0, "},\n"); } void @@ -413,7 +418,7 @@ VCC_InstanceInfo(struct vcc *tl) { Fc(tl, 0, "\nstatic const struct vpi_ii VGC_instance_info[] = {\n"); VCC_WalkSymbols(tl, vcc_instance_info, SYM_MAIN, SYM_INSTANCE); - Fc(tl, 0, "\t{ .p = NULL, .name = \"\" }\n"); + Fc(tl, 0, "\t{ .p = NULL, .name = \"\", .clip = NULL }\n"); Fc(tl, 0, "};\n"); } diff --git a/lib/libvcc/vmodtool.py b/lib/libvcc/vmodtool.py index 21d91a7b44b..a262ef0d5b6 100755 --- a/lib/libvcc/vmodtool.py +++ b/lib/libvcc/vmodtool.py @@ -770,6 +770,7 @@ def parse(self): self.rstlbl = '%s.%s()' % (self.vcc.modname, self.proto.name) self.vcc.contents.append(self) self.methods = [] + self.cli = None def rsthead(self, fo, man): if self.rstlbl: @@ -803,11 +804,15 @@ def cstuff(self, fo, w): fo.write(self.fini.cproto(['struct %s **' % sn], w)) for i in self.methods: fo.write(i.proto.cproto(['VRT_CTX', 'struct %s *' % sn], w)) + if self.cli is not None: + fo.write(self.cli.proto.cproto(['VRT_CTX', 'struct %s *' % sn], w)) fo.write("\n") def cstruct(self, fo, define): self.fmt_cstruct_proto(fo, self.init, define) self.fmt_cstruct_proto(fo, self.fini, define) + if self.cli is not None: + self.cli.cstruct(fo, define) for i in self.methods: i.cstruct(fo, define) fo.write("\n") @@ -829,6 +834,9 @@ def json(self, jl): ll.append(l2) self.fini.jsonproto(l2, self.fini.name) + if self.cli is not None: + self.cli.json(ll) + for i in self.methods: i.json(ll) @@ -931,6 +939,39 @@ def json(self, jl): jl.append(["$ALIAS", self.sym_alias, self.sym_name]) +####################################################################### + + +class CliStanza(Stanza): + + ''' $Cli INT name (STRANDS) ''' + + def parse(self): + p = self.vcc.contents[-1] + assert isinstance(p, ObjectStanza) + self.pfx = p.proto.name + self.proto = ProtoType(self, prefix=self.pfx) + if p.cli is not None: + err("$Cli %s.%s: Cli method already defined for this class" + % (self.pfx, self.proto.bname), warn=False) + if self.proto.retval.vt != "INT": + err("$Cli %s.%s: Return value needs to be INT" + % (self.pfx, self.proto.bname), warn=False) + if len(self.proto.args) != 1 or self.proto.args[0].vt != 'STRANDS': + err("$Cli %s.%s: Need single argument of type STRANDS" + % (self.pfx, self.proto.bname), warn=False) +# self.proto.obj = "x" + self.pfx +# self.rstlbl = 'x%s()' % self.proto.name + p.cli = self + + def cstruct(self, fo, define): + self.fmt_cstruct_proto(fo, self.proto, define) + + def json(self, jl): + jl.append(["$CLI", self.proto.name]) + self.proto.jsonproto(jl[-1], self.proto.cname()) + + ####################################################################### DISPATCH = { @@ -941,6 +982,7 @@ def json(self, jl): "Function": FunctionStanza, "Object": ObjectStanza, "Method": MethodStanza, + "Cli": CliStanza, "Synopsis": SynopsisStanza, "Alias": AliasStanza, "Restrict": RestrictStanza, diff --git a/vmod/tests/debug_c00001.vtc b/vmod/tests/debug_c00001.vtc new file mode 100644 index 00000000000..fdf751be36f --- /dev/null +++ b/vmod/tests/debug_c00001.vtc @@ -0,0 +1,32 @@ +varnishtest "Test vmod cli methods / vcl tell" + +varnish v1 -vcl+backend { + import debug; + + backend proforma none; + + sub vcl_init { + new obj0 = debug.obj(); + new obj1 = debug.obj("only_argument"); + new oo0 = debug.obj_opt(); + } +} -start + +# vcl2 not found +varnish v1 -clierr "300" "tell vcl2.obj0 a b c" +# No object named objX found +varnish v1 -clierr "300" "tell objX a b c" +# Object oo0 has no cli method +varnish v1 -clierr "300" "tell oo0 a b c" +# Too few parameters +varnish v1 -clierr "104" "tell obj0" + +varnish v1 -cliexpect "obj0: a b c" "tell obj0 a b c" +varnish v1 -cliexpect "obj0: a b c" "tell vcl1.obj0 a b c" +varnish v1 -cliexpect "obj1: a b c" "tell obj1 a b c" + +varnish v1 -vcl { backend proforma none; } + +varnish v1 -cliok "vcl.use vcl2" +varnish v1 -cliok "vcl.state vcl1 cold" +varnish v1 -cliexpect "obj0: a b c" "tell vcl1.obj0 a b c" diff --git a/vmod/vmod_debug.vcc b/vmod/vmod_debug.vcc index 8e9a25c3ed7..650edb0fa07 100644 --- a/vmod/vmod_debug.vcc +++ b/vmod/vmod_debug.vcc @@ -81,6 +81,8 @@ $Method VOID .enum(ENUM { phk, des, kristian, mithrandir, martin }) Testing that enums work as part of object and that the parser isn't (too) buggy. +$Cli INT cli(STRANDS) + $Method VOID .obj() Covering the fact that a method can be named like the constructor. diff --git a/vmod/vmod_debug_obj.c b/vmod/vmod_debug_obj.c index 28a6bfa0012..eb6ce6fdd9b 100644 --- a/vmod/vmod_debug_obj.c +++ b/vmod/vmod_debug_obj.c @@ -35,6 +35,7 @@ #include "cache/cache.h" #include "vcl.h" +#include "vsb.h" #include "vcc_debug_if.h" @@ -83,6 +84,29 @@ xyzzy_obj__fini(struct xyzzy_debug_obj **op) FREE_OBJ(o); } +VCL_INT v_matchproto_(td_xyzzy_objcli) +xyzzy_objcli(VRT_CTX, struct xyzzy_debug_obj *o, VCL_STRANDS s) +{ + int i; + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(o, VMOD_DEBUG_OBJ_MAGIC); + + if (s->n > 0 && ! strcmp(s->p[0], "fail")) { + VSB_cat(ctx->msg, "You asked me to fail"); + return (300); + } + VSB_cat(ctx->msg, o->vcl_name); + VSB_putc(ctx->msg, ':'); + + for (i = 0; i < s->n; i++) { + VSB_putc(ctx->msg, ' '); + VSB_cat(ctx->msg, s->p[i]); + } + + return (200); +} + VCL_VOID v_matchproto_(td_xyzzy_obj_enum) xyzzy_obj_enum(VRT_CTX, struct xyzzy_debug_obj *o, VCL_ENUM e) {