Skip to content

Commit 119ea2f

Browse files
committed
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 [<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
1 parent 5317450 commit 119ea2f

File tree

12 files changed

+263
-8
lines changed

12 files changed

+263
-8
lines changed

bin/varnishd/cache/cache_vcl.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,76 @@ vcl_cli_show(struct cli *cli, const char * const *av, void *priv)
10021002
}
10031003
}
10041004

1005+
// "tell [<vcl>.]<object> <msg> ...",
1006+
1007+
static void v_matchproto_(cli_func_t)
1008+
vcl_cli_tell(struct cli *cli, const char * const *av, void *priv)
1009+
{
1010+
struct strands args;
1011+
const char *objname;
1012+
struct vcl *vcl;
1013+
struct vrt_ctx *ctx;
1014+
struct vsb *msg;
1015+
char *n;
1016+
int i;
1017+
1018+
AZ(priv);
1019+
ASSERT_CLI();
1020+
AN(av[2]);
1021+
AN(av[3]);
1022+
1023+
objname = strchr(av[2], '.');
1024+
if (objname) {
1025+
n = strndup(av[2], objname - av[2]);
1026+
objname++;
1027+
vcl = vcl_find(n);
1028+
if (vcl == NULL) {
1029+
VCLI_SetResult(cli, CLIS_CANT);
1030+
VCLI_Out(cli, "VCL %s not found", n);
1031+
REPLACE(n, NULL);
1032+
return;
1033+
}
1034+
REPLACE(n, NULL);
1035+
} else {
1036+
vcl = vcl_active;
1037+
objname = av[2];
1038+
}
1039+
1040+
AN(vcl);
1041+
AN(objname);
1042+
1043+
if (vcl->label)
1044+
vcl = vcl->label;
1045+
AN(vcl);
1046+
1047+
i = 0;
1048+
while (av[3 + i] != NULL)
1049+
i++;
1050+
1051+
const char *p[i];
1052+
args.n = i;
1053+
args.p = p;
1054+
1055+
i = 0;
1056+
while (av[3 + i] != NULL) {
1057+
args.p[i] = av[3 + i];
1058+
i++;
1059+
}
1060+
1061+
ctx = VCL_Get_CliCtx(1);
1062+
ctx->vcl = vcl;
1063+
ctx->syntax = ctx->vcl->conf->syntax;
1064+
1065+
i = VPI_Tell(ctx, objname, &args);
1066+
1067+
msg = VCL_Rel_CliCtx(&ctx);
1068+
1069+
VCLI_SetResult(cli, i);
1070+
// could have VCLI_Cat or VCLI_Vsb
1071+
VCLI_Out(cli, "%s", VSB_data(msg));
1072+
VSB_destroy(&msg);
1073+
}
1074+
10051075
/*--------------------------------------------------------------------*/
10061076

10071077
static struct cli_proto vcl_cmds[] = {
@@ -1012,6 +1082,7 @@ static struct cli_proto vcl_cmds[] = {
10121082
{ CLICMD_VCL_USE, "", vcl_cli_use },
10131083
{ CLICMD_VCL_SHOW, "", vcl_cli_show },
10141084
{ CLICMD_VCL_LABEL, "", vcl_cli_label },
1085+
{ CLICMD_TELL, "", vcl_cli_tell},
10151086
{ NULL }
10161087
};
10171088

bin/varnishd/cache/cache_vpi.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,56 @@ VPI_Call_End(VRT_CTX, unsigned n)
302302
AN(vbm);
303303
vbit_clr(vbm, n);
304304
}
305+
306+
/*--------------------------------------------------------------------
307+
* tell interface.
308+
*
309+
* we all agree it does not quite fit the purpose of VPI, but it fits here
310+
* better than anywhere else
311+
*
312+
* XXX: Replace instance info with a tree indexed by name
313+
*/
314+
315+
int
316+
VPI_Tell(VRT_CTX, VCL_STRING objname, VCL_STRANDS msg)
317+
{
318+
struct vcl *vcl;
319+
const struct VCL_conf *conf;
320+
const struct vpi_ii *ii;
321+
vmod_cli_f *cli;
322+
323+
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
324+
AN(objname);
325+
326+
ASSERT_CLI();
327+
AZ(ctx->method);
328+
AN(ctx->msg);
329+
330+
vcl = ctx->vcl;
331+
CHECK_OBJ_NOTNULL(vcl, VCL_MAGIC);
332+
333+
conf = vcl->conf;
334+
CHECK_OBJ_NOTNULL(conf, VCL_CONF_MAGIC);
335+
336+
ii = conf->instance_info;
337+
AN(ii);
338+
while (ii->p != NULL) {
339+
if (! strcmp(ii->name, objname))
340+
break;
341+
ii++;
342+
}
343+
344+
if (ii->p == NULL) {
345+
VSB_printf(ctx->msg, "No object named %s found\n", objname);
346+
return (300);
347+
}
348+
if (ii->clip == NULL) {
349+
VSB_printf(ctx->msg, "Object %s has no cli method\n", objname);
350+
return (300);
351+
}
352+
353+
cli = (void *)*ii->clip;
354+
AN(cli);
355+
AN(*ii->p);
356+
return(cli(ctx, (void *)*ii->p, msg));
357+
}

