Skip to content

Commit

Permalink
Add CLI access to vmod objects with the "tell" command
Browse files Browse the repository at this point in the history
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 [<vcl>.]<object> <msg> ...
Tell <msg> to <object> from the given <vcl> 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
  • Loading branch information
nigoroll committed Jul 31, 2024
1 parent 8cf3547 commit 00e4210
Show file tree
Hide file tree
Showing 12 changed files with 263 additions and 8 deletions.
71 changes: 71 additions & 0 deletions bin/varnishd/cache/cache_vcl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,76 @@ vcl_cli_show(struct cli *cli, const char * const *av, void *priv)
}
}

// "tell [<vcl>.]<object> <msg> ...",

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[] = {
Expand All @@ -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 }
};

Expand Down
53 changes: 53 additions & 0 deletions bin/varnishd/cache/cache_vpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
10 changes: 10 additions & 0 deletions include/tbl/cli_cmds.h
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,16 @@ CLI_CMD(PID,
0, 0
)

CLI_CMD(TELL,
"tell",
"tell [<vcl>.]<object> <msg> ...",
"Tell <msg> to <object> from the given <vcl> or the active vcl",
" It is entirely up to the cli method implementation of the"
" respective vmod to interpret <msg>, implement any actions"
" and generate a response",
2, -1
)

#undef CLI_CMD

/*lint -restore */
1 change: 1 addition & 0 deletions include/tbl/symbol_kind.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
5 changes: 5 additions & 0 deletions include/vcc_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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);
5 changes: 3 additions & 2 deletions lib/libvcc/vcc_vmod.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
17 changes: 13 additions & 4 deletions lib/libvcc/vcc_vmod_sym.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions lib/libvcc/vcc_xref.c
Original file line number Diff line number Diff line change
Expand Up @@ -405,15 +405,20 @@ 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
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");
}

Expand Down
42 changes: 42 additions & 0 deletions lib/libvcc/vmodtool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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")
Expand All @@ -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)

Expand Down Expand Up @@ -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 = {
Expand All @@ -941,6 +982,7 @@ def json(self, jl):
"Function": FunctionStanza,
"Object": ObjectStanza,
"Method": MethodStanza,
"Cli": CliStanza,
"Synopsis": SynopsisStanza,
"Alias": AliasStanza,
"Restrict": RestrictStanza,
Expand Down
32 changes: 32 additions & 0 deletions vmod/tests/debug_c00001.vtc
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 2 additions & 0 deletions vmod/vmod_debug.vcc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 00e4210

Please sign in to comment.