Commit ed964507 authored by Leigh B. Stoller's avatar Leigh B. Stoller

Overview: Add Event Groups:

	set g1 [new EventGroup $ns]
	$g1 add  $link0 $link1
	$ns at 60.0 "$g1 down"

See the new advanced tutorial section on event groups for a better
example.

Changed tbreport to dump the event groups table when in summary mode.
At the same time, I changed tbreport to use the recently added
virt_lans:vnode and ip slots, decprecating virt_nodes:ips in one more
place. I also changed the web interface to always dump the event and
event group summaries.

The parser gets a new file (event.tcl), and the "at" method deals with
event group events by expanding them inline into individual events
sent to each member. For some agents, this is unavoidable; traffic
generators get the initial params in the event, so it is not possible
to send a single event to all members of the group. Same goes for
program objects, although program objects do default to the initial
command now, at least on new images.

Changed the event scheduler to load the event groups table. The
current operation is that the scheduler expands events sent to a
group, into a set of distinct events sent to each member of the
group. At some point we proably want to optimize this by telling the
agents (running on the nodes) what groups they are members of.

Other News: Added a "mustdelay" slot to the virt_lans table so the
parser can tell assign_wrapper that a link needs to be delayed, say if
there are events or if the link is red/gred. Previously,
assign_wrapper tried to figure this out by looking at the event list,
etc. I have removed that code; see database-migrate for instructions
on how to initialize this slot in existing experiments. assign_wrapper
is free to ignore or insert delays anyway, but having the parser do
this makes more sense.

