Commit ed964507 authored by Leigh Stoller's avatar Leigh 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($)
"virt_simnode_attributes",
"nseconfigs",
"eventlist",
"event_groups",
"ipsubnets",
"nsfiles");
......
......@@ -106,8 +106,11 @@ my %virtual_tables =
"eventlist" => { rows => undef,
tag => "events",
row => "event",
attrs => [ "vname" ]});
attrs => [ "vname" ]},
"event_groups" => { rows => undef,
tag => "event_groups",
row => "event_group",
attrs => [ "group_name", "agent-name" ]});
# XXX
# The experiment table is special. Only certain fields are allowed to
......
......@@ -108,6 +108,10 @@ int address_tuple_free(address_tuple_t);
event_notification_remove(handle, note, "OBJTYPE")
#define event_notification_set_objtype(handle, note, 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
......
/*
* 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.
*/
......@@ -42,6 +42,7 @@ static void cleanup(void);
static void quit(int);
struct agent {
char name[TBDB_FLEN_EVOBJNAME]; /* Agent *or* group name */
char nodeid[TBDB_FLEN_NODEID];
char vnode[TBDB_FLEN_VNAME];
char objname[TBDB_FLEN_EVOBJNAME];
......@@ -173,6 +174,8 @@ main(int argc, char **argv)
fatal("could not subscribe to EVENT_SCHEDULE event");
}
goto doit;
/*
* Hacky. Need to wait until all nodes in the experiment are
* in the ISUP state before we can start the event list rolling.
......@@ -195,6 +198,8 @@ main(int argc, char **argv)
sleep(3);
}
doit:
/*
* Read the static events list and schedule.
*/
......@@ -220,82 +225,105 @@ enqueue(event_handle_t handle, event_notification_t notification, void *data)
{
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 = 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;
}
int x, sent = 0;
/* 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) ||
! event_notification_get_int32(handle, event.notification, "time_sec",
! event_notification_get_int32(handle, notification, "time_sec",
(int *) &event.time.tv_sec)) {
error("could not get time from notification %p\n",
event.notification);
goto bad;
notification);
return;
}
/*
* 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))) {
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;
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);
event_notification_insert_hmac(handle, event.notification);
event.simevent = !strcmp(agents[x].objtype, TBDB_OBJECTTYPE_SIMULATOR);
if (debug > 1) {
struct timeval now;
for (x = 0; x < numagents; x++) {
if (!strcmp(agents[x].name, objname)) {
/*
* 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. 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",
event.notification,
event.time.tv_sec, event.time.tv_usec,
now.tv_sec, now.tv_usec,
x);
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);
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
......@@ -347,7 +375,7 @@ dequeue(event_handle_t handle)
static int
get_static_events(event_handle_t handle)
{
MYSQL_RES *res;
MYSQL_RES *res, *agent_res, *group_res;
MYSQL_ROW row;
int nrows;
struct timeval now, time;
......@@ -366,32 +394,50 @@ get_static_events(event_handle_t handle)
* That is, 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,o.type "
" from virt_agents as vi "
"left join reserved as r on "
" r.vname=vi.vnode and r.pid=vi.pid and r.eid=vi.eid "
"left join event_objecttypes as o on "
" o.idx=vi.objecttype "
"where vi.pid='%s' and vi.eid='%s'",
4, pid, eid);
if (!res) {
agent_res =
mydb_query("select vi.vname,vi.vnode,r.node_id,o.type "
" from virt_agents as vi "
"left join reserved as r on "
" r.vname=vi.vnode and r.pid=vi.pid and "
" r.eid=vi.eid "
"left join event_objecttypes as o on "
" o.idx=vi.objecttype "
"where vi.pid='%s' and vi.eid='%s'",
4, pid, eid);
if (!agent_res) {
error("getting virt_agents list for %s/%s", pid, eid);
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) {
error("cannot allocate memory, too many agents (%d)\n", nrows);
error("cannot allocate memory, too many agents/groups\n");
return 0;
}
nrows = mysql_num_rows(agent_res);
while (nrows--) {
row = mysql_fetch_row(res);
row = mysql_fetch_row(agent_res);
if (!row[0] || !row[1] || !row[3])
continue;
strcpy(agents[numagents].name, row[0]);
strcpy(agents[numagents].objname, row[0]);
strcpy(agents[numagents].vnode, row[1]);
strcpy(agents[numagents].objtype, row[3]);
......@@ -425,7 +471,7 @@ get_static_events(event_handle_t handle)
}
numagents++;
}
mysql_free_result(res);
mysql_free_result(agent_res);
if (debug) {
for (adx = 0; adx < numagents; adx++) {
......@@ -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
* agents table for anything we find in the list.
......@@ -491,7 +581,7 @@ get_static_events(event_handle_t handle)
firetime = atof(EXTIME);
for (adx = 0; adx < numagents; adx++) {
if (!strcmp(agents[adx].objname, OBJNAME))
if (!strcmp(agents[adx].name, OBJNAME))
break;
}
if (adx == numagents) {
......
......@@ -157,6 +157,21 @@ CREATE TABLE event_objecttypes (
PRIMARY KEY (idx)
) 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`
--
......@@ -170,6 +185,7 @@ CREATE TABLE eventlist (
vname varchar(64) NOT NULL default '',
objecttype smallint(5) unsigned NOT NULL default '0',
eventtype smallint(5) unsigned NOT NULL default '0',
isgroup tinyint(1) unsigned default '0',
arguments text,
atstring text,
PRIMARY KEY (pid,eid,idx),
......@@ -1710,6 +1726,7 @@ CREATE TABLE virt_lans (
emulated tinyint(4) default '0',
uselinkdelay tinyint(4) default '0',
nobwshaping tinyint(4) default '0',
mustdelay tinyint(1) default '0',
usevethiface tinyint(4) default '0',
trivial_ok tinyint(4) default '1',
protocol varchar(30) NOT NULL default 'ethernet',
......
......@@ -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','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','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','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);
......@@ -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_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 ('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`
......
......@@ -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
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()
my $useveth = $rowref->{"usevethiface"};
my $trivial_ok = $rowref->{"trivial_ok"};
my $protocol = $rowref->{"protocol"};
my $mustdelay = $rowref->{"mustdelay"};
# Extend the DB info with this stuff:
#
# If RED, must insert traffic shapping.
$virt_lans{$vname}->{"MUSTDELAY"} = $rowref->{"q_red"};
$virt_lans{$vname}->{"MUSTDELAY"} = $mustdelay;
# User has requested the link/lan be emulated. Not typical.
$virt_lans{$vname}->{"EMULATED"} = $isemulated;
# User has requested "endnodeshaping" (dummynet on end nodes).
......@@ -3592,21 +3593,6 @@ sub LoadVirtLans()
"$rdelay $rbandwidth $rlossrate\n";
printdb " $port:$vname is a lan of $node\n";
}
#
# Check event list. Anytime we find an event to control a link, we need
# to drop a delay node in. start/stop especially, since thats the easiest
# way to do that, even if the link has no other traffic shaping in it.
#
printdb "Checking events for LINK commands.\n";
$result =
DBQueryFatal("select distinct 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' and ex.pid='$pid' and ex.eid='$eid'");
while (($vname) = $result->fetchrow_array) {
$virt_lans{$vname}->{"MUSTDELAY"} = 1;
}
}
sub virtlanexists($) { return exists($virt_lans{$_[0]}); }
sub virtlanname($) { return $virt_lans{$_[0]}->{"VNAME"}; }
......
......@@ -16,7 +16,7 @@ include $(OBJDIR)/Makeconf
LIB_STUFF = lanlink.tcl node.tcl sim.tcl tb_compat.tcl null.tcl \
nsobject.tcl traffic.tcl vtype.tcl parse.tcl program.tcl \
nsenode.tcl nstb_compat.tcl
nsenode.tcl nstb_compat.tcl event.tcl
BOSSLIBEXEC = parse-ns
USERLIBEXEC = parse.proxy
......
# -*- tcl -*-
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# All rights reserved.
#
######################################################################
#
# Event group support.
#
######################################################################
Class EventGroup -superclass NSObject
namespace eval GLOBALS {
set new_classes(EventGroup) {}
}
EventGroup instproc init {s} {
global ::GLOBALS::last_class
$self set sim $s
$self set mytype {}
$self instvar members
array set members {}
# Link simulator to this new object.
$s add_eventgroup $self
set ::GLOBALS::last_class $self
}
EventGroup instproc rename {old new} {
$self instvar sim
$sim rename_eventgroup $old $new
}
#
# Add members to the event group.
#
EventGroup instproc add {args} {
$self instvar members
$self instvar mytype
foreach obj $args {
if {[$obj info class] == "Lan" || [$obj info class] == "Link"} {
set thisclass "LanLink"
} else {
set thisclass [$obj info class]
}
if {$mytype == {}} {
set mytype $thisclass
}
if {$thisclass != $mytype} {
perror "\[$self add $obj] All members must be of the same type!"
return
}
set members($obj) {}
}
}
#
# Return list of member objects
#
EventGroup instproc members {} {
$self instvar members
return [array names members]
}
# updatedb DB
EventGroup instproc updatedb {DB} {
var_import ::GLOBALS::pid
var_import ::GLOBALS::eid
$self instvar members
$self instvar sim
if {[array size members] == 0} {
perror "\[updatedb] $self has no member list."
return
}
foreach member [array names members] {
if {[$member info class] == "Queue"} {
set agent_name [$member agent_name]
} else {
set agent_name $member
}
set names [list "group_name" "agent_name"]
set vals [list $self $agent_name]
$sim spitxml_data "event_groups" $names $vals
}
}
......@@ -32,7 +32,9 @@ SimplexLink instproc init {link dir} {
SimplexLink instproc queue {} {
$self instvar mylink
$self instvar mydir
return [$mylink set ${mydir}queue]
set myqueue [$mylink set ${mydir}queue]
return $myqueue
}
LLink instproc init {lan node} {
$self set mylan $lan
......@@ -49,14 +51,10 @@ LLink instproc queue {} {
# Don't need any rename procs since these never use their own name and
# can not be generated during Link creation.
Queue instproc init {link type dir} {
Queue instproc init {link type node} {
$self set mylink $link
$self set mynode $node
# direction is either "to" indicating src to dst or "from" indicating
# the dst to src. I.e. to dst or from dst.
$self set direction $dir
# These control whether the link was created RED or GRED. It
# filters through the DB.
$self set gentle_ 0
......@@ -83,15 +81,23 @@ Queue instproc init {link type dir} {
if {$type == "RED"} {
set red_ 1
$link mustdelay
} elseif {$type == "GRED"} {
set red_ 1
set gentle_ 1
$link mustdelay
} elseif {$type != "DropTail"} {
punsup "Link type $type, using DropTail!"
}
}
}
Queue instproc rename {old new} {
$self instvar mylink
$mylink rename_queue $old $new
}
Queue instproc rename_lanlink {old new} {
$self instvar mylink
......@@ -104,19 +110,20 @@ Queue instproc get_link {} {
return $mylink
}
# Hacky. Need to create an association bewteen the queue direction
# and a dummynet pipe. This should happen later on, but I do not
# have time right now to make all the changes. Instead, convert
# "to" to "pipe0" and "from" to "pipe1".
Queue instproc get_pipe {} {
$self instvar direction
if {$direction == "to"} {
set pipe "pipe0"
} else {
set pipe "pipe1"
}
return $pipe
Queue instproc agent_name {} {
$self instvar mylink
$self instvar mynode
return "$mylink-$mynode"
}
#
# A queue is associated with a node on a link. Return that node.
#
Queue instproc get_node {} {
$self instvar mynode
return $mynode
}
Link instproc init {s nodes bw d type} {
......@@ -134,8 +141,8 @@ Link instproc init {s nodes bw d type} {
var_import GLOBALS::new_counter
set q1 q[incr new_counter]
Queue to$q1 $self $type to
Queue from$q1 $self $type from
Queue to$q1 $self $type $src
Queue from$q1 $self $type $dst
$self set toqueue to$q1
$self set fromqueue from$q1
......@@ -172,6 +179,13 @@ LanLink instproc init {s nodes bw d type} {