Skip to content

Commit 0f53a0b

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 5ea133b commit 0f53a0b

File tree

11 files changed

+254
-5
lines changed

11 files changed

+254
-5
lines changed

bin/varnishd/cache/cache_vcl.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,76 @@ vcl_cli_show(struct cli *cli, const char * const *av, void *priv)
988988
}
989989
}
990990

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

9931063
static struct cli_proto vcl_cmds[] = {
@@ -998,6 +1068,7 @@ static struct cli_proto vcl_cmds[] = {
9981068
{ CLICMD_VCL_USE, "", vcl_cli_use },
9991069
{ CLICMD_VCL_SHOW, "", vcl_cli_show },
10001070
{ CLICMD_VCL_LABEL, "", vcl_cli_label },
1071+
{ CLICMD_TELL, "", vcl_cli_tell},
10011072
{ NULL }
10021073
};
10031074

bin/varnishd/cache/cache_vpi.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,56 @@ VPI_Call_End(VRT_CTX, unsigned n)
186186
AN(vbm);
187187
vbit_clr(vbm, n);
188188
}
189+
190+
/*--------------------------------------------------------------------
191+
* tell interface.
192+
*
193+
* we all agree it does not quite fit the purpose of VPI, but it fits here
194+
* better than anywhere else
195+
*
196+
* XXX: Replace instance info with a tree indexed by name
197+
*/
198+
199+
int
200+
VPI_Tell(VRT_CTX, VCL_STRING objname, VCL_STRANDS msg)
201+
{
202+
struct vcl *vcl;
203+
const struct VCL_conf *conf;
204+
const struct vpi_ii *ii;
205+
vmod_cli_f *cli;
206+
207+
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
208+
AN(objname);
209+
210+
ASSERT_CLI();
211+
AZ(ctx->method);
212+
AN(ctx->msg);
213+
214+
vcl = ctx->vcl;
215+
CHECK_OBJ_NOTNULL(vcl, VCL_MAGIC);
216+
217+
conf = vcl->conf;
218+
CHECK_OBJ_NOTNULL(conf, VCL_CONF_MAGIC);
219+
220+
ii = conf->instance_info;
221+
AN(ii);
222+
while (ii->p != NULL) {
223+
if (! strcmp(ii->name, objname))
224+
break;
225+
ii++;
226+
}
227+
228+
if (ii->p == NULL) {
229+
VSB_printf(ctx->msg, "No object named %s found\n", objname);
230+
return (300);
231+
}
232+
if (ii->clip == NULL) {
233+
VSB_printf(ctx->msg, "Object %s has no cli method\n", objname);
234+
return (300);
235+
}
236+
237+
cli = (void *)*ii->clip;
238+
AN(cli);
239+
AN(*ii->p);
240+
return(cli(ctx, (void *)*ii->p, msg));
241+
}

include/tbl/cli_cmds.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,16 @@ CLI_CMD(PID,
435435
0, 0
436436
)
437437

438+
CLI_CMD(TELL,
439+
"tell",
440+
"tell [<vcl>.]<object> <msg> ...",
441+
"Tell <msg> to <object> from the given <vcl> or the active vcl",
442+
" It is entirely up to the cli method implementation of the"
443+
" respective vmod to interpret <msg>, implement any actions"
444+
" and generate a response",
445+
2, -1
446+
)
447+
438448
#undef CLI_CMD
439449

440450
/*lint -restore */

include/tbl/symbol_kind.h

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

5152
/*lint -restore */

include/vcc_interface.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ void VPI_acl_log(VRT_CTX, const char *);
7777
struct vpi_ii {
7878
uintptr_t * p;
7979
const char * const name;
80+
uintptr_t * clip;
8081
};
8182

