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

I have changed xmlconvert to do the following (note that I have not changed

the parser; it still spits out the simplistic xml stuff and xmlconvert
still reads that):

* When generating an XML representation of an experiment, I read the
  virtual tables and build a *flat* data structure.

	{"settings" => {pid:testbed, eid:foobar ....}.
	 "nodes"    => [ {vname:nodeA, osid:RHL-STD ... },  ],
	 "lans"     => [ {vname:link0, member:nodeA ... },  ],
         ...

  So, nodes is a list of node hash tables, lans is a list of hash tables,
  etc. All of the other virt_tables are represented as well.  You can find
  everything you want as long as you the names of the fields!

* Once I have the above data structure, I use the XML-RPC lib to turn it
  into an XML document, and spit out the resulting XML string (a very long
  string!) Note that XML-RPC has its own format that allows you to convert
  data structures into XML and back; thats how you issue an RPC from your
  python client to a perl server.

* Our python XMLRPC server basically uses xmlconvert as a backend so it can
  ship the above data structure back to client. We could also allow the
  client to ship the data structure back, and have xmlconvert upload the
  changes back to the DB.

  The reason for doing it in xmlconvert as a backend is so that this XML
  stuff is in just one place; whoever reads/writes this stuff has to know a
  lot of internal detail about an experiment (like what things should not
  be exported and what things we should not allow to be imported). I prefer
  to keep that all in one place since its easy to pass XML strings around
  (as long as everyone is talking the same dialect).

Future work is to pick an XML representation that is not tied to
XML-RPC. Ideally, we want to take a data structure in Perl, spit out
the XML tree (with a DTD) and be able to reconstitute that in another
program, using the XML Parser library (based on expat, which is common
to Perl, Python, C).
parent 1befb964
......@@ -9,7 +9,9 @@
use English;
use Getopt::Std;
use XML::Parser;
use RPC::XML;
use RPC::XML::Parser;
#
# Convert between XML and DB representation of a virtual experiment.
# Very simple, no DTDs, DOMs, XSLs, etc. Just the facts ...
......@@ -20,12 +22,13 @@ use XML::Parser;
#
sub usage()
{
print STDOUT "Usage: xmlconvert [-x <xmlfile> [-n]] [-d] pid eid\n";
print STDOUT "Usage: xmlconvert [-x <xmlfile> [-n] [-p]] [-d] pid eid\n";
exit(-1);
}
my $optlist = "x:nds";
my $optlist = "x:ndsp";
my $fromxml = 0;
my $fromparser = 0;
my $impotent = 0;
my $debug = 0;
# Results of parsing nse specifications. Therefore different treatment.
......@@ -47,22 +50,50 @@ my $eid;
my $XMLHEADER = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
#
# These are the virtual tables that make up an experiment.
# Each one could have multiple rows, each of which will be a
# hash table.
my %virtual_tables = ("experiments" => undef,
"virt_nodes" => undef,
"virt_lans" => undef,
"virt_lan_settings" => undef,
"virt_lan_member_settings"=> undef,
"virt_trafgens" => undef,
"virt_agents" => undef,
"virt_node_desires" => undef,
"virt_routes" => undef,
"virt_vtypes" => undef,
"virt_programs" => undef,
"nseconfigs" => undef,
"eventlist" => undef);
# These are the virtual tables that make up an experiment. Each one
# could have multiple rows, each of which will be a hash table.
#
my %virtual_tables =
("experiments" => { rows => undef,
tag => "experiment",
attrs => [ ] },
"virt_nodes" => { rows => undef,
tag => "nodes",
attrs => [ "vname" ]},
"virt_lans" => { rows => undef,
tag => "lans",
attrs => [ "vname" ]},
"virt_lan_settings" => { rows => undef,
tag => "lan_settings",
attrs => [ "vname", "capkey" ]},
"virt_lan_member_settings" => { rows => undef,
tag => "lan_member_settings",
attrs => [ "vname", "member", "capkey" ]},
"virt_trafgens" => { rows => undef,
tag => "trafgens",
attrs => [ "vname", "vnode" ]},
"virt_agents" => { rows => undef,
tag => "agents",
attrs => [ "vname", "vnode" ]},
"virt_node_desires" => { rows => undef,
tag => "node_desires",
attrs => [ "vname", "desire" ]},
"virt_routes" => { rows => undef,
tag => "routes",
attrs => [ "vname", "src", "dst" ]},
"virt_vtypes" => { rows => undef,
tag => "vtypes",
attrs => [ "name" ]},
"virt_programs" => { rows => undef,
tag => "programs",
attrs => [ "vname", "vnode" ]},
"nseconfigs" => { rows => undef,
tag => "nseconfigs",
attrs => [ "vname" ]},
"eventlist" => { rows => undef,
tag => "events",
attrs => [ "vname" ]});
# XXX
# The experiment table is special. Only certain fields are allowed to
......@@ -116,6 +147,9 @@ if (! getopts($optlist, \%options)) {
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"p"})) {
$fromparser = 1;
}
if (defined($options{"s"})) {
$simparse = 1;
}
......@@ -161,7 +195,7 @@ while (my ($idx,$type) = $query_result->fetchrow_array()) {
# Do it
if ($fromxml) {
readXML($pid, $eid, $xmlfile);
readXML($pid, $eid, $xmlfile, $fromparser);
}
else {
writeXML($pid, $eid);
......@@ -181,8 +215,8 @@ my $current_row;
my $current_slot;
my $current_data;
sub readXML($$$) {
my ($pid, $eid, $xmlfile) = @_;
sub readXML($$$$) {
my ($pid, $eid, $xmlfile, $fromparser) = @_;
my %experiment;
if ($xmlfile ne "-") {
......@@ -190,16 +224,44 @@ sub readXML($$$) {
or fatal("opening $xmlfile for STDIN: $!");
}
#
# Create a parser.
#
my $parser = new XML::Parser(Style => 'Tree');
$parser->setHandlers('Start' => \&StartElement,
'End' => \&EndElement,
'Char' => \&ProcessElement);
if ($fromparser) {
#
# Create a parser.
#
my $parser = new XML::Parser(Style => 'Tree');
$parser->setHandlers('Start' => \&StartElement,
'End' => \&EndElement,
'Char' => \&ProcessElement);
fatal($@)
if (eval { $parser->parse(*STDIN); return 1; } != 1);
}
else {
#
# Convert data structure into table rows.
#
my $resp = RPC::XML::Parser->new()->parse(STDIN);
if (!ref($resp)) {
fatal("Could not parse XML input!");
}
my $exp = $resp->value->value;
fatal($@)
if (eval { $parser->parse(*STDIN); return 1; } != 1);
if (!exists($exp->{"experiment"}) ||
!exists($exp->{"experiment"}->{"settings"})) {
fatal("Missing experiment settings structure!");
}
$virtual_tables{"experiments"}->{"rows"} =
[ $exp->{"experiment"}->{"settings"} ];
foreach my $table (keys(%virtual_tables)) {
my $tag = $virtual_tables{$table}{"tag"};
if (exists($exp->{"experiment"}->{$tag})) {
$virtual_tables{$table}->{"rows"} =
$exp->{"experiment"}->{$tag};
}
}
}
# If these are the results of parsing the nse specifications,
# we don't expect updates to the experiments table
......@@ -213,10 +275,10 @@ sub readXML($$$) {
# of stuff that is not allowed. Note that we never insert a
# experiment, but only allow updates of certain values.
#
if (scalar(@{$virtual_tables{"experiments"}}) != 1) {
if (scalar(@{ $virtual_tables{"experiments"}->{"rows"} }) != 1) {
fatal("Must be exactly one experiments table row!");
}
%experiments_table = %{@{$virtual_tables{"experiments"}}[0]};
%experiments_table = %{@{$virtual_tables{"experiments"}->{"rows"}}[0]};
foreach my $key (keys(%experiments_table)) {
delete($experiments_table{$key})
if (!exists($experiment_fields{$key}));
......@@ -229,8 +291,8 @@ sub readXML($$$) {
#
my $count = 0;
foreach my $table (keys(%virtual_tables)) {
$count += scalar(@{$virtual_tables{$table}})
if (defined($virtual_tables{$table}));
$count += scalar(@{$virtual_tables{$table}->{"rows"}})
if (defined($virtual_tables{$table}->{"rows"}));
}
if ($count > 100000) {
fatal("Too many rows of data!");
......@@ -250,7 +312,7 @@ sub readXML($$$) {
foreach my $key (keys(%experiments_table)) {
my $val = $experiments_table{$key};
if ($val eq "NULL") {
if ($val eq "NULL" || $val eq "") {
push(@setlist, "$key=NULL");
}
else {
......@@ -314,9 +376,9 @@ sub readXML($$$) {
}
}
next
if (!defined($virtual_tables{$table}));
if (!defined($virtual_tables{$table}->{"rows"}));
foreach my $rowref (@{$virtual_tables{$table}}) {
foreach my $rowref (@{$virtual_tables{$table}->{"rows"}}) {
my %rowhash = %{ $rowref };
my @fields = ("pid", "eid");
my @values = ("'$pid'", "'$eid'");
......@@ -410,8 +472,8 @@ sub StartElement ($$$)
defined($current_slot));
$current_table = $element;
if (! defined($virtual_tables{$element})) {
$virtual_tables{$element} = [];
if (! defined($virtual_tables{$element}->{"rows"})) {
$virtual_tables{$element}->{"rows"} = [];
}
print "Starting new table: $element\n"
if ($debug);
......@@ -471,7 +533,7 @@ sub EndElement ($$)
print "Adding new row to table $current_table\n"
if ($debug);
push(@{$virtual_tables{$current_table}}, $current_row);
push(@{ $virtual_tables{$current_table}->{"rows"} }, $current_row);
undef($current_row);
}
else {
......@@ -520,84 +582,43 @@ sub writeXML($$) {
if (! $query_result->numrows) {
fatal("No such experiment $pid/$eid exists!");
}
my $exp = {};
$exp->{"experiment"}->{"settings"} = $query_result->fetchrow_hashref();
spitxml_header();
spitxml_opentag("virtual_experiment pid='$pid' eid='$eid'", 0);
# Spit out all of the tables.
foreach my $table (keys(%virtual_tables)) {
spitxml_dbrows($table, 1,
"select * from $table ".
"where eid='$eid' and pid='$pid'");
foreach my $key (keys(%{ $exp->{"experiment"}->{"settings"} })) {
$exp->{"experiment"}->{"settings"}->{$key} = ""
if (!defined($exp->{"experiment"}->{"settings"}->{$key}));
}
spitxml_closetag("virtual_experiment", 0);
return 0;
}
#
# Utility functions to pretty print XML output, with specified indentation.
#
sub spitxml_opentag($$)
{
my ($tag, $level) = @_;
my $spaces = $level * 2;
printf("%${spaces}s<%s>\n", "", $tag);
}
sub spitxml_closetag($$)
{
my ($tag, $level) = @_;
my $spaces = $level * 2;
printf("%${spaces}s</%s>\n", "", $tag);
}
sub spitxml_header()
{
print "$XMLHEADER\n";
}
sub spitxml_entity($$$)
{
my ($tag, $data, $level) = @_;
my $spaces = $level * 2;
$data = "NULL"
if (!defined($data));
printf("%${spaces}s<%s>%s</%s>\n", "", $tag, xmlencode($data), $tag);
}
sub spitxml_dbrow($%)
{
my($level, %dbrow) = @_;
spitxml_opentag("row", $level);
foreach my $tag (keys(%dbrow)) {
my $data = $dbrow{$tag};
spitxml_entity($tag, $data, $level + 1);
#
# Read in a set of tables that live at top level.
#
foreach my $table (keys(%virtual_tables)) {
next
if ($table eq "experiments");
my $tag = $virtual_tables{$table}{"tag"};
if (!exists($exp->{"experiment"}->{$tag})) {
$exp->{"experiment"}->{$tag} = [];
}
$query_result =
DBQueryFatal("select * from $table ".
"where eid='$eid' and pid='$pid'");
while (my $rowref = $query_result->fetchrow_hashref()) {
foreach my $key (keys(%{ $rowref })) {
$rowref->{$key} = ""
if (!defined($rowref->{$key}));
}
push(@{ $exp->{"experiment"}->{$tag} }, $rowref);
}
}
spitxml_closetag("row", $level);
}
sub spitxml_dbrows($$$)
{
my($tag, $level, $query) = @_;
my $query_result = DBQueryFatal($query);
my $foo = new RPC::XML::response($exp);
print $foo->as_string();
if ($query_result->numrows) {
spitxml_opentag($tag, $level);
while (my %hashrow = $query_result->fetchhash()) {
spitxml_dbrow($level + 1, %hashrow);
}
spitxml_closetag($tag, $level);
}
return 0;
}
#
......
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