I also made some "rename" changes to the parser wrt queues and lans
and links. Not really necessary, but I got sidetracked (for several
hours!) trying to understand that rename stuff a little better, and
now I do.
parent c6c4a847
...@@ -2733,6 +2733,7 @@ sub TBGetSiteVar($) ...@@ -2733,6 +2733,7 @@ sub TBGetSiteVar($)
"virt_simnode_attributes", "virt_simnode_attributes",
"nseconfigs", "nseconfigs",
"eventlist", "eventlist",
"event_groups",
"ipsubnets", "ipsubnets",
"nsfiles"); "nsfiles");
......
...@@ -106,8 +106,11 @@ my %virtual_tables = ...@@ -106,8 +106,11 @@ my %virtual_tables =
"eventlist" => { rows => undef, "eventlist" => { rows => undef,
tag => "events", tag => "events",
row => "event", row => "event",
attrs => [ "vname" ]}); attrs => [ "vname" ]},
"event_groups" => { rows => undef,
tag => "event_groups",
row => "event_group",
attrs => [ "group_name", "agent-name" ]});
# XXX # XXX
# The experiment table is special. Only certain fields are allowed to # The experiment table is special. Only certain fields are allowed to
......
...@@ -108,6 +108,10 @@ int address_tuple_free(address_tuple_t); ...@@ -108,6 +108,10 @@ int address_tuple_free(address_tuple_t);
event_notification_remove(handle, note, "OBJTYPE") event_notification_remove(handle, note, "OBJTYPE")
#define event_notification_set_objtype(handle, note, buf) \ #define event_notification_set_objtype(handle, note, buf) \
event_notification_put_string(handle, note, "OBJTYPE", buf) event_notification_put_string(handle, note, "OBJTYPE", buf)
#define event_notification_clear_objname(handle, note) \
event_notification_remove(handle, note, "OBJNAME")
#define event_notification_set_objname(handle, note, buf) \
event_notification_put_string(handle, note, "OBJNAME", buf)
/* /*
* Event library sets this field. Holds the sender of the event, as * Event library sets this field. Holds the sender of the event, as
......
/* /*
* EMULAB-COPYRIGHT * EMULAB-COPYRIGHT
* Copyright (c) 2000-2003 University of Utah and the Flux Group. * Copyright (c) 2000-2004 University of Utah and the Flux Group.
* All rights reserved. * All rights reserved.
*/ */
...@@ -42,6 +42,7 @@ static void cleanup(void); ...@@ -42,6 +42,7 @@ static void cleanup(void);
static void quit(int); static void quit(int);
struct agent { struct agent {
char name[TBDB_FLEN_EVOBJNAME]; /* Agent *or* group name */
char nodeid[TBDB_FLEN_NODEID]; char nodeid[TBDB_FLEN_NODEID];
char vnode[TBDB_FLEN_VNAME]; char vnode[TBDB_FLEN_VNAME];
char objname[TBDB_FLEN_EVOBJNAME]; char objname[TBDB_FLEN_EVOBJNAME];
...@@ -173,6 +174,8 @@ main(int argc, char **argv) ...@@ -173,6 +174,8 @@ main(int argc, char **argv)
fatal("could not subscribe to EVENT_SCHEDULE event"); fatal("could not subscribe to EVENT_SCHEDULE event");
} }
goto doit;
/* /*
* Hacky. Need to wait until all nodes in the experiment are * Hacky. Need to wait until all nodes in the experiment are
* in the ISUP state before we can start the event list rolling. * in the ISUP state before we can start the event list rolling.
...@@ -195,6 +198,8 @@ main(int argc, char **argv) ...@@ -195,6 +198,8 @@ main(int argc, char **argv)
sleep(3); sleep(3);
} }
doit:
/* /*
* Read the static events list and schedule. * Read the static events list and schedule.
*/ */
...@@ -220,82 +225,105 @@ enqueue(event_handle_t handle, event_notification_t notification, void *data) ...@@ -220,82 +225,105 @@ enqueue(event_handle_t handle, event_notification_t notification, void *data)
{ {
sched_event_t event; sched_event_t event;
char objname[TBDB_FLEN_EVOBJNAME]; char objname[TBDB_FLEN_EVOBJNAME];
int x; int x, sent = 0;
/* Clone the event notification, since we want the notification to
live beyond the callback function: */
event.notification = event_notification_clone(handle, notification);
if (!event.notification) {
error("event_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);
goto bad;
}
/* Get the event's firing time: */ /* Get the event's firing time: */
if (! event_notification_get_int32(handle, event.notification, "time_usec", if (! event_notification_get_int32(handle, notification, "time_usec",
(int *) &event.time.tv_usec) || (int *) &event.time.tv_usec) ||
! event_notification_get_int32(handle, event.notification, "time_sec", ! event_notification_get_int32(handle, notification, "time_sec",
(int *) &event.time.tv_sec)) { (int *) &event.time.tv_sec)) {
error("could not get time from notification %p\n", error("could not get time from notification %p\n",
event.notification); notification);
goto bad; return;
} }
/* /*
* Must map the event to the proper agent running on a particular * Must map the event to the proper agent running on a particular
* node. * node. Might be multiple agents if its a group event; send a cloned
* event for each one.
*/ */
if (! event_notification_get_objname(handle, event.notification, if (! event_notification_get_objname(handle, notification,
objname, sizeof(objname))) { objname, sizeof(objname))) {
error("could not get object name from notification %p\n", error("could not get object name from notification %p\n",
event.notification); notification);
goto bad; return;
}
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); for (x = 0; x < numagents; x++) {
event_notification_set_host(handle, if (!strcmp(agents[x].name, objname)) {
event.notification, agents[x].ipaddr); /*
event_notification_clear_objtype(handle, event.notification); * Clone the event notification, since we want the
event_notification_set_objtype(handle, * notification to live beyond the callback function:
event.notification, agents[x].objtype); */
event_notification_insert_hmac(handle, event.notification); event.notification =
event_notification_clone(handle, notification);
event.simevent = !strcmp(agents[x].objtype, TBDB_OBJECTTYPE_SIMULATOR);
if (!event.notification) {
if (debug > 1) { error("event_notification_clone failed!\n");
struct timeval now; return;
}
/*
* Clear the scheduler flag. Not allowed to do this above
* the loop cause the notification thats comes in is
* "read-only".
*/
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;
}
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);
/*
* Indicates a group event; must change the name of the
* event to the underlying agent.
*/
if (strcmp(agents[x].objname, agents[x].name)) {
event_notification_clear_objname(handle,
event.notification);
event_notification_set_objname(handle,
event.notification, agents[x].objname);
}
event_notification_insert_hmac(handle, event.notification);
event.simevent = !strcmp(agents[x].objtype,
TBDB_OBJECTTYPE_SIMULATOR);
if (debug > 1) {
struct timeval now;
gettimeofday(&now, NULL); gettimeofday(&now, NULL);
info("Sched: note:%p at:%ld:%d now:%ld:%d agent:%d\n", info("Sched: "
event.notification, "note:%p at:%ld:%d now:%ld:%d agent:%d\n",
event.time.tv_sec, event.time.tv_usec, event.notification,
now.tv_sec, now.tv_usec, event.time.tv_sec, event.time.tv_usec,
x); now.tv_sec, now.tv_usec, x);
}
/*
* Enqueue the event notification for resending at the
* indicated time:
*/
sched_event_enqueue(event);
sent++;
}
}
if (!sent) {
error("Could not map object to an agent: %s\n", objname);
} }
/* Enqueue the event notification for resending at the indicated
time: */
sched_event_enqueue(event);
return;
bad:
event_notification_free(handle, event.notification);
} }
/* Dequeue events from the event queue and fire them at the /* Dequeue events from the event queue and fire them at the
...@@ -347,7 +375,7 @@ dequeue(event_handle_t handle) ...@@ -347,7 +375,7 @@ dequeue(event_handle_t handle)
static int static int
get_static_events(event_handle_t handle) get_static_events(event_handle_t handle)
{ {
MYSQL_RES *res; MYSQL_RES *res, *agent_res, *group_res;
MYSQL_ROW row; MYSQL_ROW row;
int nrows; int nrows;
struct timeval now, time; struct timeval now, time;
...@@ -366,32 +394,50 @@ get_static_events(event_handle_t handle) ...@@ -366,32 +394,50 @@ get_static_events(event_handle_t handle)
* That is, we want to be able to quickly map from "cbr0" to * That is, we want to be able to quickly map from "cbr0" to
* the node on which it lives (for dynamic events). * the node on which it lives (for dynamic events).
*/ */
res = mydb_query("select vi.vname,vi.vnode,r.node_id,o.type " agent_res =
" from virt_agents as vi " mydb_query("select vi.vname,vi.vnode,r.node_id,o.type "
"left join reserved as r on " " from virt_agents as vi "
" r.vname=vi.vnode and r.pid=vi.pid and r.eid=vi.eid " "left join reserved as r on "
"left join event_objecttypes as o on " " r.vname=vi.vnode and r.pid=vi.pid and "
" o.idx=vi.objecttype " " r.eid=vi.eid "
"where vi.pid='%s' and vi.eid='%s'", "left join event_objecttypes as o on "
4, pid, eid); " o.idx=vi.objecttype "
"where vi.pid='%s' and vi.eid='%s'",
if (!res) { 4, pid, eid);
if (!agent_res) {
error("getting virt_agents list for %s/%s", pid, eid); error("getting virt_agents list for %s/%s", pid, eid);
return 0; return 0;
} }
nrows = mysql_num_rows(res);
agents = calloc(nrows, sizeof(struct agent)); /*
* We also need the event_groups table so we know how many entries.
*/
group_res =
mydb_query("select group_name,agent_name from event_groups "
"where pid='%s' and eid='%s'",
2, pid, eid);
if (!group_res) {
error("getting virt_groups list for %s/%s", pid, eid);
return 0;
}
agents = calloc(mysql_num_rows(agent_res) + mysql_num_rows(group_res),
sizeof(struct agent));
if (agents == NULL) { if (agents == NULL) {
error("cannot allocate memory, too many agents (%d)\n", nrows); error("cannot allocate memory, too many agents/groups\n");
return 0; return 0;
} }
nrows = mysql_num_rows(agent_res);
while (nrows--) { while (nrows--) {
row = mysql_fetch_row(res); row = mysql_fetch_row(agent_res);
if (!row[0] || !row[1] || !row[3]) if (!row[0] || !row[1] || !row[3])
continue; continue;
strcpy(agents[numagents].name, row[0]);
strcpy(agents[numagents].objname, row[0]); strcpy(agents[numagents].objname, row[0]);
strcpy(agents[numagents].vnode, row[1]); strcpy(agents[numagents].vnode, row[1]);
strcpy(agents[numagents].objtype, row[3]); strcpy(agents[numagents].objtype, row[3]);
...@@ -425,7 +471,7 @@ get_static_events(event_handle_t handle) ...@@ -425,7 +471,7 @@ get_static_events(event_handle_t handle)
} }
numagents++; numagents++;
} }
mysql_free_result(res); mysql_free_result(agent_res);
if (debug) { if (debug) {
for (adx = 0; adx < numagents; adx++) { for (adx = 0; adx < numagents; adx++) {
...@@ -438,6 +484,50 @@ get_static_events(event_handle_t handle) ...@@ -438,6 +484,50 @@ get_static_events(event_handle_t handle)
} }
} }
/*
* Now get the group list. To make life simple, I just create
* new entries in the agents table, named by the group name, but
* with the info from the underlying agent duplicated, for each
* member of the group.
*/
nrows = mysql_num_rows(group_res);
while (nrows--) {
row = mysql_fetch_row(group_res);
/*
* Find the agent entry.
*/
for (adx = 0; adx < numagents; adx++) {
if (!strcmp(agents[adx].name, row[1]))
break;
}
if (adx == numagents) {
error("Could not find group event %s/%s",
row[0], row[1]);
return 0;
}
/*
* Copy the entry, but rename it to the group name.
*/
memcpy((void *)&agents[numagents], (void *)&agents[adx],
sizeof(struct agent));
strcpy(agents[numagents].name, row[0]);
numagents++;
}
mysql_free_result(group_res);
if (debug) {
for (adx = 0; adx < numagents; adx++) {
if (!strcmp(agents[adx].objname, agents[adx].name))
continue;
info("Group %d: %10s %10s\n", adx,
agents[adx].name,
agents[adx].objname);
}
}
/* /*
* Now get the eventlist. There should be entries in the * Now get the eventlist. There should be entries in the
* agents table for anything we find in the list. * agents table for anything we find in the list.
...@@ -491,7 +581,7 @@ get_static_events(event_handle_t handle) ...@@ -491,7 +581,7 @@ get_static_events(event_handle_t handle)
firetime = atof(EXTIME); firetime = atof(EXTIME);
for (adx = 0; adx < numagents; adx++) { for (adx = 0; adx < numagents; adx++) {
if (!strcmp(agents[adx].objname, OBJNAME)) if (!strcmp(agents[adx].name, OBJNAME))
break; break;
} }
if (adx == numagents) { if (adx == numagents) {
......
...@@ -157,6 +157,21 @@ CREATE TABLE event_objecttypes ( ...@@ -157,6 +157,21 @@ CREATE TABLE event_objecttypes (
PRIMARY KEY (idx) PRIMARY KEY (idx)
) TYPE=MyISAM; ) TYPE=MyISAM;
--
-- Table structure for table `event_groups`
--
CREATE TABLE event_groups (
pid varchar(12) NOT NULL default '',
eid varchar(32) NOT NULL default '',
idx int(10) unsigned NOT NULL auto_increment,
group_name varchar(64) NOT NULL default '',
agent_name varchar(64) NOT NULL default '',
PRIMARY KEY (pid,eid,idx),
KEY group_name (group_name),
KEY agent_name (agent_name)
) TYPE=MyISAM;
-- --
-- Table structure for table `eventlist` -- Table structure for table `eventlist`
-- --
...@@ -170,6 +185,7 @@ CREATE TABLE eventlist ( ...@@ -170,6 +185,7 @@ CREATE TABLE eventlist (
vname varchar(64) NOT NULL default '', vname varchar(64) NOT NULL default '',
objecttype smallint(5) unsigned NOT NULL default '0', objecttype smallint(5) unsigned NOT NULL default '0',
eventtype smallint(5) unsigned NOT NULL default '0', eventtype smallint(5) unsigned NOT NULL default '0',
isgroup tinyint(1) unsigned default '0',
arguments text, arguments text,
atstring text, atstring text,
PRIMARY KEY (pid,eid,idx), PRIMARY KEY (pid,eid,idx),
...@@ -1710,6 +1726,7 @@ CREATE TABLE virt_lans ( ...@@ -1710,6 +1726,7 @@ CREATE TABLE virt_lans (
emulated tinyint(4) default '0', emulated tinyint(4) default '0',
uselinkdelay tinyint(4) default '0', uselinkdelay tinyint(4) default '0',
nobwshaping tinyint(4) default '0', nobwshaping tinyint(4) default '0',
mustdelay tinyint(1) default '0',
usevethiface tinyint(4) default '0', usevethiface tinyint(4) default '0',
trivial_ok tinyint(4) default '1', trivial_ok tinyint(4) default '1',
protocol varchar(30) NOT NULL default 'ethernet', protocol varchar(30) NOT NULL default 'ethernet',
......
...@@ -539,6 +539,7 @@ REPLACE INTO table_regex VALUES ('virt_lans','widearea','int','redirect','defaul ...@@ -539,6 +539,7 @@ REPLACE INTO table_regex VALUES ('virt_lans','widearea','int','redirect','defaul
REPLACE INTO table_regex VALUES ('virt_lans','emulated','int','redirect','default:boolean',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_lans','emulated','int','redirect','default:boolean',0,0,NULL);
REPLACE INTO table_regex VALUES ('virt_lans','uselinkdelay','int','redirect','default:boolean',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_lans','uselinkdelay','int','redirect','default:boolean',0,0,NULL);
REPLACE INTO table_regex VALUES ('virt_lans','nobwshaping','int','redirect','default:boolean',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_lans','nobwshaping','int','redirect','default:boolean',0,0,NULL);
REPLACE INTO table_regex VALUES ('virt_lans','mustdelay','int','redirect','default:boolean',0,0,NULL);
REPLACE INTO table_regex VALUES ('virt_lans','usevethiface','int','redirect','default:boolean',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_lans','usevethiface','int','redirect','default:boolean',0,0,NULL);
REPLACE INTO table_regex VALUES ('virt_lans','trivial_ok','int','redirect','default:boolean',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_lans','trivial_ok','int','redirect','default:boolean',0,0,NULL);
REPLACE INTO table_regex VALUES ('virt_node_desires','pid','text','redirect','projects:pid',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_node_desires','pid','text','redirect','projects:pid',0,0,NULL);
...@@ -634,6 +635,10 @@ REPLACE INTO table_regex VALUES ('virt_lan_member_settings','capkey','text','red ...@@ -634,6 +635,10 @@ REPLACE INTO table_regex VALUES ('virt_lan_member_settings','capkey','text','red
REPLACE INTO table_regex VALUES ('virt_lan_member_settings','capval','text','redirect','virt_lan_settings:capval',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_lan_member_settings','capval','text','redirect','virt_lan_settings:capval',0,0,NULL);
REPLACE INTO table_regex VALUES ('virt_lans','est_bandwidth','int','redirect','default:int',0,2147483647,NULL); REPLACE INTO table_regex VALUES ('virt_lans','est_bandwidth','int','redirect','default:int',0,2147483647,NULL);
REPLACE INTO table_regex VALUES ('virt_lans','rest_bandwidth','int','redirect','default:int',0,2147483647,NULL); REPLACE INTO table_regex VALUES ('virt_lans','rest_bandwidth','int','redirect','default:int',0,2147483647,NULL);
REPLACE INTO table_regex VALUES ('event_groups','pid','text','redirect','projects:pid',0,0,NULL);
REPLACE INTO table_regex VALUES ('event_groups','eid','text','redirect','experiments:eid',0,0,NULL);
REPLACE INTO table_regex VALUES ('event_groups','group_name','text','redirect','eventlist:vname',0,0,NULL);
REPLACE INTO table_regex VALUES ('event_groups','agent_name','text','redirect','eventlist:vname',0,0,NULL);
-- --
-- Dumping data for table `testsuite_preentables` -- Dumping data for table `testsuite_preentables`
......
...@@ -1778,3 +1778,42 @@ last_net_act,last_cpu_act,last_ext_act); ...@@ -1778,3 +1778,42 @@ last_net_act,last_cpu_act,last_ext_act);
1.267: Remove table definition that snuck in while developing; skip to 1.267: Remove table definition that snuck in while developing; skip to
next entry; next entry;
1.268: Add event_groups table to allow users to define groups of
targets for events. The agent_name refers to an entry in the
virt_agents table. All members of an eventgroup must of course
be of the same type. I am not currently enforcing this. (note
that the vnode slot of the eventlist table was effectively
deprecated quite some time ago; the event scheduler uses the
vnode slot of the virt_agents entry instead).
CREATE TABLE event_groups (
pid varchar(12) NOT NULL default '',
eid varchar(32) NOT NULL default '',
idx int(10) unsigned NOT NULL auto_increment,
group_name varchar(64) NOT NULL default '',
agent_name varchar(64) NOT NULL default '',
PRIMARY KEY (pid,eid,idx),
KEY group_name (group_name),
KEY agent_name (agent_name)
) TYPE=MyISAM;
Also add a boolean to the eventlist table to mark an event as a
group event.
alter table eventlist add isgroup tinyint(1) unsigned \
NOT NULL default '0' after eventtype;
Add mustdelay boolean to virt_lans to relieve assign_wrapper
from the chore of guessing when a delay node needs to be
inserted; assign_wrapper can still override of course, but this
should make it less error prone.
alter table virt_lans add mustdelay tinyint(1) unsigned \
default '0' after nobwshaping;
update virt_lans set mustdelay=q_red;
Then run:
./mustdelay.pl
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use lib "/usr/testbed/lib";
use libdb;
use libtestbed;
#
# Untaint the path
#
$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
$query_result =
DBQueryFatal("select distinct ex.pid,ex.eid,vname 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 ".
"where ot.type='LINK'");
while (($pid,$eid,$lan) = $query_result->fetchrow_array()) {
DBQueryFatal("update virt_lans set mustdelay=1 ".
"where pid='$pid' and eid='$eid' and vname='$lan'");
}
...@@ -3469,11 +3469,12 @@ sub LoadVirtLans() ...@@ -3469,11 +3469,12 @@ sub LoadVirtLans()
my $useveth = $rowref->{"usevethiface"}; my $useveth = $rowref->{"usevethiface"};
my $trivial_ok = $rowref->{"trivial_ok"}; my $trivial_ok = $rowref->{"trivial_ok"};
my $protocol = $rowref->{"protocol"}; my $protocol = $rowref->{"protocol"};
my $mustdelay = $rowref->{"mustdelay"};