Skip to content

Commit dd9b8af

Browse files
author
Mark Stapp
committed
lib: initial support of yield/resume in vtysh
Support YIELD operation for long-running cli operations. A new api allows a daemon to supply a callback and context pointer; the vty lib will reschedule and call through to the daemon allowing it to resume a long-running show operation. The vty lib avoids new reads until the daemon is done. Signed-off-by: Mark Stapp <mjs@cisco.com>
1 parent c0427c6 commit dd9b8af

3 files changed

Lines changed: 159 additions & 12 deletions

File tree

lib/command.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ struct cmd_node {
246246
#define CMD_NOT_MY_INSTANCE 14
247247
#define CMD_NO_LEVEL_UP 15
248248
#define CMD_ERR_NO_DAEMON 16
249+
#define CMD_YIELD 17 /* Command has yielded but is not complete yet */
249250

250251
/* Argc max counts. */
251252
#define CMD_ARGC_MAX 256

lib/vty.c

Lines changed: 119 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ int (*nb_cli_apply_changes_mgmt_cb)(struct vty *vty, const char *xpath_base_abs)
8383
int (*nb_cli_rpc_mgmt_cb)(struct vty *vty, const char *xpath, const struct lyd_node *input);
8484

8585

86+
/* Master of the tasks. */
87+
static struct event_loop *vty_master;
88+
8689
PREDECL_DLIST(vtyservs);
8790

8891
struct vty_serv {
@@ -139,22 +142,27 @@ void vty_resume_response(struct vty *vty, int ret)
139142
uint8_t header[4] = {0, 0, 0, 0};
140143

141144
if (vty->type != VTY_FILE) {
145+
/* Send end-of-output marker */
142146
header[3] = ret;
143147
buffer_put(vty->obuf, header, 4);
144-
if (!vty->t_write && (vtysh_flush(vty) < 0)) {
145-
zlog_err("failed to vtysh_flush");
146-
/* Try to flush results; exit if a write error occurs */
147-
return;
148+
149+
/* Try to flush results; exit if a write error occurs */
150+
if (vty->t_write == NULL) {
151+
if (vtysh_flush(vty) < 0) {
152+
zlog_err("failed to vtysh_flush");
153+
return;
154+
}
148155
}
149156
}
150157

158+
/* Resume reading if possible */
151159
if (vty->status == VTY_CLOSE)
152160
vty_close(vty);
153161
else if (vty->type != VTY_FILE)
154162
vty_event(VTYSH_READ, vty);
155163
else
156164
/* should we assert here? */
157-
zlog_err("mgmtd: unexpected resume while reading config file");
165+
zlog_err("vty: unexpected resume while reading config file");
158166
}
159167

160168
void vty_frame(struct vty *vty, const char *format, ...)
@@ -175,6 +183,86 @@ void vty_endframe(struct vty *vty, const char *endtext)
175183
vty->frame_pos = 0;
176184
}
177185

186+
/*
187+
* Callback function for yield/resume; call through to application's
188+
* callback.
189+
*/
190+
static void yield_resume_cb(struct event *event)
191+
{
192+
struct vty *vty = EVENT_ARG(event);
193+
struct vty_yield_resume_s ctx;
194+
195+
/* Capture application callback info */
196+
ctx = vty->yield_resume;
197+
198+
/* Clear vty's yield callback info before calling into the application:
199+
* the application may call the "finish" or the "yield" api, so we can't
200+
* make any assumptions about the state of the 'vty' info after calling in.
201+
*/
202+
vty->yield_resume.app_cb = NULL;
203+
vty->yield_resume.arg = NULL;
204+
205+
/* Call through to application */
206+
ctx.app_cb(vty, ctx.arg);
207+
}
208+
209+
/*
210+
* Application process wants to yield while sending results/replies.
211+
* We'll schedule a task to resume, and call the application's callback.
212+
*/
213+
bool vty_yield(struct vty *vty, void (*func)(struct vty *vty, void *arg),
214+
void *arg)
215+
{
216+
vty->yield_resume.app_cb = func;
217+
vty->yield_resume.arg = arg;
218+
219+
event_add_event(vty_master, yield_resume_cb, vty, 0,
220+
&vty->yield_resume.t_resume);
221+
222+
/* Ensure reading new input is disabled */
223+
event_cancel(&vty->t_read);
224+
225+
/* Try to send buffered output */
226+
if (vty->type == VTY_SHELL_SERV)
227+
vtysh_flush(vty);
228+
else
229+
vty_event(VTY_WRITE, vty);
230+
231+
return true;
232+
}
233+
234+
/*
235+
*
236+
*/
237+
static void yield_finish_internal(struct vty *vty)
238+
{
239+
event_cancel(&(vty->yield_resume.t_resume));
240+
241+
/* Clear vty's yield/resume callback info */
242+
vty->yield_resume.app_cb = NULL;
243+
vty->yield_resume.arg = NULL;
244+
}
245+
246+
/*
247+
* Yield/resume is complete; cancel any scheduled resume task, and return
248+
* to normal vty operation (turn on reads, e.g.)
249+
*/
250+
void vty_yield_finish(struct vty *vty, int retcode)
251+
{
252+
if (vty->status != VTY_CLOSE) {
253+
if (vty->type == VTY_SHELL_SERV) {
254+
255+
/* Send end-of-output marker and resume normal processing */
256+
vty_resume_response(vty, retcode);
257+
vty_event(VTYSH_READ, vty);
258+
} else {
259+
vty_event(VTY_READ, vty);
260+
}
261+
262+
yield_finish_internal(vty);
263+
}
264+
}
265+
178266
bool vty_set_include(struct vty *vty, const char *regexp)
179267
{
180268
int errcode;
@@ -1618,7 +1706,7 @@ static void vty_flush(struct event *event)
16181706
buffer_status_t flushrc;
16191707
struct vty *vty = EVENT_ARG(event);
16201708

1621-
/* Tempolary disable read event. */
1709+
/* Temporarily disable reads. */
16221710
if (vty->lines == 0)
16231711
event_cancel(&vty->t_read);
16241712

@@ -2300,10 +2388,11 @@ static void vtysh_read(struct event *event)
23002388
if (*p == '\0') {
23012389
/* Pass this line to parser. */
23022390
ret = vty_execute(vty);
2303-
/* Note that vty_execute clears the command buffer and resets
2304-
vty->length to 0. */
2391+
/* Note that vty_execute clears the command buffer and
2392+
* resets vty->length to 0.
2393+
*/
23052394

2306-
/* Return result. */
2395+
/* Return result. */
23072396
#ifdef VTYSH_DEBUG
23082397
printf("result: %d\n", ret);
23092398
printf("vtysh node: %d\n", vty->node);
@@ -2352,6 +2441,12 @@ static void vtysh_read(struct event *event)
23522441
return;
23532442
}
23542443

2444+
/* If the application has asked to yield during
2445+
* processing, don't continue reading.
2446+
*/
2447+
if (event_is_scheduled(vty->yield_resume.t_resume))
2448+
break;
2449+
23552450
/* warning: watchfrr hardcodes this result write
23562451
*/
23572452
header[3] = ret;
@@ -2369,6 +2464,12 @@ static void vtysh_read(struct event *event)
23692464
}
23702465
}
23712466