8283
/* Compile time regexp */
@@ -102,3 +103,7 @@ enum vcl_func_fail_e VPI_Call_Check(VRT_CTX, const struct VCL_conf *conf,
102103
unsigned methods, unsigned n);
103104
void VPI_Call_Begin(VRT_CTX, unsigned n);
104105
void VPI_Call_End(VRT_CTX, unsigned n);
106+
107+
// return value should be a VCLI_status_e
108+
typedef VCL_INT vmod_cli_f(VRT_CTX, void *, VCL_STRANDS);
109+
int VPI_Tell(VRT_CTX, VCL_STRING, VCL_STRANDS);

lib/libvcc/vcc_vmod.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,13 @@ vcc_path_dlopen(void *priv, const char *fn)
7474
}
7575

7676
static void vcc_VmodObject(struct vcc *tl, struct symbol *sym);
77-
static void vcc_VmodSymbols(struct vcc *tl, const struct symbol *sym);
77+
static void vcc_VmodSymbols(struct vcc *tl, struct symbol *sym);
7878

7979
static void
80-
func_sym(struct vcc *tl, vcc_kind_t kind, const struct symbol *psym,
80+
func_sym(struct vcc *tl, vcc_kind_t kind, struct symbol *psym,
8181
const struct vjsn_val *v)
8282
{
83+
const struct vjsn_val *vv;
8384
struct symbol *sym;
8485
struct vsb *buf;
8586

@@ -115,11 +116,18 @@ func_sym(struct vcc *tl, vcc_kind_t kind, const struct symbol *psym,
115116
sym->eval_priv = v;
116117
v = VTAILQ_FIRST(&v->children);
117118
assert(vjsn_is_array(v));
119+
vv = v;
118120
v = VTAILQ_FIRST(&v->children);
119121
assert(vjsn_is_string(v));
120122
sym->type = VCC_Type(v->value);
121123
AN(sym->type);
122124
sym->r_methods = VCL_MET_TASK_ALL;
125+
if (kind == SYM_CLI_METHOD) {
126+
vv = VTAILQ_NEXT(vv, list);
127+
assert(vjsn_is_string(vv));
128+
AZ(psym->extra);
129+
psym->extra = vv->value;
130+
}
123131
}
124132

125133
static void
@@ -237,6 +245,7 @@ vcc_vmod_kind(const char *type)
237245
VMOD_KIND("$OBJ", SYM_OBJECT);
238246
VMOD_KIND("$METHOD", SYM_METHOD);
239247
VMOD_KIND("$FUNC", SYM_FUNC);
248+
VMOD_KIND("$CLI", SYM_CLI_METHOD);
240249
#undef VMOD_KIND
241250
return (SYM_NONE);
242251
}
@@ -265,7 +274,7 @@ vcc_VmodObject(struct vcc *tl, struct symbol *sym)
265274
}
266275

267276
static void
268-
vcc_VmodSymbols(struct vcc *tl, const struct symbol *sym)
277+
vcc_VmodSymbols(struct vcc *tl, struct symbol *sym)
269278
{
270279
const struct vjsn *vj;
271280
const struct vjsn_val *vv, *vv1, *vv2;
@@ -490,6 +499,7 @@ vcc_Act_New(struct vcc *tl, struct token *t, struct symbol *sym)
490499

491500
/* Scratch the generic INSTANCE type */
492501
isym->type = osym->type;
502+
isym->extra = osym->extra;
493503

494504
CAST_OBJ_NOTNULL(vv, osym->eval_priv, VJSN_VAL_MAGIC);
495505
// vv = object name

lib/libvcc/vcc_xref.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,15 +410,20 @@ vcc_instance_info(struct vcc *tl, const struct symbol *sym)
410410
AN(sym->rname);
411411
Fc(tl, 0, "\t{ .p = (uintptr_t *)&%s, .name = \"", sym->rname);
412412
VCC_SymName(tl->fc, sym);
413-
Fc(tl, 0, "\" },\n");
413+
Fc(tl, 0, "\", .clip = ");
414+
if (sym->extra)
415+
Fc(tl, 0, "(uintptr_t *)&%s", sym->extra);
416+
else
417+
Fc(tl, 0, "NULL");
418+
Fc(tl, 0, "},\n");
414419
}
415420

