Skip to content

Commit 9734f89

Browse files
committed
Add tell/ear interface for CLI access to vmod objects
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
1 parent 0b84355 commit 9734f89

File tree

11 files changed

+278
-5
lines changed

11 files changed

+278
-5
lines changed

bin/varnishd/cache/cache_vcl.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,83 @@ 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 *errvsb;
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, cli->sb);
1052+
1053+
errvsb = VCL_Rel_CliCtx(&ctx);
1054+
1055+
if (i == 0) {
1056+
VCLI_SetResult(cli, CLIS_CANT);
1057+
VCLI_Out(cli, "%s", VSB_data(errvsb));
1058+
VSB_destroy(&errvsb);
1059+
return;
1060+
}
1061+
1062+
VSB_destroy(&errvsb);
1063+
1064+
// truncate check required?
1065+
VCLI_SetResult(cli, CLIS_OK);
1066+
}
1067+
9911068
/*--------------------------------------------------------------------*/
9921069

9931070
static struct cli_proto vcl_cmds[] = {
@@ -998,6 +1075,7 @@ static struct cli_proto vcl_cmds[] = {
9981075
{ CLICMD_VCL_USE, "", vcl_cli_use },
9991076
{ CLICMD_VCL_SHOW, "", vcl_cli_show },
10001077
{ CLICMD_VCL_LABEL, "", vcl_cli_label },
1078+
{ CLICMD_TELL, "", vcl_cli_tell},
10011079
{ NULL }
10021080
};
10031081

bin/varnishd/cache/cache_vpi.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,66 @@ 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, struct vsb *respvsb)
201+
{
202+
struct vcl *vcl;
203+
const struct VCL_conf *conf;
204+
const struct vpi_ii *ii;
205+
VCL_STRANDS resp;
206+
vpi_ear_f *ear;
207+
int i;
208+
209+
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
210+
AN(objname);
211+
212+
ASSERT_CLI();
213+
AZ(ctx->method);
214+
AN(ctx->msg);
215+
216+
vcl = ctx->vcl;
217+
CHECK_OBJ_NOTNULL(vcl, VCL_MAGIC);
218+
219+
conf = vcl->conf;
220+
CHECK_OBJ_NOTNULL(conf, VCL_CONF_MAGIC);
221+
222+
ii = conf->instance_info;
223+
AN(ii);
224+
while (ii->p != NULL) {
225+
if (! strcmp(ii->name, objname))
226+
break;
227+
ii++;
228+
}
229+
230+
if (ii->p == NULL) {
231+
VSB_printf(ctx->msg, "No object named %s found\n", objname);
232+
return (0);
233+
}
234+
if (ii->earp == NULL) {
235+
VSB_printf(ctx->msg, "Object %s has no ear\n", objname);
236+
return (0);
237+
}
238+
239+
ear = (void *)*ii->earp;
240+
AN(ear);
241+
AN(*ii->p);
242+
resp = ear(ctx, (void *)*ii->p, msg);
243+
244+
if (resp == NULL)
245+
return (0);
246+
247+
for (i = 0; i < resp->n; i++)
248+
VSB_cat(respvsb, resp->p[i]);
249+
250+
return (1);
251+
}

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 ear implementation of the respective"
443+
" vmod to interpret <msg>, implement any actions and generate"
444+
" 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(EAR, ear)
4950
#undef VCC_KIND
5051

5152
/*lint -restore */

include/vcc_interface.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ typedef int acl_match_f(VRT_CTX, const VCL_IP);
6464
int VPI_acl_table(VRT_CTX, VCL_IP, unsigned n, unsigned m, const uint8_t *tbl,
6565
const char * const *str, const char *fin);
6666

67+
typedef VCL_STRANDS vpi_ear_f(VRT_CTX, void *, VCL_STRANDS);
68+
6769
struct vrt_acl {
6870
unsigned magic;
6971
#define VRT_ACL_MAGIC 0x78329d96
@@ -77,6 +79,7 @@ void VPI_acl_log(VRT_CTX, const char *);
7779
struct vpi_ii {
7880
uintptr_t * p;
7981
const char * const name;
82+
uintptr_t * earp;
8083
};
8184

8285
/* Compile time regexp */
@@ -102,3 +105,4 @@ enum vcl_func_fail_e VPI_Call_Check(VRT_CTX, const struct VCL_conf *conf,
102105
unsigned methods, unsigned n);
103106
void VPI_Call_Begin(VRT_CTX, unsigned n);
104107
void VPI_Call_End(VRT_CTX, unsigned n);
108+
int VPI_Tell(VRT_CTX, VCL_STRING, VCL_STRANDS, struct vsb *);

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_EAR) {
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("$EAR", SYM_EAR);
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, "\", .earp = ");
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 = \"\", .earp = 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.ear = 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.ear is not None:
782+
fo.write(self.ear.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.ear is not None:
789+
self.ear.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.ear is not None:
812+
self.ear.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 EarStanza(Stanza):
850+
851+
''' $Ear STRANDS 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.ear is not None:
859+
err("$Ear %s.%s: Ear already defined for this class"
860+
% (self.pfx, self.proto.bname), warn=False)
861+
if self.proto.retval.vt != "STRANDS":
862+
err("$Ear %s.%s: Return value needs to be STRANDS"
863+
% (self.pfx, self.proto.bname), warn=False)
864+
if len(self.proto.args) != 1 or self.proto.args[0].vt != 'STRANDS':
865+
err("$Ear %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.ear = 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(["$EAR", 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+
"Ear": EarStanza,
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 ears / 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 ear
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"

0 commit comments

Comments
 (0)