2467+
/* If the application has asked to yield during
2468+
* processing, don't continue reading.
2469+
*/
2470+
if (event_is_scheduled(vty->yield_resume.t_resume))
2471+
return;
2472+
23722473
if (vty->status == VTY_CLOSE)
23732474
vty_close(vty);
23742475
else
@@ -2428,6 +2529,14 @@ void vty_close(struct vty *vty)
24282529

24292530
vty->status = VTY_CLOSE;
24302531

2532+
/* If application was doing yield/resume, notify it by calling
2533+
* its callback with a NULL vty argument.
2534+
*/
2535+
if (vty->yield_resume.app_cb) {
2536+
(vty->yield_resume.app_cb)(NULL, vty->yield_resume.arg);
2537+
yield_finish_internal(vty);
2538+
}
2539+
24312540
/*
24322541
* If we reach here with pending config to commit we will be losing it
24332542
* so warn the user.
@@ -2753,6 +2862,7 @@ FILE *vty_open_config(const char *config_file, char *config_default_dir)
27532862
}
27542863
#endif /* VTYSH */
27552864
confp = fopen(config_default_dir, "r");
2865+
27562866
if (confp == NULL) {
27572867
flog_err(
27582868
EC_LIB_SYSTEM_CALL,
@@ -2911,9 +3021,6 @@ int vty_config_node_exit(struct vty *vty)
29113021
return 1;
29123022
}
29133023

2914-
/* Master of the threads. */
2915-
static struct event_loop *vty_master;
2916-
29173024
static void vty_event_serv(enum vty_event event, struct vty_serv *vty_serv)
29183025
{
29193026
switch (event) {

lib/vty.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ enum vty_status {
5454

5555
PREDECL_DLIST(vtys);
5656

57+
/* Data for yield/resume of large show outputs. Application handlers can
58+
* call the yield api before returning; this will schedule the resume callback.
59+
* When the handler returns, the vty library code will try to avoid reading
60+
* any new input.
61+
* When the application is done with the yield/resume cycles, it calls the finish
62+
* api, and resumes normal processing.
63+
*/
64+
struct vty_yield_resume_s {
65+
/* Application callback and argument */
66+
void *arg;
67+
void (*app_cb)(struct vty *vty, void *arg);
68+
69+
/* Event to schedule the resume callback */
70+
struct event *t_resume;
71+
};
72+
5773
/* VTY struct. */
5874
struct vty {
5975
struct vtys_item itm;
@@ -231,13 +247,17 @@ struct vty {
231247
uintptr_t mgmt_req_pending_data;
232248
bool mgmt_locked_candidate_ds;
233249
bool mgmt_locked_running_ds;
250+
234251
/* Incremental write/flush limit and current accumulator. When
235252
* producing large outputs, we try to avoid buffering the entire
236253
* output, sending incremental output periodically
237254
* as the application code produces it.
238255
*/
239256
size_t vty_buf_threshold;
240257
size_t vty_buf_size_accum;
258+
259+
/* Data for yield/resume of large show outputs */
260+
struct vty_yield_resume_s yield_resume;
241261
};
242262

243263
static inline void vty_push_context(struct vty *vty, int node, uint64_t id)
@@ -449,6 +469,25 @@ extern void (*vty_config_node_exit_mgmt_cb)(struct vty *vty);
449469
extern int (*nb_cli_apply_changes_mgmt_cb)(struct vty *vty, const char *xpath_base_abs);
450470
extern int (*nb_cli_rpc_mgmt_cb)(struct vty *vty, const char *xpath, const struct lyd_node *input);
451471

472+
/*
473+
* Application process wants to yield while sending results/replies.
474+
* We'll schedule a task to resume, and call the application's callback
475+
* with the 'vty' and its 'arg'. We won't restart reads on the vty until
476+
* the application tells us to.
477+
* If the vty is being closed or deleted, we'll call the callback with a NULL
478+
* 'vty', so the application can clean up, free memory, if necessary.
479+
*/
480+
bool vty_yield(struct vty *vty, void (*func)(struct vty *vty, void *arg),
481+
void *arg);
482+
483+
/*
484+
* Yield/resume is complete; cancel any scheduled resume task, and return
485+
* to normal vty operation (turn on reads, e.g.). For clients of vtysh,
486+
* send 'retcode' to signal that output is complete.
487+
* Called from the application, so we expect the application to have
488+
* cleaned-up any context, memory, etc.
489+
*/
490+
void vty_yield_finish(struct vty *vty, int retcode);
452491

453492
#ifdef __cplusplus
454493
}

0 commit comments

Comments
 (0)