416421
void
417422
VCC_InstanceInfo(struct vcc *tl)
418423
{
419424
Fc(tl, 0, "\nstatic const struct vpi_ii VGC_instance_info[] = {\n");
420425
VCC_WalkSymbols(tl, vcc_instance_info, SYM_MAIN, SYM_INSTANCE);
421-
Fc(tl, 0, "\t{ .p = NULL, .name = \"\" }\n");
426+
Fc(tl, 0, "\t{ .p = NULL, .name = \"\", .clip = NULL }\n");
422427
Fc(tl, 0, "};\n");
423428
}
424429

lib/libvcc/vmodtool.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,7 @@ def parse(self):
744744
self.rstlbl = '%s.%s()' % (self.vcc.modname, self.proto.name)
745745
self.vcc.contents.append(self)
746746
self.methods = []
747+
self.cli = None
747748

748749
def rsthead(self, fo, man):
749750
if self.rstlbl:
@@ -777,11 +778,15 @@ def cstuff(self, fo, w):
777778
fo.write(self.fini.cproto(['struct %s **' % sn], w))
778779
for i in self.methods:
779780
fo.write(i.proto.cproto(['VRT_CTX', 'struct %s *' % sn], w))
781+
if self.cli is not None:
782+
fo.write(self.cli.proto.cproto(['VRT_CTX', 'struct %s *' % sn], w))
780783
fo.write("\n")
781784

782785
def cstruct(self, fo, define):
783786
self.fmt_cstruct_proto(fo, self.init, define)
784787
self.fmt_cstruct_proto(fo, self.fini, define)
788+
if self.cli is not None:
789+
self.cli.cstruct(fo, define)
785790
for i in self.methods:
786791
i.cstruct(fo, define)
787792
fo.write("\n")
@@ -803,6 +808,9 @@ def json(self, jl):
803808
ll.append(l2)
804809
self.fini.jsonproto(l2, self.fini.name)
805810

811+
if self.cli is not None:
812+
self.cli.json(ll)
813+
806814
for i in self.methods:
807815
i.json(ll)
808816

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

837845

846+
#######################################################################
847+
848+
849+
class CliStanza(Stanza):
850+
851+
''' $Cli INT name (STRANDS) '''
852+
853+
def parse(self):
854+
p = self.vcc.contents[-1]
855+
assert isinstance(p, ObjectStanza)
856+
self.pfx = p.proto.name
857+
self.proto = ProtoType(self, prefix=self.pfx)
858+
if p.cli is not None:
859+
err("$Cli %s.%s: Cli method already defined for this class"
860+
% (self.pfx, self.proto.bname), warn=False)
861+
if self.proto.retval.vt != "INT":
862+
err("$Cli %s.%s: Return value needs to be INT"
863+
% (self.pfx, self.proto.bname), warn=False)
864+
if len(self.proto.args) != 1 or self.proto.args[0].vt != 'STRANDS':
865+
err("$Cli %s.%s: Need single argument of type STRANDS"
866+
% (self.pfx, self.proto.bname), warn=False)
867+
# self.proto.obj = "x" + self.pfx
868+
# self.rstlbl = 'x%s()' % self.proto.name
869+
p.cli = self
870+
871+
def cstruct(self, fo, define):
872+
self.fmt_cstruct_proto(fo, self.proto, define)
873+
874+
def json(self, jl):
875+
jl.append(["$CLI", self.proto.name])
876+
self.proto.jsonproto(jl[-1], self.proto.cname())
877+
878+
838879
#######################################################################
839880

840881
DISPATCH = {
@@ -845,6 +886,7 @@ def json(self, jl):
845886
"Function": FunctionStanza,
846887
"Object": ObjectStanza,
847888
"Method": MethodStanza,
889+
"Cli": CliStanza,
848890
"Synopsis": SynopsisStanza,
849891
}
850892

vmod/tests/debug_c00001.vtc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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"

vmod/vmod_debug.vcc

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

81+
$Cli INT cli(STRANDS)
82+
8183
$Method VOID .obj()
8284

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

0 commit comments

Comments
 (0)