From bf80616496ca9f6af9a61a05db214165fa83f632 Mon Sep 17 00:00:00 2001 From: "Leigh B. Stoller" <stoller@flux.utah.edu> Date: Mon, 18 Mar 2002 18:55:22 +0000 Subject: [PATCH] First cut at dynamic events. Current status is this: 1. The tevc client can run on either a client node or on users. The command line syntax is like this: Usage: ./tevc [-s server] [-c] event ./tevc [-s server] -e pid/eid time objname event [args ...] time: 'now' or '+seconds' or [[[[yy]mm]dd]HH]MMss Examples: ./tevc -e pid/eid now cbr0 set interval_=0.2 ./tevc -e pid/eid +10 cbr0 start ./tevc -e pid/eid +20 cbr0 stop The -s arg defaults to BOSSNODE when not given. Only root can send TBCONTROL events (-c), but that part of tevc is probably going to be killed off when we convert to sending TBCONTROL via tmcc/tmcd (Rob is working on that). The -e option is required for dynamic events, and is not defaulted yet. 2. There is no real checking of arguments. The event is sent over to the scheduler, which tries to map it into an agent running on a node. If the map fails (say, no such trafgen, or no delay node on that link), or if the event makes no sense for that agent, the event fails and the user never hears about it. The solution for this is a back channel so that the scheduler can generate an error condition that the user sees. 3. To facilitate better checking, I think we need to change the DB tables so that there is a mapping between the type of agent (object) and the kinds of events it can take. This would replace the flat event_eventtypes table. The event_scheduler could load this table at init time so that it can do a quick check on dynamic events when they arrive and return an error when we support error values. 4. The scheduler initialization phase tries to determine what agents are valid for an experiment (thats where you can send events) so that when a user sends an event to cbr3, the event scheduler can determine what/where that is. My method for determining this is totally ad-hoc right now; read the virt_trafgens and delays table. These are the only two types of configurable agents we support. This is rather bogus! I think assign_wrapper or some other script in the front end needs to collect this data into a single table for the scheduler to read. Not sure yet; needs more thought. Anyway, I build a mapping table that can be searched when a dynamic event comes in; this avoids a DB table lookup in the critical path. --- event/lib/event.h | 23 +++- event/sched/event-sched.c | 225 +++++++++++++++++++++++++-------- event/tbgen/tevc.c | 257 ++++++++++++++++++++++++++++++-------- 3 files changed, 397 insertions(+), 108 deletions(-) diff --git a/event/lib/event.h b/event/lib/event.h index 989d8183e4..3fcf08b24e 100644 --- a/event/lib/event.h +++ b/event/lib/event.h @@ -5,7 +5,7 @@ * * @COPYRIGHT@ * - * $Id: event.h,v 1.10 2002-03-05 16:33:41 stoller Exp $ + * $Id: event.h,v 1.11 2002-03-18 18:55:21 stoller Exp $ */ #ifndef __EVENT_H__ @@ -85,6 +85,27 @@ int address_tuple_free(address_tuple_t); #define event_notification_set_arguments(handle, note, buf) \ event_notification_put_string(handle, note, "ARGS", buf) +/* + * For dynamic events. + */ +#define event_notification_clear_host(handle, note) \ + event_notification_remove(handle, note, "HOST") +#define event_notification_set_host(handle, note, buf) \ + event_notification_put_string(handle, note, "HOST", buf) +#define event_notification_clear_objtype(handle, note) \ + event_notification_remove(handle, note, "OBJTYPE") +#define event_notification_set_objtype(handle, note, buf) \ + event_notification_put_string(handle, note, "OBJTYPE", buf) + +/* + * Event library sets this field. Holds the sender of the event, as + * determined by the library when it is initialized. + */ +#define event_notification_get_sender(handle, note, buf, len) \ + event_notification_get_string(handle, note, "___SENDER___", buf, len) +#define event_notification_set_sender(handle, note, buf) \ + event_notification_put_string(handle, note, "___SENDER___", buf) + /* The "any host" string: */ #define EVENT_HOST_ANY "*" diff --git a/event/sched/event-sched.c b/event/sched/event-sched.c index 96c344bff9..396fa870e5 100644 --- a/event/sched/event-sched.c +++ b/event/sched/event-sched.c @@ -35,6 +35,16 @@ static int debug; static void cleanup(void); static void quit(int); +#define MAXAGENTS 100 +static struct { + char nodeid[TBDB_FLEN_NODEID]; + char vnode[TBDB_FLEN_VNAME]; + char objname[TBDB_FLEN_EVOBJNAME]; + char objtype[TBDB_FLEN_EVOBJTYPE]; + char ipaddr[32]; +} agents[MAXAGENTS]; +static int numagents; + void usage() { @@ -51,7 +61,7 @@ main(int argc, char **argv) char *server = NULL; char *port = NULL; char *log = NULL; - char buf[BUFSIZ]; + char pideid[BUFSIZ], buf[BUFSIZ]; int c, count; progname = argv[0]; @@ -85,6 +95,7 @@ main(int argc, char **argv) usage(); pid = argv[0]; eid = argv[1]; + sprintf(pideid, "%s/%s", pid, eid); signal(SIGINT, quit); signal(SIGTERM, quit); @@ -138,6 +149,7 @@ main(int argc, char **argv) fatal("could not allocate an address tuple"); } tuple->scheduler = 1; + tuple->expt = pideid; if (event_subscribe(handle, enqueue, tuple, NULL) == NULL) { fatal("could not subscribe to EVENT_SCHEDULE event"); @@ -183,50 +195,81 @@ main(int argc, char **argv) static void enqueue(event_handle_t handle, event_notification_t notification, void *data) { - sched_event_t event; + sched_event_t event; + char objname[TBDB_FLEN_EVOBJNAME]; + int x; /* Clone the event notification, since we want the notification to live beyond the callback function: */ event.notification = elvin_notification_clone(notification, handle->status); if (!event.notification) { - ERROR("elvin_notification_clone failed: "); - elvin_error_fprintf(stderr, handle->status); - return; + error("elvin_notification_clone failed!\n"); + return; } /* Clear the scheduler flag */ if (! event_notification_remove(handle, event.notification, "SCHEDULER") || ! event_notification_put_int32(handle, event.notification, "SCHEDULER", 0)) { - ERROR("could not clear scheduler attribute of notification %p\n", - event.notification); - return; + error("could not clear scheduler attribute of notification %p\n", + event.notification); + goto bad; } /* Get the event's firing time: */ - - if (event_notification_get_int32(handle, event.notification, "time_sec", - (int *) &event.time.tv_sec) - == 0) - { - ERROR("could not get time.tv_sec attribute from notification %p\n", - event.notification); - return; + if (! event_notification_get_int32(handle, event.notification, "time_usec", + (int *) &event.time.tv_usec) || + ! event_notification_get_int32(handle, event.notification, "time_sec", + (int *) &event.time.tv_sec)) { + error("could not get time from notification %p\n", + event.notification); + goto bad; } - if (event_notification_get_int32(handle, event.notification, "time_usec", - (int *) &event.time.tv_usec) - == 0) - { - ERROR("could not get time.tv_usec attribute from notification %p\n", - event.notification); - return; + /* + * Must map the event to the proper agent running on a particular + * node. + */ + if (! event_notification_get_objname(handle, event.notification, + objname, sizeof(objname))) { + error("could not get object name from notification %p\n", + event.notification); + goto bad; + } + for (x = 0; x < numagents; x++) { + if (!strcmp(agents[x].objname, objname)) + break; + } + if (x == numagents) { + error("Could not map object to an agent: %s\n", objname); + goto bad; + } + event_notification_clear_host(handle, event.notification); + event_notification_set_host(handle, + event.notification, agents[x].ipaddr); + event_notification_clear_objtype(handle, event.notification); + event_notification_set_objtype(handle, + event.notification, agents[x].objtype); + + if (debug > 1) { + struct timeval now; + + gettimeofday(&now, NULL); + + info("Sched: note:%p at:%ld:%d now:%ld:%d agent:%d\n", + event.notification, + event.time.tv_sec, event.time.tv_usec, + now.tv_sec, now.tv_usec, + x); } /* Enqueue the event notification for resending at the indicated time: */ sched_event_enqueue(event); + return; + bad: + event_notification_free(handle, event.notification); } /* Returns the amount of time until EVENT fires. */ @@ -284,12 +327,9 @@ dequeue(event_handle_t handle) } if (debug > 1) { - info("firing event (event=(notification=%p, " - "time=(tv_sec=%ld, tv_usec=%ld)) " - "at time (time=(tv_sec=%ld, tv_usec=%ld))\n", - next_event.notification, - next_event.time.tv_sec, - next_event.time.tv_usec, + info("Fire: note:%p at:%ld:%d now:%ld:%d\n", + next_event.notification, + next_event.time.tv_sec, next_event.time.tv_usec, now.tv_sec, now.tv_usec); } @@ -311,28 +351,101 @@ get_static_events(event_handle_t handle) address_tuple_t tuple; char pideid[BUFSIZ]; event_notification_t notification; + int adx = 0; + + /* + * Build up a table of agents that can receive dynamic events. + * Currently, these are trafgens and delay nodes. We want to + * be able to quickly map from "cbr0" to the node on which it + * lives (for dynamic events). + */ + res = mydb_query("select vi.vname,vi.vnode,r.node_id " + " from virt_trafgens as vi " + "left join reserved as r on " + " r.vname=vi.vnode and r.pid=vi.pid and r.eid=vi.eid " + "where vi.role='source' and " + " vi.pid='%s' and vi.eid='%s'", + 3, pid, eid); + + if (!res) { + error("getting virt_trafgens list for %s/%s", pid, eid); + return 0; + } + nrows = mysql_num_rows(res); + while (nrows--) { + row = mysql_fetch_row(res); + + if (!row[0] || !row[1] || !row[2]) + continue; + + strcpy(agents[numagents].objname, row[0]); + strcpy(agents[numagents].vnode, row[1]); + strcpy(agents[numagents].nodeid, row[2]); + strcpy(agents[numagents].objtype, TBDB_OBJECTTYPE_TRAFGEN); + + if (! mydb_nodeidtoip(row[2], agents[numagents].ipaddr)) + continue; + numagents++; + } + mysql_free_result(res); + + res = mydb_query("select d.vname,r.vname,d.node_id from delays as d " + "left join reserved as r on r.node_id=d.node_id " + "where d.pid='%s' and d.eid='%s'", + 3, pid, eid); - res = mydb_query("select ex.time,ex.vnode,ex.vname,ex.arguments," - " ot.type,et.type,i.IP from eventlist as ex " + if (!res) { + error("getting delays list for %s/%s", pid, eid); + return 0; + } + nrows = mysql_num_rows(res); + while (nrows--) { + row = mysql_fetch_row(res); + + if (!row[0] || !row[1] || !row[2]) + continue; + + strcpy(agents[numagents].objname, row[0]); + strcpy(agents[numagents].vnode, row[1]); + strcpy(agents[numagents].nodeid, row[2]); + strcpy(agents[numagents].objtype, TBDB_OBJECTTYPE_LINK); + + if (! mydb_nodeidtoip(row[2], agents[numagents].ipaddr)) + continue; + numagents++; + } + mysql_free_result(res); + + if (debug) { + for (adx = 0; adx < numagents; adx++) { + info("Agent %d: %10s %10s %10s %8s %16s\n", adx, + agents[adx].objname, + agents[adx].objtype, + agents[adx].vnode, + agents[adx].nodeid, + agents[adx].ipaddr); + } + } + + /* + * Now get the eventlist. There should be entries in the + * agents table for anything we find in the list. + */ + res = mydb_query("select ex.idx,ex.time,ex.vnode,ex.vname," + " ex.arguments,ot.type,et.type from eventlist as ex " "left join event_eventtypes as et on " " ex.eventtype=et.idx " "left join event_objecttypes as ot on " " ex.objecttype=ot.idx " - "left join reserved as r on " - " ex.vnode=r.vname and ex.pid=r.pid and ex.eid=r.eid " - "left join nodes as n on r.node_id=n.node_id " - "left join node_types as nt on nt.type=n.type " - "left join interfaces as i on " - " i.node_id=r.node_id and i.iface=nt.control_iface " "where ex.pid='%s' and ex.eid='%s'", 7, pid, eid); -#define EXTIME row[0] -#define EXVNODE row[1] -#define EXVNAME row[2] -#define EXARGS row[3] -#define OBJTYPE row[4] -#define EVTTYPE row[5] -#define IPADDR row[6] +#define EXIDX row[0] +#define EXTIME row[1] +#define EXVNODE row[2] +#define OBJNAME row[3] +#define EXARGS row[4] +#define OBJTYPE row[5] +#define EVTTYPE row[6] if (!res) { error("getting static event list for %s/%s", pid, eid); @@ -364,19 +477,27 @@ get_static_events(event_handle_t handle) row = mysql_fetch_row(res); firetime = atof(EXTIME); - if (debug) - info("EV: %8s %10s %10s %10s %10s %10s %10s\n", - row[0], row[1], row[2], - row[3] ? row[3] : "", - row[4], row[5], - row[6] ? row[6] : ""); + for (adx = 0; adx < numagents; adx++) { + if (!strcmp(agents[adx].objname, OBJNAME)) + break; + } + if (adx == numagents) { + error("Could not map event index %s", EXIDX); + return 0; + } tuple->expt = pideid; - tuple->host = IPADDR; - tuple->objname = EXVNAME; + tuple->host = agents[adx].ipaddr; + tuple->objname = OBJNAME; tuple->objtype = OBJTYPE; tuple->eventtype = EVTTYPE; + if (debug) + info("%8s %10s %10s %10s %10s %10s %10s\n", + EXTIME, EXVNODE, OBJNAME, OBJTYPE, + EVTTYPE, agents[adx].ipaddr, + EXARGS ? EXARGS : ""); + event.notification = event_notification_alloc(handle, tuple); if (! event.notification) { error("could not allocate notification"); diff --git a/event/tbgen/tevc.c b/event/tbgen/tevc.c index 7d0ce3c3b6..7c0c8ce3d1 100644 --- a/event/tbgen/tevc.c +++ b/event/tbgen/tevc.c @@ -2,30 +2,44 @@ * This is used to send Testbed events (reboot, ready, etc.) to the * testbed event client. It is intended to be wrapped up by a perl * script that will use the tmcc to figure out where the event server - * lives (host/port) to construct the server string. + * lives (host/port) to construct the server string. + * + * Issues: key and pid/eid. + * valid events, names, types. + * valid arguments. + * vname to ipaddr mapping. */ #include <stdio.h> #include <ctype.h> #include <netdb.h> #include <unistd.h> +#include <time.h> +#include <math.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> +#include "config.h" #include "log.h" - +#include "tbdefs.h" #include "event.h" -static char *progname; +static int debug; void -usage() +usage(char *progname) { - fprintf(stderr, - "Usage: %s [-s server] [-p port] [-i ipaddr] <event>\n", - progname); - exit(-1); + fprintf(stderr, + "Usage: %s [-s server] [-c] event\n" + " %s [-s server] -e pid/eid time objname event [args ...]\n" + " time: 'now' or '+seconds' or [[[[yy]mm]dd]HH]MMss\n" + "Examples:\n" + " %s -e pid/eid now cbr0 set interval_=0.2\n" + " %s -e pid/eid +10 cbr0 start\n" + " %s -e pid/eid +20 cbr0 stop\n", + progname, progname, progname, progname, progname); + exit(-1); } int @@ -34,78 +48,83 @@ main(int argc, char **argv) event_handle_t handle; event_notification_t notification; address_tuple_t tuple; + char *progname; char *server = NULL; char *port = NULL; - char *ipaddr = NULL; - char buf[BUFSIZ], ipbuf[BUFSIZ], *bp; + int control = 0; + char *myeid = NULL; + char buf[BUFSIZ], *bp; + char *evtime = NULL, *objname = NULL, *event; int c; progname = argv[0]; - while ((c = getopt(argc, argv, "s:p:i:")) != -1) { + while ((c = getopt(argc, argv, "ds:p:ce:")) != -1) { switch (c) { + case 'd': + debug++; + break; case 's': server = optarg; break; case 'p': port = optarg; break; - case 'i': - ipaddr = optarg; + case 'c': + control = 1; + break; + case 'e': + myeid = optarg; break; default: - usage(); + usage(progname); } } argc -= optind; argv += optind; - if (argc != 1) - usage(); - - loginit(0, 0); + if (control) { + if (geteuid()) + fatal("Only root can send TBCONTROL events"); + + if (argc != 1) + usage(progname); + + event = argv[0]; + argc -= 1; + argv += 1; + } + else { + if (argc < 3) + usage(progname); - /* - * Uppercase event tags for now. Should be wired in list instead. - */ - bp = argv[0]; - while (*bp) { - *bp = toupper(*bp); - bp++; + evtime = argv[0]; + objname = argv[1]; + event = argv[2]; + + argc -= 3; + argv += 3; } + loginit(0, 0); /* - * Get our IP address. Thats how we name ourselves to the - * Testbed Event System. + * If server is not specified, then it defaults to BOSSNODE. + * This allows the client to work on either users.emulab.net + * or on a client node. */ - if (ipaddr == NULL) { - struct hostent *he; - struct in_addr myip; - - if (gethostname(buf, sizeof(buf)) < 0) { - fatal("could not get hostname"); - } - - if (! (he = gethostbyname(buf))) { - fatal("could not get IP address from hostname: %s", buf); - } - memcpy((char *)&myip, he->h_addr, he->h_length); - strcpy(ipbuf, inet_ntoa(myip)); - ipaddr = ipbuf; - } + if (!server) + server = BOSSNODE; /* * Convert server/port to elvin thing. * * XXX This elvin string stuff should be moved down a layer. */ - if (server) { - snprintf(buf, sizeof(buf), "elvin://%s%s%s", - server, - (port ? ":" : ""), - (port ? port : "")); - server = buf; - } + snprintf(buf, sizeof(buf), "elvin://%s%s%s", + server, + (port ? ":" : ""), + (port ? port : "")); + server = buf; /* * Construct an address tuple for generating the event. @@ -114,16 +133,54 @@ main(int argc, char **argv) if (tuple == NULL) { fatal("could not allocate an address tuple"); } - tuple->objtype = OBJECTTYPE_TESTBED; - tuple->eventtype= argv[0]; - tuple->host = ipaddr; - + /* Register with the event system: */ handle = event_register(server, 0); if (handle == NULL) { fatal("could not register with event system"); } + if (control) { + /* + * Send a control event. + */ + bp = event; + while (*bp) { + *bp = toupper(*bp); + bp++; + } + if (! tbdb_valideventtype(event)) + fatal("Unknown %s event: %s", + OBJECTTYPE_TESTBED, event); + + tuple->objtype = OBJECTTYPE_TESTBED; + tuple->eventtype= event; + } + else { + /* + * A dynamic event. + */ + if (! myeid) + fatal("Must provide pid/eid"); + + bp = event; + while (*bp) { + *bp = toupper(*bp); + bp++; + } + if (!strcmp(event, "SET")) + event = TBDB_EVENTTYPE_MODIFY; + else if (! tbdb_valideventtype(event)) + fatal("Unknown event: %s", event); + + tuple->objname = objname; + tuple->eventtype = event; + tuple->expt = myeid; + tuple->site = ADDRESSTUPLE_ANY; + tuple->host = ADDRESSTUPLE_ANY; + tuple->group = ADDRESSTUPLE_ANY; + } + /* Generate the event */ notification = event_notification_alloc(handle, tuple); @@ -131,8 +188,98 @@ main(int argc, char **argv) fatal("could not allocate notification"); } - if (event_notify(handle, notification) == 0) { - fatal("could not send test event notification"); + /* + * If there are any extra arguments, insert them into + * the notification as an arg string. + * + * XXX For now, uppercase the strings, and remove trailing _. + */ + if (argc) { + buf[0] = NULL; + while (argc) { + if (strlen(*argv) + strlen(buf) >= sizeof(buf)-2) + fatal("Too many event argument strings!"); + + bp = *argv; + while (*bp && *bp != '=') { + *bp = toupper(*bp); + bp++; + } + if (*bp != '=') + fatal("Malformed argument: %s!", *argv); + if (*(bp-1) == '_') + *(bp-1) = NULL; + *bp++ = NULL; + + sprintf(&buf[strlen(buf)], "%s=%s ", *argv, bp); + argc--; + argv++; + } + event_notification_set_arguments(handle, notification, buf); + } + + if (control) { + if (event_notify(handle, notification) == 0) { + fatal("could not send test event notification"); + } + } + else { + struct timeval when; + + /* + * Parse the time. Now is special; set the time to 0. + */ + if (!strcmp(evtime, "now")) { + gettimeofday(&when, NULL); + } + else if (evtime[0] == '+') { + gettimeofday(&when, NULL); + + if (strchr(evtime, '.')) { + double val = atof(evtime); + + when.tv_sec += (int) rint(val); + when.tv_usec += + (int) (1000000 * (val - rint(val))); + + if (when.tv_usec > 1000000) { + when.tv_sec += 1; + when.tv_usec -= 1000000; + } + } + else + when.tv_sec += atoi(evtime); + } + else { + char *format = "%y%m%d%H%M%S"; + int len = strlen(evtime); + struct tm tm; + + if ((len & 1) || (len > strlen(format)) || (len < 4)) + usage(progname); + format += strlen(format) - len; + + gettimeofday(&when, NULL); + localtime_r(&when.tv_sec, &tm); + if (!strptime(evtime, format, &tm)) + usage(progname); + + when.tv_sec = mktime(&tm); + when.tv_usec = 0; + } + + if (debug) { + struct timeval now; + + gettimeofday(&now, NULL); + + info("Now: %ld:%d\n", now.tv_sec, now.tv_usec); + info("When: %ld:%d\n", when.tv_sec, when.tv_usec); + } + + if (event_schedule(handle, notification, &when) == 0) { + fatal("could not send test event notification"); + } } event_notification_free(handle, notification); -- GitLab