include/tbl/cli_cmds.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,16 @@ CLI_CMD(PID,
451451
0, 0
452452
)
453453

454+
CLI_CMD(TELL,
455+
"tell",
456+
"tell [<vcl>.]<object> <msg> ...",
457+
"Tell <msg> to <object> from the given <vcl> or the active vcl",
458+
" It is entirely up to the cli method implementation of the"
459+
" respective vmod to interpret <msg>, implement any actions"
460+
" and generate a response",
461+
2, -1
462+
)
463+
454464
#undef CLI_CMD
455465

456466
/*lint -restore */

include/tbl/symbol_kind.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ VCC_KIND(SUB, sub)
4747
VCC_KIND(VAR, var)
4848
VCC_KIND(VCL, vcl)
4949
VCC_KIND(VMOD, vmod)
50+
VCC_KIND(CLI_METHOD, cli)
5051
#undef VCC_KIND
5152

5253
/*lint -restore */

include/vcc_interface.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ void VPI_acl_log(VRT_CTX, const char *);
9393
struct vpi_ii {
9494
uintptr_t * p;
9595
const char * const name;
96+
uintptr_t * clip;
9697
};
9798

9899
/* Compile time regexp */
@@ -118,3 +119,7 @@ enum vcl_func_fail_e VPI_Call_Check(VRT_CTX, const struct VCL_conf *conf,
118119
unsigned methods, unsigned n);
119120
void VPI_Call_Begin(VRT_CTX, unsigned n);
120121
void VPI_Call_End(VRT_CTX, unsigned n);
122+
123+
// return value should be a VCLI_status_e
124+
typedef VCL_INT vmod_cli_f(VRT_CTX, void *, VCL_STRANDS);
125+
int VPI_Tell(VRT_CTX, VCL_STRING, VCL_STRANDS);

lib/libvcc/vcc_vmod.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
STANZA(METHOD, method, SYM_METHOD) \
3737
STANZA(OBJ, obj, SYM_OBJECT) \
3838
STANZA(VMOD, vmod, SYM_NONE) \
39-
STANZA(RESTRICT, restrict, SYM_NONE)
39+
STANZA(RESTRICT, restrict, SYM_NONE) \
40+
STANZA(CLI, cli, SYM_CLI_METHOD)
4041

41-
void vcc_VmodSymbols(struct vcc *tl, const struct symbol *sym);
42+
void vcc_VmodSymbols(struct vcc *tl, struct symbol *sym);

lib/libvcc/vcc_vmod_sym.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,10 @@ func_restrict(struct vcc *tl, struct symbol *sym, vcc_kind_t kind, const struct
147147
}
148148

