Commit 36395c8f authored by Robert Ricci's avatar Robert Ricci
Browse files

New script: nsgen - Generates an NS file from some supplied values

and a template. The idea is that it's a general tool for generating
NS files for standard topologies (ie. LANs), filling in some
user-supplied values. Has some features that are useful for NS files,
like default values and skipping or commenting out sections if values
for variables are not given.

Could probably use some better error checking on the the XML file.

Does not yet get installed.
parent fac757ef
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
XML NS template file for making a simple
-->
<nstemplate>
<variable name="Count" default="10" />
<variable name="OS" default="RHL-STD" />
<variable name="HWType" default="pc" />
<nstext>
<section><![CDATA[
source tb_compat.tcl
set ns [new Simulator]
set num_pcs <Count>
set lan_string ""
# Setup PCs
for {set i 1} {$i <= $num_pcs} {incr i} {
set pc($i) [$ns node]
tb-set-node-os $pc($i) <OS>
tb-set-hardware $pc($i) <HWType>
append lan_string "$pc(${i}) "
}
set lan0 [$ns make-lan "$lan_string" 100Mb 0ms]
$ns run
]]></section>
</nstext>
</nstemplate>
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2003 University of Utah and the Flux Group.
# All rights reserved.
#
#
#
# nsgen - Generate an NS file from some supplied values and a template
#
use Getopt::Long;
use XML::Parser;
sub usage() {
print "Usage: $0 [-dh] [-o outputfile] [-v var=value ...] template\n";
print " -d Turn on debugging output\n";
print " -h This message\n";
print " -v var=value Set <var> to <value> in the template\n";
print " -o outputfile Put output in <outputfile> instead of stdout\n";
return 1;
}
#
# Process command-line args
#
my $debug = 0;
my $help = 0;
my @vars = ();
my $outfile;
if (! GetOptions("d" => \$debug, "h" => \$help, "v=s" => \@vars, "o=s" =>
\$outfile)) {
exit usage();
}
if ($help) {
exit usage();
}
if (@ARGV != 1) {
exit usage();
}
my $template_file = shift @ARGV;
#
# Body
#
my ($template,$variables) = readTemplate($template_file);
processOptions($variables,@vars);
my $nsfile = substitute($template,$variables);
#
# Read the template XML file
#
sub readTemplate($) {
my ($template_file) = @_;
open TF,"<$template_file" or fatal("Unable to open $template_file: $!");
#
# Create a parser.
#
my $parser = new XML::Parser(Style => 'Tree');
$parser->setHandlers('Start' => \&startElement,
'End' => \&endElement,
'Char' => \&processElement);
#
# Do the actual parsing
#
local ($vars,@nssections);
fatal($@) if (eval { $parser->parse(*TF); return 1; } != 1);
return (\@nssections,$vars);
}
#
# Handle the variables the user gave on the command line
#
sub processOptions($@) {
my ($variables,@options) = @_;
return unless @options;
foreach my $option (@options) {
#
# Parse values
#
@optparts = split /=/, $option;
if (@optparts != 2) {
fatal("Bad variable: $option");
}
my ($var, $value) = @optparts;
#
# Make sure they gave a real variable
#
if (!defined($variables->{$var})) {
fatal("Unknown variable $var");
}
$variables->{$var}{value} = $value;
}
}
#
# Replace variables embedded in the template
#
sub substitute($template,$variables) {
#
# Loop through each section of the template
#
foreach my $section (@$template) {
my $lines = $section->{lines};
my $depends_on = $section->{depends_on};
my $action = $section->{action};
#
# Handle sections that depend on a variable that was not given - either
# skip the section entirely, or comment it out.
#
if ($depends_on && !defined($variables->{$depends_on}{value})) {
if ($action eq "skip") {
next;
} elsif ($action eq "comment") {
$lines =~ s/^(\s*)([^\s#])/$1#$2/mg;
}
}
#
# Replace all variables, which look like <Foo>
#
while ($lines =~ /<(\w+)>/) {
if (!defined($variables->{$1})) {
fatal("Unknown variable: $1");
}
#
# Take the user value if given, default otherwise
#
my $value;
if (defined($variables->{$1}{value})) {
$value = $variables->{$1}{value};
} else {
$value = $variables->{$1}{default};
}
$lines =~ s/<(\w+)>/$value/;
}
print $lines;
}
}
#
# XML parsing callbacks
#
#
# The beginning of an XML element
#
sub startElement() {
debug("startElement called with: ", join(",",@_), "\n");
my ($expat, $element, %attrs) = @_;
if ($element eq "nstemplate") {
# Nothing to do
} elsif ($element eq "variable") {
#
# Got a variable - grab the default
#
$vars->{$attrs{name}} = {};
if (defined($attrs{default})){
$vars->{$attrs{name}}->{default} = $attrs{default};
} else {
$vars->{$attrs{name}}->{default} = undef;
}
} elsif ($element eq "nstext") {
# Nothing to do
} elsif ($element eq "section") {
#
# Start a fresh array of section lines, and grab a few optional
# attributes
#
$current_section = [];
$current_section_depends = undef;
$current_section_action = undef;
if ($attrs{depends_on}) {
$current_section_depends = $attrs{'depends_on'};
}
if ($attrs{action}) {
$current_section_action = $attrs{'action'};
}
}
}
#
# End of an XML element
#
sub endElement() {
debug("endElement called with: ", join(",",@_), "\n");
my ($expat, $element) = @_;
if ($element eq "nstemplate") {
# Nothing to do
} elsif ($element eq "variable") {
# Nothing to do
} elsif ($element eq "nstext") {
# Nothing to do
} elsif ($element eq "section") {
#
# Eat blank lines off $current_section at the beginning and end, due to
# the way that XML preserves whitespace. Lets us do more natural layout
# in the XML file.
#
if ($$current_section[0] =~ /^\s*$/) {
shift @$current_section;
}
if ($$current_section[$#{$current_section}] =~ /^\s*$/) {
pop @$current_section;
}
#
# Push into the global list of sections
#
push @nssections, {depends_on => $current_section_depends, action =>
$current_section_action, lines => join("",@$current_section)};
}
}
#
# Process text in an XML element
#
sub processElement() {
debug("processElement called with: ", join(",",@_), "\n");
my ($expat, $string) = @_;
#
# Right now, we only have to do anything in the sections
#
SWITCH: for ($expat->current_element()) {
(/^section$/) && do {
push @$current_section, $string;
};
}
}
#
# Die
#
sub fatal($)
{
my ($msg) = @_;
die("*** $0:\n".
" $msg\n");
}
#
# Debug
#
sub debug(@_) {
if ($debug) {
print STDERR @_;
}
}
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
XML NS template based on planetlab.ns - For making PlanetLab experiments
-->
<nstemplate>
<variable name="Count" default="10" />
<variable name="ResUsage" default="3" />
<variable name="HWType" default="pcplab" />
<variable name="FailOK" />
<variable name="Tarballs" default="/somedir /proj/yourproj/tarfiles/yourtarball.tar.gz" />
<variable name="RPMs" default="/proj/yourproj/rpms/yourrpm.rpm" />
<variable name="Startup" default="/somepath/yourstartupcmd" />
<nstext>
<section><![CDATA[
# planetlab.ns - NS script to allocate PlanetLab nodes on Emulab/Netbed
#
# September 17, 2003
#
# Questions and comments to testbed-ops@emulab.net
#
# Boilerplate
#
source tb_compat.tcl
set ns [new Simulator]
tb-set-colocate-factor 1
#
# Estimated resource use on each node by this experiment, used to determine
# if there are enough available resources on PlanetLab for this experiment.
# The scale is from 1 to 5, with 1 being negligible usage on a node,
# and 5 an experiment that, ideally, should have nodes to itself.
# If omitted, defaults to 3.
#
tb-set-cpu-usage <ResUsage>
tb-set-mem-usage <ResUsage>
#
# How many nodes to ask for
#
set num_nodes <Count>
#
# Type of PlanetLab nodes to request. Current choices, with counts:
# pcplab Any PlanetLab node (127)
# The following are mutually exclusive sets:
# pcplabdsl Plab nodes on DSL lines (3)
# pcplabinet Plab nodes on the commodity Internet, in North America (12)
# pcplabintl Plab nodes outside North America (24)
# pcplabinet2 Plab end-hosts (not colo sites) on Internet2 (88)
#
# Can mix and match these types all you want with, say, multiple loops below.
#
# Instead of by type, you could also request specific nodes; for example:
# tb-fix-node $nodeA plab15
# tb-fix-node $nodeB plab33
#
set hwtype "<HWType>"
#
# Select the N approximately least-loaded nodes of the given type. Fails if
# insufficient nodes are found due to excessive loads or inadequate disk space.
#
for {set i 1} {$i <= $num_nodes} {incr i} {
set node($i) [$ns node]
tb-set-hardware $node($i) $hwtype
]]></section>
<section depends_on="FailOK" action="comment"><![CDATA[
# Allow experiment setup to succeed even if setup of some vnodes fails.
# Your choice, but currently recommended due to flaky Plab nodes.
tb-set-node-failure-action $node($i) "nonfatal"
]]></section>
<section><![CDATA[
# Entirely optional stuff; see comments below.
]]></section>
<section depends_on="Tarballs" action="comment"><![CDATA[
tb-set-node-tarfiles $node($i) <Tarballs>
]]></section>
<section depends_on="RPMs" action="comment"><![CDATA[
tb-set-node-rpms $node($i) <RPMs>
]]></section>
<section depends_on="Startup" action="comment"><![CDATA[
tb-set-node-startup $node($i) <Startup>
]]></section>
<section><![CDATA[
}
# The above loop includes three optional features:
# 1) install tarballs, 2) install rpms, 3) Command execution at boot time.
#
# You can specify tarfiles and/or RPMs to install on the vnodes.
# These files must exist in your /proj directory on ops.emulab.net.
# 1) "tarfiles" syntax is an alternating space-separated list of the
# dir from which that untar should start, and the path to the tarball.
# 2) "rpms" syntax is simply a space-separated list of paths to RPMs.
# 3) The "startup" command will be executed every time the vnode boots:
# at experiment startup, swapin, and vnode reboot. Of course, it needs
# to exist on the vnode, probably from a tarball or RPM you installed.
#
# Boilerplate
#
$ns run
]]></section>
</nstext>
</nstemplate>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment