Skip to content

Commit

Permalink
Add tell/ear interface for CLI access to vmod objects
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 ears, and the CLI gets a command to tell
messages to ears.

vmod ears
---------

VMOD object classes gain a special method type $Ear, which is almost
identical to $Method, except that only one ear is supported per class,
and only the specific signature

	$Ear STRANDS earname(STRANDS)

is supported.

The ear receives input via the single STRANDS arguments and can either
return NULL for error, or a response as a STRANDS.

For the error case, the ear can write details to ctx->msg.

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 ear.

demo
----

A new test case demos the functionality: The debug.obj class has
gained an ear 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 Nov 8, 2021
1 parent 0b84355 commit 9734f89
Show file tree
Hide file tree
Showing 11 changed files with 278 additions and 5 deletions.
78 changes: 78 additions & 0 deletions bin/varnishd/cache/cache_vcl.c
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,83 @@ 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 *errvsb;
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, cli->sb);

errvsb = VCL_Rel_CliCtx(&ctx);

if (i == 0) {
VCLI_SetResult(cli, CLIS_CANT);
VCLI_Out(cli, "%s", VSB_data(errvsb));
VSB_destroy(&errvsb);
return;
}

VSB_destroy(&errvsb);

// truncate check required?
VCLI_SetResult(cli, CLIS_OK);
}

/*--------------------------------------------------------------------*/

static struct cli_proto vcl_cmds[] = {
Expand All @@ -998,6 +1075,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
63 changes: 63 additions & 0 deletions bin/varnishd/cache/cache_vpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,66 @@ 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 vsb *respvsb)
{
struct vcl *vcl;
const struct VCL_conf *conf;
const struct vpi_ii *ii;
VCL_STRANDS resp;
vpi_ear_f *ear;
int i;

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 (0);
}
if (ii->earp == NULL) {
VSB_printf(ctx->msg, "Object %s has no ear\n", objname);
return (0);
}

ear = (void *)*ii->earp;
AN(ear);
AN(*ii->p);
resp = ear(ctx, (void *)*ii->p, msg);

if (resp == NULL)
return (0);

for (i = 0; i < resp->n; i++)
VSB_cat(respvsb, resp->p[i]);

return (1);
}
10 changes: 10 additions & 0 deletions include/tbl/cli_cmds.h
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,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 ear 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 @@ -46,6 +46,7 @@ VCC_KIND(SUB, sub)
VCC_KIND(VAR, var)
VCC_KIND(VCL, vcl)
VCC_KIND(VMOD, vmod)
VCC_KIND(EAR, ear)
#undef VCC_KIND

/*lint -restore */
4 changes: 4 additions & 0 deletions include/vcc_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ typedef int acl_match_f(VRT_CTX, const VCL_IP);
int VPI_acl_table(VRT_CTX, VCL_IP, unsigned n, unsigned m, const uint8_t *tbl,
const char * const *str, const char *fin);

typedef VCL_STRANDS vpi_ear_f(VRT_CTX, void *, VCL_STRANDS);

struct vrt_acl {
unsigned magic;
#define VRT_ACL_MAGIC 0x78329d96
Expand All @@ -77,6 +79,7 @@ void VPI_acl_log(VRT_CTX, const char *);
struct vpi_ii {
uintptr_t * p;
const char * const name;
uintptr_t * earp;
};

/* Compile time regexp */
Expand All @@ -102,3 +105,4 @@ 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);
int VPI_Tell(VRT_CTX, VCL_STRING, VCL_STRANDS, struct vsb *);
16 changes: 13 additions & 3 deletions lib/libvcc/vcc_vmod.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,13 @@ vcc_path_dlopen(void *priv, const char *fn)
}

static void vcc_VmodObject(struct vcc *tl, struct symbol *sym);
static void vcc_VmodSymbols(struct vcc *tl, const struct symbol *sym);
static void vcc_VmodSymbols(struct vcc *tl, struct symbol *sym);

static void
func_sym(struct vcc *tl, vcc_kind_t kind, const struct symbol *psym,
func_sym(struct vcc *tl, vcc_kind_t kind, struct symbol *psym,
const struct vjsn_val *v)
{
const struct vjsn_val *vv;
struct symbol *sym;
struct vsb *buf;

Expand Down Expand Up @@ -115,11 +116,18 @@ 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;
if (kind == SYM_EAR) {
vv = VTAILQ_NEXT(vv, list);
assert(vjsn_is_string(vv));
AZ(psym->extra);
psym->extra = vv->value;
}
}

static void
Expand Down Expand Up @@ -237,6 +245,7 @@ vcc_vmod_kind(const char *type)
VMOD_KIND("$OBJ", SYM_OBJECT);
VMOD_KIND("$METHOD", SYM_METHOD);
VMOD_KIND("$FUNC", SYM_FUNC);
VMOD_KIND("$EAR", SYM_EAR);
#undef VMOD_KIND
return (SYM_NONE);
}
Expand Down Expand Up @@ -265,7 +274,7 @@ vcc_VmodObject(struct vcc *tl, struct symbol *sym)
}

static 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 @@ -490,6 +499,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 @@ -410,15 +410,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, "\", .earp = ");
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 = \"\", .earp = 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 @@ -744,6 +744,7 @@ def parse(self):
self.rstlbl = '%s.%s()' % (self.vcc.modname, self.proto.name)
self.vcc.contents.append(self)
self.methods = []
self.ear = None

def rsthead(self, fo, man):
if self.rstlbl:
Expand Down Expand Up @@ -777,11 +778,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.ear is not None:
fo.write(self.ear.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.ear is not None:
self.ear.cstruct(fo, define)
for i in self.methods:
i.cstruct(fo, define)
fo.write("\n")
Expand All @@ -803,6 +808,9 @@ def json(self, jl):
ll.append(l2)
self.fini.jsonproto(l2, self.fini.name)

if self.ear is not None:
self.ear.json(ll)

for i in self.methods:
i.json(ll)

Expand Down Expand Up @@ -835,6 +843,39 @@ def json(self, jl):
self.proto.jsonproto(jl[-1], self.proto.cname())


#######################################################################


class EarStanza(Stanza):

''' $Ear STRANDS 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.ear is not None:
err("$Ear %s.%s: Ear already defined for this class"
% (self.pfx, self.proto.bname), warn=False)
if self.proto.retval.vt != "STRANDS":
err("$Ear %s.%s: Return value needs to be STRANDS"
% (self.pfx, self.proto.bname), warn=False)
if len(self.proto.args) != 1 or self.proto.args[0].vt != 'STRANDS':
err("$Ear %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.ear = self

def cstruct(self, fo, define):
self.fmt_cstruct_proto(fo, self.proto, define)

def json(self, jl):
jl.append(["$EAR", self.proto.name])
self.proto.jsonproto(jl[-1], self.proto.cname())


#######################################################################

DISPATCH = {
Expand All @@ -845,6 +886,7 @@ def json(self, jl):
"Function": FunctionStanza,
"Object": ObjectStanza,
"Method": MethodStanza,
"Ear": EarStanza,
"Synopsis": SynopsisStanza,
}

Expand Down
26 changes: 26 additions & 0 deletions vmod/tests/debug_c00001.vtc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
varnishtest "Test vmod ears / 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 ear
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"
Loading

0 comments on commit 9734f89

Please sign in to comment.