149149
static void
150-
func_sym(struct vcc *tl, vcc_kind_t kind, const struct symbol *psym,
151-
const struct vjsn_val *v, const struct vjsn_val *vv)
150+
func_sym(struct vcc *tl, vcc_kind_t kind, struct symbol *psym,
151+
const struct vjsn_val *v, const struct vjsn_val *vr)
152152
{
153+
const struct vjsn_val *vv;
153154
struct symbol *sym;
154155
struct vsb *buf;
155156

@@ -189,16 +190,23 @@ func_sym(struct vcc *tl, vcc_kind_t kind, const struct symbol *psym,
189190
sym->eval_priv = v;
190191
v = VTAILQ_FIRST(&v->children);
191192
assert(vjsn_is_array(v));
193+
vv = v;
192194
v = VTAILQ_FIRST(&v->children);
193195
assert(vjsn_is_string(v));
194196
sym->type = VCC_Type(v->value);
195197
AN(sym->type);
196198
sym->r_methods = VCL_MET_TASK_ALL;
197-
func_restrict(tl, sym, kind, vv);
199+
func_restrict(tl, sym, kind, vr);
200+
if (kind == SYM_CLI_METHOD) {
201+
vv = VTAILQ_NEXT(vv, list);
202+
assert(vjsn_is_string(vv));
203+
AZ(psym->extra);
204+
psym->extra = vv->value;
205+
}
198206
}
199207

200208
void
201-
vcc_VmodSymbols(struct vcc *tl, const struct symbol *sym)
209+
vcc_VmodSymbols(struct vcc *tl, struct symbol *sym)
202210
{
203211
const struct vjsn *vj;
204212
const struct vjsn_val *vv, *vv1, *vv2;
@@ -265,6 +273,7 @@ vcc_Act_New(struct vcc *tl, struct token *t, struct symbol *sym)
265273

266274
/* Scratch the generic INSTANCE type */
267275
isym->type = osym->type;
276+
isym->extra = osym->extra;
268277

269278
CAST_OBJ_NOTNULL(vv, osym->eval_priv, VJSN_VAL_MAGIC);
270279
// vv = object name

lib/libvcc/vcc_xref.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,15 +405,20 @@ vcc_instance_info(struct vcc *tl, const struct symbol *sym)
405405
AN(sym->rname);
406406
Fc(tl, 0, "\t{ .p = (uintptr_t *)&%s, .name = \"", sym->rname);
407407
VCC_SymName(tl->fc, sym);
408-
Fc(tl, 0, "\" },\n");
408+
Fc(tl, 0, "\", .clip = ");
409+
if (sym->extra)
410+
Fc(tl, 0, "(uintptr_t *)&%s", sym->extra);
411+
else
412+
Fc(tl, 0, "NULL");
413+
Fc(tl, 0, "},\n");
409414
}
410415

411416
void
412417
VCC_InstanceInfo(struct vcc *tl)
413418
{
414419
Fc(tl, 0, "\nstatic const struct vpi_ii VGC_instance_info[] = {\n");
415420
VCC_WalkSymbols(tl, vcc_instance_info, SYM_MAIN, SYM_INSTANCE);
416-
Fc(tl, 0, "\t{ .p = NULL, .name = \"\" }\n");
421+
Fc(tl, 0, "\t{ .p = NULL, .name = \"\", .clip = NULL }\n");
417422
Fc(tl, 0, "};\n");
418423
}
419424

lib/libvcc/vmodtool.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ def parse(self):
770770
self.rstlbl = '%s.%s()' % (self.vcc.modname, self.proto.name)
771771
self.vcc.contents.append(self)
772772
self.methods = []
773+
self.cli = None
773774

774775
def rsthead(self, fo, man):
775776
if self.rstlbl:
@@ -803,11 +804,15 @@ def cstuff(self, fo, w):
803804
fo.write(self.fini.cproto(['struct %s **' % sn], w))
804805
for i in self.methods:
805806
fo.write(i.proto.cproto(['VRT_CTX', 'struct %s *' % sn], w))
807+
if self.cli is not None:
808+
fo.write(self.cli.proto.cproto(['VRT_CTX', 'struct %s *' % sn], w))
806809
fo.write("\n")
807810

808811
def cstruct(self, fo, define):
809812
self.fmt_cstruct_proto(fo, self.init, define)
810813
self.fmt_cstruct_proto(fo, self.fini, define)
814+
if self.cli is not None:
815+
self.cli.cstruct(fo, define)
811816
for i in self.methods:
812817
i.cstruct(fo, define)
813818
fo.write("\n")
@@ -829,6 +834,9 @@ def json(self, jl):
829834
ll.append(l2)
830835
self.fini.jsonproto(l2, self.fini.name)
831836

837+
if self.cli is not None:
838+
self.cli.json(ll)
839+
832840
for i in self.methods:
833841
i.json(ll)
834842

@@ -931,6 +939,39 @@ def json(self, jl):
931939
jl.append(["$ALIAS", self.sym_alias, self.sym_name])
932940

933941

942+
#######################################################################
943+
944+
945+
class CliStanza(Stanza):
946+
947+
''' $Cli INT name (STRANDS) '''
948+
949+
def parse(self):
950+
p = self.vcc.contents[-1]
951+
assert isinstance(p, ObjectStanza)
952+
self.pfx = p.proto.name
953+
self.proto = ProtoType(self, prefix=self.pfx)
954+
if p.cli is not None:
955+
err("$Cli %s.%s: Cli method already defined for this class"
956+
% (self.pfx, self.proto.bname), warn=False)
957+
if self.proto.retval.vt != "INT":
958+
err("$Cli %s.%s: Return value needs to be INT"
959+
% (self.pfx, self.proto.bname), warn=False)
960+
if len(self.proto.args) != 1 or self.proto.args[0].vt != 'STRANDS':
961+
err("$Cli %s.%s: Need single argument of type STRANDS"
962+
% (self.pfx, self.proto.bname), warn=False)
963+
# self.proto.obj = "x" + self.pfx
964+
# self.rstlbl = 'x%s()' % self.proto.name
965+
p.cli = self
966+
967+
def cstruct(self, fo, define):
968+
self.fmt_cstruct_proto(fo, self.proto, define)
969+
970+
def json(self, jl):
971+
jl.append(["$CLI", self.proto.name])
972+
self.proto.jsonproto(jl[-1], self.proto.cname())
973+
974+
934975
#######################################################################
935976

936977
DISPATCH = {
@@ -941,6 +982,7 @@ def json(self, jl):
941982
"Function": FunctionStanza,
942983
"Object": ObjectStanza,
943984
"Method": MethodStanza,
985+
"Cli": CliStanza,
944986
"Synopsis": SynopsisStanza,
945987
"Alias": AliasStanza,
946988
"Restrict": RestrictStanza,

vmod/tests/debug_c00001.vtc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
varnishtest "Test vmod cli methods / vcl tell"
2+
3+
varnish v1 -vcl+backend {
4+
import debug;
5+
6+
backend proforma none;
7+
8+
sub vcl_init {
9+
new obj0 = debug.obj();
10+
new obj1 = debug.obj("only_argument");
11+
new oo0 = debug.obj_opt();
12+
}
13+
} -start
14+
15+
# vcl2 not found
16+
varnish v1 -clierr "300" "tell vcl2.obj0 a b c"
17+
# No object named objX found
18+
varnish v1 -clierr "300" "tell objX a b c"
19+
# Object oo0 has no cli method
20+
varnish v1 -clierr "300" "tell oo0 a b c"
21+
# Too few parameters
22+
varnish v1 -clierr "104" "tell obj0"
23+
24+
varnish v1 -cliexpect "obj0: a b c" "tell obj0 a b c"
25+
varnish v1 -cliexpect "obj0: a b c" "tell vcl1.obj0 a b c"
26+
varnish v1 -cliexpect "obj1: a b c" "tell obj1 a b c"
27+
28+
varnish v1 -vcl { backend proforma none; }
29+
30+
varnish v1 -cliok "vcl.use vcl2"
31+
varnish v1 -cliok "vcl.state vcl1 cold"
32+
varnish v1 -cliexpect "obj0: a b c" "tell vcl1.obj0 a b c"

vmod/vmod_debug.vcc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ $Method VOID .enum(ENUM { phk, des, kristian, mithrandir, martin })
8181
Testing that enums work as part of object and that the parser isn't
8282
(too) buggy.
8383

84+
$Cli INT cli(STRANDS)
85+
8486
$Method VOID .obj()
8587

8688
Covering the fact that a method can be named like the constructor.

0 commit comments

Comments
 (0)